كيفية إنشاء مكون إضافي للرسم باستخدام JavaScript و HTML و CSS (الجزء 2)

نشرت: 2022-03-10
ملخص سريع ↬ في هذا الجزء الثاني من البرنامج التعليمي الخاص بنا حول إنشاء مكونات Sketch الإضافية ، سننتقل من حيث توقفنا مع بناء واجهة المستخدم الخاصة بنا ، ثم ننتقل إلى الميزة الرئيسية لإنشاء فسيفساء الطبقات لدينا بالفعل تحسين كود البرنامج المساعد النهائي.

كما هو مذكور في الجزء 1 ، هذا البرنامج التعليمي مخصص للأشخاص الذين يعرفون ويستخدمون تطبيق Sketch ولا يخشون الانغماس في الكود أيضًا. للاستفادة منها أكثر من غيرها ، ستحتاج على الأقل إلى بعض الخبرة الأساسية في كتابة JavaScript (واختياريًا ، HTML / CSS).

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

بناء واجهة مستخدم البرنامج المساعد: جعل واجهة الويب الخاصة بنا ورمز البرنامج المساعد Sketch "يتحدثان" مع بعضهما البعض

الشيء التالي الذي يتعين علينا القيام به هو إعداد اتصال بين واجهة الويب الخاصة بنا والمكوِّن الإضافي Sketch.

نحتاج إلى أن نكون قادرين على إرسال رسالة من واجهة الويب الخاصة بنا إلى المكون الإضافي Sketch عند النقر فوق الزر "تطبيق" في واجهة الويب الخاصة بنا. يجب أن تخبرنا هذه الرسالة عن الإعدادات التي أدخلها المستخدم - مثل عدد الخطوات ومقدار التدوير وعدد التكرارات التي يجب إنشاؤها وما إلى ذلك.

يجعل WKWebView هذه المهمة أسهل قليلاً بالنسبة لنا: يمكننا إرسال رسائل إلى المكون الإضافي Sketch من كود JavaScript لواجهة الويب الخاصة بنا باستخدام window.webkit.messageHandlers API.

من جانب كود Sketch ، يمكننا استخدام طريقة أخرى ، addScriptMessageHandler:name: (أو addScriptMessageHandler_name ) لتسجيل معالج الرسائل الذي سيتم استدعاؤه كلما تلقى رسالة مرسلة من واجهة الويب الخاصة بالمكون الإضافي.

