Excalidraw এবং Fugu: মূল ব্যবহারকারীর যাত্রার উন্নতি

যে কোনো পর্যাপ্ত উন্নত প্রযুক্তি যাদু থেকে আলাদা করা যায় না। না বুঝলে। আমার নাম থমাস স্টেইনার, আমি Google এ ডেভেলপার রিলেশনে কাজ করি এবং আমার Google I/O টক-এর এই লেখায়, আমি কিছু নতুন Fugu API দেখব এবং কিভাবে তারা Excalidraw PWA-তে মূল ব্যবহারকারীর যাত্রাকে উন্নত করে, যাতে আপনি এই ধারণাগুলি থেকে অনুপ্রেরণা নিতে পারেন এবং সেগুলিকে আপনার নিজের অ্যাপগুলিতে প্রয়োগ করতে পারেন৷

কিভাবে আমি Excalidraw এ এসেছি

আমি একটি গল্প দিয়ে শুরু করতে চাই। 1লা জানুয়ারী, 2020-এ, Facebook-এর একজন সফ্টওয়্যার প্রকৌশলী ক্রিস্টোফার চেডিউ একটি ছোট অঙ্কন অ্যাপ সম্পর্কে টুইট করেছিলেন যেটিতে তিনি কাজ শুরু করেছিলেন। এই টুলের সাহায্যে, আপনি বাক্স এবং তীরগুলি আঁকতে পারেন যা কার্টুনি এবং হাতে আঁকা মনে হয়। পরের দিন, আপনি উপবৃত্তাকার এবং পাঠ্য আঁকতে পারেন, সেইসাথে বস্তু নির্বাচন করুন এবং তাদের চারপাশে সরাতে পারেন। 3 জানুয়ারী, অ্যাপটি তার নাম পেয়েছে, Excalidraw, এবং প্রতিটি ভাল পার্শ্ব প্রকল্পের মত, ডোমেইন নাম কেনা ছিল ক্রিস্টোফারের প্রথম কাজগুলির মধ্যে একটি। এখন পর্যন্ত, আপনি রং ব্যবহার করতে পারেন এবং একটি PNG হিসাবে পুরো অঙ্কনটি রপ্তানি করতে পারেন।

Excalidraw প্রোটোটাইপ অ্যাপ্লিকেশনের স্ক্রিনশট দেখায় যে এটি আয়তক্ষেত্র, তীর, উপবৃত্ত এবং পাঠ্যকে সমর্থন করে।

15 জানুয়ারী, ক্রিস্টোফার একটি ব্লগ পোস্ট করেছেন যা আমার সহ টুইটারে অনেক মনোযোগ আকর্ষণ করেছিল। পোস্টটি কিছু চিত্তাকর্ষক পরিসংখ্যান দিয়ে শুরু হয়েছিল:

  • 12K অনন্য সক্রিয় ব্যবহারকারী
  • GitHub-এ 1.5K তারা
  • 26 জন অবদানকারী

একটি প্রকল্পের জন্য যা মাত্র দুই সপ্তাহ আগে শুরু হয়েছিল, এটি মোটেও খারাপ নয়। কিন্তু যে জিনিসটি সত্যিকার অর্থে আমার আগ্রহ বাড়িয়ে দিয়েছে তা পোস্টে আরও নিচে ছিল। ক্রিস্টোফার লিখেছেন যে তিনি এবার নতুন কিছু করার চেষ্টা করেছেন: যারা একটি পুল অনুরোধ অবতীর্ণ করেছেন তাদের প্রত্যেককে নিঃশর্ত কমিট অ্যাক্সেস দেওয়া। ব্লগ পোস্ট পড়ার একই দিনে, আমার কাছে একটি পুল অনুরোধ ছিল যা Excalidraw-তে ফাইল সিস্টেম অ্যাক্সেস API সমর্থন যোগ করেছে, একটি ফিচার রিকোয়েস্ট ফিক্স করে যা কেউ ফাইল করেছিল।

টুইটের স্ক্রিনশট যেখানে আমি আমার পিআর ঘোষণা করছি।

আমার পুল অনুরোধ একদিন পরে একত্রিত করা হয়েছিল এবং সেখান থেকে, আমার সম্পূর্ণ কমিট অ্যাক্সেস ছিল। বলা বাহুল্য, আমি আমার ক্ষমতার অপব্যবহার করিনি। এবং এখনও পর্যন্ত 149 জন অবদানকারীদের থেকে অন্য কেউ আসেনি।

আজ, Excalidraw হল অফলাইন সাপোর্ট সহ একটি পূর্ণাঙ্গ ইনস্টলযোগ্য প্রগতিশীল ওয়েব অ্যাপ, একটি অত্যাশ্চর্য ডার্ক মোড এবং হ্যাঁ, ফাইল সিস্টেম অ্যাক্সেস API-কে ধন্যবাদ ফাইল খোলা ও সংরক্ষণ করার ক্ষমতা।

আজকের রাজ্যে Excalidraw PWA-এর স্ক্রিনশট।

