Excalidraw ve Fugu: Temel Kullanıcı Yolculuklarını İyileştirme

Yeterince gelişmiş teknoloji, sihirden ayırt edilemez. Anlamadığınız sürece. Adım Thomas Steiner. Google'da Geliştirici İlişkileri bölümünde çalışıyorum. Google I/O konuşmamın bu yazımında yeni Fugu API'lerinden bazılarına ve Excalidraw PWA'daki temel kullanıcı yolculuklarını nasıl iyileştirdiklerine değineceğim. Böylece, bu fikirlerden ilham alıp kendi uygulamalarınıza uygulayabilirsiniz.

Excalidraw'a nasıl geldim?

Bir hikayeyle başlamak istiyorum. 1 Ocak 2020'de, Facebook'ta yazılım mühendisi olan Christopher Chedeau, üzerinde çalışmaya başladığı küçük bir çizim uygulamasıyla ilgili tweet'ler yayınladı. Bu araçla, karikatür gibi görünen ve elle çizilmiş kutular ve oklar çizebilirsiniz. Ertesi gün, elipsler ve metinler çizebildiğiniz gibi nesneleri seçip taşıyabilirsiniz. 3 Ocak'ta uygulamanın adı Excalidraw oldu ve her iyi yan projede olduğu gibi Christopher'ın ilk işlerinden biri alan adını satın almak oldu. Artık renkleri kullanabilir ve çizimin tamamını PNG olarak dışa aktarabilirsiniz.

Excalidraw prototip uygulamasının, dikdörtgenleri, okları, elipsleri ve metni desteklediğini gösteren ekran görüntüsü.

15 Ocak'ta Christopher, Twitter'da benim de dikkatimi çeken bir blog yayını yayınladı. Yayın, etkileyici istatistiklerle başladı:

  • 12.000 benzersiz etkin kullanıcı
  • GitHub'da 1.500 yıldız
  • 26 katkıda bulunan

Sadece iki hafta önce başlamış olan bir proje için bu hiç de kötü bir şey değil. Ancak asıl ilgimi çeken şey yayının daha alt kısımlarındaydı. Christopher bu sefer yeni bir şey denediğini yazdı: Bir çekme isteği gönderen herkese koşulsuz commit erişimi verme. Blog yayınını okuduğum gün, Excalidraw'a File System Access API desteği ekleyerek bir özellik isteğini düzeltmek için bir çekme isteği aldım.

PR'mi duyurduğum tweet'in ekran görüntüsü.

Push isteğim bir gün sonra birleştirildi ve daha sonra tam kaydetme erişimim oldu. Elbette gücümü kötüye kullanmadım. 149 katkıda bulunandan hiçbiri de bu konuda bir sorun yaşamadı.

Günümüzde Excalidraw, çevrimdışı destek, çarpıcı bir karanlık mod ve File System Access API sayesinde dosya açma ve kaydetme özelliğine sahip, tam teşekküllü, yüklenebilir bir ilerici web uygulamasıdır.

Bugünkü durumdaki Excalidraw PWA'nın ekran görüntüsü.

Neden bu kadar zamanını Excalidraw'a ayırdığıyla ilgili Lipis

Böylece, "Excalidraw'a nasıl geldim" hikayemin sonuna geldik. Ancak Excalidraw'ın muhteşem özelliklerinden bazılarına değinmeden önce, Panayiotis'i tanıtmaktan memnuniyet duyuyorum. İnternette lipis olarak bilinen Panayiotis Lipiridis, Excalidraw'a en çok katkıda bulunan kullanıcıdır. Lipi'lere, vaktinin çoğunu Excalidraw'a ayırmaya neyin motive ettiğini sordum:

