Excalidraw และ Fugu: ปรับปรุงเส้นทางของผู้ใช้หลัก

เทคโนโลยีที่ก้าวหน้ามากพอจะแยกแยะจากเวทมนตร์ไม่ได้ เว้นแต่คุณจะเข้าใจ ผมชื่อ Thomas Steiner ทำงานในทีม Developer Relations ที่ Google และในบทความนี้ที่สรุปจากการพูดใน Google I/O ผมจะพูดถึง Fugu API ใหม่ๆ และวิธีที่ API เหล่านี้ปรับปรุงเส้นทางของผู้ใช้หลักใน PWA ของ Excalidraw เพื่อให้คุณได้รับแรงบันดาลใจจากแนวคิดเหล่านี้และนำไปใช้กับแอปของคุณเองได้

ที่มาของ Excalidraw

ฉันอยากเริ่มด้วยเรื่องราว เมื่อวันที่ 1 มกราคม 2020 Christopher Chedeau วิศวกรซอฟต์แวร์ที่ Facebook ทวีตเกี่ยวกับแอปวาดภาพขนาดเล็กที่เขาเพิ่ง เริ่มพัฒนา เครื่องมือนี้ช่วยให้คุณวาดกรอบและลูกศรที่ดูเป็นการ์ตูนและ วาดด้วยมือได้ ในวันถัดไป คุณยังวาดวงรีและข้อความ รวมถึงเลือกออบเจ็กต์และย้าย ไปมาได้ด้วย ในวันที่ 3 มกราคม แอปนี้ได้ชื่อว่า Excalidraw และเช่นเดียวกับโปรเจ็กต์เสริมดีๆ ทุกโปรเจ็กต์ การซื้อชื่อโดเมนก็เป็นหนึ่งในการดำเนินการแรกๆ ของ Christopher ตอนนี้คุณสามารถใช้สีและส่งออกภาพวาดทั้งหมดเป็น PNG ได้แล้ว

ภาพหน้าจอของแอปพลิเคชันต้นแบบ Excalidraw แสดงว่ารองรับสี่เหลี่ยม ลูกศร วงรี และข้อความ

เมื่อวันที่ 15 มกราคม Christopher ได้เผยแพร่บล็อกโพสต์ที่ได้รับความสนใจเป็นอย่างมากบน Twitter ซึ่งรวมถึงตัวฉันด้วย โพสต์เริ่มต้นด้วยสถิติที่น่าประทับใจดังนี้

  • ผู้ใช้ที่ใช้งานอยู่ที่ไม่ซ้ำกัน 12,000 ราย
  • มีดาว 1.5 พันดวงใน GitHub
  • ผู้ร่วมให้ข้อมูล 26 คน

สำหรับโปรเจ็กต์ที่เพิ่งเริ่มได้เพียง 2 สัปดาห์ ถือว่าไม่เลวเลย แต่สิ่งที่กระตุ้นความสนใจของฉันอย่างแท้จริงคือส่วนที่อยู่ลึกลงไปในโพสต์ Christopher เขียนว่าเขาได้ลองทำสิ่งใหม่ๆ ในครั้งนี้ นั่นคือให้สิทธิ์เข้าถึงการคอมมิตแบบไม่มีเงื่อนไขแก่ทุกคนที่ส่งคำขอพุล ในวันเดียวกันกับที่อ่านบล็อกโพสต์ ฉันได้คำขอพุลที่เพิ่มการรองรับ File System Access API ลงใน Excalidraw ซึ่งแก้ไขคำขอฟีเจอร์ที่ใครบางคนได้ส่งไว้

ภาพหน้าจอของทวีตที่ฉันประกาศสถิติส่วนตัว

คำขอพุลของฉันได้รับการผสานในวันถัดมา และหลังจากนั้นฉันก็มีสิทธิ์เข้าถึงการคอมมิตอย่างเต็มรูปแบบ และแน่นอนว่า ฉันไม่ได้ใช้อำนาจในทางที่ผิด และไม่มีใครจากผู้ร่วมให้ข้อมูล 149 คนที่ผ่านมา

