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 metninde, yeni Fugu API'lerinden bazılarını ve bunların Excalidraw PWA'daki temel kullanıcı yolculuklarını nasıl iyileştirdiğini ele alacağım. 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 bir tweet paylaştı. Bu araçla, karikatür gibi görünen ve elle çizilmiş kutular ve oklar çizebilirsiniz. Ertesi gün elips ve metin çizebilir, 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

Henüz iki hafta önce başlayan bir proje için bu hiç de fena değil. Ancak ilgimi en çok çeken şey, yayının ilerleyen kısmındaydı. Christopher bu sefer yeni bir şey denediğini yazdı: Bir çekme isteği gönderen herkese koşulsuz birleştirme erişimi verme. Blog yayınını okuduğum gün, Excalidraw'a File System Access API desteği ekleyen ve bir kullanıcının gönderdiği özellik isteğini düzelten bir pull isteğinde bulundum.

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

Bir gün sonra, çekme isteğim birleştirildi ve o andan itibaren tam birleştirme erişimine sahip oldum. 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 koyu 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.

Excalidraw PWA'nın bugünkü durumunun ekran görüntüsü.

Lipis, zamanının büyük bir kısmını Excalidraw'a neden ayırdığını anlatıyor

"Excalidraw ile nasıl tanıştım?" hikayemin sonuna geldik. Excalidraw'ın muhteşem özelliklerinden bazılarını incelemeden önce Panayiotis ile tanışmak isterim. İnternette lipis olarak bilinen Panayiotis Lipiridis, Excalidraw'a en çok katkıda bulunan kullanıcıdır. lipis'e zamanının büyük bir kısmını Excalidraw'a ayırmasını sağlayan motivasyonu sorduk:

Herkes gibi ben de bu projeden Christopher'ın tweet'inden haberdar oldum. İlk katkım, Excalidraw'da hâlâ kullanılan renkleri içeren Open Color kitaplığını eklemekti. Proje büyüdükçe ve çok sayıda istek aldık. Bu nedenle, bir sonraki büyük katkım, kullanıcıların paylaşabilmesi için çizimleri depolayacak bir arka uç oluşturmak oldu. Ancak Excalidraw'ı deneyen herkesin tekrar kullanmaya devam etmek için bahane aradığını bilmek, katkıda bulunmama ilham veriyor.

Lipis'e tamamen katılıyorum. Excalidraw'ı deneyen herkes, tekrar kullanmak için bahane arıyor.

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

Şimdi Excalidraw'u pratikte nasıl kullanabileceğinizi göstermek istiyorum. Çok iyi bir sanatçı değilim ancak Google I/O logosu yeterince basit. Bir deneyeyim. Kutu, "i", çizgi ise eğik çizgi olabilir. "o" ise bir dairedir. Üst Karakter tuşunu basılı tutarak mükemmel bir daire çiziyorum. Daha iyi görünmesi için eğik çizgiyi biraz taşıyacağım. Ş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, gölgelendirme harika görünüyor. Mükemmel değil ama Excalidraw'ın amacı bu. Dosyayı kaydedeyim.

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

Logoyu değiştirip "i" harfini kırmızı yapalım. Şimdi tekrar Kaydet'i tıklarsam yaptığım değişiklik öncekiyle 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 tekrar orada.

Dosyalarla çalışma

Şu anda Dosya Sistemi Erişimi API'sini desteklemeyen tarayıcılarda her kayıt işlemi bir indirme işlemi olduğundan, değişiklik yaptığımda dosya adında artan bir sayı içeren ve İndirilenler klasörümü dolduran birden fazla dosya elde ediyorum. Ancak bu olumsuz duruma rağmen dosyayı kaydedebilirim.

Dosyaları açma

Peki sırrı nedir? Dosya Sistemi Erişimi API'yi destekleyen veya desteklemeyen farklı tarayıcılarda açma ve kaydetme işlemi nasıl çalışır? 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 dosya seçilip seçilmediğine bağlı olarak bir dosya dizisi veya tek bir dosya döndürür. Artık dosyanın yeniden alınabilmesi için dosya adını dosya nesnesine eklemeniz yeterlidir.

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üne sahip bir input öğesine dayanır. Kabul edilecek MIME türleri ve uzantılar üzerinde pazarlık yapıldıktan sonra, bir sonraki adımda dosya açma iletişim kutusunun gösterilmesi için giriş öğesi programlı olarak tıklanır. Değişiklik olduğunda, yani kullanıcı bir veya daha fazla dosya seçtiğinde söz verilen değer çözümlenir.

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 tasarrufa geçelim. Excalidraw'da kayıt 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 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. Daha önce kaydettiğim ve dosya adı zaten mevcut olduğunda kaydetme iletişim kutusunun gösterilmesi gerekmez. Ancak bu ilk kayıtsa bir dosya iletişim kutusu gösterilir ve uygulama gelecekte kullanmak üzere bir dosya tutamacını alır. Geriye kalan işlem, dosyaya yazmaktır. Bu işlem, yazılabilir bir akış üzerinden gerçekleşir.

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ı bozmaz.

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, ankraj öğesi programatik olarak tıklanır. Bellek sızıntısını önlemek için blob URL'sinin kullanımdan sonra iptal edilmesi gerekir. Bu işlem yalnızca bir indirme olduğundan hiçbir zaman dosya kaydetme iletişim kutusu gösterilmez ve tüm dosyalar varsayılan Downloads klasörüne kaydedilir.

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ır ve düzenlemeye başlayabilirim. 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 tutamacını, yukarıdaki birkaç paragraftan hatırlayabileceğiniz loadFromBlob() işlevine iletirim. Dosyalarla yapabileceğiniz çok şey var: açma, kaydetme, üzerine kaydetme, sürükleme, bırakma. İş arkadaşım Pete ve ben, tüm bu ipuçlarını ve daha fazlasını makalemizde ele aldık. Bu bilgilere göz atarak daha önce kaçırdığınız konuları öğrenebilirsiniz.

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'sidir. Burada, Dosyalar uygulamasındaki Downloads klasöründeyim. Biri untitled şeklinde açıklayıcı 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

