วิธีสร้างปลั๊กอิน Sketch ด้วย 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 ของเราได้ ตรงไปที่ 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/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" ); // ... };

ตอนนี้ให้รันปลั๊กอิน (คุณอาจต้องปิดหน้าต่าง Mosaic ที่คุณเปิดไว้ก่อน) ป้อนค่าบางค่า จากนั้นคลิก "Apply" คุณควรเห็นการแจ้งเตือนดังตัวอย่างด้านล่าง ซึ่งหมายความว่าทุกอย่างเชื่อมต่ออย่างถูกต้องและข้อความของเราผ่านสำเร็จ! หากไม่เป็นเช่นนั้น ให้ย้อนกลับไปยังขั้นตอนก่อนหน้าและตรวจดูให้แน่ใจว่าทุกอย่างเป็นไปตามที่อธิบายไว้

รูปภาพแสดงกล่องโต้ตอบที่คุณควรเห็นหลังจากคลิกปุ่ม "ใช้" ใน UI ของปลั๊กอิน
กล่องโต้ตอบที่คุณควรเห็นปรากฏขึ้นเมื่อคุณคลิกใช้ (ตัวอย่างขนาดใหญ่)

ตอนนี้เราสามารถส่งข้อความจากอินเทอร์เฟซของเราไปยังปลั๊กอินแล้ว เราสามารถไปยังการเขียนโค้ดที่ทำสิ่งที่มีประโยชน์กับข้อมูลนั้นจริงๆ ได้ นั่นคือ การสร้างเลเยอร์โมเสคของเรา

การสร้างโมเสคเลเยอร์

มาดูสิ่งที่จำเป็นเพื่อให้สิ่งนี้เกิดขึ้นกันเถอะ ทำให้สิ่งต่าง ๆ ง่ายขึ้น สิ่งที่โค้ดของเราต้องทำคือ:

  1. ค้นหาเอกสารปัจจุบัน
  2. ค้นหาเลเยอร์ที่เลือกของเอกสารปัจจุบัน
  3. ทำซ้ำเลเยอร์ที่เลือก (เราจะเรียกว่าเลเยอร์ เทมเพลต ) x จำนวนครั้ง
  4. สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับเปลี่ยนตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าเฉพาะ (จำนวน) ที่ผู้ใช้กำหนด

ตอนนี้เรามีแผนที่เหมาะสมแล้ว เรามาเขียนต่อกัน ยึดตามรูปแบบของการทำให้โค้ดเป็นโมดูล มาสร้างไฟล์ใหม่ mosaic.js ในโฟลเดอร์ Sketch/ และเพิ่มโค้ดต่อไปนี้ลงไป:

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

เราจะใช้ฟังก์ชันนี้เป็นการส่งออกโมดูลนี้เพียงอย่างเดียว เนื่องจากทำให้ API ใช้งานง่ายขึ้นเมื่อเรานำเข้าแล้ว เราสามารถเรียก mosaic() ด้วยตัวเลือกใดก็ได้ที่เราได้รับจากอินเทอร์เฟซบนเว็บ

สองขั้นตอนแรกที่เราต้องทำคือการรับเอกสารปัจจุบัน จากนั้นจึงเลือกเลเยอร์ที่เลือก Sketch API มีไลบรารี่ในตัวสำหรับการจัดการเอกสาร ซึ่งเราสามารถเข้าถึงได้โดยการนำเข้าโมดูล sketch/dom ตอนนี้เราต้องการแค่วัตถุ Document ดังนั้นเราจะดึงมันออกมาอย่างชัดเจน ที่ด้านบนของไฟล์ ให้เพิ่ม:

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

ออบเจ็กต์ Document มีเมธอดเฉพาะสำหรับการเข้าถึงเอกสารปัจจุบันที่เราสามารถใช้ได้ เรียกว่า getSelectedDocument() เมื่อเรามีอินสแตนซ์ของเอกสารปัจจุบันแล้ว เราสามารถเข้าถึงเลเยอร์ใดก็ได้ที่ผู้ใช้ selectedLayers ผ่านคุณสมบัติ SelectLayers ของเอกสาร ในกรณีของเรา เราสนใจเฉพาะการเลือกเลเยอร์เดียว ดังนั้นเราจะคว้าเฉพาะเลเยอร์แรกที่ผู้ใช้เลือก:

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

หมายเหตุ: คุณอาจเคยคาดหวังว่า selectedLayers จะเป็นอาร์เรย์ แต่ก็ไม่ใช่ แต่เป็นตัวอย่างของคลาส Selection มีเหตุผลสำหรับสิ่งนี้: คลาส Selection ประกอบด้วยเมธอด Helper ที่มีประโยชน์มากมายสำหรับจัดการการเลือก เช่น clear, map, reduce และ 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:

  • ทำซ้ำเลเยอร์เทมเพลต x จำนวนครั้ง
  • สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับแต่งตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าที่กำหนดโดยผู้ใช้