ปัจจุบัน Excalidraw เป็น Progressive Web App ที่ติดตั้งได้เต็มรูปแบบพร้อม การรองรับแบบออฟไลน์ โหมดมืดที่สวยงาม และแน่นอนว่าสามารถเปิดและบันทึกไฟล์ได้ด้วย File System Access API

ภาพหน้าจอของ PWA ของ Excalidraw ในสถานะปัจจุบัน

Lipis เกี่ยวกับเหตุผลที่เขาอุทิศเวลาให้กับ Excalidraw มากขนาดนี้

เรื่องราว "ฉันมาใช้ Excalidraw ได้อย่างไร" ก็จบลงตรงนี้ แต่ก่อนที่จะไปดูฟีเจอร์ที่ยอดเยี่ยมบางอย่างของ Excalidraw ฉันขอแนะนำ Panayiotis Panayiotis Lipiridis หรือที่รู้จักกันในอินเทอร์เน็ตว่า lipis เป็นผู้มีส่วนร่วมที่สร้างผลงานมากที่สุดใน Excalidraw เราถาม Lipis ว่าอะไรเป็นแรงจูงใจให้เขาอุทิศเวลามากมายให้กับ Excalidraw

ฉันได้ทราบเกี่ยวกับโปรเจ็กต์นี้จากทวีตของ Christopher เหมือนกับคนอื่นๆ การมีส่วนร่วมครั้งแรกของฉัน คือการเพิ่มคลัง Open Color ซึ่งเป็นสีที่ยังคง เป็นส่วนหนึ่งของ Excalidraw ในปัจจุบัน เมื่อโปรเจ็กต์เติบโตขึ้นและเราได้รับคำขอจำนวนมาก การมีส่วนร่วมที่สำคัญครั้งต่อไปของฉันคือการสร้างแบ็กเอนด์สำหรับจัดเก็บภาพวาดเพื่อให้ผู้ใช้แชร์ภาพวาดได้ แต่สิ่งที่ กระตุ้นให้ฉันมีส่วนร่วมจริงๆ คือใครก็ตามที่เคยลองใช้ Excalidraw จะพยายามหาข้ออ้างเพื่อใช้ อีกครั้ง

ฉันเห็นด้วยกับ lipis อย่างยิ่ง ใครก็ตามที่เคยลองใช้ Excalidraw ก็คงอยากหาข้ออ้างเพื่อใช้มันอีกครั้ง

Excalidraw ขณะใช้งาน

ตอนนี้ฉันจะแสดงให้คุณเห็นวิธีใช้ Excalidraw ในทางปฏิบัติ ฉันไม่ใช่ศิลปินที่เก่งกาจ แต่โลโก้ Google I/O นั้นเรียบง่ายพอ ฉันจึงอยากลองวาดดู โดยกล่องคือ "i" เส้นคือ เครื่องหมายทับ และ "o" คือวงกลม ฉันกดปุ่ม Shift ค้างไว้เพื่อให้ได้วงกลมที่สมบูรณ์ เราจะย้าย เครื่องหมายทับเล็กน้อยเพื่อให้ดูดีขึ้น ตอนนี้ก็ใส่สีให้กับตัว "i" และ "o" สีน้ำเงินบ่งชี้ว่าดี อาจเป็น รูปแบบการเติมสีอื่น ทึบทั้งหมดหรือลายตาราง ไม่นะ เส้นชั้นความสูงดูดีแล้ว แม้จะไม่สมบูรณ์แบบ แต่ นั่นคือแนวคิดของ Excalidraw ดังนั้นฉันจะบันทึกไว้

ฉันคลิกไอคอนบันทึกและป้อนชื่อไฟล์ในกล่องโต้ตอบการบันทึกไฟล์ ใน Chrome ซึ่งเป็นเบราว์เซอร์ที่ รองรับ File System Access API การดำเนินการนี้ไม่ใช่การดาวน์โหลด แต่เป็นการดำเนินการบันทึกจริง ซึ่งฉันสามารถ เลือกตำแหน่งและชื่อของไฟล์ได้ และหากฉันแก้ไข ฉันก็สามารถบันทึกการแก้ไขลงใน ไฟล์เดียวกันได้

