Excalidraw dan Fugu: Meningkatkan Perjalanan Pengguna Inti

Setiap teknologi yang cukup canggih tidak dapat dibedakan dari keajaiban. Kecuali Anda memahaminya. Nama saya Thomas Steiner, saya bekerja di Developer Relations di Google dan dalam tulisan ini tentang pidato Google I/O saya, saya akan melihat beberapa API Fugu baru dan bagaimana 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 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 tangan. Keesokan harinya, Anda juga dapat menggambar elips dan teks, serta memilih objek dan memindahkannya. Pada 3 Januari, aplikasi tersebut mendapatkan namanya, Excalidraw, dan seperti setiap project sampingan 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 yang menunjukkan bahwa aplikasi tersebut mendukung persegi panjang, panah, elips, dan teks.

Pada 15 Januari, Christopher memublikasikan postingan blog yang menarik banyak perhatian di Twitter, termasuk saya. Postingan ini dimulai dengan beberapa statistik yang mengesankan:

  • 12 ribu pengguna aktif unik
  • 1.500 bintang di GitHub
  • 26 kontributor

Untuk project yang baru dimulai dua minggu lalu, itu tidak buruk sama sekali. Namun, hal yang benar-benar membangkitkan minat saya ada di bagian bawah postingan. Christopher menulis bahwa dia mencoba sesuatu yang baru kali ini: memberi akses commit tanpa syarat kepada semua orang yang membuka pull request. Pada hari yang sama setelah membaca postingan blog tersebut, saya membuat pull request yang menambahkan dukungan File System Access API ke Excalidraw, sehingga memperbaiki permintaan fitur yang diajukan seseorang.

Screenshot tweet saat saya mengumumkan PR saya.

Pull request saya digabungkan sehari kemudian dan sejak saat itu, saya memiliki akses commit penuh. Tak perlu dikatakan, saya tidak menyalahgunakan kekuasaan saya. Begitu juga dengan 149 kontributor lainnya hingga saat ini.

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

Screenshot PWA Excalidraw dalam status saat ini.

Lipis tentang alasan ia mendedikasikan banyak waktunya untuk Excalidraw

Jadi, ini menandai akhir cerita "bagaimana saya menemukan Excalidraw", tetapi sebelum saya membahas beberapa fitur luar biasa Excalidraw, saya ingin memperkenalkan Panayiotis. Panayiotis Lipiridis, yang di Internet dikenal sebagai lipis, adalah kontributor paling produktif untuk Excalidraw. Saya bertanya kepada lipis apa yang memotivasinya untuk mendedikasikan begitu banyak waktunya untuk Excalidraw:

Seperti orang lain, saya mengetahui proyek ini dari tweet Christopher. Kontribusi pertama saya adalah menambahkan library Open Color, warna yang masih menjadi bagian dari Excalidraw hingga saat ini. Seiring berkembangnya project dan banyaknya permintaan yang kami terima, kontribusi besar saya berikutnya adalah membangun backend untuk menyimpan gambar sehingga pengguna dapat membagikannya. Namun, yang benar-benar mendorong saya untuk berkontribusi adalah bahwa siapa pun yang mencoba Excalidraw akan mencari alasan untuk menggunakannya lagi.

Saya sepenuhnya setuju dengan lipis. Siapa pun yang mencoba Excalidraw pasti 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 bisa berupa garis miring, dan "o" adalah lingkaran. Saya menahan tombol shift, sehingga saya mendapatkan lingkaran yang sempurna. Saya akan memindahkan garis miring sedikit agar terlihat lebih baik. Sekarang, beberapa warna untuk "i" dan "o". Biru bagus. Mungkin gaya pengisian yang berbeda? Penuh atau garis silang? Nah, hachure terlihat bagus. Memang tidak sempurna, tetapi itulah ide Excalidraw, jadi saya akan menyimpannya.

Saya mengklik ikon simpan dan memasukkan nama file dalam dialog penyimpanan file. Di Chrome, browser yang mendukung File System Access API, ini bukan download, tetapi operasi penyimpanan yang sebenarnya, di mana saya dapat memilih lokasi dan nama file, dan jika saya melakukan pengeditan, saya dapat menyimpannya ke file yang sama.

Saya akan mengubah logo dan membuat huruf "i" berwarna merah. Jika saya mengklik simpan lagi, modifikasi saya akan disimpan ke file yang sama seperti sebelumnya. Sebagai bukti, saya akan mengosongkan kanvas dan membuka kembali file. Seperti yang dapat Anda lihat, logo merah-biru yang dimodifikasi muncul lagi.

Bekerja dengan file

Di browser yang saat ini tidak mendukung File System Access API, setiap operasi penyimpanan adalah download, jadi saat saya membuat perubahan, saya akan mendapatkan beberapa file dengan nomor yang bertambah di nama file yang memenuhi folder Download saya. Namun, meskipun ada kekurangan ini, saya tetap dapat menyimpan file.

Membuka file

