قراءة الملفات والأدلة وكتابتها باستخدام مكتبة open-fs-access

قدرة المتصفحات على التعامل مع الملفات والأدلة لوقت طويل. File API توفر ميزات لتمثيل كائنات الملفات في تطبيقات الويب، بالإضافة إلى اختيارها آليًا والوصول إلى بياناتها. مع ذلك، في اللحظة التي تنظر فيها عن قرب، لا يكون كل ذلك بريقًا ذهبيًا.

الطريقة التقليدية للتعامل مع الملفات

فتح الملفات

يمكنك كمطور برامج فتح الملفات وقراءتها من خلال <input type="file"> العنصر. في أبسط صوره، يمكن أن يبدو فتح ملف مشابهًا لنموذج التعليمات البرمجية أدناه. يمنحك الكائن input السمة FileList، والتي في الحالة أدناه تتكون من قاعدة واحدة فقط File File هو نوع معيّن من Blob، ويمكن استخدامها في أي سياق يمكن أن تفعله Blob.

const openFile = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

فتح الأدلة

لفتح المجلدات (أو الأدلة)، يمكنك ضبط <input webkitdirectory> . بخلاف ذلك، تعمل جميع العناصر الأخرى بالطريقة نفسها كما هو موضح أعلاه. على الرغم من أن اسم البائع الذي يبدأه البائع، لا يمكن استخدام webkitdirectory في Chromium وWebKit فحسب، بل يمكن استخدامه أيضًا في Edge القديم المستند إلى EdgeHTML وكذلك في Firefox.

حفظ الملفات (بدلاً من تنزيلها)

لحفظ ملف، تقتصر عادةً على تنزيل الملف، والذي يعمل بفضل <a download> . يمكنك ضبط السمة href الخاصة بالارتساء على عنوان URL للسمة blob: يمكنك الحصول عليه من خلال URL.createObjectURL() .

