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-fitur ini meningkatkan perjalanan pengguna inti di PWA Excalidraw, sehingga Anda dapat mengambil inspirasi dari ide-ide ini dan menerapkannya ke aplikasi Anda sendiri.

Bagaimana saya bisa sampai di Excalidraw

Saya ingin mulai dengan sebuah cerita. Pada tanggal 1 Januari 2020, Christopher Chedeau, seorang software engineer di Facebook, tweet tentang aplikasi menggambar kecil yang dia miliki mulai dikerjakan. Dengan alat ini, Anda bisa menggambar kotak dan panah yang terlihat seperti kartun dan digambar tangan. Keesokan harinya, Anda juga bisa menggambar elips dan teks, serta memilih objek dan bergerak dengan mereka. Pada tanggal 3 Januari, aplikasi ini mendapatkan namanya, Excalidraw, dan, hampir semua sisi baiknya , membeli nama domain adalah salah satu tindakan pertama Christopher. Menurut 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 tanggal 15 Januari, Christopher mengeluarkan postingan blog yang menarik yang menarik perhatian di Twitter, termasuk akun saya. Postingan ini dimulai dengan beberapa statistik yang mengesankan:

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

Untuk proyek yang dimulai hanya dua minggu yang lalu, itu sama sekali tidak masalah. Tapi hal yang paling melonjak minat saya pada bagian yang lebih dalam. Christopher menulis bahwa dia mencoba sesuatu yang baru ini waktu: memberi semua orang yang mendapatkan akses commit tanpa syarat untuk permintaan pull. Hari yang sama membaca postingan blog, saya menerima permintaan pull yang menambahkan dukungan API Akses Sistem File ke Excalidraw, memperbaiki permintaan fitur yang telah diajukan oleh seseorang.

Screenshot tweet tempat saya mengumumkan PR saya.

Permintaan pull saya digabungkan sehari kemudian, dan dari sana, saya memiliki akses commit penuh. Tentu saja, Aku tidak menyalahgunakan kekuatanku. Dan tidak ada 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 ya, kemampuan untuk membuka dan menyimpan file berkat API Akses Sistem File.

Screenshot PWA Excalidraw dalam status saat ini.

Lipi menjelaskan alasan dia mendedikasikan sebagian besar waktunya untuk Excalidraw

Ini menandai akhir dari "bagaimana saya sampai di Excalidraw" tetapi sebelum saya menyelami beberapa Fitur Excalidraw yang luar biasa, dengan senang hati saya memperkenalkan Panayiotis. Panayiotis Lipiridis, aktif Internet yang dikenal sebagai lipis, merupakan kontributor paling produktif terhadap Excalidraw. Saya bertanya kepada lipis, apa yang memotivasinya untuk mendedikasikan sebagian besar waktunya untuk Excalidraw:

Seperti orang lain, saya mempelajari proyek ini dari tweet Christopher. Kontribusi pertama saya adalah menambahkan Open Color library, warna yang masih bagian dari Excalidraw hari ini. Seiring berkembangnya proyek dan kami memiliki banyak permintaan, proyek besar saya kontribusinya adalah membangun backend untuk menyimpan gambar sehingga pengguna dapat membagikannya. Tapi apa yang sangat mendorong saya untuk berkontribusi adalah siapa pun yang mencoba Excalidraw ingin menemukan alasan untuk menggunakan bisa digunakan lagi.

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

Beraksi dalam Excalidraw

Saya ingin menunjukkan kepada Anda sekarang Anda bisa menggunakan Excalidraw dalam praktik. Saya bukan seniman yang hebat, tapi Logo Google I/O cukup sederhana, jadi saya akan mencobanya. Sebuah kotak adalah “i”, sebuah garis bisa berupa dan "o" adalah lingkaran. Saya menahan shift, agar saya mendapatkan lingkaran yang sempurna. Saya akan bergerak sedikit garis miring, agar terlihat lebih baik. Sekarang beberapa warna untuk "i" dan "o". Biru itu bagus. Mungkin gaya isian yang berbeda? Semuanya solid, atau cross-hatch? Tidak, hachure sepertinya keren. Tidak sempurna, tapi itu adalah ide Excalidraw, jadi saya akan menyimpannya.

