Excalidraw و Fugu: بهبود سفرهای اصلی کاربر

هر فن آوری به اندازه کافی پیشرفته غیر قابل تشخیص از سحر و جادو است. مگر اینکه بفهمی نام من توماس اشتاینر است، من در روابط توسعه‌دهنده در Google کار می‌کنم و در این نوشتار صحبت‌های Google I/O خود، به برخی از Fugu APIهای جدید و چگونگی بهبود سفرهای کاربر اصلی در Excalidraw PWA نگاه خواهم کرد. می توانید از این ایده ها الهام بگیرید و آنها را در برنامه های خود به کار ببرید.

توماس اشتاینر
توماس اشتاینر

چگونه به Excalidraw آمدم

من می خواهم با یک داستان شروع کنم. در اول ژانویه 2020، کریستوفر چدو ، مهندس نرم‌افزار فیس‌بوک، در توییتی درباره یک برنامه طراحی کوچک که شروع به کار روی آن کرده بود، نوشت . با استفاده از این ابزار می‌توانید کادرها و فلش‌هایی را بکشید که احساس کارتونی و دستی دارند. روز بعد، می‌توانید بیضی‌ها و متن‌ها را ترسیم کنید، همچنین اشیا را انتخاب کرده و آنها را جابه‌جا کنید. در 3 ژانویه، برنامه نام خود را به نام Excalidraw دریافت کرد، و مانند هر پروژه جانبی خوب، خرید نام دامنه یکی از اولین اقدامات کریستوفر بود. در حال حاضر، می‌توانید از رنگ‌ها استفاده کنید و کل نقشه را به صورت PNG صادر کنید.

تصویری از برنامه نمونه اولیه Excalidraw که نشان می دهد از مستطیل، فلش، بیضی و متن پشتیبانی می کند.

در 15 ژانویه، کریستوفر یک پست وبلاگی منتشر کرد که توجه زیادی را در توییتر، از جمله من، به خود جلب کرد. این پست با آمارهای چشمگیر شروع شد:

  • 12 هزار کاربر فعال منحصر به فرد
  • 1.5 هزار ستاره در GitHub
  • 26 مشارکت کننده

برای پروژه ای که فقط دو هفته پیش شروع شد، اصلا بد نیست. اما چیزی که واقعاً علاقه من را برانگیخت پایین تر در پست بود. کریستوفر نوشت که این بار چیز جدیدی را امتحان کرده است: دادن دسترسی بدون قید و شرط به هر کسی که درخواست کشش را دریافت کرده است. در همان روزی که پست وبلاگ را خواندم، یک درخواست pull up داشتم که پشتیبانی از File System Access API را به Excalidraw اضافه می‌کرد و یک درخواست ویژگی را که شخصی ثبت کرده بود برطرف می‌کرد.

اسکرین شات توییتی که در آن روابط عمومی خود را اعلام می کنم.

درخواست pull من یک روز بعد ادغام شد و از آن به بعد، من به commit دسترسی کامل داشتم. ناگفته نماند که من از قدرتم سوء استفاده نکردم. و نه هیچ کس دیگری از 149 مشارکت کننده تاکنون.

امروزه، Excalidraw یک برنامه وب پیشرفته قابل نصب کامل با پشتیبانی آفلاین، حالت تاریک خیره کننده، و بله، توانایی باز کردن و ذخیره فایل ها به لطف File System Access API است.

تصویری از Excalidraw PWA در وضعیت امروزی.

لیپیس در مورد اینکه چرا او زمان زیادی را به Excalidraw اختصاص می دهد

بنابراین این پایان داستان من "چگونه به Excalidraw آمدم" است، اما قبل از اینکه به برخی از ویژگی های شگفت انگیز Excalidraw بپردازم، خوشحالم که Panayiotis را معرفی کنم. Panayiotis Lipiridis، که در اینترنت به سادگی به عنوان lipis شناخته می شود، پرکارترین مشارکت کننده Excalidraw است. از لیپیس پرسیدم که چه انگیزه ای او را برای اختصاص دادن زمان زیادی به Excalidraw می کند:

من هم مثل بقیه در مورد این پروژه از توییت کریستوفر یاد گرفتم. اولین مشارکت من اضافه کردن کتابخانه Open Color بود، رنگ هایی که امروزه هنوز بخشی از Excalidraw هستند. همانطور که پروژه رشد کرد و درخواست های زیادی داشتیم، سهم بزرگ بعدی من ساختن یک Backend برای ذخیره نقاشی ها بود تا کاربران بتوانند آنها را به اشتراک بگذارند. اما چیزی که واقعاً مرا به مشارکت می‌کشاند این است که هرکسی Excalidraw را امتحان کرد به دنبال بهانه‌هایی برای استفاده مجدد از آن است.

من کاملا با lipis موافقم. هر کسی که Excalidraw را امتحان کرد به دنبال یافتن بهانه ای برای استفاده مجدد از آن است.

Excalidraw در عمل

اکنون می خواهم به شما نشان دهم که چگونه می توانید از Excalidraw در عمل استفاده کنید. من هنرمند بزرگی نیستم، اما لوگوی Google I/O به اندازه کافی ساده است، پس اجازه دهید آن را امتحان کنم. یک کادر "i" است، یک خط می تواند اسلش باشد و "o" یک دایره است. Shift را پایین نگه می‌دارم، بنابراین یک دایره کامل می‌گیرم. اجازه دهید من خط بریده را کمی جابجا کنم تا بهتر به نظر برسد. حالا مقداری رنگ برای "i" و "o". آبی خوبه شاید یک سبک پر کردن متفاوت؟ همه جامد، یا متقاطع؟ نه، هاچور عالی به نظر می رسد. کامل نیست، اما این ایده Excalidraw است، پس اجازه دهید آن را ذخیره کنم.

من روی نماد ذخیره کلیک می کنم و نام فایل را در گفتگوی ذخیره فایل وارد می کنم. در کروم، مرورگری که از File System Access API پشتیبانی می‌کند، این یک دانلود نیست، بلکه یک عملیات ذخیره واقعی است، جایی که می‌توانم مکان و نام فایل را انتخاب کنم، و اگر ویرایش کنم، می‌توانم آنها را ذخیره کنم. به همان فایل

اجازه دهید لوگو را تغییر دهم و "i" را قرمز کنم. اگر اکنون دوباره روی ذخیره کلیک کنم، تغییر من در همان فایل قبلی ذخیره می شود. به عنوان مدرک، اجازه دهید من بوم را پاک کنم و فایل را دوباره باز کنم. همانطور که می بینید، لوگوی اصلاح شده قرمز-آبی دوباره وجود دارد.

کار با فایل ها

در مرورگرهایی که در حال حاضر از File System Access API پشتیبانی نمی‌کنند، هر عملیات ذخیره‌سازی یک بارگیری است، بنابراین وقتی تغییراتی ایجاد می‌کنم، در نهایت با چندین فایل با یک عدد در حال افزایش در نام فایل مواجه می‌شوم که پوشه Downloads من را پر می‌کند. اما با وجود این ایراد، من همچنان می‌توانم فایل را ذخیره کنم.

باز کردن فایل ها

پس راز چیست؟ چگونه باز کردن و ذخیره کردن آن در مرورگرهای مختلفی که ممکن است از 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() که از یک کتابخانه کوچکی که من نوشتم به نام مرورگر-fs-access می آید که در Excalidraw استفاده می کنیم. این کتابخانه دسترسی به سیستم فایل را از طریق File System Access API با یک بازگشت قدیمی فراهم می کند، بنابراین می توان از آن در هر مرورگری استفاده کرد.

اجازه دهید ابتدا پیاده سازی را برای زمانی که API پشتیبانی می شود به شما نشان دهم. پس از مذاکره در مورد انواع MIME پذیرفته شده و پسوندهای فایل، قطعه مرکزی تابع showOpenFilePicker() 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 مورد پذیرش، گام بعدی این است که به صورت برنامه‌ریزی روی عنصر ورودی کلیک کنید تا گفتگوی باز فایل نشان داده شود. در تغییر، یعنی زمانی که کاربر یک یا چند فایل را انتخاب کرده باشد، وعده حل می شود.

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() را فراخوانی می کند. این تابع نیز توسط کتابخانه مرورگر-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();
};