লিপিস কেন তিনি তার এত বেশি সময় Excalidraw-এ উৎসর্গ করেন

সুতরাং এটি আমার "কীভাবে আমি এক্সক্যালিড্রে এলাম" গল্পের সমাপ্তি চিহ্নিত করে, কিন্তু এক্সক্যালিড্রের কিছু আশ্চর্যজনক বৈশিষ্ট্যের মধ্যে ডুব দেওয়ার আগে, আমি Panayiotis-এর সাথে পরিচয় করিয়ে দিতে পেরে আনন্দিত। প্যানাইওটিস লিপিরিডিস, ইন্টারনেটে যা লিপিস নামে পরিচিত, এটি এক্সক্যালিড্রের সবচেয়ে বেশি অবদানকারী। আমি লিপিসকে জিজ্ঞাসা করেছি যে তাকে তার এত বেশি সময় Excalidraw-এ উৎসর্গ করতে অনুপ্রাণিত করে:

অন্য সবার মতো আমি ক্রিস্টোফারের টুইট থেকে এই প্রকল্প সম্পর্কে শিখেছি। আমার প্রথম অবদান ছিল ওপেন কালার লাইব্রেরি যোগ করা, যে রঙগুলি আজও এক্সক্যালিড্রের অংশ। যেহেতু প্রকল্পটি বেড়েছে এবং আমাদের কাছে অনেক অনুরোধ ছিল, আমার পরবর্তী বড় অবদান ছিল অঙ্কন সংরক্ষণের জন্য একটি ব্যাকএন্ড তৈরি করা যাতে ব্যবহারকারীরা সেগুলি ভাগ করতে পারে। কিন্তু যা আমাকে অবদান রাখতে চালিত করে তা হল যে কেউ Excalidraw চেষ্টা করেছে তা আবার ব্যবহার করার জন্য অজুহাত খুঁজছে।

আমি লিপিসের সাথে সম্পূর্ণ একমত। যে কেউ Excalidraw চেষ্টা করেছে তা আবার ব্যবহার করার জন্য অজুহাত খুঁজছে।

কর্মে Excalidraw

আমি আপনাকে এখন দেখাতে চাই কিভাবে আপনি অনুশীলনে Excalidraw ব্যবহার করতে পারেন। আমি একজন মহান শিল্পী নই, কিন্তু Google I/O লোগো যথেষ্ট সহজ, তাই আমাকে এটি চেষ্টা করতে দিন। একটি বাক্স হল "i", একটি লাইন স্ল্যাশ হতে পারে এবং "o" একটি বৃত্ত। আমি শিফট ধরে রাখি, তাই আমি একটি নিখুঁত বৃত্ত পাই। আমাকে স্ল্যাশটি একটু সরাতে দিন, যাতে এটি আরও ভাল দেখায়। এখন "i" এবং "o" এর জন্য কিছু রঙ। নীল ভালো। হয়তো একটি ভিন্ন ভরাট শৈলী? সব কঠিন, নাকি ক্রস হ্যাচ? নাহ, হাচুরে দারুন লাগছে। এটি নিখুঁত নয়, তবে এটি এক্সক্যালিড্রের ধারণা, তাই আমাকে এটি সংরক্ষণ করতে দিন।

আমি সংরক্ষণ আইকনে ক্লিক করি এবং ফাইল সংরক্ষণ ডায়ালগে একটি ফাইলের নাম লিখি। ক্রোমে, একটি ব্রাউজার যা ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে, এটি একটি ডাউনলোড নয়, কিন্তু একটি সত্যিকারের সংরক্ষণ অপারেশন, যেখানে আমি ফাইলের অবস্থান এবং নাম চয়ন করতে পারি এবং যেখানে, যদি আমি সম্পাদনা করি, আমি সেগুলিকে একই ফাইলে সংরক্ষণ করতে পারি৷

আমাকে লোগো পরিবর্তন এবং "i" লাল করা যাক. যদি আমি এখন আবার সংরক্ষণ ক্লিক করি, আমার পরিবর্তন আগের মতই একই ফাইলে সংরক্ষিত হবে। একটি প্রমাণ হিসাবে, আমাকে ক্যানভাস পরিষ্কার করুন এবং ফাইলটি পুনরায় খুলুন। আপনি দেখতে পাচ্ছেন, পরিবর্তিত লাল-নীল লোগোটি আবার রয়েছে।

ফাইল নিয়ে কাজ করা

বর্তমানে ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে না এমন ব্রাউজারগুলিতে, প্রতিটি সংরক্ষণ অপারেশন একটি ডাউনলোড, তাই যখন আমি পরিবর্তন করি, তখন আমার ডাউনলোড ফোল্ডারটি পূরণ করার ফাইলের নামটিতে একটি ক্রমবর্ধমান সংখ্যা সহ একাধিক ফাইলের সাথে শেষ হয়। কিন্তু এই খারাপ দিক থাকা সত্ত্বেও, আমি এখনও ফাইলটি সংরক্ষণ করতে পারি।

ফাইল খোলা হচ্ছে

