Excalidraw dan Fugu: Meningkatkan Perjalanan Pengguna Inti

Teknologi yang cukup canggih tidak dapat dibedakan dengan sihir. Kecuali Anda memahaminya. Saya Thomas Steiner, saya bekerja di bidang Hubungan Developer di Google. Dalam presentasi Google I/O ini, saya akan membahas beberapa API Fugu yang baru dan bagaimana API tersebut meningkatkan perjalanan pengguna inti di Excalidraw PWA, sehingga Anda dapat mengambil inspirasi dari ide-ide ini dan menerapkannya pada aplikasi Anda sendiri.

Bagaimana saya bisa menggunakan Excalidraw

Saya ingin mulai dengan sebuah cerita. Pada 1 Januari 2020, Christopher Chedeau, software engineer di Facebook, men-tweet tentang aplikasi menggambar kecil yang sudah dia mulai kerjakan. 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 3 Januari, aplikasi tersebut mendapatkan namanya, Excalidraw, dan, seperti pada setiap project sampingan yang bagus, membeli nama domain adalah salah satu tindakan pertama Christopher. Sekarang Anda bisa menggunakan warna dan mengekspor seluruh gambar sebagai PNG.

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

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

  • 12 rb pengguna aktif unik
  • 1,5 ribu bintang di GitHub
  • 26 kontributor

Untuk proyek yang dimulai hanya dua minggu yang lalu, itu sama sekali tidak buruk. Tetapi hal yang benar-benar meningkatkan minat saya jauh di bawah postingan itu. Christopher menulis bahwa dia mencoba sesuatu yang baru kali ini: memberi semua orang yang mengirim permintaan pull akses commit tanpa syarat. Pada hari yang sama saat membaca postingan blog, saya mendapat permintaan tarik yang menambahkan dukungan File System Access API ke Excalidraw, yang memperbaiki permintaan fitur yang diajukan seseorang.

Screenshot tweet tempat saya mengumumkan PR saya.

Permintaan pull saya digabungkan sehari kemudian dan sejak saat itu, saya memiliki akses commit penuh. Bisa dikatakan, saya tidak menyalahgunakan kekuasaan saya. Begitu juga orang lain dari 149 kontributor sejauh ini.

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

Screenshot PWA Excalidraw dengan status saat ini.

Lipis tentang alasan dia mendedikasikan begitu banyak waktunya untuk Excalidraw

Jadi ini adalah akhir dari kisah "cara saya datang ke Excalidraw", tetapi sebelum saya menyelami beberapa fitur Excalidraw yang luar biasa, saya dengan senang hati memperkenalkan Panayiotis. Panayiotis Lipiridis, di internet yang dikenal sebagai lipis, adalah kontributor paling produktif untuk Excalidraw. Saya bertanya kepada lipis apa yang memotivasinya untuk mendedikasikan begitu banyak waktunya untuk Excalidraw:

Seperti semua orang yang saya pelajari tentang proyek ini dari tweet Christopher. Kontribusi pertama saya adalah menambahkan library Open Color, warna yang masih menjadi bagian Excalidraw saat ini. Seiring dengan berkembangnya project dan kami mendapatkan cukup banyak permintaan, kontribusi besar berikutnya adalah membuat backend untuk menyimpan gambar sehingga pengguna dapat membagikannya. Namun, yang sebenarnya mendorong saya untuk berkontribusi adalah bahwa siapa pun yang mencoba Excalidraw ingin menemukan alasan untuk menggunakannya lagi.

Saya sepenuhnya setuju dengan lipis. Siapa pun yang mencoba Excalidraw ingin mencari alasan untuk menggunakannya lagi.

Cara kerja Excalidraw

Saya ingin menunjukkan cara menggunakan Excalidraw dalam latihan. 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 itu bagus. Mungkin gaya isian yang berbeda? Semuanya solid atau bisa silang? Tidak, hachure terlihat keren. Memang tidak sempurna, tapi itulah ide Excalidraw, jadi biarkan saya menyimpannya.

Saya klik ikon {i>save<i} dan memasukkan nama {i>file<i} pada dialog {i>file<i} disimpan. Di Chrome, browser yang mendukung File System Access API, ini bukan download, melainkan operasi penyimpanan yang sebenarnya, yang memungkinkan saya memilih lokasi dan nama file, serta di mana, jika saya mengedit, saya bisa menyimpannya ke file yang sama.

Biarkan saya mengubah logo dan membuat "i" menjadi merah. Jika sekarang saya mengklik {i>save<i} lagi, perubahan saya akan disimpan ke {i>file<i} yang sama seperti sebelumnya. Sebagai bukti, izinkan saya membersihkan kanvas dan membuka kembali file tersebut. Seperti yang Anda lihat, logo biru merah yang dimodifikasi ada lagi di sana.

Bekerja dengan file

Pada browser yang saat ini tidak mendukung File System Access API, setiap operasi penyimpanan adalah download. Jadi, saat saya membuat perubahan, akhirnya saya akan menemukan beberapa file dengan angka yang bertambah dalam nama file yang mengisi folder Download saya. Tetapi terlepas dari kekurangan ini, saya masih bisa menyimpan file tersebut.