const saveFile = async (blob) => {
  const a = document.createElement('a');
  a.download = 'my-file.txt';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

المشكلة

ومن الجانب السلبي الكبير في أسلوب التنزيل أنه ليست هناك طريقة لإنشاء ملف فتح ← تعديل ← حفظ الإجراء، أي أنه لا توجد طريقة لاستبدال الملف الأصلي. بدلاً من ذلك، ستحصل على نسخة جديدة من الملف الأصلي. في مجلد Downloads (التنزيلات) التلقائي لنظام التشغيل كلما اخترت "الحفظ".

واجهة برمجة التطبيقات File System Access API

تجعل واجهة برمجة التطبيقات File System Access API كلاً من العمليات والفتح والحفظ أسهل كثيرًا. ويتيح ذلك أيضًا الحفظ الصحيح، أي لا يمكنك فقط اختيار مكان حفظ الملف، ولكن أيضًا استبدال ملف حالي.

فتح الملفات

باستخدام File System Access API، يعتمد فتح ملف على استدعاء واحد لطريقة window.showOpenFilePicker(). تعرض هذه الاستدعاء مؤشر ملف يمكنك من خلاله الحصول على File الفعلي بطريقة getFile().

const openFile = async () => {
  try {
    // Always returns an array.
    const [handle] = await window.showOpenFilePicker();
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

فتح الأدلة

يمكنك فتح دليل من خلال الاتصال window.showDirectoryPicker() يؤدي إلى إتاحة إمكانية اختيار الأدلة في مربّع الحوار للملف.

حفظ الملفات

وبالمثل، فإن حفظ الملفات أمر واضح تمامًا. من اسم معرِّف ملف، يمكنك إنشاء بث قابل للكتابة عبر createWritable()، ثم تكتب بيانات Blob عن طريق استدعاء طريقة write() للبث، وأخيرًا، يتم إغلاق البث من خلال استدعاء طريقة close().

const saveFile = async (blob) => {
  try {
    const handle = await window.showSaveFilePicker({
      types: [{
        accept: {
          // Omitted
        },
      }],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
    return handle;
  } catch (err) {
    console.error(err.name, err.message);
  }
};

لمحة عن متصفّح-fs-access

تمامًا مثل واجهة برمجة تطبيقات File System Access API، أنها ليست متاحة على نطاق واسع بعد.

جدول دعم المتصفّح لواجهة File System Access API تظهر علامة &quot;لا يتوفر دعم&quot; على جميع المتصفّحات. أو &quot;وراء علم&quot;.
جدول دعم المتصفّح لواجهة File System Access API (المصدر)

ولهذا السبب، أرى أن واجهة File System Access API هي تحسين تدريجي. وبالتالي، أريد استخدامها عندما يتيح المتصفّح ذلك، واستخدام النهج التقليدي إن لم يكن كذلك؛ وكل ذلك مع عدم معاقبة المستخدم بعمليات تنزيل غير ضرورية لرمز JavaScript غير متوافق. يعمل متصفّح browser-fs-access والمكتبة هي ردي على هذا التحدي.

فلسفة التصميم

ونظرًا لأنه من المحتمل أن تتغير واجهة برمجة تطبيقات File System Access API في المستقبل، لم يتم تصميم واجهة برمجة تطبيقات browser-fs-access بعد ذلك. وهذا يعني أنّ المكتبة ليست polyfill، بل بالأحرى عبارة عن قصة قصيرة. يمكنك (بشكل ثابت أو ديناميكي) استيراد أي وظائف تحتاجها بشكل حصري من أجل إبقاء تطبيقك صغيرًا قدر الإمكان. الطرق المتاحة يتم تسميتها بشكل مناسب fileOpen()، directoryOpen() و fileSave() داخليًا، تكتشف ميزة المكتبة ما إذا كانت واجهة برمجة التطبيقات File System Access API متوافقة، ثم استيراد مسار الرمز المقابل.

استخدام مكتبة المتصفّح-fs-access

من السهل استخدام الطرق الثلاث. يمكنك تحديد mimeTypes أو الملف extensions المقبولَين لتطبيقك، وضبط علامة multiple. للسماح بتحديد عدة ملفات أو أدلة أو عدم السماح بتحديدها. للحصول على التفاصيل الكاملة، يمكنك مراجعة مستندات واجهة برمجة التطبيقات Browser-fs-access يوضح نموذج التعليمات البرمجية أدناه كيفية فتح ملفات الصور وحفظها.

// The imported methods will use the File
// System Access API or a fallback implementation.
import {
  fileOpen,
  directoryOpen,
  fileSave,
} from 'https://unpkg.com/browser-fs-access';

(async () => {
  // Open an image file.
  const blob = await fileOpen({
    mimeTypes: ['image/*'],
  });

  // Open multiple image files.
  const blobs = await fileOpen({
    mimeTypes: ['image/*'],
    multiple: true,
  });

  // Open all files in a directory,
  // recursively including subdirectories.
  const blobsInDirectory = await directoryOpen({
    recursive: true
  });

  // Save a file.
  await fileSave(blob, {
    fileName: 'Untitled.png',
  });
})();

عرض توضيحي

يمكنك رؤية الرمز أعلاه وهو قيد التطبيق في عرض توضيحي لأداة Glitch. وبالمثل، يتوفر رمز المصدر هناك. نظرًا لعدم السماح للإطارات الفرعية من مصادر متعددة بعرض أداة اختيار الملفات، فهذا ليس مسموحًا به، لا يمكن تضمين العرض التوضيحي في هذه المقالة.

مكتبة متصفّح fs-access متاحة في البرية

في وقت فراغي، أساهم قليلاً في تطبيق ويب تقدّمي (PWA) قابل للتثبيت واسمه ExcaliDraw، أداة سبورة تتيح لك رسم المخططات البيانية بسهولة بطريقة مرسومة باليد. وهو مستجيب بالكامل ويعمل بشكل جيد على مجموعة من الأجهزة بدءًا من الهواتف المحمولة الصغيرة إلى أجهزة الكمبيوتر ذات الشاشات الكبيرة. هذا يعني أنها بحاجة إلى التعامل مع الملفات على جميع الأنظمة الأساسية المختلفة ما إذا كانت متوافقة مع واجهة برمجة التطبيقات File System Access API أم لا. وهذا يجعله مرشحًا رائعًا لمكتبة web-fs-access.

يمكنني، على سبيل المثال، بدء رسم على هاتف iPhone، حفظه (من الناحية الفنية: التنزيل، لأنّ Safari لا يتوافق مع File System Access API) إلى مجلد عمليات التنزيل على iPhone، وفتح الملف على سطح المكتب (بعد نقله من هاتفي)، أو تعديل الملف واستبداله بالتغييرات التي أجريتها، أو حتى حفظه كملف جديد.

رسم توضيحي على هاتف iPhone
بدء رسم ExcaliDraw على هاتف iPhone حيث تكون واجهة برمجة التطبيقات File System Access API غير متاحة، ولكن يمكن حفظ (تنزيله) ملف في مجلد "عمليات التنزيل"
رسم ExcaliDraw المعدّل على Chrome على سطح المكتب
فتح رسم ExcaliDraw وتعديله على سطح المكتب حيث تكون واجهة برمجة التطبيقات File System Access API متوافقة، وبالتالي يمكن الوصول إلى الملف من خلال واجهة برمجة التطبيقات.
استبدال الملف الأصلي بالتعديلات
استبدال الملف الأصلي بالتعديلات على ملف رسم ExcaliDraw الأصلي يعرض المتصفّح مربّع حوار يسألني ما إذا كان ذلك مقبولاً.
حفظ التعديلات على ملف رسم ExcaliDraw جديد.
حفظ التعديلات على ملف ExcaliDraw جديد. ويبقى الملف الأصلي بدون أي تغيير.

عيّنة من التعليمات البرمجية الواقعية

يمكنك الاطّلاع أدناه على مثال حقيقي لإذن الوصول إلى المتصفِّح (fs-access) كما هو مُستخدَم في ExcaliDraw. هذا المقتطف مأخوذ من /src/data/json.ts تشكّل الطريقة saveAsJSON() أهمية خاصة في ما يتعلّق بتمرير مؤشر الملف أو null إلى browser-fs-access. fileSave()، تؤدي إلى استبدالها عند توفير اسم معرِّف، أو حفظه في ملف جديد إذا لم يكن كذلك.

export const saveAsJSON = async (
  elements: readonly ExcalidrawElement[],
  appState: AppState,
  fileHandle: any,
) => {
  const serialized = serializeAsJSON(elements, appState);
  const blob = new Blob([serialized], {
    type: "application/json",
  });
  const name = `${appState.name}.excalidraw`;
  (window as any).handle = await fileSave(
    blob,
    {
      fileName: name,
      description: "Excalidraw file",
      extensions: ["excalidraw"],
    },
    fileHandle || null,
  );
};

export const loadFromJSON = async () => {
  const blob = await fileOpen({
    description: "Excalidraw files",
    extensions: ["json", "excalidraw"],
    mimeTypes: ["application/json"],
  });
  return loadFromBlob(blob);
};

اعتبارات واجهة المستخدم

سواء كنت تستخدم ExcaliDraw أو في تطبيقك يجب أن تتكيّف واجهة المستخدم مع وضع دعم المتصفح. إذا كانت واجهة برمجة التطبيقات File System Access API متوافقة (if ('showOpenFilePicker' in window) {}) يمكنك عرض زر حفظ باسم بالإضافة إلى الزر حفظ. توضح لقطات الشاشة أدناه الفرق بين شريط أدوات التطبيق الرئيسي سريع الاستجابة في ExcaliDraw على iPhone وعلى Chrome على أجهزة الكمبيوتر المكتبي. لاحظ كيف أن الزر حفظ باسم غير موجود على iPhone.

شريط أدوات تطبيق ExcaliDraw على iPhone مع النقر على &quot;حفظ&quot; فقط .
شريط أدوات تطبيق Excali draw على هاتف iPhone: فيه زر حفظ فقط
شريط أدوات تطبيق ExcaliDraw على Chrome على سطح المكتب مع الزر &quot;حفظ&quot; وخيار &quot;حفظ باسم&quot; .
أداة ExcaliDraw لشريط أدوات التطبيقات على Chrome مع زر حفظ وزر حفظ باسم مركَّز.

الاستنتاجات

يمكن التعامل مع ملفات النظام من الناحية الفنية على جميع المتصفحات الحديثة. في المتصفحات التي تتيح استخدام واجهة برمجة التطبيقات File System Access API، يمكنك تحسين التجربة من خلال السماح للحفظ والاستبدال الفعلي (وليس فقط التنزيل) للملفات من خلال السماح للمستخدمين بإنشاء ملفات جديدة أينما أرادوا، كل ذلك مع الحفاظ على استخدامها في المتصفحات التي لا تتوافق مع واجهة برمجة التطبيقات File System Access API. متصفّح browser-fs-access يسهّل حياتك من خلال التعامل مع التفاصيل الدقيقة للتحسين التدريجي وجعل التعليمة البرمجية بسيطة قدر الإمكان.

شكر وتقدير

تمت مراجعة هذه المقالة بواسطة جو ميدلي و كايس باسك. نتوجّه بالشكر إلى مساهمين في ExcaliDraw عن عمله في المشروع ومراجعة طلبات السحب. الصورة الرئيسية من تصميم إيليا بافلوف على موقع Unspark.