তাহলে রহস্য কি? ফাইল সিস্টেম অ্যাক্সেস 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() ফাংশন যা একটি ছোট লাইব্রেরি থেকে আসে যা আমি লিখেছিলাম ব্রাউজার-এফএস-অ্যাক্সেস যা আমরা এক্সক্যালিড্রতে ব্যবহার করি। এই লাইব্রেরিটি একটি লিগ্যাসি ফলব্যাক সহ ফাইল সিস্টেম অ্যাক্সেস API এর মাধ্যমে ফাইল সিস্টেম অ্যাক্সেস প্রদান করে, তাই এটি যে কোনও ব্রাউজারে ব্যবহার করা যেতে পারে।

এপিআই সমর্থিত হলে আমি প্রথমে আপনাকে বাস্তবায়ন দেখাই। গৃহীত MIME প্রকার এবং ফাইল এক্সটেনশন নিয়ে আলোচনা করার পরে, কেন্দ্রীয় অংশটি ফাইল সিস্টেম অ্যাক্সেস API-এর ফাংশন showOpenFilePicker() কল করছে। এই ফাংশনটি একাধিক ফাইল নির্বাচন করা হয়েছে কিনা তার উপর নির্ভর করে ফাইলের একটি অ্যারে বা একটি একক ফাইল প্রদান করে। তারপর যা বাকি আছে তা হল ফাইলের হ্যান্ডেলটি ফাইল অবজেক্টে রাখা, যাতে এটি আবার পুনরুদ্ধার করা যায়।

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

ফলব্যাক বাস্তবায়ন "file" টাইপের একটি input উপাদানের উপর নির্ভর করে। গ্রহণযোগ্য 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-কে একটি ব্লব-এ রূপান্তর করে এবং তারপর fileSave() নামে একটি ফাংশনকে কল করে। এই ফাংশনটি একইভাবে ব্রাউজার-fs-অ্যাক্সেস লাইব্রেরি দ্বারা সরবরাহ করা হয়।

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

আবার আমাকে প্রথমে ফাইল সিস্টেম অ্যাক্সেস 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;
};

"সেভ এজ" বৈশিষ্ট্য

যদি আমি ইতিমধ্যে বিদ্যমান ফাইল হ্যান্ডেলকে উপেক্ষা করার সিদ্ধান্ত নিই, তাহলে বিদ্যমান ফাইলের উপর ভিত্তি করে একটি নতুন ফাইল তৈরি করতে আমি একটি "সেভ অ্যাজ" বৈশিষ্ট্য প্রয়োগ করতে পারি। এটি দেখানোর জন্য, আমাকে একটি বিদ্যমান ফাইল খুলতে দিন, কিছু পরিবর্তন করতে দিন, এবং তারপরে বিদ্যমান ফাইলটি ওভাররাইট করবেন না, তবে সেভ-এজ বৈশিষ্ট্যটি ব্যবহার করে একটি নতুন ফাইল তৈরি করুন। এটি মূল ফাইলটি অক্ষত রাখে।

ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে না এমন ব্রাউজারগুলির বাস্তবায়ন সংক্ষিপ্ত, যেহেতু এটি শুধুমাত্র একটি download অ্যাট্রিবিউট সহ একটি অ্যাঙ্কর উপাদান তৈরি করে যার মানটি কাঙ্খিত ফাইলের নাম এবং একটি ব্লব URL এর 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();
};

নোঙ্গর উপাদান তারপর প্রোগ্রাম্যাটিকভাবে ক্লিক করা হয়. মেমরি লিক প্রতিরোধ করার জন্য, ব্লব ইউআরএল ব্যবহারের পরে প্রত্যাহার করা দরকার। যেহেতু এটি শুধুমাত্র একটি ডাউনলোড, কোনো ফাইল সংরক্ষণ ডায়ালগ কখনও দেখানো হয় না এবং সমস্ত ফাইল ডিফল্ট Downloads ফোল্ডারে ল্যান্ড করে।

টেনে আনুন

ডেস্কটপে আমার প্রিয় সিস্টেম ইন্টিগ্রেশনগুলির মধ্যে একটি হল টানুন এবং ড্রপ করুন। Excalidraw-এ, যখন আমি অ্যাপ্লিকেশনে একটি .excalidraw ফাইল ড্রপ করি, তখনই এটি খুলে যায় এবং আমি সম্পাদনা শুরু করতে পারি। ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে এমন ব্রাউজারগুলিতে, আমি আমার পরিবর্তনগুলি অবিলম্বে সংরক্ষণ করতে পারি। ড্র্যাগ অ্যান্ড ড্রপ অপারেশন থেকে প্রয়োজনীয় ফাইল হ্যান্ডেল পাওয়া গেছে বলে একটি ফাইল সংরক্ষণ ডায়ালগের মাধ্যমে যাওয়ার দরকার নেই।

ফাইল সিস্টেম অ্যাক্সেস API সমর্থিত হলে ডেটা স্থানান্তর আইটেমে getAsFileSystemHandle() কল করে এটি ঘটানোর গোপনীয়তা। তারপর আমি এই ফাইল হ্যান্ডেলটি 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