Herkes gibi ben de bu projeden Christopher'ın tweet'inden haberdar oldum. İlk katkım, bugün Excalidraw'ın parçası olan renkler olan Open Color kitaplığı'nı eklemekti. Proje büyüdükçe ve çok fazla talep aldıkça bir sonraki büyük katkım, çizimlerin depolanacağı bir arka uç oluşturarak kullanıcıların bunları paylaşabilmesi oldu. Ancak Excalidraw'ı deneyen herkesin tekrar kullanmaya devam etmek için bahane aradığını bilmek, katkıda bulunmama ilham veriyor.

Lipi'lere tamamen katılıyorum. Excalidraw'ı deneyen her kişi, bu programı tekrar kullanmak için bahaneler arıyor.

Excalidraw'un kullanıldığı bir örnek

Şimdi Excalidraw'u pratikte nasıl kullanabileceğinizi göstermek istiyorum. İyi bir sanatçı değilim, ancak Google I/O logosu yeterince basit, hemen deneyeyim. Kutu "i", çizgi eğik çizgi ve "o" daire olabilir. Mükemmel bir daire çizebilmek için üst karakter tuşunu basılı tutuyorum. Daha iyi görünmesi için eğik çizgiyi biraz hareket ettireceğim. Şimdi "i" ve "o" için biraz renk ekleyin. Mavi iyidir. Farklı bir dolgu stili mi? Tümüyle dolu mu yoksa çapraz tarama mı? Hayır, hachure harika görünüyor. Mükemmel değil ama Excalidraw'ın amacı bu. Dosyayı kaydedeyim.

Kaydet simgesini tıklıyorum ve dosya kaydetme iletişim kutusuna bir dosya adı giriyorum. Chrome'da, File System Access API'yi destekleyen bir tarayıcı. Bu bir indirme değil, dosyanın konumunu ve adını seçip düzenleme yaptığımda bunları aynı dosyaya kaydedebileceğim gerçek bir kaydetme işlemidir.

Logoyu değiştirip "i"yi kırmızı yapalım. Şimdi tekrar Kaydet'i tıklarsanız yaptığım değişiklik öncekiyle aynı dosyaya kaydedilir. Kanıt olarak, tuvali temizleyip dosyayı yeniden açıyorum. Gördüğünüz gibi, değiştirilmiş kırmızı-mavi logo tekrar orada.

Dosyalarla çalışma

Şu anda File System Access API'yi desteklemeyen tarayıcılarda her kaydetme işlemi bir indirme işlemidir. Bu nedenle, değişiklik yaptığımda, dosya adında İndirilenler klasörümü dolduran, sayıları artan birden çok dosyayla karşılaşıyorum. Ancak bu olumsuz duruma rağmen dosyayı kaydedebilirim.

Dosya açma

Peki sırrı nedir? File System Access API'yi destekleyen veya desteklemeyen farklı tarayıcılarda açma ve kaydetme işlemi nasıl yapılabilir? Excalidraw'da bir dosyanın açılması, loadFromJSON)( adlı bir işlevde gerçekleşir. Bu işlev de fileOpen() adlı bir işlevi çağırır.

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);
};

fileOpen() işlevi, yazdığım ve Excalidraw'da kullandığımız browser-fs-access adlı küçük bir kitaplıktan gelir. Bu kitaplık, eski bir yedeklemeyle File System Access API aracılığıyla dosya sistemi erişimi sağlar. Bu nedenle, herhangi bir tarayıcıda kullanılabilir.

Öncelikle API'nin desteklendiği durumlardaki uygulamayı göstereyim. Kabul edilen MIME türleri ve dosya uzantılarıyla ilgili pazarlık yapıldıktan sonra, merkezi parça File System Access API'nin showOpenFilePicker() işlevini çağırır. Bu işlev, birden fazla dosyanın seçilip seçilmediğine bağlı olarak bir dosya dizisi veya tek bir dosya döndürür. Bundan sonra tek yapmanız gereken, dosya tanıtıcısını dosya nesnesine yerleştirmektir. Böylece yeniden alınabilir.

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;
  };
};

