كان بإمكان المتصفّحات التعامل مع الملفات والأدلة منذ وقت طويل. توفر واجهة برمجة تطبيقات الملف ميزات لتمثيل عناصر الملفات في تطبيقات الويب، بالإضافة إلى اختيارها آليًا والوصول إلى بياناتها. ولكن عند التدقيق، ستلاحظ أنّ كل ما يلمع ليس ذهبًا.
الطريقة التقليدية للتعامل مع الملفات
فتح الملفات
بصفتك مطوّرًا، يمكنك فتح الملفات وقراءتها من خلال العنصر
<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 فقط، بل يمكن استخدامه أيضًا في متصفّح 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();
};
المشكلة
من بين الجوانب السلبية الكبيرة لطريقة التنزيل أنّه لا يمكن تنفيذ الخطوات الكلاسيكية التالية: الفتح→التعديل→الحفظ، أي أنّه لا يمكن استبدال الملف الأصلي. بدلاً من ذلك، ستحصل على نسخة جديدة من الملف الأصلي في مجلد "عمليات التنزيل" التلقائي في نظام التشغيل عند "الحفظ".
واجهة برمجة التطبيقات 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 هي ميزة تحسين تدريجي. ولهذا السبب، أريد استخدامها عندما يكون المتصفّح متوافقًا معها، واستخدام النهج التقليدي في حال عدم توافقه، مع عدم تحميل المستخدم لأي رمز JavaScript غير متوافق. مكتبة browser-fs-access هي الحلّ الذي أقدّمه لهذا التحدي.
فلسفة التصميم
وبما أنّه من المحتمل أن تتغيّر واجهة برمجة التطبيقات File System Access API في المستقبل،
لم يتم تصميم نموذج لـ browser-fs-access API.
وهذا يعني أنّ المكتبة ليست polyfill،
بل عبارة عن ponyfill.
يمكنك استيراد أي وظائف تحتاجها حصريًا (سواءً بشكل ثابت أو ديناميكي) للحفاظ على حجم تطبيقك صغيرًا قدر الإمكان.
والطرق المتاحة هي ذات الاسم المناسب
fileOpen()
وdirectoryOpen()
وfileSave()
.
داخل المكتبة، ترصد ميزة المكتبة ما إذا كانت واجهة برمجة التطبيقات File System Access API متوافقة، ثم تستورد مسار الرمز البرمجي المقابل.
استخدام مكتبة web-fs-access
الطرق الثلاث سهلة الاستخدام.
يمكنك تحديد mimeTypes
أو ملف extensions
المقبولَين في تطبيقك، وضبط علامة multiple
للسماح باختيار ملفات أو أدلة متعددة أو عدم السماح بذلك.
للاطّلاع على التفاصيل الكاملة، يُرجى الاطّلاع على
مستندات واجهة برمجة التطبيقات browser-fs-access API.
يوضح نموذج التعليمات البرمجية أدناه كيفية فتح ملفات الصور وحفظها.
// 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. يتوفّر أيضًا رمز المصدر هناك. نظرًا لعدم السماح للإطارات الفرعية من مصادر متعددة بعرض أداة اختيار الملفات لأسباب أمنية، لا يمكن تضمين العرض التوضيحي في هذه المقالة.
مكتبة browser-fs-access في الاستخدام الفعلي
في وقت فراغي، أساهم قليلاً في تطوير تطبيق متوافق مع الأجهزة الجوّالة (PWA) قابل للتثبيت يُسمى Excalidraw، وهو أداة لوح معلومات تتيح لك بسهولة رسم مخطّطات بيانية بأسلوب يشبه الرسم اليدوي. وهو مستجيب بالكامل ويعمل بشكل جيد على مجموعة من الأجهزة بدءًا من الهواتف المحمولة الصغيرة إلى أجهزة الكمبيوتر ذات الشاشات الكبيرة. وهذا يعني أنّه يجب التعامل مع الملفات على جميع الأنظمة الأساسية المختلفة سواء كانت متوافقة مع واجهة برمجة التطبيقات File System Access API أم لا. وهذا يجعله مرشحًا رائعًا لمكتبة browser-fs-access.
على سبيل المثال، يمكنني بدء رسم على هاتف iPhone، وحفظه (من الناحية الفنية: تنزيله، لأنّ Safari لا يتيح استخدام واجهة برمجة التطبيقات File System Access API) في مجلد "عمليات التنزيل" على هاتف iPhone، وفتح الملف على الكمبيوتر المكتبي (بعد نقله من هاتفي)، وتعديل الملف واستبداله بتغييراتي، أو حتى حفظه كملف جديد.
عيّنة تعليمات برمجية من واقع الحياة
في ما يلي مثال فعلي على استخدام browser-fs-access في Excalidraw.
هذا المقتطف مأخوذ من /src/data/json.ts
.
من المثير للاهتمام معرفة كيفية تمرير الطريقة saveAsJSON()
إما معالِج ملف أو null
إلى الطريقة
fileSave()
في browser-fs-access، ما يؤدي إلى استبدال الملف عند تقديم معالِج،
أو حفظه في ملف جديد في حال عدم تقديم معالِج.
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.
الاستنتاجات
من الناحية الفنية، يمكن التعامل مع ملفات النظام على جميع المتصفّحات الحديثة. في المتصفّحات التي تتوافق مع File System Access API، يمكنك تحسين تجربة الاستخدام من خلال السماح بحفظ الملفات واستبدالها بشكل حقيقي (وليس فقط تنزيلها)، وكذلك السماح للمستخدمين بإنشاء ملفات جديدة أينما أرادوا، وكل ذلك مع الاحتفاظ بوظائفهم على المتصفحات التي لا تتوافق مع واجهة File System Access API. تسهِّل لك واجهة برمجة التطبيقات browser-fs-access الحياة، من خلال التعامل مع التفاصيل الدقيقة للتحسين التدريجي وجعل رمزك البرمجي بسيطًا قدر الإمكان.
شكر وتقدير
تمت مراجعة هذه المقالة من قِبل جو ميدلي و كايسي باسكيز. نشكر المساهمين في Excali draw على عملهم في المشروع وعلى مراجعة "طلبات السحب" التي أقدّمها. الصورة الرئيسية من أعمال Ilya Pavlov على Unsplash