เริ่มต้นด้วยการดึงข้อมูลที่เกี่ยวข้องทั้งหมดที่เราต้องการออกจาก options : จำนวนครั้งในการทำสำเนา ตัวเลือกเริ่มต้น และตัวเลือกขั้นตอน เราสามารถใช้ destructuring ได้อีกครั้ง (เหมือนที่เราทำก่อนหน้านี้กับ 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 ควรทำ — ควรจะเกี่ยวข้องกับสิ่งที่จำเป็นในการแสดงสิ่งที่เกี่ยวกับอินเทอร์เฟซบนหน้าจอเป็นหลัก — ไม่ใช่การสร้างภาพโมเสคเอง เพื่อแยกความรับผิดชอบเหล่านี้ออกจากกัน เราจะเพิ่มอาร์กิวเมนต์ที่สองใน 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 โดยค่าเริ่มต้น สคริปต์จะ "ใช้งานได้" จนกว่าจะถึงด้านล่างสุดของสคริปต์ของคุณ หลังจากนั้น Sketch จะทำลายสคริปต์และเพิ่มพื้นที่ว่างในทรัพยากรที่ใช้

นี่เป็นปัญหาสำหรับเรา เพราะมันหมายความว่าทุกอย่างที่เราจำเป็นต้องเกิดขึ้นแบบอะซิงโครนัส (ในกรณีนี้คือหลังจากถึงด้านล่างสุดของโค้ดของเราแล้ว) เช่นเดียวกับการรับข้อความ ไม่สามารถทำได้ เนื่องจากสคริปต์ของเราถูกทำลาย ซึ่งหมายความว่าเราจะไม่ได้รับข้อความใดๆ จากอินเทอร์เฟซเว็บ เนื่องจากเราไม่ได้อยู่ใกล้เพื่อรับและตอบกลับข้อความเหล่านั้น!

มีวิธีส่งสัญญาณไปยัง Sketch ว่าเราต้องการให้สคริปต์ของเรามีชีวิตอยู่เหนือจุดนี้ โดยใช้ Fibers ด้วยการสร้าง Fiber เราบอก 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 ให้แนวคิดเกี่ยวกับ Groups ซึ่งเราสามารถใช้เพื่อแก้ปัญหานี้ได้
  2. ประการที่สอง เราต้องการวิธีบอกความแตกต่างระหว่างกลุ่มปกติที่สร้างโดยผู้ใช้และกลุ่มโมเสค API ของ Sketch ยังช่วยให้เราสามารถจัดเก็บข้อมูลในเลเยอร์ที่กำหนด ซึ่งเราสามารถใช้เป็นแท็กทางและระบุกลุ่มเป็นกลุ่มโมเสค 'พิเศษ' ในภายหลัง

มาทบทวนตรรกะที่เราเขียนไว้ในส่วนก่อนหน้านี้เพื่อแก้ไขปัญหานี้ รหัสเดิมของเราทำตามขั้นตอนต่อไปนี้:

  1. ค้นหาเอกสารปัจจุบัน
  2. ค้นหาเลเยอร์ที่เลือกของเอกสารปัจจุบัน
  3. ทำซ้ำเลเยอร์ที่เลือก (เราจะเรียกว่าเลเยอร์ เทมเพลต ) x จำนวนครั้ง
  4. สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับเปลี่ยนตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าเฉพาะ (จำนวน) ที่ผู้ใช้กำหนด

เพื่อให้การไหลของผู้ใช้ใหม่ของเราเป็นไปได้ เราจำเป็นต้องเปลี่ยนขั้นตอนเหล่านี้เป็น:

  1. หยิบเอกสารปัจจุบัน
  2. หยิบเลเยอร์ที่เลือกของเอกสารปัจจุบัน
  3. กำหนดว่าเลเยอร์ที่เลือกเป็นกลุ่มโมเสคหรือไม่
    • หากเป็นเลเยอร์อื่น ใช้เป็นเลเยอร์เทมเพลตและไปที่ขั้นตอนที่ 4
    • หาก เป็น กลุ่มโมเสค ให้พิจารณาเลเยอร์แรกในกลุ่มเป็นเลเยอร์เทมเพลต และไปที่ขั้นตอนที่ 5
  4. ห่อเลเยอร์แม่แบบในกลุ่ม และทำเครื่องหมายกลุ่มนั้นเป็นกลุ่มโมเสค
  5. ลบเลเยอร์ทั้งหมดออกจากภายในกลุ่ม ยกเว้นเลเยอร์เทมเพลต
  6. ทำซ้ำเลเยอร์เทมเพลต x จำนวนครั้ง
  7. สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับแต่งตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าที่กำหนดโดยผู้ใช้

เรามีสามขั้นตอนใหม่ สำหรับขั้นตอนใหม่ขั้นแรก ขั้นตอนที่ 3 เราจะสร้างฟังก์ชันชื่อ findOrMakeSpecialGroupIfNeeded ซึ่งจะดูเลเยอร์ที่ส่งผ่านไปเพื่อพิจารณาว่าเป็นกลุ่มโมเสคหรือไม่ ถ้าเป็นเราจะคืนให้ เนื่องจากผู้ใช้สามารถเลือกเลเยอร์ย่อยที่ซ้อนอยู่ในกลุ่ม Mosaic ได้ เราจึงต้องตรวจสอบพาเรนต์ของเลเยอร์ที่เลือกด้วยเพื่อดูว่าพวกเขาเป็นหนึ่งในกลุ่ม Mosaic ของเราด้วยหรือไม่:

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

หากเราไม่สามารถหากลุ่ม Mosaic ได้ เราจะทำการห่อเลเยอร์ที่เราถูกส่งผ่านไปภายใน Group แล้วแท็กเป็นกลุ่ม Mosaic

กลับมาที่ด้านบนสุดของไฟล์ เราจะต้องดึงคลาส 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 อย่างเป็นทางการ ที่เก็บ GitHub ส่วนกลางของปลั๊กอิน Sketch คุณสามารถส่งปลั๊กอินของคุณได้ที่นี่ และแชร์กับส่วนที่เหลือของชุมชน Sketch!