ฉันขอเปลี่ยนโลโก้และเปลี่ยนตัว "i" เป็นสีแดง หากตอนนี้ฉันคลิกบันทึกอีกครั้ง ระบบจะบันทึกการแก้ไขของฉันลงใน ไฟล์เดียวกับก่อนหน้านี้ เพื่อเป็นการพิสูจน์ ฉันจะล้าง Canvas แล้วเปิดไฟล์อีกครั้ง ดังที่คุณเห็น โลโก้สีแดงและสีน้ำเงินที่แก้ไขแล้วกลับมาปรากฏอีกครั้ง

การทำงานกับไฟล์

ในเบราว์เซอร์ที่ไม่รองรับ File System Access API ในปัจจุบัน การดำเนินการบันทึกแต่ละครั้งจะเป็นการดาวน์โหลด ดังนั้นเมื่อฉันทำการเปลี่ยนแปลง ฉันจึงมีไฟล์หลายไฟล์ที่มีหมายเลขเพิ่มขึ้นในชื่อไฟล์ซึ่งทำให้โฟลเดอร์ดาวน์โหลดเต็ม แต่ถึงจะมีข้อเสียนี้ ฉันก็ยังบันทึกไฟล์ได้

การเปิดไฟล์

แล้วเคล็ดลับคืออะไร การเปิดและบันทึกจะทำงานในเบราว์เซอร์ต่างๆ ที่อาจรองรับหรือไม่รองรับ File System Access API ได้อย่างไร การเปิดไฟล์ใน Excalidraw จะเกิดขึ้นในฟังก์ชันที่ชื่อ loadFromJSON)( ซึ่งจะเรียกใช้ฟังก์ชันที่ชื่อ 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);
};

ฟังก์ชัน fileOpen() ที่มาจากไลบรารีขนาดเล็กที่ฉันเขียนชื่อว่า browser-fs-access ซึ่งเราใช้ใน Excalidraw ไลบรารีนี้ให้สิทธิ์เข้าถึงระบบไฟล์ผ่าน File System Access API พร้อมการสำรองข้อมูลแบบเดิม จึงใช้ได้ในเบราว์เซอร์ทุกเบราว์เซอร์

ก่อนอื่นเรามาดูการติดตั้งใช้งานเมื่อระบบรองรับ API กัน หลังจากเจรจาต่อรอง ประเภท MIME และส่วนขยายไฟล์ที่ยอมรับแล้ว ส่วนสำคัญคือการเรียกใช้ฟังก์ชัน showOpenFilePicker() ของ File System Access API ฟังก์ชันนี้จะแสดงอาร์เรย์ของไฟล์หรือไฟล์เดียว ทั้งนี้ขึ้นอยู่กับว่ามีการเลือกหลายไฟล์หรือไม่ จากนั้นก็เพียงแค่ใส่ตัวแฮนเดิลไฟล์ในออบเจ็กต์ ไฟล์เพื่อให้เรียกข้อมูลได้อีกครั้ง

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

การใช้งานสำรองจะขึ้นอยู่กับองค์ประกอบ input ประเภท "file" หลังจากเจรจาต่อรอง ประเภท MIME และส่วนขยายที่จะยอมรับแล้ว ขั้นตอนถัดไปคือการคลิกองค์ประกอบอินพุต โดยอัตโนมัติเพื่อให้กล่องโต้ตอบการเปิดไฟล์แสดงขึ้น เมื่อมีการเปลี่ยนแปลง นั่นคือเมื่อผู้ใช้เลือกไฟล์อย่างน้อย 1 ไฟล์ สัญญาจะได้รับการแก้ไข

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

การบันทึกไฟล์

มาถึงการบันทึกแล้ว ใน Excalidraw การบันทึกจะเกิดขึ้นในฟังก์ชันที่ชื่อ saveAsJSON() โดยจะ แปลงอาร์เรย์ขององค์ประกอบ Excalidraw เป็น JSON ก่อน จากนั้นแปลง JSON เป็น Blob แล้วเรียกใช้ ฟังก์ชันที่ชื่อ fileSave() ฟังก์ชันนี้จัดทำโดยไลบรารี 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 };
};

