Исследуйте новые и будущие возможности браузера для вашего PWA: от Fugu с любовью

1. Прежде чем начать

Прогрессивные веб-приложения (PWA) — это тип прикладного программного обеспечения, предоставляемого через Интернет и созданного с использованием распространённых веб-технологий, включая HTML, CSS и JavaScript. Они предназначены для работы на любой платформе, использующей браузер, соответствующий стандартам.

В этой лабораторной работе вы начнете с базового PWA, а затем изучите новые возможности браузера, которые в конечном итоге дадут вашему PWA суперспособности 🦸.

Многие из этих новых возможностей браузера находятся в стадии разработки и стандартизации, поэтому иногда для их использования вам потребуется установить флаги браузера.

Предпосылки

Для выполнения этой лабораторной работы вам необходимо быть знакомым с современным JavaScript, в частности, с promises и async/await. Поскольку не все этапы работы поддерживаются на всех платформах, для тестирования будет полезно иметь под рукой дополнительные устройства, например, телефон Android или ноутбук с другой операционной системой, нежели та, на которой вы редактируете код. В качестве альтернативы реальным устройствам вы можете использовать симуляторы, например, Android Simulator, или онлайн-сервисы, например, BrowserStack, которые позволяют проводить тестирование с вашего текущего устройства. В противном случае вы можете просто пропустить любой этап — они не зависят друг от друга.

Что вы построите

Вы создадите веб-приложение для создания поздравительных открыток и узнаете, как новые и будущие возможности браузера могут улучшить ваше приложение, сделав его более удобным в определенных браузерах (но оставаясь полезным во всех современных браузерах).

Вы узнаете, как добавлять вспомогательные возможности, такие как доступ к файловой системе, доступ к буферу обмена системы, извлечение контактов, периодическую фоновую синхронизацию, блокировку экрана, функции общего доступа и многое другое.

После проработки кода вы получите четкое представление о том, как постепенно улучшать свои веб-приложения с помощью новых функций браузера, при этом не создавая нагрузку по загрузке для подгруппы ваших пользователей, которые используют несовместимые браузеры, и, что самое важное, не исключая их из вашего приложения изначально.

Что вам понадобится

В настоящее время полностью поддерживаемые браузеры:

Рекомендуется использовать конкретный канал Dev.

2. Проект Фугу

Прогрессивные веб-приложения (PWA) создаются и совершенствуются с помощью современных API-интерфейсов, обеспечивая расширенные возможности, надежность и удобство установки, а также охватывая любого пользователя Интернета, в любой точке мира и с помощью любого типа устройства.

Некоторые из этих API очень мощные, и при неправильном обращении всё может пойти не так. Как и рыба фугу 🐡: если её правильно порезать, она станет деликатесом, но если неправильно, она может быть смертельно опасна (но не волнуйтесь, в этой лабораторной работе ничего не сломается).

Вот почему внутреннее кодовое название проекта Web Capabilities (в рамках которого участвующие компании разрабатывают эти новые API) — Project Fugu.

Возможности Интернета, доступные уже сегодня, позволяют крупным и малым предприятиям создавать решения исключительно на базе браузера, что зачастую обеспечивает более быстрое развертывание с меньшими затратами на разработку по сравнению с разработкой на основе конкретной платформы.

3. Начните

Загрузите любой из браузеров, а затем установите следующий флаг времени выполнения 🚩, перейдя по адресу about://flags , который работает как в Chrome, так и в Edge:

  • #enable-experimental-web-platform-features

После включения перезапустите браузер.

Вы будете использовать платформу Glitch , поскольку она позволяет размещать ваши PWA и имеет хороший редактор. Glitch также поддерживает импорт и экспорт на GitHub, поэтому нет привязки к поставщику. Перейдите на сайт fugu-paint.glitch.me , чтобы опробовать приложение. Это простое приложение для рисования 🎨, которое вы улучшите во время кодлабораторной работы.

Fugu Greetings — базовый PWA с большим холстом, на котором нарисовано слово «Google».

Поэкспериментировав с приложением, создайте ремикс , чтобы создать свою копию, которую можно будет редактировать. URL-адрес вашего ремикса будет выглядеть примерно так: glitch.com/edit/#!/bouncy-candytuft (вместо «bouncy-candytuft» вы можете использовать другое название). Этот ремикс доступен по всему миру. Войдите в свою существующую учётную запись или создайте новую на Glitch, чтобы сохранить свою работу. Вы можете увидеть своё приложение, нажав кнопку «🕶 Показать», а URL-адрес размещённого приложения будет выглядеть примерно так: bouncy-candytuft.glitch.me (обратите внимание на .me вместо .com в качестве домена верхнего уровня).

Теперь вы готовы редактировать и улучшать своё приложение. После внесения изменений приложение будет перезагружаться, и ваши изменения будут сразу видны.

Glitch IDE, демонстрирующая редактирование HTML-документа.