سپس عنصر لنگر به صورت برنامه‌ریزی کلیک می‌شود. برای جلوگیری از نشت حافظه، URL blob پس از استفاده باید باطل شود. از آنجایی که این فقط یک دانلود است، هیچ گفتگوی ذخیره فایل هرگز نشان داده نمی شود و همه فایل ها در پوشه پیش فرض Downloads قرار می گیرند.

کشیدن و انداختن

یکی از سیستم های مورد علاقه من در دسکتاپ کشیدن و رها کردن است. در Excalidraw، وقتی یک فایل .excalidraw را روی برنامه می‌ریزم، بلافاصله باز می‌شود و می‌توانم ویرایش را شروع کنم. در مرورگرهایی که از File System Access API پشتیبانی می‌کنند، حتی می‌توانم بلافاصله تغییراتم را ذخیره کنم. از آنجایی که دسته فایل مورد نیاز از عملیات کشیدن و رها کردن به دست آمده است، نیازی به عبور از یک گفتگوی ذخیره فایل نیست.

راز تحقق این امر با فراخوانی getAsFileSystemHandle() بر روی آیتم انتقال داده زمانی است که از File System Access API پشتیبانی می شود. سپس این دسته فایل را به loadFromBlob() می‌دهم که ممکن است از چند پاراگراف بالا به خاطر بیاورید. کارهای زیادی که می توانید با فایل ها انجام دهید: باز کردن، ذخیره کردن، ذخیره بیش از حد، کشیدن، رها کردن. من و همکارم پیت همه این ترفندها و موارد دیگر را در مقاله خود ثبت کرده‌ایم تا در صورتی که همه اینها کمی سریع پیش رفت، بتوانید پیگیری کنید.

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 هستم. من می توانم دو فایل را ببینم، یکی از آنها با نام غیر توصیفی untitled و یک برچسب زمانی. برای بررسی محتوای آن، روی سه نقطه کلیک می کنم، سپس اشتراک گذاری می کنم و یکی از گزینه هایی که ظاهر می شود Excalidraw است. وقتی روی نماد ضربه می زنم، می توانم ببینم که فایل دوباره حاوی لوگوی I/O است.

Lipis در نسخه قدیمی Electron

یکی از کارهایی که می‌توانید با فایل‌هایی که من هنوز در مورد آن صحبت نکرده‌ام انجام دهید این است که روی آنها دوبار کلیک کنید. چیزی که معمولاً هنگام دوبار کلیک کردن روی یک فایل اتفاق می‌افتد این است که برنامه‌ای که با نوع MIME فایل مرتبط است باز می‌شود. برای مثال برای .docx این مایکروسافت ورد خواهد بود.

Excalidraw قبلاً یک نسخه الکترونیکی از برنامه داشت که از چنین ارتباط های نوع فایلی پشتیبانی می کرد، بنابراین وقتی روی یک فایل .excalidraw دوبار کلیک می کردید، برنامه Excalidraw Electron باز می شد. لیپیس، که قبلاً با او آشنا شده بودید، هم خالق و هم منکر Excalidraw Electron بود. از او پرسیدم که چرا فکر می‌کند می‌توان نسخه الکترون را منسوخ کرد:

مردم از ابتدا درخواست یک برنامه Electron را داشتند، عمدتاً به این دلیل که می خواستند فایل ها را با دوبار کلیک کردن باز کنند. همچنین قصد داشتیم این اپلیکیشن را در اپ استورها قرار دهیم. به موازات آن، شخصی پیشنهاد ایجاد یک PWA را به جای آن داد، بنابراین ما هر دو را انجام دادیم. خوشبختانه ما با APIهای Project Fugu مانند دسترسی به سیستم فایل، دسترسی به کلیپ بورد، مدیریت فایل و موارد دیگر آشنا شدیم. تنها با یک کلیک می توانید برنامه را روی دسکتاپ یا موبایل خود نصب کنید، بدون اینکه وزن اضافی الکترون داشته باشد. منسوخ کردن نسخه Electron، تمرکز فقط روی برنامه وب و تبدیل آن به بهترین PWA ممکن، تصمیم آسانی بود. علاوه بر این، اکنون می‌توانیم PWA را در Play Store و Microsoft Store منتشر کنیم! این بزرگ است!

می توان گفت Excalidraw برای Electron منسوخ نشد زیرا Electron بد است، نه به هیچ وجه، بلکه به این دلیل که وب به اندازه کافی خوب شده است. من این را دوست دارم!