Dosyalarla yapabileceğiniz ve henüz bahsetmediğim bir işlem de dosyaları çift tıklamaktır. Bir dosyayı çift tıkladığınızda genellikle dosyanın MIME türüyle ilişkili uygulama açılır. Örneğin, .docx için Microsoft Word olur.

Excalidraw'ın bir Electron sürümü vardı. Bu sürüm, bu tür dosya türü ilişkilendirmelerini desteklerdi. Böylece, 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 geliştiricisi hem de desteği sonlandırılmasını sağlayan kişidir. 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ün desteğini sonlandırmak, yalnızca web uygulamasına odaklanmak ve bunu mümkün olan en iyi PWA haline getirmek kolay bir karardı. Ayrıca artık Play Store ve Microsoft Store'da PWA yayınlayabiliriz. Bu çok önemli.

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 kurulumudur. Ş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. Elbette çift tıklama da işe yarar. Ancak ekran kaydı yaparken bu yöntemi göstermek daha az etkileyici olur.

Peki, bu nasıl çalışıyor? İlk adım, uygulamamın işleyebileceği dosya türlerini işletim sistemine bildirmektir. Bunu web uygulaması manifest dosyasında 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 nesnesi, MIME türlerinin ve ilişkili dosya uzantılarının anahtar/değer çiftleridir.

{
  "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, launchQueuearayüzünde meydana gelir. Bu arayüzde, setConsumer() çağrısı yaparak bir tüketici ayarlamam gerekir. Bu işlevin parametresi, launchParams değerini alan bir asynkron 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 bilgiler çok hızlı geçtiyse File Handling API hakkında daha fazla bilgiyi makalemde bulabilirsiniz. Deneysel web platformu özellikleri işaretini ayarlayarak dosya işlemeyi etkinleştirebilirsiniz. Bu özelliğin bu yılın ilerleyen aylarında Chrome'a eklenmesi planlanmaktadır.

Klavye panosuna entegrasyon

Excalidraw'ın bir diğer harika özelliği de panosuyla entegrasyondur. Ç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.

Bu işlemin işleyiş şekli şaşırtıcı derecede basittir. Tek ihtiyacım olan, tuvalin blob olarak ifadesidir. Ardından, blob ile birlikte ClipboardItem içeren bir tek öğeli diziyi navigator.clipboard.write() işlevine ileterek tuvali Clipboard'e yazarım. 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, Pixel 3a telefonum ve iPad Pro'm üzerinde Google I/O logosu üzerinde çalışarak yerel bir ortak çalışma oturumu simüle ettim. Bir cihazda yaptığım değişikliklerin diğer tüm cihazlara yansıtıldığını görebilirsiniz.

Hatta tüm imleçlerin hareket ettiğini 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

Gerçek zamanlı ortak çalışma deneyimini iyileştirmek için boşta kalma algılama sistemi bile çalışır. iPad Pro'yu kullandığımda imleç 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ı takip edenler, boşta kalma algılamanın Fugu Projesi bağlamında üzerinde çalışılan erken aşamadaki bir öneri olan Boşta Kalma Algılama API'si aracılığıyla gerçekleştirildiğini düşünebilir. 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 geçmeye karar verdik.

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

Boş Durma Algılama API'sinin neden kullanım alanımızı çözmediğine dair geri bildirim gönderdik. Project Fugu API'lerinin tümü açık olarak geliştiriliyor. Böylece herkes fikirlerini paylaşabilir ve seslerini 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 harika bir API'dir ancak ne biliyor musunuz? Şu anda önemsediğim dosyaların çoğu, sabit diskimde değil Dropbox'ta veya Google Drive'da bulunuyor. 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, dosyalarının güvendikleri bulut sağlayıcıda güvende olduğunu bilerek rahatlayabilir.

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, panos, web paylaşımı ve web paylaşımı hedefi. Ancak bir konuyu daha hatırlatmak isteriz. Şimdiye 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ür.

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 bağımsız olarak bu dosyalarda çalışabilirim.

Sekmeli uygulama modu henüz ilk aşamalarında olduğundan her şey kesin değildir. Bu özellik ilginizi çekiyorsa makalemde bu özelliğin mevcut durumu hakkında bilgi edinebilirsiniz.

Kapanış

Bu ve diğer özelliklerden haberdar olmak için Fugu API takipçimizi izlemeyi unutmayın. Web'i ileriye taşımak ve platformda daha fazla şey yapmanıza olanak tanımak için sabırsızlanıyoruz. Sürekli gelişen Excalidraw'a ve oluşturacağınız tüm muhteşem uygulamalara selam olsun. 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. Twitter'da ve internette @tomayac olarak beni bulabilirsiniz. İzlediğiniz için çok teşekkürler. Google I/O'nun geri kalanını keyifle izleyin.