В идеале следующие задачи следует выполнять по порядку, но, как уже отмечалось, вы всегда можете пропустить шаг, если у вас нет доступа к совместимому устройству. Помните, что каждая задача отмечена значком 🐟 (безобидная пресноводная рыбка) или 🐡 (рыба фугу с пометкой «обращаться осторожно»), что предупреждает о том, является ли функция экспериментальной.

Проверьте консоль в DevTools, поддерживается ли API на текущем устройстве. Мы также используем Glitch, чтобы вы могли легко проверить одно и то же приложение на разных устройствах, например, на мобильном телефоне и настольном компьютере.

Совместимость API регистрируется в консоли DevTools.

4. 🐟 Добавить поддержку API Web Share

Создавать потрясающие рисунки скучно, если их никто не оценит. Добавьте функцию, позволяющую пользователям делиться своими рисунками со всем миром в виде поздравительных открыток.

API Web Share поддерживает обмен файлами, и, как вы, возможно, помните, File — это всего лишь особый тип Blob . Поэтому в файле share.mjs импортируйте кнопку «Поделиться» и вспомогательную функцию toBlob() , которая преобразует содержимое холста в blob , и добавьте функцию общего доступа, как показано в коде ниже.

Если вы реализовали эту функцию, но не видите кнопку, это значит, что ваш браузер не поддерживает 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. 🐟 Добавьте поддержку API Web Share Target

Теперь ваши пользователи могут делиться поздравительными открытками, созданными в приложении, но вы также можете разрешить пользователям делиться изображениями в вашем приложении и превращать их в поздравительные открытки. Для этого можно использовать API Web Share Target .

В манифесте веб-приложения необходимо указать приложению, какие типы файлов оно может принимать и какой URL-адрес браузер должен использовать при предоставлении доступа к одному или нескольким файлам. Это показано в приведённом ниже фрагменте файла 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"]
        }
      ]
    }
  }
}

Затем сервис-воркер обрабатывает полученные файлы. URL-адрес ./share-target/ на самом деле не существует, приложение просто обрабатывает его в обработчике fetch и перенаправляет запрос на корневой URL-адрес, добавляя параметр запроса ?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 */

  /* ... */
});

При загрузке приложение проверяет, задан ли этот параметр запроса, и если да, отображает общее изображение на холсте и удаляет его из кэша. Всё это происходит в 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');
  }
};

Эта функция затем используется при инициализации приложения.

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

6. 🐟 Добавить поддержку импорта изображений

Рисовать всё с нуля сложно. Добавьте функцию, позволяющую пользователям загружать локальное изображение со своего устройства в приложение.

Сначала изучите функцию drawImage() холста. Затем ознакомьтесь с тегом <​input
type=file>
элемент.

Вооружившись этими знаниями, вы можете отредактировать файл import_image_legacy.mjs и добавить следующий фрагмент кода. В начале файла импортируется кнопка импорта и удобная функция drawBlob() , позволяющая нарисовать кляксу на холсте.

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. 🐟 Добавить поддержку экспорта изображений

Как пользователь сохранит файл, созданный в приложении, на своём устройстве? Традиционно это делалось с помощью <​a
download>
элемент.

В файл export_image_legacy.mjs добавьте следующее содержимое. Импортируйте кнопку экспорта и вспомогательную функцию toBlob() , которая преобразует содержимое холста в объект 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. 🐟 Добавить поддержку API доступа к файловой системе

Делиться — это заботливо, но ваши пользователи, вероятно, захотят сохранить свои лучшие работы на своих устройствах. Добавьте функцию, позволяющую пользователям сохранять (и повторно открывать) свои рисунки.

Раньше вы использовали устаревший подход <​input type=file> для импорта файлов и устаревший подход <​a download> для экспорта файлов. Теперь вы будете использовать API доступа к файловой системе для улучшения работы.

Этот API позволяет открывать и сохранять файлы из файловой системы операционной системы. Отредактируйте два файла, import_image.mjs и export_image.mjs , добавив следующее содержимое. Чтобы эти файлы загрузились, удалите эмодзи 🐡 из script.mjs .

Заменить эту строку:

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

...этой строкой:

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

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

В 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. 🐟 Добавить поддержку API выбора контактов

Ваши пользователи могут захотеть добавить сообщение к своей поздравительной открытке и обратиться к кому-то лично. Добавьте функцию, позволяющую пользователям выбрать один (или несколько) из своих локальных контактов и добавить их имена в сообщение.

На устройствах Android или iOS API Contact Picker позволяет выбирать контакты из приложения-менеджера контактов устройства и возвращать их в приложение. Отредактируйте файл contacts.mjs и добавьте следующий код.

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. 🐟 Добавить поддержку API асинхронного буфера обмена

