วิธีสร้างปลั๊กอิน Sketch ด้วย JavaScript, HTML และ CSS (ตอนที่ 2)
เผยแพร่แล้ว: 2022-03-10ตามที่กล่าวไว้ในตอนที่ 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" คุณควรเห็นการแจ้งเตือนดังตัวอย่างด้านล่าง ซึ่งหมายความว่าทุกอย่างเชื่อมต่ออย่างถูกต้องและข้อความของเราผ่านสำเร็จ! หากไม่เป็นเช่นนั้น ให้ย้อนกลับไปยังขั้นตอนก่อนหน้าและตรวจดูให้แน่ใจว่าทุกอย่างเป็นไปตามที่อธิบายไว้
ตอนนี้เราสามารถส่งข้อความจากอินเทอร์เฟซของเราไปยังปลั๊กอินแล้ว เราสามารถไปยังการเขียนโค้ดที่ทำสิ่งที่มีประโยชน์กับข้อมูลนั้นจริงๆ ได้ นั่นคือ การสร้างเลเยอร์โมเสคของเรา
การสร้างโมเสคเลเยอร์
มาดูสิ่งที่จำเป็นเพื่อให้สิ่งนี้เกิดขึ้นกันเถอะ ทำให้สิ่งต่าง ๆ ง่ายขึ้น สิ่งที่โค้ดของเราต้องทำคือ:
- ค้นหาเอกสารปัจจุบัน
- ค้นหาเลเยอร์ที่เลือกของเอกสารปัจจุบัน
- ทำซ้ำเลเยอร์ที่เลือก (เราจะเรียกว่าเลเยอร์ เทมเพลต ) x จำนวนครั้ง
- สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับเปลี่ยนตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าเฉพาะ (จำนวน) ที่ผู้ใช้กำหนด
ตอนนี้เรามีแผนที่เหมาะสมแล้ว เรามาเขียนต่อกัน ยึดตามรูปแบบของการทำให้โค้ดเป็นโมดูล มาสร้างไฟล์ใหม่ 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 ให้ป้อนการตั้งค่าบางอย่าง จากนั้นคลิกใช้:
การปรับปรุงขั้นสุดท้าย
ตอนนี้เราได้ใช้ฟังก์ชันส่วนใหญ่ของปลั๊กอินแล้ว เราสามารถลอง "ซูมออก" เล็กน้อยและมองภาพรวม
การปรับปรุงประสบการณ์ของผู้ใช้
หากคุณเคยลองใช้ปลั๊กอินนี้ในสถานะปัจจุบัน คุณอาจสังเกตเห็นว่าปัญหาที่ใหญ่ที่สุดจุดหนึ่งปรากฏขึ้นเมื่อคุณพยายามแก้ไขภาพโมเสค เมื่อคุณสร้างแล้ว คุณต้องกดเลิกทำ ปรับตัวเลือก จากนั้นคลิก 'ใช้' (หรือกด Enter) นอกจากนี้ยังทำให้ยากต่อการแก้ไขโมเสกหลังจากที่คุณออกจากเอกสารและกลับมายังในภายหลัง เนื่องจากประวัติการเลิกทำ/ทำซ้ำของคุณจะถูกลบออก ทำให้คุณลบเลเยอร์ที่ซ้ำกันด้วยตนเอง
ในขั้นตอนที่เหมาะสมยิ่งขึ้น ผู้ใช้สามารถเลือกกลุ่ม Mosaic ปรับตัวเลือก และดูการอัปเดต Mosaic จนกว่าพวกเขาจะได้รับการจัดเรียงที่ต้องการ ในการดำเนินการนี้ เรามีปัญหาสองประการที่ต้องแก้ไข:
- อันดับแรก เราต้องการวิธีจัดกลุ่มรายการที่ซ้ำกันซึ่งประกอบเป็นโมเสคเข้าด้วยกัน Sketch ให้แนวคิดเกี่ยวกับ Groups ซึ่งเราสามารถใช้เพื่อแก้ปัญหานี้ได้
- ประการที่สอง เราต้องการวิธีบอกความแตกต่างระหว่างกลุ่มปกติที่สร้างโดยผู้ใช้และกลุ่มโมเสค API ของ Sketch ยังช่วยให้เราสามารถจัดเก็บข้อมูลในเลเยอร์ที่กำหนด ซึ่งเราสามารถใช้เป็นแท็กทางและระบุกลุ่มเป็นกลุ่มโมเสค 'พิเศษ' ในภายหลัง
มาทบทวนตรรกะที่เราเขียนไว้ในส่วนก่อนหน้านี้เพื่อแก้ไขปัญหานี้ รหัสเดิมของเราทำตามขั้นตอนต่อไปนี้:
- ค้นหาเอกสารปัจจุบัน
- ค้นหาเลเยอร์ที่เลือกของเอกสารปัจจุบัน
- ทำซ้ำเลเยอร์ที่เลือก (เราจะเรียกว่าเลเยอร์ เทมเพลต ) x จำนวนครั้ง
- สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับเปลี่ยนตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าเฉพาะ (จำนวน) ที่ผู้ใช้กำหนด
เพื่อให้การไหลของผู้ใช้ใหม่ของเราเป็นไปได้ เราจำเป็นต้องเปลี่ยนขั้นตอนเหล่านี้เป็น:
- หยิบเอกสารปัจจุบัน
- หยิบเลเยอร์ที่เลือกของเอกสารปัจจุบัน
- กำหนดว่าเลเยอร์ที่เลือกเป็นกลุ่มโมเสคหรือไม่
- หากเป็นเลเยอร์อื่น ใช้เป็นเลเยอร์เทมเพลตและไปที่ขั้นตอนที่ 4
- หาก เป็น กลุ่มโมเสค ให้พิจารณาเลเยอร์แรกในกลุ่มเป็นเลเยอร์เทมเพลต และไปที่ขั้นตอนที่ 5
- ห่อเลเยอร์แม่แบบในกลุ่ม และทำเครื่องหมายกลุ่มนั้นเป็นกลุ่มโมเสค
- ลบเลเยอร์ทั้งหมดออกจากภายในกลุ่ม ยกเว้นเลเยอร์เทมเพลต
- ทำซ้ำเลเยอร์เทมเพลต x จำนวนครั้ง
- สำหรับรายการที่ซ้ำกันแต่ละรายการ ให้ปรับแต่งตำแหน่ง การหมุน ความทึบ ฯลฯ ตามค่าที่กำหนดโดยผู้ใช้
เรามีสามขั้นตอนใหม่ สำหรับขั้นตอนใหม่ขั้นแรก ขั้นตอนที่ 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!