อีกครั้งที่ฉันจะดูการติดตั้งใช้งานสำหรับเบราว์เซอร์ที่รองรับ File System Access API ก่อน บรรทัดแรกๆ อาจดูซับซ้อนเล็กน้อย แต่ทั้งหมดนี้มีไว้เพื่อเจรจาประเภท MIME และนามสกุลไฟล์ เมื่อฉันบันทึกไว้ก่อนหน้านี้และมีแฮนเดิลไฟล์อยู่แล้ว ก็ไม่จำเป็นต้องแสดงกล่องโต้ตอบการบันทึก แต่หากเป็นการบันทึกครั้งแรก กล่องโต้ตอบไฟล์จะแสดงขึ้นและแอปจะได้รับแฮนเดิลไฟล์ กลับมาเพื่อใช้ในอนาคต ส่วนที่เหลือก็คือการเขียนลงในไฟล์ ซึ่งจะเกิดขึ้นผ่านสตรีมที่เขียนได้

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

ฟีเจอร์ "บันทึกเป็น"

หากฉันตัดสินใจที่จะไม่สนใจแฮนเดิลไฟล์ที่มีอยู่แล้ว ฉันสามารถใช้ฟีเจอร์ "บันทึกเป็น" เพื่อสร้าง ไฟล์ใหม่โดยอิงตามไฟล์ที่มีอยู่ได้ หากต้องการแสดงให้เห็นภาพ ให้ฉันเปิดไฟล์ที่มีอยู่ ทำการแก้ไข บางอย่าง แล้วไม่เขียนทับไฟล์ที่มีอยู่ แต่สร้างไฟล์ใหม่โดยใช้ฟีเจอร์บันทึกเป็น ซึ่งจะทำให้ไฟล์ต้นฉบับยังคงเดิม

การติดตั้งใช้งานสำหรับเบราว์เซอร์ที่ไม่รองรับ File System Access API นั้นสั้น เนื่องจากสิ่งที่ทำทั้งหมดคือการสร้างองค์ประกอบ Anchor ที่มีแอตทริบิวต์ download ซึ่งมีค่าเป็นชื่อไฟล์ที่ต้องการ และ URL ของ Blob เป็นค่าแอตทริบิวต์ href

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

จากนั้นระบบจะคลิกองค์ประกอบ Anchor โดยอัตโนมัติ หากต้องการป้องกันหน่วยความจำรั่วไหล คุณต้องเพิกถอน URL ของ Blob หลังจากใช้งาน เนื่องจากเป็นการดาวน์โหลดเท่านั้น ระบบจึงไม่แสดงกล่องโต้ตอบการบันทึกไฟล์ และไฟล์ทั้งหมดจะอยู่ในโฟลเดอร์ Downloads เริ่มต้น

ลากและวาง

การผสานรวมระบบอย่างหนึ่งที่ฉันชอบมากในเดสก์ท็อปคือการลากและวาง ใน Excalidraw เมื่อวาง .excalidrawไฟล์ลงในแอปพลิเคชัน ไฟล์จะเปิดขึ้นทันทีและฉันก็เริ่มแก้ไขได้ ในเบราว์เซอร์ ที่รองรับ File System Access API ฉันจะบันทึกการเปลี่ยนแปลงได้ทันที ไม่จำเป็นต้องผ่านกล่องโต้ตอบการบันทึกไฟล์เนื่องจากได้รับแฮนเดิลไฟล์ที่จำเป็นจากการดำเนินการลากและวางแล้ว

