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

Yeterince gelişmiş teknolojiler, sihirden ayırt edilemez. Ama siz anlamadıkça. Adım Thomas Steiner. Google'da Geliştirici İlişkileri bölümünde çalışıyorum. Google I/O konuşmamdaki bu yazıda, bazı yeni Fugu API'lerine ve bu API'lerin 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ızda kullanabilirsiniz.

Excalidraw'a nasıl geldim?

Bir hikayeyle başlamak istiyorum. 1 Ocak 2020'de Facebook'ta yazılım mühendisi olan Christopher Chedeau, daha önce üzerinde çalışmaya başladığı küçük bir çizim uygulaması hakkında tweet attı. Bu aracı kullanarak, karikatür tarzında ve elle çizilmiş kutular ve oklar çizebilirsiniz. Ertesi gün, elipsler ve metin çizebilir, nesneleri seçebilir ve bunları hareket ettirebilirsiniz. 3 Ocak'ta uygulama Excalidraw adını almıştı ve her iyi yan projede olduğu gibi alan adını satın almak Christopher'ın ilk faaliyetlerinden biriydi. Artık renkleri kullanıp tüm çizimi 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ü.

Christopher, 15 Ocak'ta benimki de dahil olmak üzere Twitter'da çok fazla ilgi çeken bir blog yayını yayınladı. Gönderi bazı etkileyici istatistiklerle başladı:

  • 12 bin benzersiz etkin kullanıcı
  • GitHub'da 1,5K yıldız
  • 26 katkıda bulunan kullanıcı

Henüz iki hafta önce başlamış bir proje için bu durum hiç de fena sayılmaz. Ancak ilgimi gerçekten artıran şey, yazının daha aşağılarındaydı. Christopher bu kez yeni bir şey denediğini yazdı: Çekme isteği alan herkese koşulsuz kaydetme erişimi verdi. Blog yayınını okuduğum gün aynı gün, Excalidraw'a File System Access API desteğini ekleyerek birinin gönderdiği bir özellik isteğini düzelttiğini bildiren bir çekme isteği aldım.

Halkla ilişkilerımı duyurduğum tweet'in ekran görüntüsü.

Pull isteğim bir gün sonra birleştirildi ve bundan sonra tam kayıt erişimine sahip oldum. Gücümü kötüye kullanmadım, söz etmeye gerek yok. Ayrıca şimdiye kadar katkıda bulunan 149 katılımcı arasından başka hiç kimse çıkmadı.

Şu anda Excalidraw çevrimdışı destek ve baş döndürücü bir koyu moda sahip olmanın yanı sıra, Dosya Sistemi Erişimi API'si sayesinde dosya açıp kaydetme olanağı sunan tam kapsamlı bir yüklenebilir progresif web uygulamasıdır.

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

Vaktini neden Excalidraw'a ayırdığı hakkında Lipis

Böylece, "Excalidraw'a nasıl geldim" hikayemin sonuna geldik. Ancak, Excalidraw'un muhteşem özelliklerinden bazılarına geçmeden önce, size Panayiotis'i tanıtmaktan memnuniyet duyuyorum. İnternette lipis olarak bilinen Panayiotis Lipiridis, Excalidraw'a en çok katkıda bulunan isimdir. Lipis ile bu kadar çok vaktini Excalidraw'a ayırmaya neyin motive ettiğini sordum:

Bu projeyle ilgili herkes gibi Christopher'ın tweet'inden de haberdar oldum. İlk yaptığım iş, bugün Excalidraw'da hâlâ yer alan renkler Open Color kitaplığı'nı eklemek oldu. Proje büyüdükçe ve çok sayıda talep aldıkça, bir sonraki büyük katkım kullanıcıların bunları paylaşabilmesi için çizimlerin saklanacağı bir arka uç oluşturmak oldu. Ancak beni katkıda bulunmaya iten asıl şey, Excalidraw'u deneyenlerin tekrar kullanmak için bahaneler bulmaya çalışması.

