Excalidraw dan Fugu: Meningkatkan Perjalanan Pengguna Inti

Teknologi apa pun yang cukup canggih tidak bisa dibedakan dengan sihir. Kecuali Anda memahaminya. Nama saya Thomas Steiner, saya bekerja di bagian Developer Relations di Google dan dalam diskusi Google I/O ini, saya akan membahas beberapa Fugu API baru dan bagaimana fitur 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 mulai dengan sebuah cerita. Pada 1 Januari 2020, Christopher Chedeau, seorang software engineer di Facebook, men-tweet tentang aplikasi menggambar kecil yang mulai dikerjakannya. Dengan alat ini, Anda dapat menggambar kotak dan panah yang terasa seperti kartun dan digambar dengan tangan. Keesokan harinya, Anda juga dapat menggambar elipsis dan teks, serta memilih objek dan memindahkannya. Pada tanggal 3 Januari, aplikasi ini mendapatkan namanya, Excalidraw, dan, seperti halnya project sampingan lain yang bagus, membeli nama domain adalah salah satu tindakan pertama Christopher. Sekarang, Anda dapat menggunakan warna dan mengekspor seluruh gambar sebagai PNG.

Screenshot aplikasi prototipe Excalidraw menunjukkan bahwa aplikasi ini mendukung persegi panjang, panah, elips, dan teks.

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 rb 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 dia mencoba sesuatu yang baru kali ini: memberi semua orang yang mendapatkan akses komitmen tanpa syarat dari permintaan pull. Pada hari yang sama setelah membaca postingan blog, saya menerima permintaan pull yang menambahkan dukungan File System Access API ke Excalidraw, yang memperbaiki permintaan fitur yang telah diajukan oleh seseorang.

Screenshot tweet tempat saya mengumumkan PR saya.

Pull request saya digabungkan sehari kemudian dan sejak saat itu, saya memiliki akses commit penuh. Tidak perlu dikatakan, saya tidak menyalahgunakan kekuasaan saya. Dan tidak ada orang lain dari 149 kontributor sejauh ini.

Saat ini, Excalidraw adalah aplikasi web progresif yang dapat diinstal dan sudah lengkap dengan dukungan offline, mode gelap yang menakjubkan, dan ya, kemampuan untuk membuka dan menyimpan file berkat File System Access API.

Screenshot PWA Excalidraw dalam status saat ini.

Lipi menjelaskan alasan dia mendedikasikan sebagian besar waktunya untuk Excalidraw

Jadi ini menandai akhir dari kisah "bagaimana saya datang ke Excalidraw", tetapi sebelum saya menyelami beberapa fitur menakjubkan 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 mendedikasikan sebagian besar waktunya untuk Excalidraw:

Seperti orang lain, saya mengetahui project ini dari tweet Christopher. Kontribusi pertama saya adalah menambahkan Open Color library, warna yang masih menjadi bagian dari Excalidraw saat ini. Seiring berkembangnya project dan memiliki 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 mencoba Excalidraw akan mencari alasan untuk menggunakannya lagi.

Cara kerja Excalidraw

Sekarang, saya ingin menunjukkan cara menggunakan Excalidraw dalam praktik. Saya bukan seniman yang 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. Saya akan memindahkan sedikit garis miring, agar terlihat lebih baik. Sekarang beberapa warna untuk "i" dan "o". Biru itu bagus. Mungkin gaya isian yang berbeda? Semua solid, atau garis silang? Tidak, hachure sepertinya keren. 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 hasil download, melainkan operasi penyimpanan yang sebenarnya. Di Chrome, saya dapat memilih lokasi dan nama file. Selain itu, jika saya melakukan pengeditan, file ini bisa disimpan di 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 Anda lihat, logo merah-biru yang dimodifikasi telah muncul lagi.

Bekerja dengan file

Pada browser yang saat ini tidak mendukung File System Access API, setiap operasi penyimpanan adalah proses download. Jadi, saat saya membuat perubahan, saya akan mendapatkan beberapa file dengan angka yang terus bertambah di nama file yang mengisi folder Download saya. Tapi meskipun ada kelemahan, saya masih bisa menyimpan file.

Membuka file

Jadi apa rahasianya? Bagaimana cara membuka dan menyimpan pekerjaan di berbagai browser yang mungkin mendukung File System Access API, mungkin tidak? Pembukaan file di Excalidraw terjadi dalam fungsi bernama loadFromJSON)(), yang kemudian memanggil fungsi bernama 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 memberikan akses sistem file melalui File System Access API dengan penggantian lama, sehingga dapat digunakan di browser apa pun.

Pertama-tama, izinkan saya menunjukkan implementasi saat API didukung. Setelah menegosiasikan jenis MIME dan ekstensi file yang diterima, bagian utama akan memanggil fungsi showOpenFilePicker() File System Access API. Fungsi ini menampilkan array file atau satu file, bergantung pada apakah beberapa file dipilih atau tidak. 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 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 ke menyimpan. Di Excalidraw, penyimpanan terjadi dalam fungsi yang disebut saveAsJSON(). Pertama-tama, array elemen Excalidraw akan diserialisasi ke JSON, konversi JSON menjadi 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, izinkan saya melihat implementasi untuk browser dengan dukungan File System Access API. Beberapa baris pertama terlihat sedikit rumit, tetapi yang dilakukan hanyalah menegosiasikan jenis MIME dan ekstensi file. Jika saya telah menyimpan sebelumnya dan sudah memiliki handle file, dialog simpan tidak perlu ditampilkan. Namun, jika ini adalah penyimpanan pertama, dialog file akan ditampilkan dan aplikasi akan menangani kembali file untuk digunakan di lain waktu. Sisanya kemudian 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 memutuskan untuk mengabaikan handle file yang sudah ada, saya dapat menerapkan fitur "simpan sebagai" untuk membuat file baru berdasarkan file yang ada. Untuk menampilkannya, saya akan membuka file yang sudah ada, membuat beberapa modifikasi, lalu tidak menimpa file yang sudah ada. Saya akan membuat file baru dengan menggunakan fitur simpan sebagai. Tindakan ini membiarkan file asli tetap utuh.