Yedek uygulama, "file" türünde bir input öğesini kullanır. Kabul edilecek MIME türleri ve uzantıları belirlendikten sonraki adım, dosya açma iletişim kutusunun gösterilmesi için giriş öğesini programatik olarak tıklamaktır. Değişiklik yapıldığında, yani kullanıcı bir veya daha fazla dosya seçtiğinde taahhüt yerine getirilir.

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();
  });
};

Dosya kaydetme

Şimdi kaydedilmeye geçelim. Excalidraw'da kaydetme işlemi, saveAsJSON() adlı bir işlevde gerçekleşir. Önce Excalidraw öğeleri dizisini JSON olarak serileştirir, JSON'u bir blob'a dönüştürür ve ardından fileSave() adlı bir işlevi çağırır. Bu işlev de aynı şekilde browser-fs-access kitaplığı tarafından sağlanır.

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 };
};

Öncelikle File System Access API desteği olan tarayıcılarda uygulamaya bakalım. İlk birkaç satır biraz karmaşık görünse de bunların tek yaptıkları MIME türleri ve dosya uzantılarıyla pazarlık yapmaktır. Önceden kaydettiğim bir dosya herkese açık kullanıcı adına sahipse kaydetme iletişim kutusunun gösterilmesi gerekmez. Ancak bu ilk kayıtsa bir dosya iletişim kutusu görüntülenir ve uygulamaya daha sonra kullanabileceğiniz bir dosya adı verilir. Geri kalanı ise sadece dosyaya yazılır ve bu işlem yazılabilir bir akış aracılığıyla gerçekleştirilir.

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;
};

"Farklı kaydet" özelliği

Mevcut bir dosya adını yoksaymaya karar verirsem mevcut bir dosyaya dayalı yeni bir dosya oluşturmak için "farklı kaydet" özelliğini uygulayabilirim. Bunu göstermek için mevcut bir dosyayı açıp bazı değişiklikler yapacağım. Ardından, mevcut dosyanın üzerine yazmak yerine "farklı kaydet" özelliğini kullanarak yeni bir dosya oluşturacağım. Bu işlem, orijinal dosyayı olduğu gibi bırakır.

Tek yaptığı, değeri istenen dosya adı olan bir download özelliği ve href özellik değeri olarak bir blob URL'si olan bir ana sayfa öğesi oluşturmak olduğundan, Dosya Sistemi Erişimi API'sini desteklemeyen tarayıcılar için uygulama kısadır.

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();
};

Ardından, sabit öğe programatik olarak tıklanır. Bellek sızıntılarını önlemek için blob URL'sinin kullanımdan sonra iptal edilmesi gerekir. Bu yalnızca bir indirme işlemi olduğundan, hiçbir dosya kaydetme iletişim kutusu gösterilmez ve tüm dosyalar varsayılan Downloads klasörüne yerleştirilir.

Sürükle ve bırak

Masaüstünde en sevdiğim sistem entegrasyonlarından biri sürükle ve bırak özelliğidir. Excalidraw'da bir .excalidraw dosyasını uygulamaya bıraktığımda dosya hemen açılıyor ve düzenlemeye başlayabiliyorum. File System Access API'yi destekleyen tarayıcılarda değişikliklerimi hemen kaydedebilirim. Gerekli dosya tutamaç sürükle ve bırak işleminden elde edildiğinden dosya kaydetme iletişim kutusuna gitmenize gerek yoktur.

Bunun için File System Access API desteklendiğinde veri aktarımı öğesinde getAsFileSystemHandle() çağrısı yapmanız gerekir. Ardından, bu dosya herkese açık kullanıcı adını loadFromBlob() hesabına iletiyorum. Yukarıdaki birkaç paragrafta bunu hatırlarsınız. Dosyalarla yapabileceğiniz pek çok şey vardır: açma, kaydetme, fazla kaydetme, sürükleme, bırakma. İş arkadaşım Pete ve ben, tüm bu püf noktalarını ve daha fazlasını makalemizde açıkladık. Böylece tüm bu işler biraz hızlı ilerlerse görebilirsiniz.

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 });
  });
}