Saya mengeklik ikon {i>save<i} dan memasukkan nama {i>file<i} dalam dialog {i>file storage<i}. Di Chrome, browser yang mendukung File System Access API, ini bukan download, tapi operasi penyimpanan yang sesungguhnya, di mana saya bisa memilih lokasi dan nama file, dan di mana, jika saya mengedit, saya bisa menyimpannya ke file yang sama.

Saya akan mengubah logo dan membuat "i" merah. Jika sekarang saya mengklik simpan lagi, perubahan saya akan disimpan ke file yang sama seperti sebelumnya. Sebagai buktinya, saya akan membersihkan kanvas dan membuka kembali file tersebut. Sekarang Anda memahami bahwa logo merah-biru yang dimodifikasi ada di sana lagi.

Bekerja dengan file

Pada browser yang saat ini tidak mendukung File System Access API, setiap operasi penyimpanan mengunduh, jadi ketika saya membuat perubahan, saya mendapatkan banyak file dengan angka yang bertambah di yang memenuhi folder {i>Downloads<i}. Tapi meskipun ada kelemahan, saya masih bisa menyimpan file.

Membuka file

Jadi apa rahasianya? Bagaimana cara membuka dan menyimpan berfungsi di browser berbeda, yang mungkin atau tidak mendukung File System Access API? Membuka file di Excalidraw terjadi dalam fungsi yang disebut 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 menyediakan akses sistem file melalui File System Access API dengan penggantian lama, sehingga dapat digunakan di semua lokasi browser.

Pertama-tama, saya akan menunjukkan implementasi saat API didukung. Setelah menegosiasikan jenis MIME dan ekstensi file yang diterima, bagian utamanya adalah fungsi showOpenFilePicker(). Fungsi ini menampilkan array file atau satu file, bergantung pada pada apakah beberapa file akan dipilih. Langkah terakhir adalah meletakkan handle file pada file , sehingga 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 jenis "file". Setelah negosiasi jenis dan ekstensi MIME yang akan diterima, langkah berikutnya adalah mengklik input secara terprogram sehingga dialog {i>file open<i} akan ditampilkan. Saat berubah, yaitu, ketika pengguna telah memilih satu atau beberapa file, promise tersebut 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 menyimpan. Di Excalidraw, penyimpanan terjadi dalam fungsi yang disebut saveAsJSON(). Pertama melakukan serialisasi array elemen Excalidraw ke JSON, mengonversi JSON menjadi blob, lalu memanggil fungsi yang disebut fileSave(). Fungsi ini juga disediakan oleh 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. Tujuan beberapa baris pertama terlihat sedikit rumit, tetapi yang dilakukan hanyalah menegosiasikan jenis dan file MIME ekstensi. Setelah saya menyimpan sebelumnya dan sudah memiliki {i>handle<i} file, tidak perlu ada dialog {i>save<i} ditampilkan. Tetapi jika ini adalah penyimpanan pertama, dialog file akan ditampilkan dan aplikasi mendapatkan handle file untuk digunakan di masa mendatang. 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;
};

Opsi "simpan sebagai" fitur

Jika memutuskan untuk mengabaikan handle file yang sudah ada, saya dapat menerapkan "{i>save as<i}" fitur untuk membuat file baru berdasarkan file yang sudah ada. Untuk menampilkannya, izinkan saya membuka file yang ada, membuat beberapa modifikasi, dan tidak menimpa file yang ada, namun buat file baru dengan menggunakan perintah {i>save-as<i} aplikasi baru. Tindakan ini membiarkan file asli tetap utuh.

Implementasi untuk browser yang tidak mendukung File System Access API berlangsung singkat, karena 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 memerlukan akan dicabut setelah digunakan. Karena ini hanyalah download, tidak akan ada dialog penyimpanan file yang ditampilkan, dan 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 menjatuhkan .excalidraw ke aplikasi, akan langsung terbuka dan saya dapat mulai mengedit. Di browser yang mendukung File System Access API, saya bahkan dapat segera menyimpan perubahan saya. Tidak perlu pergi melalui dialog penyimpanan file karena handle file yang diperlukan diperoleh dari aktivitas tarik lalu lepas operasi.