Ваши пользователи могут захотеть вставить изображение из другого приложения в ваше приложение или скопировать рисунок из вашего приложения в другое. Добавьте функцию, позволяющую пользователям копировать изображения в ваше приложение и из него. API асинхронного буфера обмена поддерживает изображения в формате PNG, поэтому теперь вы можете читать и записывать данные изображений в буфер обмена.

Найдите файл clipboard.mjs и добавьте следующее:

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. 🐟 Добавить поддержку API для значков

Когда пользователи установят ваше приложение, на их главном экране появится значок. Вы можете использовать этот значок для передачи интересной информации, например, количества мазков кисти, использованных для создания рисунка.

Добавьте функцию, которая подсчитывает количество очков на значке каждый раз, когда пользователь делает новый мазок кистью. API Badging позволяет установить числовой значок на значок приложения. Вы можете обновлять значок при каждом событии pointerdown (то есть при мазке кистью) и сбрасывать его при очистке холста.

Поместите код ниже в файл 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. 🐟 Добавить поддержку API блокировки экрана

Иногда вашим пользователям может понадобиться несколько мгновений, чтобы просто посмотреть на рисунок, достаточно долгих, чтобы пришло вдохновение. Добавьте функцию, которая не даёт экрану заснуть и предотвращает включение заставки. API блокировки экрана предотвращает переход экрана пользователя в спящий режим. Блокировка экрана автоматически снимается при изменении видимости, как определено в параметре «Видимость страницы» . Поэтому блокировку экрана необходимо повторно активировать, когда страница снова становится видимой.

Найдите файл wake_lock.mjs и добавьте в него следующее содержимое. Чтобы проверить, работает ли это, настройте заставку на запуск через минуту.

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. 🐟 Добавить поддержку API периодической фоновой синхронизации

Начинать с чистого листа может быть скучно. Вы можете использовать API периодической фоновой синхронизации , чтобы каждый день инициализировать холсты пользователей новым изображением, например, ежедневной фотографией фугу от Unsplash.

Для этого требуются два файла: файл periodic_background_sync.mjs , который регистрирует периодическую фоновую синхронизацию, и еще один файл image_of_the_day.mjs , который отвечает за загрузку изображения дня.

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

В 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. 🐟 Добавить поддержку API распознавания форм

Иногда рисунки ваших пользователей или используемые фоновые изображения могут содержать полезную информацию, например, штрихкоды. API распознавания фигур , и в частности API распознавания штрихкодов, позволяет извлекать эту информацию. Добавьте функцию, которая попытается распознать штрихкоды в рисунках ваших пользователей. Найдите файл barcode.mjs и добавьте его содержимое ниже. Чтобы протестировать эту функцию, просто загрузите или вставьте изображение со штрихкодом на холст. Вы можете скопировать пример штрихкода из поиска изображений по запросу 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. 🐡 Добавить поддержку API обнаружения простоя

Если представить, что ваше приложение работает в режиме киоска, полезной функцией будет сброс холста после определённого периода бездействия. API обнаружения бездействия позволяет определить, когда пользователь больше не взаимодействует со своим устройством.

Найдите файл idle_detection.mjs и вставьте его содержимое ниже.

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. 🐡 Добавить поддержку API обработки файлов

Что, если бы пользователи могли просто дважды щёлкнуть по файлу изображения, и ваше приложение открывалось бы? API обработки файлов позволяет это сделать.

Вам необходимо зарегистрировать PWA в качестве обработчика файлов изображений. Это делается в манифесте веб-приложения, как показано в приведённом ниже фрагменте файла manifest.webmanifest . (Это уже часть манифеста, добавлять её самостоятельно не нужно.)

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

Для фактической обработки открытых файлов добавьте следующий код в 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. Поздравления

🎉 Ура, вы сделали это!

В рамках проекта Fugu 🐡 разрабатывается так много интересных API-интерфейсов браузеров, что эта лабораторная работа едва ли может охватить их всю.

Для более глубокого погружения или просто для того, чтобы узнать больше, следите за нашими публикациями на нашем сайте web.dev .

Целевая страница раздела «Возможности» сайта web.dev.

Но это ещё не всё. Чтобы узнать об обновлениях, которые ещё не опубликованы, воспользуйтесь нашим трекером API Fugu со ссылками на все отправленные предложения, находящиеся в стадии первоначального тестирования или тестирования для разработчиков, все предложения, работа над которыми уже началась, а также на всё, что рассматривается, но ещё не запущено.

Сайт-трекер Fugu API

Эту лабораторную работу написал Томас Штайнер ( @tomayac ). Я с радостью отвечу на ваши вопросы и с нетерпением жду ваших отзывов! Особая благодарность Хеманту ХМ ( @GNUmanth ), Кристиану Либелю ( @christianliebel ), Свену Мэю ( @Svenmay ), Ларсу Кнудсену ( @larsgk ) и Джеки Хану ( @hanguokai ), которые помогли в подготовке этой лабораторной работы!