Dosyaları paylaşma

Şu anda Android, ChromeOS ve Windows'ta kullanılan bir diğer sistem entegrasyonu da Web Paylaşımı Hedef API'dir. Downloads klasörümdeki Dosyalar uygulamasındayım. Biri untitled şeklinde anlamlı olmayan bir ada ve zaman damgasına sahip iki dosya görüyorum. İçeriğini kontrol etmek için üç noktayı ve ardından paylaş'ı tıklıyorum. Açılan seçeneklerden biri de Excalidraw. Simgeye dokunduğumda, dosyanın yine yalnızca G/Ç logosunu içerdiğini görüyorum.

Desteği sonlandırılan Electron sürümünde Lipis

Henüz bahsetmediğim dosyalarla yapabileceklerinizden biri de onları doubleclick'e yazmaktır. Bir dosyayı tıkladığınızda genellikle dosyanın MIME türüyle ilişkilendirilmiş uygulama açılır. Örneğin, .docx için Microsoft Word kullanılır.

Excalidraw'ın bir Electron sürümü vardı. Bu sürüm, bu tür dosya türü ilişkilendirmelerini desteklerdi. Bu nedenle, bir .excalidraw dosyasını çift tıkladığınızda Excalidraw Electron uygulaması açılırdı. Daha önce tanıştığınız Lipis, Excalidraw Electron'un hem yaratıcısı hem de çürüten kişiydi. Electron sürümünün desteğinin neden sonlandırılabileceğini düşündüğünü sordum:

Kullanıcılar, başta dosyaları çift tıklayarak açmak istedikleri için başlangıçtan beri bir Electron uygulaması istiyordu. Ayrıca uygulamayı uygulama mağazalarına da koymayı planlıyorduk. Bu sırada, bunun yerine bir PWA oluşturmayı önerdiler. Bu yüzden ikisini de yaptık. Neyse ki dosya sistemi erişimi, pano erişimi, dosya işleme ve daha fazlası gibi Project Fugu API'lerini kullanmaya başladık. Electron'un ek yükünü yüklemeden, tek bir tıklamayla uygulamayı masaüstünüze veya mobil cihazınıza yükleyebilirsiniz. Electron sürümünü kullanımdan kaldırmaya, yalnızca web uygulamasına odaklanmayı ve onu mümkün olan en iyi PWA'ya dönüştürmeye kolayca karar verdik. Ayrıca artık Play Store ve Microsoft Store'da PWA yayınlayabiliriz. Muazzam!

Electron için Excalidraw'ın desteğinin sonlandırılmasının nedeni Electron'un kötü olması değil, web'in yeterince iyi hale gelmesidir. Bunu beğendim!

Dosya işleme

"Web artık yeterince iyi" derken, yakında kullanıma sunulacak Dosya İşleme gibi özelliklerden bahsediyorum.

Bu, normal bir macOS Big Sur yüklemesidir. Şimdi bir Excalidraw dosyasını sağ tıkladığımda ne olduğuna göz atın. Dosyayı, yüklü PWA olan Excalidraw ile açmayı seçebilirim. Çift tıklama da elbette işe yarar. Ekran video kaydında bunu göstermek daha az dramatiktir.

Peki bu nasıl çalışır? İlk adım, uygulamamın işleyebileceği dosya türlerini işletim sistemi tarafından bilinmesini sağlamak. Bunu, web uygulaması manifestindeki file_handlers adlı yeni bir alanda yapıyorum. Değeri, bir işlem ve accept özelliğine sahip bir nesne dizisidir. İşlem, işletim sisteminin uygulamanızı başlattığı URL yolunu belirler ve kabul edilen nesne, MIME türlerinin anahtar/değer çiftleri ve ilişkili dosya uzantılarıdır.