لنبدأ بالتأكد من أنه يمكننا تلقي رسائل من واجهة مستخدم الويب الخاصة بنا. توجه إلى ui.js لملف createWebView ، وأضف ما يلي:

 function createWebView(pageURL){ const webView = WKWebView.alloc().init(); // Set handler for messages from script const userContentController = webView.configuration().userContentController(); const ourMessageHandler = ... userContentController.addScriptMessageHandler_name( ourMessageHandler, "sketchPlugin" ); // Load page into web view webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent()); return webView; };

هنا نستخدم خاصية userContentController لعرض الويب لإضافة معالج رسالة أطلقنا عليه اسم "sketchPlugin". "وحدة التحكم في محتوى المستخدم" هي الجسر الذي يضمن وصول الرسائل من عرض الويب الخاص بنا.

المزيد بعد القفز! أكمل القراءة أدناه ↓

ربما لاحظت شيئًا غريبًا بشأن الكود أعلاه: الكائن الذي نضيفه باعتباره معالج الرسالة ، ourMessageHandler ، غير موجود بعد! لسوء الحظ ، لا يمكننا استخدام كائن أو وظيفة JavaScript عادية فقط كمعالج ، حيث تتوقع هذه الطريقة نوعًا معينًا من الكائنات الأصلية.

لحسن الحظ بالنسبة لنا ، يمكننا التغلب على هذا القيد باستخدام MochaJSDelegate ، وهي مكتبة صغيرة كتبتها تجعل من الممكن إنشاء نوع الكائن الأصلي الذي نحتاجه باستخدام JavaScript عادي. ستحتاج إلى تنزيله يدويًا وحفظه في حزمة المكون الإضافي الخاص بك ضمن Sketch/MochaJSDelegate.js .

لاستخدامه ، سنحتاج أولاً إلى استيراده إلى ui.js أضف ما يلي في أعلى الملف:

 const MochaJSDelegate = require("./MochaJSDelegate");

الآن يمكننا استخدام MochaJSDelegate لإنشاء نوع معالج الرسائل addScriptMessageHandler:name: يتوقع:

 function createWebView(pageURL){ const webView = WKWebView.alloc().init(); // Set handler for messages from script const userContentController = webView.configuration().userContentController(); const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { /* handle message here */ } }).getClassInstance(); userContentController.addScriptMessageHandler_name( scriptMessageHandler, "sketchPlugin" ); // Load page into web view webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent()); return webView; };

الكود الذي أضفناه للتو ينشئ الكائن الأصلي الذي نحتاجه. كما تحدد طريقة على هذا الكائن تسمى userContentController:didReceiveScriptMessage: - يتم استدعاء هذه الطريقة بالرسالة التي نريدها كمتغير ثانٍ. نظرًا لأننا لم نرسل أي رسائل بالفعل حتى الآن ، فسيتعين علينا العودة إلى هنا في وقت لاحق وإضافة بعض الرموز لتحليل الرسائل التي نتلقاها والتعامل معها.

بعد ذلك ، نحتاج إلى إضافة بعض التعليمات البرمجية إلى واجهة الويب الخاصة بنا لإرسال هذه الرسائل إلينا. توجه إلى /Resources/web-ui/script.js . ستجد أنني كتبت بالفعل معظم الكود الذي يتعامل مع استرداد قيم HTML <inputs /> التي سيدخل المستخدم خياراته فيها.

ما زال علينا القيام به هو إضافة الكود الذي يرسل القيم فعليًا إلى كود Sketch:

ابحث عن وظيفة apply وأضف ما يلي إلى نهايتها:

 // Send user inputs to sketch plugin window.webkit.messageHandlers.sketchPlugin.postMessage(JSON.stringify({ stepCount, startingOptions, stepOptions }));

هنا نستخدم window.webkit.messageHandlers API التي ذكرناها سابقًا للوصول إلى معالج الرسائل الذي سجلناه أعلاه كـ sketchPlugin . ثم أرسل رسالة إليها بسلسلة JSON تحتوي على مدخلات المستخدم.

لنتأكد من إعداد كل شيء بشكل صحيح. عد إلى / Sketch/ /Sketch/ui.js . للتأكد من وصول الرسائل كما نتوقع ، سنقوم بتعديل الطريقة التي حددناها مسبقًا بحيث تعرض مربع حوار عندما نتلقى رسالة:

 function createWebView(pageURL){ // ... const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const UI = require("sketch/ui"); UI.alert("Hey, a message!", wkMessage.body()); } }).getClassInstance(); userContentController.addScriptMessageHandler_name( scriptMessageHandler, "sketchPlugin" ); // ... };

الآن قم بتشغيل المكون الإضافي (قد تحتاج أولاً إلى إغلاق أي نافذة فسيفساء موجودة قمت بفتحها) ، أدخل بعض القيم ، ثم انقر فوق "تطبيق". يجب أن ترى تنبيهًا مثل التنبيه أدناه - وهذا يعني أن كل شيء تم توصيله بشكل صحيح وتم إرسال رسالتنا بنجاح! إذا لم يكن الأمر كذلك ، فارجع إلى الخطوات السابقة وتأكد من أن كل شيء قد تم كما هو موضح.

صورة توضح مربع الحوار الذي يجب أن تراه بعد النقر على الزر "تطبيق" في واجهة مستخدم المكون الإضافي.
يظهر مربع الحوار الذي ستراه بمجرد النقر فوق "تطبيق". (معاينة كبيرة)

الآن بعد أن أصبحنا قادرين على إرسال رسائل من واجهتنا إلى المكون الإضافي الخاص بنا ، يمكننا الانتقال إلى كتابة الكود الذي يفعل شيئًا مفيدًا بالفعل بهذه المعلومات: إنشاء فسيفساء الطبقة الخاصة بنا.

توليد طبقة الفسيفساء

دعنا نقيم ما هو ضروري لتحقيق ذلك. تبسيط الأشياء قليلاً ، ما يحتاج إليه الكود الخاص بنا هو:

  1. ابحث عن المستند الحالي.
  2. ابحث عن الطبقة المحددة للمستند الحالي.
  3. قم بتكرار الطبقة المحددة (سنسميها طبقة القالب ) × عدد المرات.
  4. لكل نسخة ، قم بتعديل موضعها ، وتدويرها ، وشفافيتها ، وما إلى ذلك ، من خلال القيم المحددة (المبالغ) التي حددها المستخدم.

الآن بعد أن أصبح لدينا خطة معقولة ، فلنواصل الكتابة. بالتمسك بنمطنا الخاص بتعديل الكود الخاص بنا ، فلنقم بإنشاء ملف جديد ، mosaic.js في Sketch/ المجلد ، وأضف إليه الكود التالي:

 function mosaic(options){ }; module.export = mosaic;

سنستخدم هذه الوظيفة كالتصدير الوحيد لهذه الوحدة لأنها تجعل واجهة برمجة التطبيقات أبسط للاستخدام بمجرد استيرادها - يمكننا فقط استدعاء mosaic() مع أي خيارات نحصل عليها من واجهة الويب.

أول خطوتين يتعين علينا القيام بهما هما الحصول على المستند الحالي ، ثم طبقته المحددة. يحتوي Sketch API على مكتبة مضمنة لمعالجة المستندات والتي يمكننا الوصول إليها عن طريق استيراد وحدة sketch/dom . نحتاج فقط إلى كائن Document في الوقت الحالي ، لذلك سنقوم بسحبه بشكل صريح. في الجزء العلوي من الملف ، أضف:

 const { Document } = require("sketch/dom");

يحتوي كائن Document على طريقة خاصة للوصول إلى المستند الحالي الذي يمكننا استخدامه ، تسمى getSelectedDocument() . بمجرد حصولنا على نسخة المستند الحالية ، يمكننا الوصول إلى أي طبقات يختارها المستخدم عبر خاصية الطبقات selectedLayers في المستند. على الرغم من ذلك ، في حالتنا ، نحن نهتم فقط بالتحديدات أحادية الطبقة ، لذلك سننتزع فقط الطبقة الأولى التي حددها المستخدم:

 function mosaic(options){ const document = Document.getSelectedDocument(); const selectedLayer = document.selectedLayers.layers[0]; }; module.export = mosaic;

ملاحظة: ربما كنت تتوقع أن تكون selectedLayers نفسها مصفوفة ، لكنها ليست كذلك. بدلاً من ذلك ، إنه مثيل لفئة Selection . هناك سبب لذلك: تحتوي فئة Selection على مجموعة من الأساليب المساعدة المفيدة لمعالجة التحديد مثل clear ، و map ، و reduction ، و forEach. يعرض مصفوفة الطبقة الفعلية عبر خاصية layer .

دعنا نضيف أيضًا بعض التعليقات التحذيرية في حالة نسيان المستخدم فتح مستند أو تحديد شيء ما:

 const UI = require("sketch/ui"); function mosaic(options){ const document = Document.getSelectedDocument(); // Safety check: if(!document){ UI.alert("Mosaic", "️ Please select/focus a document."); return; } // Safety check: const selectedLayer = document.selectedLayers.layers[0]; if(!selectedLayer){ UI.alert("Mosaic", "️ Please select a layer to duplicate."); return; } }; module.export = mosaic;

الآن بعد أن كتبنا الكود للخطوتين 1 و 2 (إيجاد المستند الحالي والطبقة المحددة) ، نحتاج إلى معالجة الخطوتين 3 و 4:

  • قم بتكرار طبقة القالب × عدد المرات.
  • لكل نسخة ، قم بتعديل موضعها ، وتدويرها ، وشفافيتها ، وما إلى ذلك ، من خلال القيم المحددة التي حددها المستخدم.

لنبدأ بسحب جميع المعلومات ذات الصلة التي نحتاجها من options : عدد مرات التكرار وخيارات البدء وخيارات الخطوة. يمكننا مرة أخرى استخدام التدمير (كما فعلنا سابقًا مع Document ) لسحب تلك الخصائص من options :

 function mosaic(options) { // ... // Destructure options: var { stepCount, startingOptions, stepOptions } = options; }

بعد ذلك ، دعنا نعقم مدخلاتنا ونتأكد من أن عدد الخطوات دائمًا على الأقل 1:

 function mosaic(options) { // ... // Destructure options: var { stepCount, startingOptions, stepOptions } = options; stepCount = Math.max(1, stepCount); }

نحتاج الآن إلى التأكد من أن عتامة طبقة القالب ، وتدويرها ، وما إلى ذلك ، تتطابق جميعها مع قيم البداية التي يريدها المستخدم. نظرًا لأن تطبيق خيارات المستخدم على طبقة سيكون شيئًا سنفعله كثيرًا ، فسننقل هذا العمل إلى طريقته الخاصة:

 function configureLayer(layer, options, shouldAdjustSpacing){ const { opacity, rotation, direction, spacing } = options; layer.style.opacity = opacity / 100; layer.transform.rotation = rotation; if(shouldAdjustSpacing){ const directionAsRadians = direction * (Math.PI / 180); const vector = { x: Math.cos(directionAsRadians), y: Math.sin(directionAsRadians) }; layer.frame.x += vector.x * spacing; layer.frame.y += vector.y * spacing; } };

ونظرًا لأن التباعد يحتاج فقط إلى أن يتم تطبيقه بين التكرارات وليس طبقة القالب ، فقد أضفنا علامة معينة ، shouldAdjustSpacing ، والتي يمكننا ضبطها على true أو false اعتمادًا على ما إذا كنا نطبق خيارات على طبقة قالب أو ليس. بهذه الطريقة يمكننا ضمان تطبيق التدوير والتعتيم على القالب ، ولكن ليس التباعد.

بالعودة إلى طريقة mosaic ، دعنا نتأكد الآن من تطبيق خيارات البداية على طبقة القالب:

 function mosaic(options){ // ... // Configure template layer var layer = group.layers[0]; configureLayer(layer, startingOptions, false); }

بعد ذلك ، نحتاج إلى إنشاء التكرارات الخاصة بنا. أولاً ، لنقم بإنشاء متغير يمكننا استخدامه لتتبع خيارات التكرار الحالي:

 function mosaic(options){ // ... var currentOptions; // ... }

نظرًا لأننا طبقنا بالفعل خيارات البداية على طبقة القالب ، نحتاج إلى اتخاذ تلك الخيارات التي طبقناها للتو وإضافة القيم النسبية لـ stepOptions من أجل الحصول على الخيارات للتطبيق على الطبقة التالية. نظرًا لأننا سنفعل هذا عدة مرات في الحلقة ، فسننقل هذا العمل أيضًا إلى طريقة معينة ، stepOptionsBy :

 function stepOptionsBy(start, step){ const newOptions = {}; for(let key in start){ newOptions[key] = start[key] + step[key]; } return newOptions; };

بعد ذلك ، نحتاج إلى كتابة حلقة تكرار الطبقة السابقة ، وتطبيق الخيارات الحالية عليها ، ثم إزاحة (أو "خطوات") الخيارات الحالية من أجل الحصول على خيارات التكرار التالي:

 function mosaic(options) { // ... var currentOptions = stepOptionsBy(startingOptions, stepOptions); for(let i = 0; i < (stepCount - 1); i++){ let duplicateLayer = layer.duplicate(); configureLayer(duplicateLayer, currentOptions, true); currentOptions = stepOptionsBy(currentOptions, stepOptions); layer = duplicateLayer; } }

تم الانتهاء - لقد كتبنا بنجاح جوهر ما يفترض أن يقوم به المكون الإضافي الخاص بنا! الآن ، نحتاج إلى ربط الأشياء بحيث عندما ينقر المستخدم بالفعل على الزر "تطبيق" ، يتم استدعاء رمز الفسيفساء الخاص بنا.

دعنا نعود إلى ui.js رمز معالجة الرسائل. ما يتعين علينا القيام به هو تحليل سلسلة خيارات JSON التي نحصل عليها حتى يتم تحويلها إلى كائن يمكننا استخدامه بالفعل. بمجرد أن تكون لدينا هذه الخيارات ، يمكننا عندئذٍ استدعاء وظيفة mosaic معهم.

أولا ، الاعراب. سنحتاج إلى تحديث وظيفة معالجة الرسائل لدينا لتحليل رسالة JSON التي نحصل عليها:

 function createWebView(pageURL){ // ... const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const message = JSON.parse(wkMessage.body()); } }); }

بعد ذلك ، سنحتاج إلى تمرير هذا إلى وظيفة mosaic الخاصة بنا. ومع ذلك ، هذا ليس شيئًا يجب أن يفعله الكود الخاص بنا في ui.js - من المفترض أن يهتم بشكل أساسي بما هو ضروري لعرض الأشياء المتعلقة بالواجهة على الشاشة - وليس إنشاء الفسيفساء نفسها. للإبقاء على هذه المسؤوليات منفصلة ، سنضيف وسيطة ثانية لإنشاء WebView تأخذ وظيفة ، createWebView هذه الوظيفة عندما نتلقى خيارات من واجهة الويب.

دعنا نسمي هذه الحجة onApplyMessage :

 function createWebView(pageURL, onApplyMessage){ // ... const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const message = JSON.parse(wkMessage.body()); onApplyMessage(message); } }); }

سنحتاج أيضًا إلى تعديل أسلوبنا المُصدَّر ، loadAndShow ، لأخذ وسيطة onApplyMessage هذه أيضًا وتمريرها إلى createWebView :

 function loadAndShow(baseURL, onApplyMessage){ // ... const webView = createWebView(pageURL, onApplyMessage); }

أخيرًا ، توجه إلى main.js نحتاج الآن إلى استيراد وظيفة mosaic الخاصة بنا ، واستدعائها بالخيارات التي نتلقاها من واجهة مستخدم المكون الإضافي:

 const mosaic = require("./mosaic"); function onRun(context){ UI.loadAndShow(context.scriptURL, options => { mosaic(options); }); };

نحن على وشك الانتهاء!

ومع ذلك ، إذا قمنا بتشغيل الكود الخاص بنا الآن وقمنا بالنقر فوق الزر "تطبيق" في واجهة البرنامج المساعد ، فلن يحدث شيء. لماذا ا؟ يرجع السبب إلى كيفية تشغيل سكيتش سكربتات نصية: افتراضيًا ، "تعيش" فقط حتى الوصول إلى الجزء السفلي من النص ، وبعد ذلك يقوم سكتش بتدميرها وتحرير أي موارد كانت تستخدمها.

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

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

 // ... const Async = require("sketch/async"); var fiber; function onRun(context){ if(!fiber){ fiber = Async.createFiber(); fiber.onCleanup(() => { UI.cleanup(); }); } UI.loadAndShow(context.scriptURL, options => { mosaic(options); }); };

هاهو! دعنا نجرب البرنامج المساعد الخاص بنا الآن. بعد تحديد طبقة في Sketch ، أدخل بعض الإعدادات ، ثم انقر فوق تطبيق:

لنجرب المكون الإضافي الآن - مع طبقة محددة في Sketch ، أدخل بعض الإعدادات ، ثم انقر فوق "تطبيق".

التحسينات النهائية

الآن بعد أن تم تنفيذ غالبية وظائف المكون الإضافي الخاص بنا ، يمكننا محاولة "التصغير" قليلاً وإلقاء نظرة على الصورة الكبيرة.

تحسين تجربة المستخدم

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

في تدفق أكثر مثالية ، يمكن للمستخدم فقط تحديد مجموعة Mosaic ، وضبط الخيارات ومشاهدة تحديث Mosaic حتى يحصلوا على الترتيب الدقيق الذي يبحثون عنه. لتنفيذ ذلك ، لدينا مشكلتان يجب حلهما:

  1. أولاً ، سنحتاج إلى طريقة لتجميع التكرارات التي تشكل فسيفساء معًا. يوفر Sketch مفهوم المجموعات ، والتي يمكننا استخدامها لحل هذه المشكلة.
  2. ثانيًا ، سنحتاج إلى طريقة لمعرفة الفرق بين المجموعة العادية التي أنشأها المستخدم ومجموعة الفسيفساء. تعطينا واجهة برمجة تطبيقات Sketch أيضًا طريقة لتخزين المعلومات على أي طبقة معينة ، والتي يمكننا استخدامها كعلامة طريق ثم تحديد المجموعة لاحقًا كواحدة من مجموعات الفسيفساء "الخاصة" الخاصة بنا.

دعنا نعيد النظر في المنطق الذي كتبناه في القسم السابق لمعالجة هذا الأمر. الكود الأصلي الخاص بنا يتبع الخطوات التالية:

  1. ابحث عن المستند الحالي.
  2. ابحث عن الطبقة المحددة للمستند الحالي.
  3. قم بتكرار الطبقة المحددة (سنسميها طبقة القالب ) × عدد المرات.
  4. لكل نسخة ، قم بتعديل موضعها ، وتدويرها ، وشفافيتها ، وما إلى ذلك ، من خلال القيم المحددة (المبالغ) التي حددها المستخدم.

لجعل تدفق المستخدمين الجدد ممكنًا ، نحتاج إلى تغيير هذه الخطوات إلى:

  1. احصل على المستند الحالي.
  2. احصل على الطبقة المحددة للمستند الحالي.
  3. حدد ما إذا كانت الطبقة المحددة عبارة عن مجموعة فسيفساء أم لا.
    • إذا كانت هناك طبقة أخرى ، فاستخدمها كطبقة قالب وانتقل إلى الخطوة 4.
    • إذا كانت مجموعة فسيفساء ، ففكر في الطبقة الأولى فيها كطبقة قالب ، وانتقل إلى الخطوة 5.
  4. قم بلف طبقة القالب داخل مجموعة ، وقم بتمييز هذه المجموعة كمجموعة فسيفساء.
  5. قم بإزالة كل الطبقات من داخل المجموعة باستثناء طبقة القالب.
  6. قم بتكرار طبقة القالب × عدد المرات.
  7. لكل نسخة ، قم بتعديل موضعها ، وتدويرها ، وشفافيتها ، وما إلى ذلك ، من خلال القيم المحددة التي حددها المستخدم.

لدينا ثلاث خطوات جديدة. للخطوة الجديدة الأولى ، الخطوة 3 ، سننشئ وظيفة باسم findOrMakeSpecialGroupIfNeeded والتي ستنظر في الطبقة التي تم تمريرها إليها لتحديد ما إذا كانت مجموعة فسيفساء أم لا. إذا كان الأمر كذلك ، فسنعيده فقط. نظرًا لأنه من المحتمل أن يختار المستخدم طبقة فرعية متداخلة في عمق مجموعة فسيفساء ، فسنحتاج أيضًا إلى التحقق من آباء الطبقة المحددة لمعرفة ما إذا كانوا ضمن مجموعات الفسيفساء لدينا أيضًا:

 function findOrMakeSpecialGroupIfNeeded(layer){ // Loop up through the parent hierarchy, looking for a special group var layerToCheck = layer; while(layerToCheck){ if(/* TODO: is mosaic layer? */){ return layerToCheck; } layerToCheck = layerToCheck.parent; } };

إذا لم نتمكن من العثور على مجموعة فسيفساء ، فسنقوم ببساطة بلف الطبقة التي مررنا بها داخل Group ، ثم نضع علامة عليها كمجموعة فسيفساء.

بالعودة إلى أعلى الملف ، سنحتاج إلى سحب فئة المجموعة الآن أيضًا:

 const { Document, Group } = require("sketch/dom");
 function findOrMakeSpecialGroupIfNeeded(layer){ // Loop up through the parent hierarchy, looking for a special group var layerToCheck = layer; while(layerToCheck){ if(/* TODO: is mosaic layer? */){ return layerToCheck; } layerToCheck = layerToCheck.parent; } // Group const destinationParent = layer.parent; const group = new Group({ name: "Mosaic Group", layers: [ layer ], parent: destinationParent }); /* TODO: mark group as mosaic layer */ return group; };

الآن نحن بحاجة لملء الفراغات (todo's). بادئ ذي بدء ، نحتاج إلى وسيلة لتحديد ما إذا كانت المجموعة هي إحدى المجموعات الخاصة التي تنتمي إلينا أم لا. هنا ، تأتي وحدة Settings في مكتبة Sketch لإنقاذنا. يمكننا استخدامه لتخزين المعلومات المخصصة على طبقة معينة ، وكذلك لقراءتها مرة أخرى.

بمجرد استيراد الوحدة في الجزء العلوي من الملف:

 const Settings = require("sketch/settings");

يمكننا بعد ذلك استخدام طريقتين رئيسيتين يوفرهما ، setLayerSettingForKey و layerSettingForKey ، لتعيين وقراءة البيانات خارج طبقة:

 function findOrMakeSpecialGroupIfNeeded(layer){ const isSpecialGroupKey = "is-mosaic-group"; // Loop up through the parent hierarchy, looking for a special group var layerToCheck = layer; while(layerToCheck){ let isSpecialGroup = Settings.layerSettingForKey(layerToCheck, isSpecialGroupKey); if(isSpecialGroup) return layerToCheck; layerToCheck = layerToCheck.parent; } // Group const destinationParent = layer.parent; layer.remove(); // explicitly remove layer from it's existing parent before adding it to group const group = new Group({ name: "Mosaic Group", layers: [ layer ], parent: destinationParent }); Settings.setLayerSettingForKey(group, isSpecialGroupKey, true); return group; };

الآن بعد أن أصبح لدينا طريقة تتعامل مع التفاف طبقة في مجموعة فسيفساء (أو ، إذا كانت مجموعة فسيفساء بالفعل ، فقم فقط بإعادتها) يمكننا الآن توصيلها بطريقة mosaic الرئيسية لدينا بعد فحوصات السلامة لدينا:

 function mosaic(options){ // ... safety checks ... // Group selection if needed: const group = findOrMakeSpecialGroupIfNeeded(selectedLayer); }

بعد ذلك سنضيف حلقة لإزالة جميع الطبقات من المجموعة باستثناء طبقة القالب (وهي الأولى):

 function mosaic(options) { // ... // Remove all layers except the first: while(group.layers.length > 1){ group.layers[group.layers.length - 1].remove(); } }

أخيرًا ، سنتأكد من أن حجم المجموعة مناسب لمحتوياتها الجديدة نظرًا لأن المستخدم ربما حدد في الأصل طبقة متداخلة داخل المجموعة القديمة (طبقة ربما نكون قد أزلناها).

سنحتاج أيضًا إلى التأكد من تعيين التحديد الحالي لمجموعة الفسيفساء الخاصة بنا نفسها. سيضمن هذا أنه في حالة قيام المستخدم بإجراء مجموعة من التغييرات السريعة على نفس مجموعة الفسيفساء ، فلن يتم إلغاء تحديده. بعد الكود الذي كتبناه بالفعل لتكرار طبقة ، أضف:

 function mosaic(options) { // ... // Fit group to duplicates group.adjustToFit(); // Set selection to the group document.selectedLayers.clear(); group.selected = true; }

جرب البرنامج المساعد مرة أخرى. يجب أن تجد أن تحرير الفسيفساء أصبح أكثر سلاسة الآن!

تحسين الواجهة

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

تتمثل إحدى طرق إصلاح ذلك في الاستماع إلى وقت انتهاء تحميل واجهة الويب ، وعندها فقط تظهر نافذتنا. هناك طريقة ، webView:didFinishNavigation: ، سيتصل بها WKWebView عندما تنتهي الصفحة الحالية من التحميل. يمكننا استخدامه للحصول على الإشعار الذي نبحث عنه بالضبط.

بالعودة إلى ui.js ، سنقوم بتوسيع مثيل MochaJSDelegate الذي أنشأناه لتنفيذ هذه الطريقة ، والتي بدورها ستستدعي الوسيطة onLoadFinish التي سنمررها إلى createWebView :

 function createWebView(pageURL, onApplyMessage, onLoadFinish){ const webView = WKWebView.alloc().init(); // Create delegate const delegate = new MochaJSDelegate({ "webView:didFinishNavigation:": (webView, navigation) => { onLoadFinish(); }, "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const message = JSON.parse(wkMessage.body()); onApplyMessage(message); } }).getClassInstance(); // Set load complete handler webView.navigationDelegate = delegate; // Set handler for messages from script const userContentController = webView.configuration().userContentController(); userContentController.addScriptMessageHandler_name(delegate, "sketchPlugin"); // Load page into web view webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent()); return webView; };

وبالعودة إلى طريقة loadAndShow ، سنقوم بتعديلها بحيث تظهر النافذة بمجرد تحميل عرض الويب:

 function loadAndShow(baseURL, onApplyMessage){ // ... const window = createWindow(); const webView = createWebView(pageURL, onApplyMessage, () => { showWindow(window); }); window.contentView = webView; _window = window; };

بنغو! الآن يتم عرض نافذتنا فقط عند انتهاء تحميل عرض الويب ، لتجنب هذا الوميض البصري المزعج.

خاتمة

تهانينا ، لقد أنشأت أول مكون إضافي لبرنامج Sketch!

إذا كنت ترغب في التثبيت والتشغيل مع Mosaic ، فيمكنك تنزيل المكون الإضافي الكامل من GitHub. وقبل أن تذهب ، إليك بعض الموارد التي قد تكون مفيدة خلال بقية رحلتك:

  • developer.sketchapp.com المورد الرسمي فيما يتعلق بتطوير البرنامج المساعد Sketch. يحتوي على العديد من الأدلة المفيدة ، بالإضافة إلى مرجع API لمكتبة Sketch JavaScript.
  • sketchplugins.com مجتمع رائع ومفيد لمطوري البرنامج المساعد Sketch. رائع للحصول على إجابات لجميع أسئلتك الملحة.
  • github.com/sketchplugins/plugin-directory Official ، مستودع GitHub المركزي لمكونات Sketch الإضافية. يمكنك إرسال الملحقات الخاصة بك هنا ومشاركتها مع بقية مجتمع Sketch!