رسیدگی به پرونده

وقتی می گویم "وب به اندازه کافی خوب شده است"، به دلیل ویژگی هایی مانند مدیریت فایل های آینده است.

این یک نصب معمولی MacOS Big Sur است. حالا ببینید وقتی روی یک فایل Excalidraw راست کلیک می کنم چه اتفاقی می افتد. من می توانم آن را با Excalidraw، PWA نصب شده، باز کنم. البته دوبار کلیک کردن نیز کارساز خواهد بود، فقط نمایش آن در یک نمایشگر نمایشگر کمتر است.

خب این چطور کار میکند؟ اولین گام این است که انواع فایل‌هایی را که برنامه من می‌تواند مدیریت کند برای سیستم عامل شناخته شود. من این کار را در یک فیلد جدید به نام file_handlers در مانیفست برنامه وب انجام می دهم. مقدار آن آرایه ای از اشیاء با یک عمل و یک ویژگی 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 دارای یک فیلد به نام فایل است که آرایه ای از دسته های فایل را برای کار با من می دهد. من فقط برای اولین مورد اهمیت می‌دهم و از این دسته فایل یک حباب دریافت می‌کنم که سپس به دوست قدیمی‌مان 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 });
      });
    });
}

دوباره، اگر این خیلی سریع پیش رفت، می‌توانید در مقاله من درباره API Handling File بیشتر بخوانید. می‌توانید با تنظیم پرچم ویژگی‌های پلتفرم وب آزمایشی، مدیریت فایل را فعال کنید. قرار است اواخر امسال در کروم قرار گیرد.

ادغام کلیپ بورد

یکی دیگر از ویژگی های جالب Excalidraw یکپارچه سازی کلیپ بورد است. می‌توانم کل نقاشی یا فقط بخش‌هایی از آن را در کلیپ‌بورد کپی کنم، شاید اگر بخواهم یک واترمارک اضافه کنم و سپس آن را در برنامه دیگری جای‌گذاری کنم. به هر حال این یک نسخه وب از برنامه Windows 95 Paint است.

روش کار به طرز شگفت آوری ساده است. تنها چیزی که من نیاز دارم بوم به عنوان یک حباب است، که سپس با ارسال یک آرایه تک عنصری با یک ClipboardItem به همراه blob به تابع navigator.clipboard.write() روی کلیپ بورد می نویسم. برای اطلاعات بیشتر در مورد آنچه که می توانید با API کلیپ بورد انجام دهید، به مقاله Jason and my مراجعه کنید.

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 حالت مشارکتی نیز دارد؟ افراد مختلف می توانند روی یک سند با هم کار کنند. برای شروع یک جلسه جدید، روی دکمه همکاری زنده کلیک می کنم و سپس یک جلسه را شروع می کنم. به لطف Web Share API که Excalidraw یکپارچه کرده است، می توانم URL جلسه را به راحتی با همکارانم به اشتراک بگذارم.

همکاری زنده

من با کار بر روی نشان‌واره Google I/O در Pixelbook، تلفن Pixel 3a و iPad Pro خود، یک جلسه همکاری را به صورت محلی شبیه‌سازی کرده‌ام. می بینید که تغییراتی که در یک دستگاه ایجاد می کنم در همه دستگاه های دیگر منعکس می شود.

حتی می توانم تمام مکان نماها را ببینم که در اطراف حرکت می کنند. مکان‌نمای پیکسل‌بوک به‌طور پیوسته حرکت می‌کند، زیرا با یک ترک‌پد کنترل می‌شود، اما مکان‌نمای تلفن Pixel 3a و مکان‌نمای تبلت iPad Pro به اطراف می‌پرند، زیرا من با ضربه زدن با انگشتم این دستگاه‌ها را کنترل می‌کنم.

مشاهده وضعیت همکاران

برای بهبود تجربه همکاری بلادرنگ، حتی یک سیستم تشخیص بی‌حرکتی در حال اجراست. هنگام استفاده از آیپد پرو، مکان نما یک نقطه سبز رنگ را نشان می دهد. وقتی به برگه یا برنامه مرورگر دیگری جابجا می شوم، نقطه سیاه می شود. و هنگامی که در برنامه Excalidraw هستم، اما کاری انجام نمی دهم، مکان نما من را به صورت بیکار نشان می دهد که نماد آن با سه zZZ است.