Rahasia untuk mewujudkannya adalah dengan memanggil getAsFileSystemHandle() pada item transfer data jika File System Access API didukung. Saya akan meneruskan ke loadFromBlob(), yang mungkin Anda ingat dari beberapa paragraf di atas. Banyak hal-hal yang dapat Anda lakukan dengan file: membuka, menyimpan, menyimpan terlalu banyak, menyeret, melepas. Rekan saya, Pete Saya telah mendokumentasikan semua trik ini dan lainnya di artikel kami sehingga Anda dapat kejar 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 lainnya saat ini di Android, ChromeOS, dan Windows adalah melalui Web Share Target API. Sekarang saya berada di aplikasi Files dalam folder Downloads saya. diri dapat melihat dua file, salah satunya dengan nama non-deskriptif untitled dan stempel waktu. Untuk memeriksa apa berisi, saya klik pada tiga titik, lalu bagikan, 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.

Lipi pada versi Electron yang tidak digunakan lagi

Satu hal yang bisa Anda lakukan dengan file yang belum saya bahas adalah DoubleClick. Apa yang biasanya terjadi jika Anda melakukan doubleclick pada file adalah aplikasi yang terkait dengan jenis MIME file terbuka. Misalnya untuk .docx, ini adalah Microsoft Word.

Excalidraw yang sebelumnya memiliki versi Electron aplikasi yang mendukung asosiasi jenis file tersebut, jadi ketika Anda mengklik dua kali file .excalidraw, Aplikasi Excalidraw Electron akan terbuka. Lipis, yang sudah Anda temui sebelumnya, adalah kreator dan penghentian Excalidraw Electron. Saya bertanya mengapa dia merasa bahwa penggunaan Versi elektron:

Orang-orang telah meminta aplikasi Electron sejak awal, terutama karena mereka ingin membuka file dengan mengeklik dua kali. Kami juga bermaksud untuk memasukkan aplikasi tersebut di app store. Secara paralel, seseorang menyarankan untuk membuat PWA, jadi kami hanya melakukan keduanya. Untungnya kita diperkenalkan dengan Project Fugu API seperti akses sistem file, akses papan klip, penanganan file, dan lainnya. Dengan sekali klik, Anda dapat instal aplikasi tersebut di desktop atau perangkat seluler Anda, tanpa perlu menggunakan Electron ekstra. Ini adalah pengalaman yang mudah keputusan untuk menghentikan penggunaan versi Electron, berkonsentrasi hanya pada aplikasi web, dan PWA yang terbaik. Selain itu, kini kami dapat memublikasikan PWA ke Play Store dan Microsoft Simpan! Besar sekali!

Bisa dikatakan {i>Excalidraw<i} untuk Elektron tidak digunakan lagi karena Elektron itu buruk, sama sekali tidak, tapi karena web telah menjadi cukup baik. Saya suka ini!

Proses penanganan file

Saat saya mengatakan "web telah menjadi cukup baik", hal itu disebabkan oleh fitur seperti File Penanganan.

Ini adalah penginstalan reguler macOS Big Sur. Sekarang lihat apa yang terjadi ketika saya mengklik kanan sebuah File Excalidraw. Saya dapat memilih untuk membukanya dengan Excalidraw, PWA yang terinstal. Tentu saja klik dua kali juga akan berfungsi, hanya saja tidak terlalu dramatis untuk didemonstrasikan di screencast.

Jadi bagaimana cara kerjanya? Langkah pertama adalah membuat jenis file yang dapat ditangani aplikasi saya yang dibutuhkan serta sistem operasi. Saya melakukan ini di kolom baru bernama file_handlers di manifes aplikasi web. Ini adalah array objek dengan tindakan dan properti accept. Tindakan menentukan URL jalur tempat sistem operasi meluncurkan aplikasi Anda dan objek Accept adalah pasangan nilai kunci MIME dan ekstensi file yang 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 launchQueue di mana saya perlu menetapkan konsumen dengan memanggil setConsumer(). Parameter ke adalah fungsi asinkron yang menerima launchParams. Objek launchParams ini memiliki bidang bernama file yang memberi saya serangkaian {i>handle<i} file untuk digunakan. Saya hanya peduli dengan pertama dan dari nama sebutan file ini, saya mendapatkan blob yang kemudian saya teruskan ke teman lama 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 berjalan terlalu cepat, Anda dapat membaca lebih lanjut tentang File Handling API di artikel saya. Anda dapat mengaktifkan penanganan file dengan menyetel platform web eksperimental penanda fitur. Fitur ini dijadwalkan untuk hadir di Chrome pada akhir tahun ini.