Membuka file

Jadi apa rahasianya? Bagaimana cara membuka dan menyimpan pekerjaan di browser lain yang mungkin mendukung atau tidak mendukung File System Access API? Pembukaan file di Excalidraw dilakukan 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 kami gunakan dalam Excalidraw. Library ini memberikan akses sistem file melalui File System Access API dengan penggantian lama, sehingga dapat digunakan di browser apa pun.

Pertama-tama, saya akan menunjukkan implementasinya 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 atau tidak. Selanjutnya, Anda hanya perlu meletakkan handle file pada objek file agar dapat diambil kembali.

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 menegosiasikan jenis dan ekstensi MIME yang akan diterima, langkah selanjutnya adalah mengklik elemen input secara terprogram sehingga dialog pembukaan file ditampilkan. Saat diubah, yaitu saat pengguna telah memilih satu atau beberapa file, promise akan di-resolve.

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 penyimpanan. Di Excalidraw, penyimpanan terjadi dalam fungsi yang disebut saveAsJSON(). Metode ini terlebih dahulu melakukan serialisasi array elemen Excalidraw ke JSON, mengonversi JSON menjadi 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, pertama-tama saya akan melihat implementasi untuk browser dengan dukungan File System Access API. Beberapa baris pertama terlihat sedikit rumit, tetapi yang perlu dilakukan 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 mendapatkan kembali handle file untuk digunakan di lain waktu. Sisanya kemudian hanya menulis ke file, yang terjadi melalui aliran 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 "simpan sebagai" untuk membuat file baru berdasarkan file yang ada. Untuk menampilkannya, saya akan membuka file yang sudah ada, melakukan beberapa modifikasi, lalu tidak menimpa file yang sudah ada, tetapi membuat file baru dengan menggunakan fitur simpan sebagai. Tindakan ini akan membiarkan file asli tetap utuh.

Implementasi untuk browser yang tidak mendukung File System Access API tidak akan terlalu 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 hanyalah download, tidak akan ada dialog penyimpanan file yang ditampilkan, dan semua file akan ditempatkan di folder Downloads default.

Tarik lalu lepas

Salah satu integrasi sistem favorit saya di {i>desktop<i} adalah {i>drag and drop<i}. 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 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. Begitu banyak hal yang dapat Anda lakukan dengan file: membuka, menyimpan, menyimpan secara berlebihan, menyeret, melepas. Rekan saya, Pete, dan saya telah mendokumentasikan semua trik ini dan lainnya di artikel kami sehingga Anda dapat mengetahui jika semua hal 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 ada di Android, ChromeOS, dan Windows adalah melalui Web Share Target API. Saya berada di aplikasi File di folder Downloads saya. Saya dapat melihat dua file, salah satunya dengan nama non-deskripsi untitled dan stempel waktu. Untuk memeriksa isinya, klik tiga titik, lalu bagikan, dan salah satu opsi yang muncul adalah Excalidraw. Ketika saya mengetuk ikon tersebut, saya dapat melihat bahwa {i>file<i} berisi logo I/O lagi.

Lipis pada versi Electron yang tidak digunakan lagi

Satu hal yang dapat Anda lakukan dengan file yang belum saya bicarakan adalah menggandakan file tersebut. Hal yang biasanya terjadi jika Anda menggandakan file adalah aplikasi yang terkait dengan jenis MIME file tersebut akan terbuka. Misalnya, .docx adalah Microsoft Word.

Excalidraw sebelumnya memiliki versi Electron untuk aplikasi yang mendukung pengaitan jenis file tersebut, sehingga saat Anda mengklik dua kali file .excalidraw, aplikasi Excalidraw Electron akan terbuka. Lipis, yang sudah Anda temui sebelumnya, adalah pembuat dan deprecator Excalidraw Electron. Saya bertanya mengapa dia merasa bahwa penggunaan versi Elektron dapat dihentikan:

Orang-orang telah meminta aplikasi Electron sejak awal, terutama karena ingin membuka file dengan mengklik dua kali. Kami juga bermaksud untuk menempatkan aplikasi di app store. Secara paralel, seseorang menyarankan pembuatan PWA, jadi kami melakukan keduanya. Untungnya kami 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 membebani Elektron ekstra. Sangatlah mudah untuk menghentikan penggunaan versi Electron, berkonsentrasi hanya pada aplikasi web, dan menjadikannya PWA terbaik. Selain itu, sekarang kami dapat memublikasikan PWA ke Play Store dan Microsoft Store. Besar sekali!

Dapat dikatakan bahwa Excalidraw untuk Elektron tidak digunakan lagi karena Elektron itu buruk, bukan sama sekali, tetapi karena web telah menjadi cukup baik. Aku suka ini!

Penanganan file

Ketika saya mengatakan "web telah menjadi cukup baik", itu karena fitur seperti Penanganan File yang akan datang.

Ini adalah penginstalan Big Sur macOS biasa. Sekarang perhatikan apa yang terjadi ketika saya mengklik kanan file Excalidraw. Saya dapat memilih untuk membukanya dengan Excalidraw, PWA yang diinstal. Tentu saja mengklik dua kali juga akan berfungsi, tetapi mendemonstrasikannya di screencast menjadi tidak begitu menarik.