{
  "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"]
      }
    }
  ]
}

Bir sonraki adım, uygulama başlatıldığında dosyayı işleme almaktır. Bu işlem launchQueue arayüzünde, setConsumer() numaralı telefonu arayarak bir tüketici ayarlamam gerekiyor. Bu işlevin parametresi, launchParams parametresini alan eşzamansız bir işlevdir. Bu launchParams nesnesinde, üzerinde çalışacağım bir dizi dosya tutamaçını alan files adlı bir alan vardır. Yalnızca ilkiyle ilgileniyorum ve bu dosya tutamacından bir blob alıyorum. Ardından bu blob'u eski dostumuz loadFromBlob()'e iletiyorum.

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 });
      });
    });
}

Bu işlem çok hızlı ilerlediyse bu makalede File handling API ile ilgili daha fazla bilgi edinebilirsiniz. Deneysel web platformu özellikleri işaretini ayarlayarak dosya işlemeyi etkinleştirebilirsiniz. Bu yılın ilerleyen dönemlerinde Chrome'da yayınlanması planlanıyor.

Pano entegrasyonu

Excalidraw'ın bir diğer iyi özelliği de pano entegrasyonu. Çizimimin tamamını veya yalnızca bir kısmını panoya kopyalayıp dilerseniz üzerine filigran ekleyerek başka bir uygulamaya yapıştırabilirim. Bu arada, bu Windows 95 Paint uygulamasının web sürümüdür.

Bunun çalışma şekli şaşırtıcı derecede basit. İhtiyacım olan tek şey tuvali bir blob olarak göndermek. Daha sonra bunu navigator.clipboard.write() işlevine blob içeren ClipboardItem içeren tek öğeli bir dizi ileterek panoya yazıyorum. Pano API'si ile neler yapabileceğiniz hakkında daha fazla bilgi için Jason'ın ve benim makalemizi inceleyin.

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);
    }
  });
};

Diğer kullanıcılarla ortak çalışma

Oturum URL'sini paylaşma

Excalidraw'un ortak çalışma modu olduğunu biliyor muydunuz? Farklı kullanıcılar aynı dokümanda birlikte çalışabilir. Yeni bir oturum başlatmak için canlı ortak çalışma düğmesini tıklayıp oturum başlatıyorum. Excalidraw'ın entegre ettiği Web Share API sayesinde oturum URL'sini ortak çalışanlarımla kolayca paylaşabiliyorum.

Canlı ortak çalışma

Pixelbook cihazım, Pixel 3a telefonum ve iPad Pro'mdaki Google I/O logosu üzerinde çalışarak yerel olarak ortak çalışma oturumunu simüle ettim. Bir cihazda yaptığım değişikliklerin diğer tüm cihazlara yansıtıldığını görebilirsiniz.

Tüm imleçlerin hareket ettiğini bile görebiliyorum. Pixelbook'un imleci, dokunmatik yüzeyle kontrol edildiği için sabit bir şekilde hareket eder. Ancak Pixel 3a telefonun imleci ve iPad Pro'nun tablet imleci, bu cihazları parmağımla dokunarak kontrol ettiğim için etrafta hareket eder.

Ortak çalışan durumlarını görme

Hatta gerçek zamanlı ortak çalışma deneyimini iyileştirmek için çalışan boşta kalma algılama sistemi bile var. iPad Pro'yu kullandığımda imleci yeşil bir nokta gösteriyor. Farklı bir tarayıcı sekmesine veya uygulamaya geçtiğimde nokta siyah olur. Excalidraw uygulamasındayken hiçbir şey yapmadığımda ise imleç üç zZZ ile gösterilerek boşta olduğumu belirtir.