เคล็ดลับในการดำเนินการนี้คือการเรียกใช้ getAsFileSystemHandle() ในรายการการโอนข้อมูลเมื่อระบบรองรับ File System Access API จากนั้นฉันจะส่งผ่านแฮนเดิลไฟล์นี้ไปยัง loadFromBlob() ซึ่งคุณอาจจำได้จากย่อหน้า 2-3 ย่อหน้าด้านบน คุณทำสิ่งต่างๆ กับไฟล์ได้มากมาย ไม่ว่าจะเป็นการเปิด บันทึก บันทึกทับ ลาก วาง Pete เพื่อนร่วมงานของฉัน และฉันได้บันทึกเคล็ดลับทั้งหมดนี้และอื่นๆ ไว้ในบทความของเราเพื่อให้คุณ ตามทันในกรณีที่ทุกอย่างเกิดขึ้นเร็วเกินไป

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

การแชร์ไฟล์

การผสานรวมระบบอีกอย่างที่ปัจจุบันมีใน Android, ChromeOS และ Windows คือผ่าน Web Share Target API ตอนนี้ฉันอยู่ในแอป Files ในโฟลเดอร์ Downloads ฉัน เห็นไฟล์ 2 ไฟล์ ไฟล์หนึ่งมีชื่อที่ไม่สื่อความหมายว่า untitled และมีการประทับเวลา หากต้องการตรวจสอบว่ามีอะไรอยู่ภายใน ฉันคลิกจุด 3 จุด แล้วแชร์ และตัวเลือกหนึ่งที่ปรากฏขึ้นคือ Excalidraw เมื่อแตะไอคอน ฉันจะเห็นว่าไฟล์มีเพียงโลโก้ I/O อีกครั้ง

Lipis ในเวอร์ชัน Electron ที่เลิกใช้งานแล้ว

สิ่งหนึ่งที่คุณทำได้กับไฟล์ที่ฉันยังไม่ได้พูดถึงคือการดับเบิลคลิก โดยปกติแล้ว เมื่อคุณดับเบิลคลิกไฟล์ แอปที่เชื่อมโยงกับประเภท MIME ของไฟล์ จะเปิดขึ้น เช่น สำหรับ .docx จะเป็น Microsoft Word

Excalidraw เคยมีแอปเวอร์ชัน Electron ที่รองรับการเชื่อมโยงประเภทไฟล์ดังกล่าว ดังนั้นเมื่อคุณดับเบิลคลิกไฟล์ .excalidraw แอป Excalidraw Electron จะเปิดขึ้น Lipis ซึ่งคุณเคยพบมาก่อนแล้วเป็นทั้งผู้สร้าง และผู้เลิกใช้งาน Excalidraw Electron ฉันถามเขาว่าทำไมถึงคิดว่าสามารถเลิกใช้งานเวอร์ชัน Electron ได้

ผู้ใช้ขอให้เราสร้างแอป Electron มาตั้งแต่แรกเริ่ม โดยเหตุผลหลักคือต้องการ เปิดไฟล์ด้วยการดับเบิลคลิก นอกจากนี้ เรายังตั้งใจที่จะนำแอปไปไว้ใน App Store ด้วย ในขณะเดียวกัน ก็มีคน แนะนำให้สร้าง PWA แทน เราจึงทำทั้ง 2 อย่าง โชคดีที่เราได้รู้จักกับ Project Fugu API เช่น การเข้าถึงระบบไฟล์ การเข้าถึงคลิปบอร์ด การจัดการไฟล์ และอื่นๆ คุณสามารถ ติดตั้งแอปบนเดสก์ท็อปหรืออุปกรณ์เคลื่อนที่ได้ด้วยการคลิกเพียงครั้งเดียวโดยไม่ต้องใช้ Electron เราตัดสินใจเลิกใช้งานเวอร์ชัน Electron ได้อย่างง่ายดาย โดยมุ่งเน้นเฉพาะเว็บแอปและทำให้เป็น PWA ที่ดีที่สุดเท่าที่จะเป็นไปได้ นอกจากนี้ ตอนนี้เรายังเผยแพร่ PWA ไปยัง Play Store และ Microsoft Store ได้แล้วด้วย ถือเป็นก้าวกระโดดครั้งใหญ่เลยทีเดียว

เราอาจกล่าวได้ว่า Excalidraw สำหรับ Electron ไม่ได้เลิกใช้งานเนื่องจาก Electron ไม่ดี แต่เป็นเพราะเว็บดีพอแล้ว ฉันชอบวิดีโอนี้