Jadi, apa rahasianya? Bagaimana cara kerja membuka dan menyimpan di browser yang berbeda yang mungkin atau mungkin 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 kami gunakan di Excalidraw. Library ini menyediakan akses sistem file melalui File System Access API dengan penggantian versi lama, sehingga dapat digunakan di browser mana pun.

Pertama-tama, saya akan menunjukkan implementasi saat API didukung. Setelah menegosiasikan jenis MIME dan ekstensi file yang diterima, bagian utamanya adalah memanggil fungsi File System Access API showOpenFilePicker(). Fungsi ini menampilkan array file atau satu file, bergantung pada apakah beberapa file dipilih. Yang tersisa adalah 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 berjenis "file". Setelah negosiasi jenis MIME dan ekstensi yang akan diterima, langkah selanjutnya adalah mengklik elemen input secara terprogram sehingga dialog buka file ditampilkan. Saat perubahan, 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 beralih ke penghematan. Di Excalidraw, penyimpanan terjadi dalam fungsi yang disebut saveAsJSON(). Pertama-tama, fungsi ini akan menserialisasi array elemen Excalidraw ke JSON, mengonversi JSON ke blob, lalu memanggil 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 terlebih dahulu penerapan untuk browser dengan dukungan File System Access API. Beberapa baris pertama terlihat agak rumit, tetapi yang dilakukan hanyalah menegosiasikan jenis MIME dan ekstensi file. Jika saya telah menyimpan sebelumnya dan sudah memiliki handle file, tidak ada dialog penyimpanan yang perlu ditampilkan. Namun, jika ini adalah penyimpanan pertama, dialog file akan ditampilkan dan aplikasi akan mendapatkan kembali handle file untuk digunakan pada masa mendatang. Selanjutnya, hanya perlu menulis ke file, yang terjadi melalui writable stream.

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 "simpan sebagai" untuk membuat file baru berdasarkan file yang ada. Untuk menunjukkannya, izinkan saya membuka file yang ada, melakukan beberapa perubahan, dan kemudian tidak menimpa file yang ada, tetapi membuat file baru menggunakan fitur simpan sebagai. Tindakan ini membuat file asli tetap utuh.

Implementasi untuk browser yang tidak mendukung File System Access API singkat, 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 berupa download, tidak ada dialog penyimpanan 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 melepaskan 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 membuka dialog penyimpanan file karena handle file yang diperlukan telah diperoleh dari operasi tarik lalu lepas.

Rahasia untuk mewujudkannya 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. Banyak hal yang dapat Anda lakukan dengan file: membuka, menyimpan, menyimpan ulang, menarik, melepaskan. Rekan kerja saya, Pete, dan saya telah mendokumentasikan semua trik ini dan lainnya dalam artikel kami sehingga Anda dapat mengejar ketinggalan jika semua ini berjalan 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 tersedia di Android, ChromeOS, dan Windows adalah melalui Web Share Target API. Di sini saya berada di aplikasi File di folder Downloads saya. Saya dapat melihat dua file, salah satunya dengan nama untitled yang tidak jelas dan stempel waktu. Untuk memeriksa isinya, saya mengklik tiga titik, lalu bagikan, dan salah satu opsi yang muncul adalah Excalidraw. Saat mengetuk ikon, saya dapat melihat bahwa file tersebut hanya berisi logo I/O lagi.

Lipis pada versi Electron yang tidak digunakan lagi

Salah satu hal yang dapat Anda lakukan dengan file yang belum saya bahas adalah mengkliknya dua kali. Biasanya, saat Anda mengklik dua kali file, aplikasi yang terkait dengan jenis MIME file tersebut akan terbuka. Misalnya, untuk .docx, ini adalah Microsoft Word.

Excalidraw dulu memiliki aplikasi versi Electron yang mendukung asosiasi jenis file tersebut, jadi saat Anda mengklik dua kali file .excalidraw, aplikasi Electron Excalidraw akan terbuka. Lipis, yang sudah Anda temui sebelumnya, adalah pembuat dan penghentian penggunaan Excalidraw Electron. Saya bertanya kepadanya mengapa dia merasa versi Electron dapat dihentikan:

Pengguna telah meminta aplikasi Electron sejak awal, terutama karena mereka ingin membuka file dengan mengklik dua kali. Kami juga bermaksud menempatkan aplikasi tersebut di toko aplikasi. Secara paralel, seseorang menyarankan untuk membuat PWA, jadi kami melakukan keduanya. Untungnya, kami diperkenalkan dengan API Project Fugu seperti akses sistem file, akses papan klip, penanganan file, dan banyak lagi. Dengan satu 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 sebaik mungkin adalah keputusan yang mudah. Selain itu, kami kini dapat memublikasikan PWA ke Play Store dan Microsoft Store. Itu sangat besar!

Orang dapat mengatakan 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 adanya fitur seperti File Handling yang akan segera hadir.

