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

เทคโนโลยีขั้นสูงมากพอจะแยกไม่ออกจากเวทมนตร์ เว้นแต่คุณจะเข้าใจ ผมชื่อ Thomas Steiner ทำงานฝ่ายนักพัฒนาซอฟต์แวร์ของ 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 เป็นผู้มีส่วนร่วมใน Excaliburaw มากที่สุด เราถาม lipis ว่าอะไรคือแรงจูงใจที่ทำให้เขาทุ่มเทเวลาให้กับ Excalidraw มากมายขนาดนี้

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

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

การใช้งาน Excalidraw

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

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

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

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

ในเบราว์เซอร์ที่ไม่รองรับ 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 ซึ่งเราใช้ใน Excaliburaw ไลบรารีนี้ให้สิทธิ์เข้าถึงระบบไฟล์ผ่าน 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 จะสั้น เนื่องจากสิ่งที่ทํามีเพียงการสร้างองค์ประกอบแอตทริบิวต์ 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();
};

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

ลากและวาง

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

เคล็ดลับในการทำให้การดำเนินการนี้เกิดขึ้นคือการเรียกใช้ getAsFileSystemHandle() ในรายการการโอนข้อมูลเมื่อระบบรองรับ File System Access API จากนั้นฉันจะส่งตัวแฮนเดิลไฟล์นี้ไปยัง loadFromBlob() ซึ่งคุณอาจจำได้จากย่อหน้าด้านบน คุณทำสิ่งต่างๆ กับไฟล์ได้มากมาย เช่น เปิด บันทึก บันทึกซ้ำ ลาก วาง เพื่อนร่วมงานของฉัน 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 จุด แล้วเลือกแชร์ แล้วตัวเลือกที่ปรากฏขึ้นตัวเลือกหนึ่งคือ Excaliburaw เมื่อแตะไอคอนดังกล่าว ฉันเห็นว่าไฟล์มีเพียงโลโก้ 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 ที่ดีที่สุด นอกจากนี้ เรายังเผยแพร่ PWA ไปยัง Play Store และ Microsoft Store ได้แล้ว เยี่ยมไปเลย

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

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

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

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

แล้วฟีเจอร์นี้ทำงานอย่างไร ขั้นตอนแรกคือทำให้ระบบปฏิบัติการทราบประเภทไฟล์ที่แอปพลิเคชันของฉันจัดการได้ ฉันทำในช่องใหม่ชื่อ file_handlers ในไฟล์ Manifest ของเว็บแอป ค่าของaccept การดำเนินการจะกำหนดเส้นทาง URL ที่ระบบปฏิบัติการจะเปิดแอปของคุณ และออบเจ็กต์ accept คือคู่คีย์-ค่าของประเภท 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 ได้ในบทความของฉัน คุณเปิดใช้การจัดการไฟล์ได้โดยการตั้งค่า Flag ฟีเจอร์แพลตฟอร์มเว็บเวอร์ชันทดลอง ซึ่งคาดว่าจะเปิดตัวใน Chrome ภายในปีนี้

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

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

วิธีการทำงานของฟีเจอร์นี้ง่ายเกินคาด สิ่งที่ฉันต้องการคือผืนผ้าใบในรูปแบบ 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 ซึ่งเป็นข้อเสนอระยะเริ่มต้นที่ดำเนินการอยู่ในบริบทของ Project 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