การจัดการไฟล์

เมื่อฉันพูดว่า "เว็บดีพอแล้ว" ก็เป็นเพราะฟีเจอร์ต่างๆ เช่น File Handling ที่กำลังจะเปิดตัว

นี่คือการติดตั้ง macOS Big Sur ตามปกติ ตอนนี้มาดูสิ่งที่เกิดขึ้นเมื่อฉันคลิกขวาที่ไฟล์ Excalidraw ฉันเลือกเปิดด้วย Excalidraw ซึ่งเป็น PWA ที่ติดตั้งไว้ได้ แน่นอนว่าการดับเบิลคลิกก็ใช้ได้เช่นกัน เพียงแต่ดูไม่น่าตื่นเต้นเท่าในการสาธิตในภาพหน้าจอ

แล้วฟีเจอร์นี้ทำงานอย่างไร ขั้นตอนแรกคือการทำให้ระบบปฏิบัติการรู้จักประเภทไฟล์ที่แอปพลิเคชันของฉันจัดการได้ ฉันทำสิ่งนี้ในช่องใหม่ที่ชื่อ file_handlers ในไฟล์ Manifest ของเว็บแอป ค่าของพร็อพเพอร์ตี้นี้คืออาร์เรย์ของออบเจ็กต์ที่มีพร็อพเพอร์ตี้การดำเนินการและ accept การดำเนินการจะกำหนดเส้นทาง URL ที่ระบบปฏิบัติการเปิดแอปของคุณ และออบเจ็กต์การยอมรับคือคู่คีย์-ค่าของ MIME ประเภทและนามสกุลไฟล์ที่เกี่ยวข้อง

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

ขั้นตอนถัดไปคือการจัดการไฟล์เมื่อแอปพลิเคชันเปิดตัว ซึ่งเกิดขึ้นในlaunchQueue อินเทอร์เฟซที่ฉันต้องตั้งค่าผู้บริโภคโดยการเรียกใช้ setConsumer() พารามิเตอร์ของฟังก์ชันนี้ คือฟังก์ชันแบบไม่พร้อมกันที่รับ launchParams launchParamsออบเจ็กต์ นี้มีฟิลด์ชื่อ files ซึ่งจะแสดงอาร์เรย์ของแฮนเดิลไฟล์ให้ฉันใช้ ฉันสนใจเฉพาะ รายการแรก และจากตัวแฮนเดิลของไฟล์นี้ ฉันจะได้รับ Blob ที่ฉันจะส่งต่อไปยังเพื่อนเก่าของเรา 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 });
      });
    });
}

อีกครั้ง หากคุณคิดว่าเราพูดเร็วเกินไป คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ File Handling API ได้ในบทความของฉัน คุณเปิดใช้การจัดการไฟล์ได้โดยตั้งค่าฟีเจอร์ทดลองของแพลตฟอร์มเว็บ โดยมีกำหนดจะเปิดตัวใน Chrome ภายในปีนี้

การผสานรวมคลิปบอร์ด

อีกฟีเจอร์ที่ยอดเยี่ยมของ Excalidraw คือการผสานรวมคลิปบอร์ด ฉันสามารถคัดลอกทั้งภาพวาดหรือ เพียงบางส่วนลงในคลิปบอร์ด อาจเพิ่มลายน้ำหากต้องการ แล้ววางลงใน แอปอื่น นี่คือเวอร์ชันเว็บของแอป Paint ใน Windows 95

วิธีทำงานของฟีเจอร์นี้เรียบง่ายอย่างไม่น่าเชื่อ สิ่งที่ฉันต้องการคือ Canvas เป็น Blob จากนั้นฉันจะเขียนลงในคลิปบอร์ดโดยส่งอาร์เรย์ที่มีองค์ประกอบเดียวพร้อม ClipboardItem ที่มี Blob ไปยังฟังก์ชัน navigator.clipboard.write() ดูข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่คุณทำได้ด้วย Clipboard API ได้ในบทความของ Jason และบทความของฉัน

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

ทำงานร่วมกันกับผู้อื่น

