Setiap teknologi yang cukup canggih tidak dapat dibedakan dari keajaiban. Kecuali jika Anda memahaminya. Nama saya Thomas Steiner, saya bekerja di Developer Relations di Google dan dalam laporan presentasi Google I/O saya ini, saya akan melihat beberapa Fugu API baru dan cara API tersebut meningkatkan perjalanan pengguna inti di PWA Excalidraw, sehingga Anda dapat mengambil inspirasi dari ide-ide ini dan menerapkannya ke aplikasi Anda sendiri.
Bagaimana saya menemukan Excalidraw
Saya ingin memulai dengan sebuah cerita. Pada 1 Januari 2020, Christopher Chedeau, seorang engineer software di Facebook, membuat tweet tentang aplikasi gambar kecil yang telah dia mulai kerjakan. Dengan alat ini, Anda dapat menggambar kotak dan panah yang terlihat seperti kartun dan digambar tangan. Keesokan harinya, Anda juga dapat menggambar elipsis dan teks, serta memilih objek dan memindahkannya. Pada 3 Januari, aplikasi tersebut telah mendapatkan namanya, Excalidraw, dan, seperti setiap project sampingan yang baik, membeli nama domain adalah salah satu tindakan pertama Christopher. Sekarang, Anda dapat menggunakan warna dan mengekspor seluruh gambar sebagai PNG.
Pada 15 Januari, Christopher memposting postingan blog yang menarik banyak perhatian di Twitter, termasuk saya. Postingan dimulai dengan beberapa statistik yang mengesankan:
- 12 ribu pengguna aktif unik
- 1,5 ribu bintang di GitHub
- 26 kontributor
Untuk project yang baru dimulai dua minggu lalu, itu tidak buruk sama sekali. Namun, hal yang benar-benar meningkatkan minat saya ada di bagian bawah postingan. Christopher menulis bahwa ia mencoba sesuatu yang baru kali ini: memberikan akses commit tanpa syarat kepada semua orang yang mengirimkan permintaan pull. Pada hari yang sama dengan membaca postingan blog, saya memiliki permintaan pull yang menambahkan dukungan File System Access API ke Excalidraw, memperbaiki permintaan fitur yang telah diajukan seseorang.
Pull request saya digabungkan sehari kemudian dan sejak saat itu, saya memiliki akses commit penuh. Tidak perlu dikatakan, saya tidak menyalahgunakan kekuatan saya. Begitu juga dengan 149 kontributor lainnya sejauh ini.
Saat ini, Excalidraw adalah aplikasi web progresif lengkap yang dapat diinstal dengan dukungan offline, mode gelap yang memukau, dan kemampuan untuk membuka serta menyimpan file berkat File System Access API.
Lipis menjelaskan alasan dia menghabiskan banyak waktu untuk Excalidraw
Jadi, ini menandai akhir dari cerita "bagaimana saya menemukan Excalidraw", tetapi sebelum saya membahas beberapa fitur luar biasa Excalidraw, dengan senang hati saya memperkenalkan Panayiotis. Panayiotis Lipiridis, di Internet dikenal sebagai lipis, adalah kontributor paling produktif untuk Excalidraw. Saya bertanya kepada lipis apa yang memotivasinya untuk mencurahkan begitu banyak waktunya untuk Excalidraw:
Seperti orang lain, saya mengetahui project ini dari tweet Christopher. Kontribusi pertama saya adalah menambahkan library Open Color, warna yang masih menjadi bagian dari Excalidraw saat ini. Seiring berkembangnya project dan kami memiliki cukup banyak permintaan, kontribusi besar saya berikutnya adalah membuat backend untuk menyimpan gambar sehingga pengguna dapat membagikannya. Namun, yang benar-benar mendorong saya untuk berkontribusi adalah siapa pun yang mencoba Excalidraw akan mencari alasan untuk menggunakannya lagi.
Saya sepenuhnya setuju dengan lipis. Siapa pun yang telah mencoba Excalidraw akan mencari alasan untuk menggunakannya lagi.
Cara kerja Excalidraw
Sekarang, saya ingin menunjukkan cara menggunakan Excalidraw dalam praktik. Saya bukan seniman hebat, tetapi logo Google I/O cukup sederhana, jadi saya akan mencobanya. Kotak adalah "i", garis dapat berupa garis miring, dan "o" adalah lingkaran. Saya menahan shift, sehingga saya mendapatkan lingkaran yang sempurna. Izinkan saya memindahkan garis miring sedikit, agar terlihat lebih baik. Sekarang beberapa warna untuk "i" dan "o". Biru bagus. Mungkin gaya isian yang berbeda? Semua solid, atau garis silang? Nah, hachure terlihat bagus. Hasilnya tidak sempurna, tetapi itulah ide Excalidraw, jadi izinkan saya menyimpannya.
Saya mengklik ikon simpan dan memasukkan nama file di dialog simpan file. Di Chrome, browser yang mendukung File System Access API, ini bukan download, tetapi operasi simpan yang sebenarnya, tempat saya dapat memilih lokasi dan nama file, dan jika saya melakukan pengeditan, saya dapat menyimpannya ke file yang sama.
Mari kita ubah logo dan buat "i" menjadi merah. Jika saya sekarang mengklik simpan lagi, modifikasi saya akan disimpan ke file yang sama seperti sebelumnya. Sebagai bukti, izinkan saya menghapus kanvas dan membuka kembali file. Seperti yang dapat Anda lihat, logo merah-biru yang diubah sudah ada lagi.
Bekerja dengan file
Di browser yang saat ini tidak mendukung File System Access API, setiap operasi simpan adalah download, jadi saat saya membuat perubahan, saya akan mendapatkan beberapa file dengan nomor yang bertambah di nama file yang mengisi folder Download. Namun, meskipun ada kekurangan ini, saya tetap dapat menyimpan file.
Membuka file
Jadi, apa rahasianya? Bagaimana cara membuka dan menyimpan berfungsi di berbagai browser yang mungkin mendukung atau tidak
mendukung File System Access API? Membuka file di Excalidraw terjadi dalam fungsi yang disebut
loadFromJSON)(
), yang pada gilirannya memanggil fungsi yang disebut fileOpen()
.
export const loadFromJSON = async (localAppState: AppState) => {
const blob = await fileOpen({
description: 'Excalidraw files',
extensions: ['.json', '.excalidraw', '.png', '.svg'],
mimeTypes: ['application/json', 'image/png', 'image/svg+xml'],
});
return loadFromBlob(blob, localAppState);
};
Fungsi fileOpen()
yang berasal dari library kecil yang saya tulis bernama
browser-fs-access yang kita gunakan di
Excalidraw. Library ini menyediakan akses sistem file melalui
File System Access API dengan penggantian lama, sehingga dapat digunakan di browser
mana pun.
Pertama-tama, izinkan saya menunjukkan implementasi saat API didukung. Setelah menegosiasikan
jenis MIME dan ekstensi file yang diterima, bagian tengah akan memanggil fungsi
showOpenFilePicker()
File System Access API. Fungsi ini menampilkan array file atau satu file, bergantung
pada apakah beberapa file dipilih. Yang tersisa hanyalah menempatkan handle file pada objek
file, sehingga dapat diambil lagi.
export default async (options = {}) => {
const accept = {};
// Not shown: deal with extensions and MIME types.
const handleOrHandles = await window.showOpenFilePicker({
types: [
{
description: options.description || '',
accept: accept,
},
],
multiple: options.multiple || false,
});
const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
if (options.multiple) return files;
return files[0];
const getFileWithHandle = async (handle) => {
const file = await handle.getFile();
file.handle = handle;
return file;
};
};
Implementasi penggantian bergantung pada elemen input
dari jenis "file"
. Setelah negosiasi
jenis dan ekstensi MIME yang akan diterima, langkah berikutnya adalah mengklik elemen
input secara terprogram sehingga dialog buka file ditampilkan. Saat berubah, yaitu saat pengguna telah memilih satu atau beberapa file, promise akan diselesaikan.
export default async (options = {}) => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
const accept = [
...(options.mimeTypes ? options.mimeTypes : []),
options.extensions ? options.extensions : [],
].join();
input.multiple = options.multiple || false;
input.accept = accept || '*/*';
input.addEventListener('change', () => {
resolve(input.multiple ? Array.from(input.files) : input.files[0]);
});
input.click();
});
};
Menyimpan file
Sekarang saatnya menyimpan. Di Excalidraw, penyimpanan terjadi dalam fungsi yang disebut saveAsJSON()
. Pertama,
serialisasi array elemen Excalidraw ke JSON, konversikan JSON ke blob, lalu panggil
fungsi yang disebut fileSave()
. Fungsi ini juga disediakan oleh
library browser-fs-access.
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: 'application/vnd.excalidraw+json',
});
const fileHandle = await fileSave(
blob,
{
fileName: appState.name,
description: 'Excalidraw file',
extensions: ['.excalidraw'],
},
appState.fileHandle,
);
return { fileHandle };
};
Sekali lagi, mari kita lihat implementasi untuk browser dengan dukungan File System Access API terlebih dahulu. Beberapa baris pertama terlihat sedikit rumit, tetapi yang dilakukannya hanyalah menegosiasikan jenis MIME dan ekstensi file. Jika saya telah menyimpan sebelumnya dan sudah memiliki handle file, tidak ada dialog simpan yang perlu ditampilkan. Namun, jika ini adalah penyimpanan pertama, dialog file akan ditampilkan dan aplikasi akan mendapatkan handle file kembali untuk digunakan pada masa mendatang. Selanjutnya, hanya menulis ke file, yang terjadi melalui aliran data yang dapat ditulis.
export default async (blob, options = {}, handle = null) => {
options.fileName = options.fileName || 'Untitled';
const accept = {};
// Not shown: deal with extensions and MIME types.
handle =
handle ||
(await window.showSaveFilePicker({
suggestedName: options.fileName,
types: [
{
description: options.description || '',
accept: accept,
},
],
}));
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
};
Fitur "simpan sebagai"
Jika saya memutuskan untuk mengabaikan handle file yang sudah ada, saya dapat menerapkan fitur "save as" untuk membuat file baru berdasarkan file yang ada. Untuk menunjukkan hal ini, izinkan saya membuka file yang ada, melakukan beberapa perubahan, lalu tidak menimpa file yang ada, tetapi membuat file baru menggunakan fitur simpan sebagai. Tindakan ini akan mempertahankan file asli.
Implementasi untuk browser yang tidak mendukung File System Access API singkat, karena semua yang dilakukannya
adalah membuat elemen anchor dengan atribut download
yang nilainya adalah nama file yang diinginkan dan
URL blob sebagai nilai atribut href
-nya.
export default async (blob, options = {}) => {
const a = document.createElement('a');
a.download = options.fileName || 'Untitled';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', () => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
Elemen anchor kemudian diklik secara terprogram. Untuk mencegah kebocoran memori, URL blob perlu
dicabut setelah digunakan. Karena ini hanya download, tidak ada dialog simpan file yang ditampilkan, dan semua
file akan berada di folder Downloads
default.
Tarik lalu lepas
Salah satu integrasi sistem favorit saya di desktop adalah tarik lalu lepas. Di Excalidraw, saat saya meletakkan
file .excalidraw
ke aplikasi, file tersebut akan langsung terbuka dan saya dapat mulai mengedit. Di browser
yang mendukung File System Access API, saya bahkan dapat langsung menyimpan perubahan. Tidak perlu melalui
dialog simpan file karena handle file yang diperlukan telah diperoleh dari operasi
tarik lalu lepas.
Rahasianya adalah dengan memanggil getAsFileSystemHandle()
pada
item transfer data saat File System Access API didukung. Kemudian, saya meneruskan handle file ini ke loadFromBlob()
, yang mungkin Anda ingat dari beberapa paragraf di atas. Ada begitu banyak
hal yang dapat Anda lakukan dengan file: membuka, menyimpan, menyimpan ulang, menarik, melepas. Saya dan rekan kerja saya, Pete,
telah mendokumentasikan semua trik ini dan lainnya dalam artikel kami sehingga Anda dapat
membacanya jika semua ini terlalu cepat.
const file = event.dataTransfer?.files[0];
if (file?.type === 'application/json' || file?.name.endsWith('.excalidraw')) {
this.setState({ isLoading: true });
// Provided by browser-fs-access.
if (supported) {
try {
const item = event.dataTransfer.items[0];
file as any.handle = await item as any
.getAsFileSystemHandle();
} catch (error) {
console.warn(error.name, error.message);
}
}
loadFromBlob(file, this.state).then(({ elements, appState }) =>
// Load from blob
).catch((error) => {
this.setState({ isLoading: false, errorMessage: error.message });
});
}
Membagikan file
Integrasi sistem lain yang saat ini ada di Android, ChromeOS, dan Windows adalah melalui
Web Share Target API. Di sini saya berada di aplikasi File di folder Downloads
. Saya
dapat melihat dua file, salah satunya dengan nama non-deskriptif untitled
dan stempel waktu. Untuk memeriksa
isinya, saya mengklik tiga titik, lalu berbagi, dan salah satu opsi yang muncul adalah
Excalidraw. Saat mengetuk ikon, saya dapat melihat bahwa file hanya berisi logo I/O lagi.
Lipis pada versi Electron yang tidak digunakan lagi
Satu hal yang dapat Anda lakukan dengan file yang belum saya bahas adalah mengkliknya dua kali. Yang biasanya
terjadi saat Anda mengklik dua kali file adalah aplikasi yang terkait dengan jenis MIME file
akan terbuka. Misalnya, untuk .docx
, ini adalah Microsoft Word.
Excalidraw memiliki versi Electron aplikasi yang
mendukung pengaitan jenis file tersebut, sehingga saat Anda mengklik dua kali file .excalidraw
, aplikasi Excalidraw Electron akan terbuka. Lipis, yang telah Anda temui sebelumnya, adalah pencipta
dan penghenti penggunaan Excalidraw Electron. Saya bertanya kepadanya mengapa ia merasa versi Electron dapat dihentikan:
Orang-orang telah meminta aplikasi Electron sejak awal, terutama karena mereka ingin membuka file dengan mengklik dua kali. Kami juga bermaksud untuk menempatkan aplikasi di app store. Secara paralel, seseorang menyarankan untuk membuat PWA, jadi kami hanya melakukan keduanya. Untungnya, kami diperkenalkan dengan Project Fugu API seperti akses sistem file, akses papan klip, penanganan file, dan lainnya. Dengan sekali klik, Anda dapat menginstal aplikasi di desktop atau perangkat seluler, tanpa beban tambahan Electron. Keputusan untuk menghentikan penggunaan versi Electron, berkonsentrasi hanya pada aplikasi web, dan menjadikannya PWA terbaik adalah keputusan yang mudah. Selain itu, kini kita dapat memublikasikan PWA ke Play Store dan Microsoft Store. Itu adalah jumlah yang sangat besar.
Dapat dikatakan bahwa Excalidraw untuk Electron tidak dihentikan karena Electron buruk, sama sekali tidak, tetapi karena web sudah cukup baik. Saya suka ini.
Penanganan file
Saat saya mengatakan "web sudah cukup baik", hal itu karena fitur seperti Penanganan File mendatang.
Ini adalah penginstalan macOS Big Sur reguler. Sekarang, lihat apa yang terjadi saat saya mengklik kanan file Exaclidraw. Saya dapat memilih untuk membukanya dengan Excalidraw, PWA yang diinstal. Tentu saja, mengklik dua kali juga akan berfungsi, tetapi tidak terlalu dramatis untuk ditunjukkan dalam screencast.
Jadi, bagaimana cara kerjanya? Langkah pertama adalah membuat jenis file yang dapat ditangani aplikasi saya diketahui oleh
sistem operasi. Saya melakukannya di kolom baru bernama file_handlers
dalam manifes aplikasi web. Nilainya adalah array objek dengan tindakan dan properti accept
. Tindakan ini menentukan jalur URL
tempat sistem operasi meluncurkan aplikasi Anda dan objek terima adalah key-value pair dari jenis
MIME dan ekstensi file terkait.
{
"name": "Excalidraw",
"description": "Excalidraw is a whiteboard tool...",
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff",
"file_handlers": [
{
"action": "/",
"accept": {
"application/vnd.excalidraw+json": [".excalidraw"]
}
}
]
}
Langkah berikutnya adalah menangani file saat aplikasi diluncurkan. Hal ini terjadi di antarmuka launchQueue
tempat saya perlu menetapkan konsumen dengan memanggil, setConsumer()
. Parameter untuk fungsi
ini adalah fungsi asinkron yang menerima launchParams
. Objek launchParams
ini
memiliki kolom bernama file yang memberi saya array handle file untuk digunakan. Saya hanya peduli dengan
yang pertama dan dari handle file ini, saya mendapatkan blob yang kemudian saya teruskan ke teman lama kita
loadFromBlob()
.
if ('launchQueue' in window && 'LaunchParams' in window) {
window as any.launchQueue
.setConsumer(async (launchParams: { files: any[] }) => {
if (!launchParams.files.length) return;
const fileHandle = launchParams.files[0];
const blob: Blob = await fileHandle.getFile();
blob.handle = fileHandle;
loadFromBlob(blob, this.state).then(({ elements, appState }) =>
// Initialize app state.
).catch((error) => {
this.setState({ isLoading: false, errorMessage: error.message });
});
});
}
Sekali lagi, jika ini terlalu cepat, Anda dapat membaca lebih lanjut File Handling API di artikel saya. Anda dapat mengaktifkan penanganan file dengan menetapkan flag fitur platform web eksperimental. Fitur ini dijadwalkan akan hadir di Chrome pada tahun ini.
Integrasi papan klip
Fitur keren lainnya dari Excalidraw adalah integrasi papan klip. Saya dapat menyalin seluruh gambar atau hanya sebagian ke papan klip, mungkin menambahkan watermark jika saya mau, lalu menempelkannya ke aplikasi lain. Ini adalah versi web dari aplikasi Paint Windows 95.
Cara kerjanya sangat sederhana. Yang saya perlukan hanyalah kanvas sebagai blob, yang kemudian saya tulis
ke papan klip dengan meneruskan array satu elemen dengan ClipboardItem
dengan blob ke
fungsi navigator.clipboard.write()
. Untuk informasi selengkapnya tentang hal yang dapat Anda lakukan dengan API
clipboard, lihat artikel Jason dan saya.
export const copyCanvasToClipboardAsPng = async (canvas: HTMLCanvasElement) => {
const blob = await canvasToBlob(canvas);
await navigator.clipboard.write([
new window.ClipboardItem({
'image/png': blob,
}),
]);
};
export const canvasToBlob = async (canvas: HTMLCanvasElement): Promise<Blob> => {
return new Promise((resolve, reject) => {
try {
canvas.toBlob((blob) => {
if (!blob) {
return reject(new CanvasError(t('canvasError.canvasTooBig'), 'CANVAS_POSSIBLY_TOO_BIG'));
}
resolve(blob);
});
} catch (error) {
reject(error);
}
});
};
Berkolaborasi dengan orang lain
Membagikan URL sesi
Tahukah Anda bahwa Excalidraw juga memiliki mode kolaboratif? Orang yang berbeda dapat bekerja sama pada dokumen yang sama. Untuk memulai sesi baru, saya mengklik tombol kolaborasi live, lalu memulai sesi. Saya dapat membagikan URL sesi dengan kolaborator dengan mudah berkat Web Share API yang telah terintegrasi dengan Excalidraw.
Kolaborasi live
Saya telah menyimulasikan sesi kolaborasi secara lokal dengan mengerjakan logo Google I/O di Pixelbook, ponsel Pixel 3a, dan iPad Pro saya. Anda dapat melihat bahwa perubahan yang saya buat di satu perangkat akan tercermin di semua perangkat lainnya.
Saya bahkan dapat melihat semua kursor bergerak. Kursor Pixelbook bergerak dengan stabil, karena dikontrol oleh trackpad, tetapi kursor ponsel Pixel 3a dan kursor tablet iPad Pro berpindah-pindah, karena saya mengontrol perangkat ini dengan mengetuk dengan jari.
Melihat status kolaborator
Untuk meningkatkan pengalaman kolaborasi real-time, bahkan ada sistem deteksi tidak ada aktivitas yang berjalan. Kursor iPad Pro menampilkan titik hijau saat saya menggunakannya. Titik berubah menjadi hitam saat saya beralih ke tab atau aplikasi browser yang berbeda. Dan saat saya berada di aplikasi Excalidraw, tetapi tidak melakukan apa pun, kursor akan menampilkan saya sebagai tidak ada aktivitas, yang dilambangkan dengan tiga zZZ.
Pembaca setia publikasi kami mungkin cenderung berpikir bahwa deteksi tidak ada aktivitas diwujudkan melalui Idle Detection API, proposal tahap awal yang telah dikerjakan dalam konteks Project Fugu. Peringatan spoiler: tidak. Meskipun kami memiliki implementasi berdasarkan API ini di Excalidraw, pada akhirnya, kami memutuskan untuk menggunakan pendekatan yang lebih tradisional berdasarkan pengukuran pergerakan pointer dan visibilitas halaman.
Kami mengajukan masukan tentang alasan Idle Detection API tidak dapat menyelesaikan kasus penggunaan yang kami miliki. Semua Project Fugu API sedang dikembangkan secara terbuka, sehingga semua orang dapat ikut serta dan suara mereka didengar.
Lipis membahas hal yang menghambat Excalidraw
Bicara soal itu, saya mengajukan pertanyaan terakhir kepada lipis tentang apa yang menurutnya tidak ada di platform web yang menghambat Excalidraw:
File System Access API sangat bagus, tetapi tahukah Anda? Sebagian besar file yang saya anggap penting saat ini disimpan di Dropbox atau Google Drive, bukan di hard disk. Saya berharap File System Access API akan menyertakan lapisan abstraksi untuk penyedia sistem file jarak jauh seperti Dropbox atau Google untuk berintegrasi dan yang dapat di-coding oleh developer. Pengguna kemudian dapat bersantai dan mengetahui bahwa file mereka aman dengan penyedia cloud yang mereka percayai.
Saya sepenuhnya setuju dengan lipis, saya juga hidup di cloud. Semoga fitur ini akan segera diterapkan.
Mode aplikasi dengan tab
Wow! Kami telah melihat banyak integrasi API yang sangat bagus di Excalidraw. Sistem file, penanganan file, papan klip, berbagi web, dan target berbagi web. Namun, ada satu hal lagi. Hingga saat ini, saya hanya dapat mengedit satu dokumen dalam satu waktu. Jangan khawatir. Nikmati untuk pertama kalinya versi awal mode aplikasi dengan tab di Excalidraw. Beginilah tampilannya.
Saya memiliki file yang sudah terbuka di PWA Excalidraw yang diinstal dan berjalan dalam mode mandiri. Sekarang, saya membuka tab baru di jendela mandiri. Ini bukan tab browser biasa, tetapi tab PWA. Di tab baru ini, saya dapat membuka file sekunder, dan mengerjakannya secara independen dari jendela aplikasi yang sama.
Mode aplikasi dengan tab masih dalam tahap awal dan tidak semuanya sudah pasti. Jika Anda tertarik, pastikan untuk membaca status fitur ini saat ini di artikel saya.
Penutup
Untuk terus mendapatkan info terbaru tentang fitur ini dan fitur lainnya, pastikan untuk menonton pelacak Fugu API kami. Kami sangat antusias untuk mendorong web maju dan memungkinkan Anda melakukan lebih banyak hal di platform ini. Selamat untuk Excalidraw yang terus berkembang, dan selamat untuk semua aplikasi luar biasa yang akan Anda buat. Mulailah membuat di excalidraw.com.
Saya tidak sabar untuk melihat beberapa API yang telah saya tampilkan hari ini muncul di aplikasi Anda. Nama saya Tom, Anda dapat menemukan saya sebagai @tomayac di Twitter dan internet secara umum. Terima kasih banyak telah menonton, dan nikmati acara Google I/O lainnya.