Dudaklara kesinlikle katılıyorum. Excalidraw'u deneyen herkes tekrar kullanmak için bahane bulmaya çalışır.

Excalidraw iş başında

Şimdi size Excalidraw'u nasıl kullanabileceğinizi göstermek istiyorum. Harika bir sanatçı değilim ancak Google I/O logosu yeterince basit. O zaman deneyeyim. Kutu "i"dir, çizgi eğik çizgi, "o" ise daire olabilir. Mükemmel daireyi elde etmek için üst karakter tuşunu basılı tutuyorum. Daha iyi görünmesi için eğik çizgiyi biraz değiştireyim. Şimdi "i" ve "o" için renklendirin. Mavi iyidir. Farklı bir dolgu stili olabilir mi? Tamamen düz mü yoksa çapraz hat mı? Hayır, hachure harika görünüyor. Bu mükemmel değil, ama Excalidraw'un amacı bu. O halde kaydedeyim.

Kaydet simgesini tıklayıp dosya kaydetme iletişim kutusunda bir dosya adı giriyorum. Chrome'da, File System Access API'yi destekleyen bir tarayıcı, indirme değil, dosyanın konumunu ve adını seçebildiğim ve düzenleme yaparsam bunları aynı dosyaya kaydedebileceğim gerçek bir kaydetme işlemidir.

Logoyu değiştireyim ve "i"yi kırmızı yapalım. Kaydet'i tekrar tıklarsam değişikliğim önceki dosyayla aynı dosyaya kaydedilir. Kanıt olarak, kanvası temizleyip dosyayı yeniden açacağım. Gördüğünüz gibi, değiştirilmiş kırmızı-mavi logo yine 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 İndirilenler klasörümü dolduran, dosya adında artan rakama sahip birden çok dosya elde ediyorum. Ancak bu olumsuz yanıma rağmen dosyayı kaydedebilirim.

Dosyaları açma

Peki sırrı ne? Dosya açma ve kaydetme, File System Access API'yi destekleyen veya desteklemeyecek farklı tarayıcılarda nasıl çalışabilir? Excalidraw'da bir dosya açılırken loadFromJSON)( adlı bir işlev kullanılır. 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);
};

Yazdığım browser-fs-access adlı küçük bir kitaplıktan gelen ve Excalidraw'da kullandığımız fileOpen() işlevi. Bu kitaplık, eski bir yedek ile File System Access API üzerinden dosya sistemine erişim sağlar. Böylece tüm tarayıcılarda kullanılabilir.

Öncelikle API'nın desteklendiği zamanlardaki uygulamayı göstereyim. Kabul edilen MIME türleri ve dosya uzantıları için pazarlık yapıldıktan sonra, ana unsur File System Access API'nin işlevini showOpenFilePicker() çağırır. Bu işlev, birden fazla dosyanın seçili olup olmamasına bağlı olarak bir dosya dizisi veya tek bir dosya döndürür. Geriye sadece dosya tutamacının dosya nesnesine yerleştirilmesi, böylece tekrar 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 uygulaması "file" türünde bir input öğesine dayanır. Kabul edilecek MIME türlerinin ve uzantılarının görüşülmesinden sonraki adım, dosya açma iletişim kutusunun gösterilmesi için giriş öğesini programlı olarak tıklamaktır. Değişimde, kullanıcı bir veya daha fazla dosya seçtiğinde amacın gerçeğe dönüşmesidir.

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

Dosyalar kaydediliyor

Şimdi kaydetmeye başlayabilirsiniz. Excalidraw'da, kaydetme işlemi saveAsJSON() adlı bir işlevde gerçekleşir. Öncelikle Excalidraw öğeler dizisini JSON'a dönüştürür, JSON'ı 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 };
};