Yayınlarımızın tutkulu okuyucuları, boşta kalma algılamasının, Fugu Projesi bağlamında üzerinde çalışılan erken aşamadaki bir teklif olan Idle Detection API ile gerçekleştirildiğini düşünmeye meyilli olabilir. Spoiler uyarısı: Bu doğru değil. Excalidraw'da bu API'ye dayalı bir uygulamamız olsa da sonunda işaretçi hareketini ve sayfa görünürlüğünü ölçmeye dayalı daha geleneksel bir yaklaşıma karar verdik.

WICG Boşta Kalma Algılama deposuna gönderilen Boşta Algılama geri bildiriminin ekran görüntüsü.

Idle Detection API'nin sahip olduğumuz kullanım alanını neden çözmediği hakkında geri bildirim gönderdik. Tüm Project Fugu API'leri açık ortamda geliştirilmektedir. Bu sayede herkes sohbete katılıp sesini duyurabilir.

Lipis, Excalidraw'ın gelişimini engelleyen faktörler hakkında

Bu arada, Lipis'e web platformunda Excalidraw'ın gelişimini engelleyen eksikliklerle ilgili son bir soru sordum:

File System Access API çok iyi, ama ne olduğunu biliyor musunuz? Bugünlerde önemsediğim çoğu dosya sabit diskimde değil, Dropbox'ımda veya Google Drive'ımda. File System Access API'nin, Dropbox veya Google gibi uzak dosya sistemi sağlayıcıların entegre edebileceği ve geliştiricilerin kod yazabileceği bir soyutlama katmanı içermesini isterdim. Böylece kullanıcılar güvendikleri bulut sağlayıcı ile rahatlayıp dosyalarının güvende olduğunu anlayabilir.

lipis\'in dediğine tamamen katılıyorum. Ben de bulutta yaşıyorum. Bu özelliğin yakında kullanıma sunulmasını umuyoruz.

Sekmeli uygulama modu

İnanılmaz! Excalidraw'da çok sayıda harika API entegrasyonu gördük. Dosya sistemi, dosya işleme, pano, web paylaşımı ve web paylaşımı hedefi. Ancak bir konuyu daha hatırlatmak isteriz. Şu ana kadar tek seferde yalnızca bir dokümanı düzenleyebiliyordum. Artık değil. Lütfen Excalidraw'da sekmeli uygulama modunun ilk sürümünü kullanmanın keyfini çıkarın. Bu şekilde görünüyor.

Yüklü Excalidraw PWA'da bağımsız modda çalışan mevcut bir dosyam var. Ardından, bağımsız pencerede yeni bir sekme açıyorum. Bu normal bir tarayıcı sekmesi değil, PWA sekmesidir. Bu yeni sekmede, ikincil bir dosya açabilir ve aynı uygulama penceresinden bu dosyalar üzerinde bağımsız olarak çalışabilirim.

Sekmeli uygulama modu henüz ilk aşamalarındadır ve tüm özellikleri sabitlenmiş değildir. İlgileniyorsanız bu özelliğin mevcut durumu hakkında bilgi edinmek için makalemi inceleyebilirsiniz.

Kapanış

Bu ve diğer özelliklerden haberdar olmak için Fugu API izleyicimizi izlemeyi unutmayın. Web'i ileriye taşıyacak ve platformda daha fazlasını yapabilmenizi sağlamaktan büyük heyecan duyuyoruz. Sürekli gelişen Excalidraw'ı ve geliştireceğiniz muhteşem uygulamaları burada bulabilirsiniz. excalidraw.com adresinden çizim yapmaya başlayın.

Bugün gösterdiğim API'lerden bazılarının uygulamalarınızda kullanıldığını görmek için sabırsızlanıyorum. Adım Tom. Beni Twitter'da ve genel olarak internette @tomayac olarak bulabilirsiniz. İzlediğiniz için çok teşekkür eder ve Google I/O'daki diğer başarılarınızın keyfini çıkarmanızı dileriz.