Ini adalah penginstalan macOS Big Sur biasa. Sekarang, lihat apa yang terjadi saat saya mengklik kanan file Excalidraw. Saya dapat memilih untuk membukanya dengan Excalidraw, PWA yang diinstal. Tentu saja, mengklik dua kali juga bisa, hanya saja kurang dramatis untuk didemonstrasikan dalam screencast.

Jadi, bagaimana cara kerjanya? Langkah pertama adalah membuat jenis file yang dapat ditangani oleh 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 menentukan jalur URL yang digunakan sistem operasi untuk meluncurkan aplikasi Anda dan objek accept 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 menyetel konsumen dengan memanggil, ya, 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 dikerjakan. 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 terlalu cepat, Anda dapat membaca lebih lanjut tentang File Handling API di artikel saya. Anda dapat mengaktifkan penanganan file dengan menyetel tanda fitur platform web eksperimental. Fitur ini dijadwalkan untuk hadir di Chrome pada tahun ini.

Integrasi papan klip

Fitur keren lain dari Excalidraw adalah integrasi papan klip. Saya dapat menyalin seluruh gambar atau hanya sebagiannya ke papan klip, mungkin menambahkan tanda air jika saya mau, lalu menempelkannya ke aplikasi lain. Ini adalah versi web 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 mengetahui informasi selengkapnya tentang apa yang dapat Anda lakukan dengan Clipboard API, lihat artikel Jason 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 dalam dokumen yang sama. Untuk memulai sesi baru, saya mengklik tombol kolaborasi live, lalu memulai sesi. Saya dapat membagikan URL sesi kepada kolaborator dengan mudah berkat Web Share API yang telah diintegrasikan Excalidraw.

Kolaborasi langsung

Saya telah menyimulasikan sesi kolaborasi secara lokal dengan mengerjakan logo Google I/O di Pixelbook, ponsel Pixel 3a, dan iPad Pro. Anda dapat melihat bahwa perubahan yang saya buat di satu perangkat akan diterapkan 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 bergerak-gerak karena saya mengontrol perangkat ini dengan mengetuk menggunakan jari.

Melihat status kolaborator

Untuk meningkatkan pengalaman kolaborasi real-time, ada juga sistem deteksi aktivitas tidak ada yang berjalan. Kursor iPad Pro menampilkan titik hijau saat saya menggunakannya. Titik berubah menjadi hitam saat saya beralih ke tab atau aplikasi browser lain. Dan saat saya berada di aplikasi Excalidraw, tetapi tidak melakukan apa pun, kursor menunjukkan bahwa saya tidak aktif, yang disimbolkan 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 penerapan berdasarkan API ini di Excalidraw, pada akhirnya, kami memutuskan untuk menggunakan pendekatan yang lebih tradisional berdasarkan pengukuran pergerakan pointer dan visibilitas halaman.

Screenshot masukan Deteksi Tidak Ada Aktivitas yang diajukan di repo Deteksi Tidak Ada Aktivitas WICG.

Kami mengirimkan masukan tentang alasan mengapa Idle Detection API tidak menyelesaikan kasus penggunaan yang kami miliki. Semua API Project Fugu dikembangkan secara terbuka, sehingga semua orang dapat berpartisipasi dan menyampaikan pendapatnya.

Lipis tentang apa yang menghambat Excalidraw

Omong-omong, saya mengajukan satu pertanyaan terakhir kepada lipis mengenai apa yang menurutnya kurang dari platform web yang menghambat Excalidraw:

File System Access API sangat bagus, tetapi tahukah Anda? Sebagian besar file yang saya perhatikan saat ini berada di Dropbox atau Google Drive saya, bukan di hard disk saya. Saya berharap File System Access API akan menyertakan lapisan abstraksi bagi penyedia sistem file jarak jauh seperti Dropbox atau Google untuk berintegrasi dan agar developer dapat membuat kode. Pengguna kemudian dapat bersantai dan mengetahui bahwa file mereka aman dengan penyedia cloud yang mereka percayai.

Saya sangat setuju dengan lipis, saya juga tinggal di cloud. Semoga fitur ini segera diterapkan.

Mode aplikasi bertab

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 bertab di Excalidraw. Beginilah tampilannya.

Saya membuka file yang sudah ada 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 terpisah dari jendela aplikasi yang sama.

Mode aplikasi bertab masih dalam tahap awal dan belum semuanya ditetapkan. Jika Anda tertarik, pastikan untuk membaca status terkini fitur ini di artikel saya.

Penutup

Untuk terus mendapatkan info terbaru tentang fitur ini dan fitur lainnya, pastikan untuk memantau pelacak Fugu API kami. Kami sangat senang untuk memajukan web dan memungkinkan Anda melakukan lebih banyak hal di platform ini. Semoga Excalidraw terus meningkat, dan semoga semua aplikasi luar biasa yang akan Anda buat berhasil. Mulai membuat di excalidraw.com.

Saya tidak sabar ingin melihat beberapa API yang telah saya tunjukkan 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 sisa acara Google I/O.