Tekrar, öncelikle File System Access API desteği olan tarayıcılardaki uygulamaya göz atmak istiyorum. İlk birkaç satır biraz karmaşık görünse de tek yaptıkları MIME türleri ve dosya uzantıları üzerinde anlaşmaktır. Daha önce kaydettim ve zaten bir dosya işleyicim olduğunda, kaydetme iletişim kutusunun gösterilmesine gerek yoktur. Ancak bu ilk kaydetme işlemiyse, bir dosya iletişim kutusu görüntülenir ve uygulama, gelecekte kullanılmak üzere geri bir dosya tanıtıcısı alır. Gerisi yalnızca dosyaya yazılır ve bu işlem yazılabilir bir akış üzerinden 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

Zaten mevcut bir dosya tanıtıcısı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 yapıp mevcut dosyanın üzerine yazmayacağım. Farklı kaydet özelliğini kullanarak yeni bir dosya oluşturalım. Bu işlem, orijinal dosyayı olduğu gibi bırakır.

File System Access API'yi desteklemeyen tarayıcılar için uygulama kısadır. Bunun nedeni, değeri istenen dosya adı olan download özelliğine sahip bir bağlayıcı öğe ve href özellik değeri olarak da bir blob URL'si oluşturmaktı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();
};

Daha sonra bağlayıcı öğe programatik olarak tıklanır. Bellek sızıntılarını önlemek için blob URL'sinin kullanıldıktan sonra iptal edilmesi gerekir. Bu yalnızca bir indirme işlemi olduğundan hiçbir zaman 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ği. Excalidraw'da, bir .excalidraw dosyasını uygulamaya bıraktığımda, dosya hemen açılır ve düzenlemeye başlayabilirim. File System Access API'yi destekleyen tarayıcılarda değişikliklerimi anında kaydedebiliyorum. Gereken dosya tanıtıcısı, sürükle ve bırak işlemiyle elde edildiği için dosya kaydetme iletişim kutusunu kullanmaya gerek yoktur.

Bunu yapmanın sırrı, File System Access API desteklendiğinde veri aktarımı öğesinde getAsFileSystemHandle() çağrısı yapmaktır. Daha sonra, bu dosya tanıtıcısını yukarıdaki birkaç paragraftan hatırlayabileceğiniz loadFromBlob() öğesine iletiyorum. 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 listeledik. Böylece her şeyin biraz hızlı ilerlemesi ihtimaline karşı önlem alabilirsiniz.

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'da kullanılan bir başka sistem entegrasyonu ise Web Paylaşımı Hedef API'si üzerinden yapılır. İşte Downloads klasörümdeki Dosyalar uygulamasında. Birinde açıklayıcı olmayan ad untitled ve zaman damgası olan iki dosya görüyorum. İçeriğini kontrol etmek için üç noktayı tıklayıp Paylaş'ı görüyorum. Görüntülenen seçeneklerden biri de Excalidraw. Simgeye dokunduğumda dosyanın yalnızca I/O logosunu içerdiğini görebiliyorum.

Kullanımdan kaldırılan Electron sürümündeki Lipi'ler

Henüz ele almadığım dosyalarla yapabileceğiniz bir şey, dosyalarla ilgili olarak çift eklemektir. Bir dosyaya DoubleClick'i tıkladığınızda genellikle dosyanın MIME türüyle ilişkili uygulama açılır. Örneğin, .docx için bu Microsoft Word olur.

Excalidraw, bu tür dosya türü ilişkilendirmelerini destekleyen uygulamanın bir Electron sürümüne sahipti. Böylece, .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 kullanımdan kaldıranlarıydı. Ona Electron sürümünü neden kullanımdan kaldırmanın mümkün olduğunu sordum:

Kullanıcılar, en başından beri Electron uygulamasını talep ediyorlardı. Bunun en önemli nedeni dosyaları çift tıklayarak açmaktı. Uygulamayı uygulama mağazalarında da sunmayı amaçladık. Buna paralel olarak birisi PWA yerine PWA oluşturmayı önerdi, bu yüzden biz de ikisini de yaptık. Neyse ki dosya sistemi erişimi, pano erişimi, dosya işleme gibi Project Fugu API'leriyle tanıştık. Tek bir tıklamayla uygulamayı Electron'un fazla ağırlığı olmadan masaüstünüze veya mobil cihazınıza yükleyebilirsiniz. Electron sürümünü kullanımdan kaldırmak, yalnızca web uygulamasına odaklanmak ve onu mümkün olan en iyi PWA yapmak çok kolay bir karardı. Üstelik, PWA'ları artık Play Store ve Microsoft Store'da yayınlayabiliyoruz. Muazzam!

Örneğin, Electron kötü olduğu için Excalidraw'ın kullanımdan kaldırılmadığını söyleyebiliriz. Kesinlikle değil, ancak web yeterince iyi hale geldiği için. Bunu beğendim!

Dosya işleme

"Web yeterince iyi hale geldi" dememin nedeni, yakında kullanıma sunulacak Dosya İşleme gibi özellikler.

Bu, normal bir macOS Big Sur yüklemesidir. Şimdi bir Excalidraw dosyasını sağ tıkladığımda ne olduğuna bakalım. Uzantıyı yüklü PWA olan Excalidraw ile açmayı seçebilirim. Çift tıklama da tabii ki işe yarayacaktır. Ekran video kaydında göstermek daha az dramatiktir.

Peki bu nasıl çalışır? İlk adım, uygulamamın işleyebileceği dosya türlerini işletim sistemine bildirmektir. Bunu web uygulaması manifest dosyasındaki file_handlers adlı yeni bir alanda yapıyorum. Değeri, bir işlem ve accept özelliği olan bir nesne dizisidir. İşlem, işletim sisteminin uygulamanızı başlattığı URL yolunu belirler ve kabul nesnesi, 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"]
      }
    }
  ]
}

Sonraki adım, uygulama başlatıldığında dosyayı işlemektir. Bu işlem launchQueue arayüzünde, setConsumer() numaralı telefonu arayarak bir tüketici belirlemem gerekiyor. Bu işlevin parametresi, launchParams parametresini alan eşzamansız bir işlevdir. Bu launchParams nesnesinde, üzerinde çalışabileceğim bir dizi dosya işleyicisi getiren, dosyalar adında bir alan var. Yalnızca ilki önemsiyorum ve bu dosya işleyicisinden daha sonra eski arkadaşımıza ileteceğim loadFromBlob() bir blob alıyorum.

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 sorun hızlı sonuçlandıysa makalemde File Handling API hakkında daha fazla bilgiye ulaşabilirsiniz. Deneysel web platformu özellikleri bayrağını ayarlayarak dosya işlemeyi etkinleştirebilirsiniz. Bu yılın ilerleyen günlerinde Chrome'da kullanıma sunulması planlanmıştır.

Pano entegrasyonu

Excalidraw'un bir başka güzel özelliği de pano entegrasyonudur. Çizimimin tamamını veya yalnızca bir kısmını panoya kopyalayabilir, istersem bir filigran ekleyebilir ve sonra başka bir uygulamaya yapıştırabilirim. Bu arada, bu Windows 95 Paint uygulamasının web sürümüdür.

Bunun işleyiş şekli şaşırtıcı derecede basittir. Kanvasın blob olarak gösterilmesi gerekiyor. Daha sonra bunu, blob içeren ClipboardItem içeren tek öğeli bir diziyi navigator.clipboard.write() işlevine geçirerek panoya yazıyorum. Pano API ile neler yapabileceğiniz hakkında daha fazla bilgi için Jason's ve my makale bölümüne göz atın.

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

Başkalarıyla ortak çalışma

Oturum URL'sini paylaşma

