1. Прежде чем начать
Прогрессивные веб-приложения (PWA) — это тип прикладного программного обеспечения, доставляемого через Интернет и созданного с использованием распространенных веб-технологий, включая HTML, CSS и JavaScript. Они предназначены для работы на любой платформе, использующей браузер, соответствующий стандартам.
В этой лабораторной работе вы начнете с базового PWA, а затем изучите новые возможности браузера, которые в конечном итоге придадут вашим PWA сверхспособности 🦸.
Многие из этих новых возможностей браузера находятся в разработке и все еще стандартизируются, поэтому иногда вам нужно будет установить флаги браузера, чтобы использовать их.
Предпосылки
Для работы с этой лабораторией кода вы должны быть знакомы с современным JavaScript, особенно с promises и async/await. Поскольку не все этапы лаборатории кода поддерживаются на всех платформах, для тестирования полезно иметь под рукой дополнительные устройства, например, телефон Android или ноутбук с операционной системой, отличной от устройства, на котором вы редактируете код. В качестве альтернативы реальным устройствам вы можете попробовать использовать симуляторы, такие как симулятор Android, или онлайн-сервисы, такие как 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 , чтобы попробовать приложение. Это базовое приложение для рисования 🎨, которое вы улучшите во время кодлаба.
Поиграв с приложением, сделайте ремикс приложения, чтобы создать собственную копию, которую вы сможете редактировать. URL-адрес вашего ремикса будет выглядеть примерно так: glitch.com/edit/#!/bouncy-candytuft («bouncy-candytuft» будет для вас чем-то другим). Этот ремикс доступен напрямую по всему миру. Войдите в свою существующую учетную запись или создайте новую учетную запись на Glitch, чтобы сохранить свою работу. Вы можете увидеть свое приложение, нажав кнопку «🕶 Показать», и URL-адрес размещенного приложения будет примерно таким, как bouncy-candytuft.glitch.me (обратите внимание на .me
вместо .com
в качестве домена верхнего уровня).
Теперь вы готовы редактировать свое приложение и улучшать его. Всякий раз, когда вы вносите изменения, приложение перезагружается, и ваши изменения будут видны напрямую.
В идеале следующие задачи следует выполнять по порядку, но, как отмечалось выше, вы всегда можете пропустить шаг, если у вас нет доступа к совместимому устройству. Помните, что каждое задание помечено либо 🐟, безобидной пресноводной рыбой, либо 🐡, рыбой фугу, требующей осторожного обращения, предупреждая вас о том, насколько экспериментальной является эта функция.
Проверьте консоль в DevTools, чтобы узнать, поддерживается ли API на текущем устройстве. Мы также используем Glitch, чтобы вы могли легко проверять одно и то же приложение на разных устройствах, например, на своем мобильном телефоне и настольном компьютере.
4. 🐟 Добавить поддержку API веб-ресурсов
Создавать самые удивительные рисунки скучно, если их некому оценить. Добавьте функцию, позволяющую вашим пользователям делиться своими рисунками со всем миром в виде поздравительных открыток.
Web Share API поддерживает общий доступ к файлам, и, как вы, возможно, помните, File
— это просто особый тип Blob
. Поэтому в файле с именем share.mjs
импортируйте кнопку общего доступа и функцию удобства toBlob()
, которая преобразует содержимое холста в большой двоичный объект, и добавьте функциональность общего доступа в соответствии с приведенным ниже кодом.
Если вы реализовали это, но не видите кнопку, это значит, что ваш браузер не поддерживает 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
Теперь ваши пользователи могут делиться поздравительными открытками, сделанными с помощью приложения, но вы также можете разрешить пользователям делиться изображениями с вашим приложением и превращать их в поздравительные открытки. Для этого можно использовать 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()
, которая преобразует содержимое холста в большой двоичный объект.
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 выбора контактов позволяет выбирать контакты из приложения диспетчера контактов устройства и возвращать их в приложение. Отредактируйте файл 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 Screen Wake Lock предотвращает засыпание экрана пользователя. Блокировка пробуждения автоматически снимается, когда происходит событие изменения видимости, определенное параметром Page Visibility . Следовательно, блокировка пробуждения должна быть повторно получена, когда страница снова появится в поле зрения.
Найдите файл 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 обнаружения простоя
Если вы представляете, что ваше приложение работает в режиме киоска, полезной функцией будет сброс холста после определенного периода бездействия. Idle Detection 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 обработки файлов
Что, если бы ваши пользователи могли просто дважды щелкнуть файл изображения, и появилось бы ваше приложение? File Handling 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 .
Но это еще не все. Для обновлений, еще не опубликованных, вы можете получить доступ к нашему трекеру API Fugu со ссылками на все предложения, которые были отправлены, находятся в исходной пробной версии или пробной версии для разработчиков, все предложения, над которыми началась работа, и все, что рассматривается, но еще не началось.
Эта кодовая лаборатория была написана Томасом Штайнером ( @tomayac ), я буду рад ответить на ваши вопросы и с нетерпением жду ваших отзывов! Особая благодарность Hemanth HM ( @GNUmanth ), Christian Liebel ( @christianliebel ), Sven May ( @Svenmay ), Lars Knudsen ( @larsgk ) и Jackie Han ( @hanguokai ), которые помогли сформировать эту лабораторию кода!