Implementasi untuk browser yang tidak mendukung File System Access API tidak berjalan lama karena yang dilakukan hanyalah 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 harus dicabut setelah digunakan. Karena ini hanya download, tidak akan ada dialog penyimpanan file yang ditampilkan, dan semua file akan masuk ke 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. Pada browser yang mendukung File System Access API, saya bahkan dapat segera menyimpan perubahan. Anda tidak perlu melakukan dialog penyimpanan file karena handle file yang diperlukan telah diperoleh dari operasi tarik lalu lepas.

Rahasia untuk melakukannya 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. Begitu banyak hal yang dapat Anda lakukan dengan file: membuka, menyimpan, menyimpan secara berlebihan, 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 lainnya saat ini di Android, ChromeOS, dan Windows adalah melalui Web Share Target API. Di sini saya berada di aplikasi File di folder Downloads. Saya bisa melihat dua file, salah satunya dengan nama yang tidak deskriptif untitled dan stempel waktu. Untuk memeriksa isinya, saya mengklik tiga titik, lalu berbagi, dan salah satu opsi yang muncul adalah Excalidraw. Ketika saya mengetuk ikon tersebut, saya dapat melihat bahwa {i>file<i} tersebut 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 melakukan doubleclick pada file adalah aplikasi yang terkait dengan jenis MIME file akan terbuka. Misalnya untuk .docx, ini adalah Microsoft Word.

Excalidraw sebelumnya memiliki versi Electron aplikasi yang mendukung asosiasi 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 mengapa dia merasa versi Elektron bisa digunakan:

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, kita telah diperkenalkan dengan Project Fugu API seperti akses sistem file, akses papan klip, penanganan file, dan banyak lagi. Dengan sekali klik, Anda dapat menginstal aplikasi di desktop atau perangkat seluler, tanpa beban tambahan Electron. Ini adalah keputusan yang mudah untuk menghentikan penggunaan versi Electron, berkonsentrasi hanya pada aplikasi web, dan menjadikannya PWA terbaik. Yang paling utama, kini kami dapat memublikasikan PWA ke Play Store dan Microsoft Store. Besar sekali!

Dapat dikatakan bahwa Excalidraw untuk Electron tidak dihentikan karena Electron buruk, sama sekali tidak, tetapi karena web sudah cukup baik. Saya suka ini!

Proses penanganan file

Saat saya mengatakan "web sudah cukup baik", hal ini karena fitur seperti Penanganan File mendatang.

Ini adalah penginstalan reguler macOS Big Sur. Sekarang, lihat apa yang terjadi saat saya mengklik kanan file Exaclidraw. Saya dapat memilih untuk membukanya dengan Excalidraw, PWA yang terinstal. Tentu saja, mengklik dua kali juga dapat berfungsi, tetapi tidak terlalu dramatis jika didemonstrasikan di 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 tuas 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 butuhkan 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 apa yang dapat Anda lakukan dengan API papan klip, Lihat artikel Jaka dan artikel 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 langsung, 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 diterapkan di semua perangkat lainnya.

Saya bahkan bisa 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 menunjukkan 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.

Screenshot masukan Idle Detection yang diajukan di repositori WICG Idle Detection.

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 tentang 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 memang bagus, tetapi Anda tahu? Kebanyakan file yang saya cari sekarang ada di Dropbox atau Google Drive, bukan di {i>hard disk<i}. Saya berharap File System Access API akan menyertakan lapisan abstraksi untuk penyedia sistem file jarak jauh seperti Dropbox atau Google untuk diintegrasikan dan yang dapat digunakan oleh developer dalam membuat kode. Dengan demikian, pengguna dapat merasa tenang dan mengetahui file mereka aman dengan penyedia cloud yang mereka percayai.

Saya sepenuhnya setuju dengan lipis, saya juga hidup di cloud. Semoga perubahan 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 versi awal mode aplikasi tab di Excalidraw untuk pertama kalinya. Seperti inilah tampilannya.

Saya memiliki file yang sudah terbuka di PWA Excalidraw yang diinstal dan berjalan dalam mode mandiri. Sekarang saya akan membuka tab baru di jendela {i>standalone<i}. Ini bukan tab browser biasa, tetapi tab PWA. Di tab baru ini, saya kemudian dapat membuka file sekunder, dan mengerjakannya secara terpisah dari jendela aplikasi yang sama.

Mode aplikasi tab masih dalam tahap awal dan tidak semuanya sempurna. Jika Anda tertarik, pastikan untuk membaca status fitur ini saat ini di artikel saya.

Penutup

Untuk terus mendapatkan info terbaru tentang hal ini dan fitur lainnya, pastikan untuk menonton pelacak Fugu API kami. Kami sangat bersemangat untuk mengembangkan web 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 konten di excalidraw.com.

Saya tidak sabar untuk melihat beberapa API yang telah saya tampilkan hari ini muncul di aplikasi Anda. Nama saya Tomi. Anda dapat menemukan saya sebagai @tomayac di Twitter dan internet secara umum. Terima kasih telah menonton, dan selamat menikmati Google I/O selanjutnya.