Excalidraw'un aynı zamanda ortak çalışma modunu da içerdiğini biliyor muydunuz? Farklı kişiler aynı doküman üzerinde birlikte çalışabilir. Yeni bir oturum başlatmak için canlı ortak çalışma düğmesini tıklayıp bir oturum başlatıyorum. Excalidraw'un entegre ettiği Web Paylaşımı API'si sayesinde oturum URL'sini ortak çalıştığım kişilerle kolayca paylaşabiliyorum.

Canlı ortak çalışma

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

Tüm imleçlerin hareket ettiğini bile görebiliyorum. Pixelbook'un imleci, dokunmatik yüzey tarafından kontrol edildiği için durağan biçimde hareket ediyor, ancak bu cihazları parmağımla dokunarak kontrol ettiğim için Pixel 3a telefonun ve iPad Pro'nun tablet imleci etrafta zıplıyor.

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

Gerçek zamanlı ortak çalışma deneyimini iyileştirmek için çalışan bir boşta kalma algılama sistemi bile vardır. iPad Pro'yu kullandığımda imlecin yanında yeşil bir nokta görünüyor. Farklı bir tarayıcı sekmesine veya

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

WICG Boşta Kalma Algılama deposuna kaydedilen 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 konusunda geri bildirim aldık. Tüm Fugu Project API'leri açık havada geliştiriliyor. Böylece herkes devreye girebilir ve sesini duyurabilir.

Excalidraw'u tutan burçtaki lipis

Lipi'ye, Excalidraw'un gizlendiği web platformunda eksik olduğunu düşündüğü şeylerle ilgili son bir soru sordum:

File System Access API harika bir özellik. Peki ne olduğunu biliyor musun? Bugünlerde önemsediğim çoğu dosya sabit diskimde değil, Dropbox'ımda veya Google Drive'ımda yaşıyor. 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ıda dosyalarının güvende olduğunu anlayabilirler.

Lipi'lere kesinlikle katılıyorum, bulutta da yaşıyorum. Bunun yakında uygulanmasını umuyoruz.

Sekmeli uygulama modu

İnanılmaz! Excalidraw'da birçok muhteşem API entegrasyonu gördük. Dosya sistemi, dosya işleme, pano, web paylaşımı ve web paylaşımı hedefi. Ama bir şey daha var. Şu ana kadar aynı anda yalnızca bir dokümanı düzenleyebildim. Ama artık değil. Lütfen Excalidraw'da sekmeli uygulama modunun erken bir sürümünü ilk kez kullanın. Bu şekilde görünür.

Bağımsız modda çalışan yüklü Excalidraw PWA'da açık bir dosyam var. Şimdi bağımsız pencerede yeni bir sekme açıyorum. Bu normal bir tarayıcı sekmesi değil, PWA sekmesidir. Bu yeni sekmede daha sonra ikincil bir dosya açabiliyor ve aynı uygulama penceresinden bağımsız olarak bu dosyalar üzerinde çalışabiliyorum.

Sekmeli uygulama modu henüz ilk aşamalarındadır ve kesin bir çözüm değildir. Bu konu ilginizi çekiyorsa, bu özelliğin güncel durumunu öğrenmek için makalemi ziyaret etmeyi unutmayın.

Kapanma

Bu ve diğer özelliklerle ilgili gelişmelerden haberdar olmak için Fugu API izleyicimizi izlemeyi unutmayın. Web'i ileriye taşımak ve platformda daha fazla şey yapmanıza olanak sağlamaktan büyük heyecan duyuyoruz. Hep gelişen bir Excalidraw için ve burada geliştireceğiniz harika uygulamalar için buradayım. excalidraw.com adresine giderek içerik oluşturmaya başlayın.

Bugün gösterdiğim API'lerden bazılarını uygulamalarınızda görmek için sabırsızlanıyorum. Adım Tom, beni Twitter'da ve internette @tomayac olarak bulabilirsiniz. İzlediğiniz için çok teşekkür eder, Google I/O'nun geri kalanını keyifle izleyin.