การแชร์ URL ของเซสชัน

คุณทราบไหมว่า Excalidraw มีโหมดการทำงานร่วมกันด้วย ผู้ใช้หลายคนสามารถทำงานร่วมกันในเอกสารเดียวกันได้ หากต้องการเริ่มเซสชันใหม่ ฉันจะคลิกปุ่มการทำงานร่วมกันแบบเรียลไทม์ แล้วเริ่มเซสชัน ฉันแชร์ URL ของเซสชันกับผู้ทำงานร่วมกันได้อย่างง่ายดายด้วย Web Share API ที่ Excalidraw ผสานรวมไว้

การทำงานร่วมกันแบบเรียลไทม์

ฉันได้จำลองเซสชันการทำงานร่วมกันในเครื่องโดยทำงานบนโลโก้ Google I/O ใน Pixelbook โทรศัพท์ Pixel 3a และ iPad Pro คุณจะเห็นว่าการเปลี่ยนแปลงที่ฉันทำในอุปกรณ์เครื่องหนึ่งจะแสดงใน อุปกรณ์อื่นๆ ทั้งหมด

ฉันยังเห็นเคอร์เซอร์ทั้งหมดเคลื่อนที่ไปมาด้วย เคอร์เซอร์ของ Pixelbook จะเคลื่อนที่อย่างต่อเนื่องเนื่องจากควบคุมด้วยแทร็กแพด แต่เคอร์เซอร์ของโทรศัพท์ Pixel 3a และเคอร์เซอร์แท็บเล็ตของ iPad Pro จะกระโดดไปมาเนื่องจากฉันควบคุมอุปกรณ์เหล่านี้ด้วยการแตะด้วยนิ้ว

การดูสถานะของผู้ทำงานร่วมกัน

เรามีระบบตรวจหาการไม่มีการใช้งานเพื่อปรับปรุงประสบการณ์การทำงานร่วมกันแบบเรียลไทม์ เคอร์เซอร์ของ iPad Pro แสดงจุดสีเขียวเมื่อฉันใช้งาน จุดจะเปลี่ยนเป็นสีดำเมื่อฉันเปลี่ยนไปใช้ แท็บเบราว์เซอร์หรือแอปอื่น และเมื่อฉันอยู่ในแอป Excalidraw แต่ไม่ได้ทำอะไร เคอร์เซอร์จะแสดงว่าฉันไม่ได้ใช้งาน โดยมีสัญลักษณ์เป็น zZZ 3 ตัว

ผู้อ่านที่ติดตามข่าวสารของเราอย่างสม่ำเสมออาจคิดว่าการตรวจหาการไม่มีการใช้งานนั้นเกิดขึ้นผ่าน Idle Detection API ซึ่งเป็นข้อเสนอในระยะแรกที่เราได้ดำเนินการในบริบทของโปรเจ็กต์ Fugu เฉลย: ไม่ใช่ แม้ว่าเราจะมีการติดตั้งใช้งานตาม API นี้ ใน Excalidraw แต่สุดท้ายเราก็ตัดสินใจใช้วิธีการแบบดั้งเดิมมากกว่าโดยอิงตามการวัด การเคลื่อนที่ของเคอร์เซอร์และความสามารถในการมองเห็นหน้าเว็บ

ภาพหน้าจอของความคิดเห็นเกี่ยวกับการตรวจหาการไม่ได้ใช้งานที่ยื่นในที่เก็บการตรวจหาการไม่ได้ใช้งานของ WICG

เราได้ส่งความคิดเห็นเกี่ยวกับสาเหตุที่ Idle Detection API ไม่สามารถแก้ปัญหา Use Case ที่เรามี เรากำลังพัฒนา API ของ Project Fugu แบบเปิด เพื่อให้ทุกคนสามารถแสดงความคิดเห็นและแสดงจุดยืนของตนเองได้

Lipis เกี่ยวกับสิ่งที่ทำให้ Excalidraw ไม่ก้าวหน้า