Jadi, bagaimana cara kerjanya? Langkah pertama adalah membuat jenis file yang dapat ditangani aplikasi saya diketahui oleh sistem operasi. Saya melakukan ini di kolom baru bernama file_handlers dalam manifes aplikasi web. Nilainya adalah array objek dengan tindakan dan properti accept. Tindakan tersebut akan menentukan jalur URL tempat sistem operasi meluncurkan aplikasi Anda, dan objek yang menerima merupakan 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 dalam 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 mempedulikan yang pertama. Dari handle file ini, saya mendapatkan blob yang kemudian diteruskan 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 proses ini berjalan terlalu cepat, Anda dapat membaca selengkapnya tentang File Handling API di artikel saya. Anda dapat mengaktifkan penanganan file dengan menyetel tanda fitur platform web eksperimental. Aplikasi tersebut dijadwalkan akan tersedia di Chrome akhir tahun ini.

Integrasi papan klip

Fitur keren lainnya dari Excalidraw adalah integrasi papan klip. Saya dapat menyalin seluruh gambar atau hanya sebagian gambar ke papan klip, mungkin menambahkan watermark, lalu menempelkannya ke aplikasi lain. Ini adalah versi web aplikasi Windows 95 Paint.

Cara kerjanya sangat sederhana. Yang saya butuhkan hanyalah kanvas sebagai blob, yang kemudian saya tulis ke papan klip dengan meneruskan array satu elemen bersama ClipboardItem bersama blob ke fungsi navigator.clipboard.write(). Untuk informasi selengkapnya tentang apa yang dapat Anda lakukan dengan API clipboard, Lihat 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 pada dokumen yang sama. Untuk memulai sesi baru, saya mengklik tombol kolaborasi langsung, lalu memulai sesi. Saya dapat membagikan URL sesi kepada kolaborator dengan mudah berkat Web Share API yang telah terintegrasi dengan Excalidraw.

Kolaborasi live

Saya telah menyimulasikan sesi kolaborasi secara lokal dengan menggunakan logo Google I/O di Pixelbook, ponsel Pixel 3a, dan iPad Pro saya. Anda dapat melihat bahwa perubahan yang saya buat pada satu perangkat akan diterapkan di semua perangkat lain.

Saya bahkan bisa melihat semua kursor bergerak. Kursor Pixelbook bergerak perlahan karena dikontrol oleh trackpad, tetapi kursor ponsel Pixel 3a dan kursor tablet iPad Pro melompat-lompat, karena saya mengontrol perangkat ini dengan mengetuk menggunakan jari saya.

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 browser atau aplikasi lain. Saat membuka aplikasi Excalidraw, tetapi tidak melakukan apa pun, kursor menunjukkan saya tidak ada aktivitas, yang dilambangkan dengan tiga titik zZZ.

Pembaca aktif publikasi kami mungkin cenderung berpikir bahwa deteksi tidak ada aktivitas direalisasikan melalui Idle Detection API, proposal tahap awal yang telah dikerjakan dalam konteks Project Fugu. Peringatan {i>spoiler<i}: tidak benar. Meskipun kami memiliki implementasi berdasarkan API ini di Excalidraw, pada akhirnya, kami memutuskan untuk menggunakan pendekatan yang lebih tradisional berdasarkan pengukuran gerakan pointer dan visibilitas halaman.

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

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

Lipis di atas apa yang menahan Excalidraw

Berbicara mengenai hal tersebut, saya mengajukan satu pertanyaan terakhir kepada lipis mengenai apa yang menurutnya hilang dari platform web yang menahan Excalidraw:

File System Access API memang hebat, tetapi tahukah Anda? Sebagian besar file yang penting bagi saya saat ini 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 berintegrasi dan dapat digunakan oleh developer untuk melakukan coding. Pengguna kemudian dapat bersantai dan tahu bahwa file mereka aman dengan penyedia cloud yang mereka percayai.

Saya sepenuhnya setuju dengan lipis, saya juga tinggal di cloud. Semoga hal ini dapat segera diimplementasikan.

Mode aplikasi dengan tab

Hebat! Kami telah melihat banyak integrasi API yang sangat bagus di Excalidraw. Sistem file, penanganan file, papan klip, berbagi web, dan target berbagi web. Tapi, ada satu hal lagi. Sampai sekarang, saya hanya bisa mengedit satu dokumen pada waktu tertentu. Jangan lagi. Untuk pertama kalinya, nikmati versi awal mode aplikasi bertab di Excalidraw. Seperti inilah tampilannya.

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

Mode aplikasi dengan tab masih dalam tahap awal dan tidak semuanya bersifat permanen. 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 menonton pelacak API Fugu. Kami sangat bersemangat untuk memajukan web dan memungkinkan Anda melakukan lebih banyak hal di platform ini. Mari kita sambut Excalidraw yang terus ditingkatkan, dan inilah untuk semua aplikasi luar biasa yang akan Anda bangun. Mulai buat konten di excalidraw.com.

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