Integrasi papan klip

Fitur keren lainnya dari Excalidraw adalah integrasi papan klip. Saya dapat menyalin seluruh gambar atau sebagian ke dalam {i>clipboard<i}, menambahkan {i> watermark<i} jika perlu, lalu menempelkannya ke aplikasi lain. Ini adalah versi web dari 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 dengan ClipboardItem dengan blob ke fungsi navigator.clipboard.write(). Untuk informasi selengkapnya tentang hal yang dapat Anda lakukan dengan papan klip 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 kamu bahwa Excalidraw juga memiliki mode kolaboratif? Orang yang berbeda dapat bekerja sama dalam dokumen yang sama. Untuk memulai sesi baru, saya mengklik tombol kolaborasi langsung, lalu memulai sesi. Saya dapat membagikan URL sesi kepada kolaborator saya dengan mudah berkat Web Share API yang telah diintegrasikan 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 tercermin pada semua perangkat lainnya.

Saya bahkan bisa melihat semua kursor bergerak. Kursor Pixelbook bergerak stabil, karena terkontrol dengan 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 menunjukkan titik hijau saat saya menggunakannya. Titik menjadi hitam ketika saya beralih ke tab atau aplikasi browser yang berbeda. Dan saat saya berada di aplikasi Excalidraw, tetapi tidak melakukan apa pun, kursor menunjukkan saya sebagai tidak ada aktivitas, disimbolkan dengan tiga zZZ.

Pembaca aktif terhadap publikasi kami mungkin cenderung berpikir bahwa deteksi tidak ada aktivitas diwujudkan melalui Idle Detection API, proposal tahap awal yang telah dikerjakan dalam konteks proyek Fugu. Peringatan spoiler: sebenarnya tidak. Meskipun kita 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 memberikan masukan tentang alasan Idle Detection API tidak menyelesaikan kasus penggunaan kami. Semua Project Fugu API sedang dikembangkan secara terbuka, sehingga semua orang bisa ikut serta dan suaranya didengar!

Lipis tentang hal yang menghambat Excalidraw

Mengenai itu, saya mengajukan satu pertanyaan terakhir kepada lipis mengenai apa yang menurutnya hilang dari web platform yang menahan Excalidraw:

File System Access API memang bagus, tetapi Anda tahu? Sebagian besar file yang saya perlukan sekarang berada di Dropbox atau Google Drive, bukan di {i>hard disk<i} saya. Saya berharap File System Access API menyertakan lapisan abstraksi untuk penyedia sistem file jarak jauh seperti Dropbox atau Google untuk mengintegrasikan dan yang bisa digunakan pengembang untuk membuat kode. Pengguna kemudian dapat merasa tenang dan mengetahui bahwa file mereka aman dengan penyedia {i>cloud <i} yang mereka percaya.

Saya sepenuhnya setuju dengan lipis, dan saya juga tinggal di cloud. Semoga perubahan ini akan diterapkan segera.

Mode aplikasi dengan tab

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

Saya sudah membuka file di PWA Excalidraw terinstal yang berjalan dalam mode mandiri. Baru saja Saya membuka tab baru di jendela mandiri. Ini bukan tab browser biasa, tetapi tab PWA. Di sini tab baru 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 final. Jika Anda tertarik, pastikan untuk membaca tentang status fitur saat ini di artikel saya.

Penutup

Untuk terus mendapatkan info terbaru tentang fitur ini dan fitur lainnya, pastikan untuk menonton Pelacak Fugu API. Kami sangat bersemangat untuk memajukan web dan memungkinkan Anda melakukan lebih banyak hal di platform ini. Semoga Excalidraw semakin baik, dan kami ucapkan aplikasi luar biasa yang akan Anda bangun. Mulai berkreasi dari excalidraw.com.

Saya tidak sabar ingin 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 telah menonton, dan selamat menikmati Google I/O selanjutnya.