ممکن است خوانندگان مشتاق انتشارات ما فکر کنند که تشخیص بیکاری از طریق Idle Detection API انجام می شود، یک پیشنهاد مرحله اولیه که در زمینه پروژه Fugu روی آن کار شده است. هشدار اسپویلر: اینطور نیست. در حالی که ما یک پیاده سازی بر اساس این API در Excalidraw داشتیم، در پایان، تصمیم گرفتیم به رویکرد سنتی تری بر اساس اندازه گیری حرکت اشاره گر و دید صفحه برویم.

تصویری از بازخورد تشخیص بیکاری که در مخزن WICG Idle Detection ثبت شده است.

ما در مورد اینکه چرا Idle Detection API مشکل استفاده ما را حل نمی‌کند، بازخورد ارسال کردیم. همه API های Project Fugu در حال توسعه هستند، بنابراین همه می توانند زنگ بزنند و صدایشان شنیده شود!

Lipis در مورد چیزی که مانع Excalidraw است

در مورد آن، آخرین سوال را از لیپیس پرسیدم که فکر می‌کند چه چیزی از پلتفرم وب که مانع Excalidraw می‌شود وجود ندارد:

File System Access API عالی است، اما می دانید چیست؟ اکثر فایل‌هایی که این روزها به آنها اهمیت می‌دهم در Dropbox یا Google Drive من قرار دارند، نه روی هارد دیسک من. ای کاش API دسترسی به فایل سیستم شامل یک لایه انتزاعی برای ارائه دهندگان سیستم های فایل از راه دور مانند Dropbox یا Google می شد تا با آن ادغام شوند و توسعه دهندگان بتوانند بر خلاف آن کدنویسی کنند. سپس کاربران می‌توانند آرامش داشته باشند و بدانند فایل‌هایشان با ارائه‌دهنده ابری که به آن اعتماد دارند، ایمن هستند.

من کاملا با lipis موافقم، من هم در ابر زندگی می کنم. در اینجا امیدواریم که این امر به زودی اجرا شود.

حالت برنامه زبانه دار

وای! ما شاهد ادغام های API واقعا عالی در Excalidraw هستیم. سیستم فایل ، مدیریت فایل ، کلیپ بورد ، اشتراک گذاری وب و هدف اشتراک گذاری وب . اما در اینجا یک چیز دیگر وجود دارد. تا به حال، من فقط می توانستم یک سند را در یک زمان خاص ویرایش کنم. دیگر نه. لطفاً برای اولین بار از نسخه اولیه حالت برنامه تب دار در Excalidraw لذت ببرید. اینطور به نظر می رسد.

من یک فایل موجود در Excalidraw PWA نصب شده باز دارم که در حالت مستقل اجرا می شود. اکنون یک تب جدید در پنجره مستقل باز می کنم. این یک برگه معمولی مرورگر نیست، بلکه یک برگه PWA است. در این برگه جدید می توانم یک فایل ثانویه را باز کنم و به طور مستقل از همان پنجره برنامه روی آنها کار کنم.

حالت برنامه Tabbed در مراحل اولیه خود است و همه چیز در سنگ تنظیم نشده است. اگر علاقه مند هستید، حتماً وضعیت فعلی این ویژگی را در مقاله من بخوانید.

بسته شدن

برای اطلاع از این ویژگی و سایر ویژگی‌ها، حتماً ردیاب Fugu API ما را تماشا کنید. ما بسیار هیجان‌زده هستیم که وب را به جلو پیش ببریم و به شما اجازه می‌دهیم کارهای بیشتری در این پلتفرم انجام دهید. در اینجا یک Excalidraw همیشه در حال بهبود است، و در اینجا به همه برنامه های کاربردی شگفت انگیزی که خواهید ساخت. شروع به ایجاد در excalidraw.com کنید.

من نمی توانم صبر کنم تا برخی از API هایی را که امروز نشان داده ام در برنامه های شما ظاهر شوند. نام من تام است، شما می توانید من را به عنوان @tomayac در توییتر و به طور کلی اینترنت پیدا کنید. از تماشای شما بسیار متشکریم و از بقیه Google I/O لذت ببرید.