และในเรื่องนี้ ผมได้ถามคำถามสุดท้ายกับ lipis เกี่ยวกับสิ่งที่เขาคิดว่าขาดหายไปจากแพลตฟอร์มเว็บที่ทำให้ Excalidraw ไม่สามารถพัฒนาต่อไปได้

File System Access API นั้นยอดเยี่ยม แต่คุณรู้ไหมว่า ไฟล์ส่วนใหญ่ที่ฉันสนใจในปัจจุบัน อยู่ใน Dropbox หรือ Google ไดรฟ์ ไม่ได้อยู่ในฮาร์ดดิสก์ ฉันหวังว่า File System Access API จะมีเลเยอร์การแยกข้อมูลสำหรับผู้ให้บริการระบบไฟล์ระยะไกล เช่น Dropbox หรือ Google เพื่อผสานรวมด้วย และนักพัฒนาแอปสามารถเขียนโค้ดได้ จากนั้นผู้ใช้ก็สามารถวางใจได้ว่าไฟล์ของตนจะปลอดภัย กับผู้ให้บริการระบบคลาวด์ที่เชื่อถือ

ฉันเห็นด้วยกับ lipis อย่างยิ่ง ฉันก็อยู่ในระบบคลาวด์เช่นกัน หวังว่าฟีเจอร์นี้จะพร้อมใช้งานในเร็วๆ นี้

โหมดแอปพลิเคชันแบบแท็บ

ว้าว เราได้เห็นการผสานรวม API ที่ยอดเยี่ยมมากมายใน Excalidraw ระบบไฟล์ การจัดการไฟล์ คลิปบอร์ด การแชร์บนเว็บ และ เป้าหมายการแชร์บนเว็บ แต่มีอีกเรื่องหนึ่ง ก่อนหน้านี้ ฉันแก้ไขเอกสารได้ครั้งละ 1 ฉบับเท่านั้น ปัญหานี้จะหมดไป โปรดเพลิดเพลินกับโหมดแอปพลิเคชันแบบแท็บเวอร์ชันก่อนเปิดตัวใน Excalidraw เป็นครั้งแรก ซึ่งจะมีลักษณะดังนี้

ฉันมีไฟล์ที่เปิดอยู่ใน PWA ของ Excalidraw ที่ติดตั้งไว้ซึ่งทำงานในโหมดสแตนด์อโลน ตอนนี้ ฉันเปิดแท็บใหม่ในหน้าต่างแบบสแตนด์อโลน นี่ไม่ใช่แท็บเบราว์เซอร์ปกติ แต่เป็นแท็บ PWA ในแท็บใหม่นี้ ฉันสามารถเปิดไฟล์รองและทำงานกับไฟล์เหล่านั้นแยกกันได้จากหน้าต่างแอปเดียวกัน

โหมดแอปพลิเคชันแบบแท็บยังอยู่ในช่วงเริ่มต้นและยังไม่มีการกำหนดทุกอย่างอย่างแน่นอน หากสนใจ โปรดอ่านข้อมูลเกี่ยวกับสถานะปัจจุบันของฟีเจอร์นี้ในบทความของฉัน

เปิดจากขอบ

หากต้องการติดตามข่าวสารเกี่ยวกับฟีเจอร์นี้และฟีเจอร์อื่นๆ โปรดดูเครื่องมือติดตาม Fugu API เราตื่นเต้นเป็นอย่างยิ่งที่จะได้พัฒนาเว็บต่อไปและ ช่วยให้คุณทำสิ่งต่างๆ ได้มากขึ้นบนแพลตฟอร์ม ขอให้ Excalidraw พัฒนาต่อไปเรื่อยๆ และขอให้คุณสร้างแอปพลิเคชันที่ยอดเยี่ยมต่อไป เริ่มสร้างได้ที่ excalidraw.com

เราตั้งตารอที่จะเห็น API บางรายการที่เราแสดงในวันนี้ปรากฏในแอปของคุณ ฉันชื่อ Tom คุณสามารถติดต่อฉันได้ที่ @tomayac บน Twitter และอินเทอร์เน็ตโดยทั่วไป ขอขอบคุณที่รับชม และขอให้สนุกกับ Google I/O ที่เหลือ