So erstellen Sie ein Sketch-Plugin mit JavaScript, HTML und CSS (Teil 2)
Veröffentlicht: 2022-03-10Wie in Teil 1 erwähnt, richtet sich dieses Tutorial an Personen, die die Sketch-App kennen und verwenden und keine Angst haben, sich auch mit Code zu beschäftigen. Um am meisten davon zu profitieren, müssen Sie zumindest einige grundlegende Erfahrungen mit dem Schreiben von JavaScript (und optional HTML/CSS) haben.
Im vorherigen Teil dieses Tutorials haben wir die grundlegenden Dateien kennengelernt, aus denen ein Plugin besteht, und wie die Benutzeroberfläche des Plugins erstellt wird. In diesem zweiten und letzten Teil lernen wir, wie man die Benutzeroberfläche mit dem Kern-Plugin-Code verbindet und wie man die Hauptfunktionen des Plugins implementiert. Zu guter Letzt lernen wir auch, wie man den Code und die Funktionsweise des Plugins optimiert.
Erstellen der Benutzeroberfläche des Plugins: Unser Webinterface und den Code des Sketch-Plugins „sprechen“ lassen
Als nächstes müssen wir die Kommunikation zwischen unserer Weboberfläche und dem Sketch-Plugin einrichten.
Wir müssen in der Lage sein, eine Nachricht von unserer Weboberfläche an das Sketch-Plug-in zu senden, wenn auf die Schaltfläche „Übernehmen“ in unserer Weboberfläche geklickt wird. Diese Nachricht muss uns mitteilen, welche Einstellungen der Benutzer eingegeben hat – wie die Anzahl der Schritte, die Rotationsmenge, die Anzahl der zu erstellenden Duplikate und so weiter.
WKWebView
erleichtert uns diese Aufgabe ein wenig: Wir können Nachrichten an unser Sketch-Plugin aus dem JavaScript-Code unserer Weboberfläche senden, indem wir die API window.webkit.messageHandlers
.
Auf der Seite unseres Sketch-Codes können wir eine andere Methode verwenden, addScriptMessageHandler:name:
(oder addScriptMessageHandler_name
), um einen Message-Handler zu registrieren, der aufgerufen wird, wenn er eine Nachricht empfängt, die von unserer Plugin-Webschnittstelle gesendet wird.
Beginnen wir damit, sicherzustellen, dass wir Nachrichten von unserer Web-Benutzeroberfläche empfangen können. Gehen Sie zur createWebView
-Funktion unserer ui.js
-Datei und fügen Sie Folgendes hinzu:
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; };
Hier verwenden wir die userContentController
-Eigenschaft der Webansicht, um einen Message-Handler hinzuzufügen, den wir „sketchPlugin“ genannt haben. Dieser „User Content Controller“ ist die Brücke, die sicherstellt, dass Nachrichten von unserer Webansicht herüberkommen.
Vielleicht ist Ihnen am obigen Code etwas Merkwürdiges aufgefallen: Das Objekt, das wir als Message-Handler hinzufügen, ourMessageHandler
, existiert noch nicht! Leider können wir nicht einfach ein reguläres JavaScript-Objekt oder eine Funktion als Handler verwenden, da diese Methode eine bestimmte Art von nativem Objekt erwartet.
Glücklicherweise können wir diese Einschränkung umgehen, indem wir MochaJSDelegate
verwenden, eine von mir geschriebene Mini-Bibliothek, die es ermöglicht, die Art von nativem Objekt zu erstellen, das wir brauchen, indem wir normales altes JavaScript verwenden. Sie müssen es manuell herunterladen und in Ihrem Plugin-Paket unter Sketch/MochaJSDelegate.js
.
Um es zu verwenden, müssen wir es zuerst in ui.js
. Fügen Sie am Anfang der Datei Folgendes hinzu:
const MochaJSDelegate = require("./MochaJSDelegate");
Jetzt können wir MochaJSDelegate
verwenden, um den Typ des Nachrichtenhandlers zu erstellen, den addScriptMessageHandler:name:
erwartet:
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; };
Der gerade hinzugefügte Code erstellt das native Objekt, das wir benötigen. Es definiert auch eine Methode für dieses Objekt namens userContentController:didReceiveScriptMessage:
— diese Methode wird dann mit der gewünschten Nachricht als zweites Argument aufgerufen. Da wir noch keine Nachrichten senden, müssen wir zu einem späteren Zeitpunkt hierher zurückkehren und etwas Code hinzufügen, um die empfangenen Nachrichten tatsächlich zu analysieren und zu verarbeiten.
Als Nächstes müssen wir unserer Weboberfläche Code hinzufügen, um uns diese Nachrichten zu senden. Gehen Sie zu /Resources/web-ui/script.js
. Sie werden feststellen, dass ich bereits den größten Teil des Codes geschrieben habe, der das Abrufen der Werte der HTML- <inputs />
handhabt, in die der Benutzer seine Optionen eingibt.
Was uns noch bleibt, ist den Code hinzuzufügen, der die Werte tatsächlich an unseren Sketch-Code sendet:
Suchen Sie die apply
Funktion und fügen Sie am Ende Folgendes hinzu:
// Send user inputs to sketch plugin window.webkit.messageHandlers.sketchPlugin.postMessage(JSON.stringify({ stepCount, startingOptions, stepOptions }));
Hier verwenden wir die zuvor erwähnte window.webkit.messageHandlers
, um auf den Nachrichtenhandler zuzugreifen, den wir oben als sketchPlugin
registriert haben. Senden Sie ihm dann eine Nachricht mit einem JSON-String, der die Eingaben des Benutzers enthält.
Stellen wir sicher, dass alles richtig eingerichtet ist. Gehen Sie zurück zu /Sketch/ui.js
. Um sicherzustellen, dass wir Nachrichten wie erwartet erhalten, ändern wir die zuvor definierte Methode so, dass sie einen Dialog anzeigt, wenn wir eine Nachricht erhalten:
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" ); // ... };
Führen Sie nun das Plugin aus (möglicherweise müssen Sie zuerst ein vorhandenes Mosaic-Fenster schließen), geben Sie einige Werte ein und klicken Sie dann auf „Übernehmen“. Sie sollten eine Warnung wie die unten sehen – das bedeutet, dass alles richtig verkabelt ist und unsere Nachricht erfolgreich durchgegangen ist! Wenn nicht, gehen Sie die vorherigen Schritte noch einmal durch und stellen Sie sicher, dass alles wie beschrieben durchgeführt wurde.

Jetzt, da wir in der Lage sind, Nachrichten von unserer Schnittstelle an unser Plugin zu senden, können wir damit fortfahren, den Code zu schreiben, der mit diesen Informationen tatsächlich etwas Nützliches macht: unsere Ebenenmosaike generieren.
Erzeugen des Schichtmosaiks
Machen wir eine Bestandsaufnahme dessen, was notwendig ist, um dies zu erreichen. Um die Dinge ein wenig zu vereinfachen, was unser Code tun muss, ist:
- Finden Sie das aktuelle Dokument.
- Suchen Sie die ausgewählte Ebene des aktuellen Dokuments.
- Duplizieren Sie die ausgewählte Ebene (wir nennen sie die Vorlagenebene ) x-mal.
- Optimieren Sie für jedes Duplikat seine Position, Drehung, Deckkraft usw. anhand der vom Benutzer festgelegten spezifischen Werte (Beträge).
Jetzt, wo wir einen vernünftigen Plan haben, schreiben wir weiter. Bleiben wir bei unserem Muster der Modularisierung unseres Codes, erstellen wir eine neue Datei, mosaic.js
, im Ordner Sketch/
und fügen ihr den folgenden Code hinzu:
function mosaic(options){ }; module.export = mosaic;
Wir verwenden diese Funktion als einzigen Export dieses Moduls, da sie nach dem Import eine einfachere API zu verwenden macht – wir können einfach mosaic()
mit allen Optionen aufrufen, die wir von der Weboberfläche erhalten.
Die ersten beiden Schritte, die wir unternehmen müssen, sind das Abrufen des aktuellen Dokuments und dann seiner ausgewählten Ebene. Die Sketch-API verfügt über eine integrierte Bibliothek zur Dokumentenbearbeitung, auf die wir durch Importieren des sketch/dom
-Moduls zugreifen können. Wir brauchen jetzt nur das Document
Objekt, also ziehen wir es explizit heraus. Fügen Sie oben in der Datei Folgendes hinzu:
const { Document } = require("sketch/dom");
Das Document
-Objekt hat eine Methode speziell für den Zugriff auf das aktuelle Dokument, die wir verwenden können, genannt getSelectedDocument()
. Sobald wir die aktuelle Dokumentinstanz haben, können wir auf alle Ebenen zugreifen, die der Benutzer über die Eigenschaft selectedLayers des Dokuments selectedLayers
hat. In unserem Fall kümmern wir uns jedoch nur um die Auswahl einzelner Ebenen, sodass wir nur die erste Ebene erfassen, die der Benutzer ausgewählt hat:
function mosaic(options){ const document = Document.getSelectedDocument(); const selectedLayer = document.selectedLayers.layers[0]; }; module.export = mosaic;
Hinweis: Sie haben vielleicht erwartet, dass selectedLayers
selbst ein Array ist, aber das ist es nicht. Stattdessen ist es eine Instanz der Selection
-Klasse. Dafür gibt es einen Grund: Die Selection
-Klasse enthält eine Reihe nützlicher Hilfsmethoden zum Manipulieren der Auswahl, z. B. Clear, Map, Reduce und ForEach. Es legt das eigentliche Layer-Array über die layer
-Eigenschaft offen.
Lassen Sie uns auch ein Warn-Feedback hinzufügen, falls der Benutzer vergisst, ein Dokument zu öffnen oder etwas auszuwählen:
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;
Nachdem wir nun den Code für die Schritte 1 und 2 geschrieben haben (Suchen des aktuellen Dokuments und der ausgewählten Ebene), müssen wir uns mit den Schritten 3 und 4 befassen:
- Duplizieren Sie die Vorlagenebene x-mal.
- Optimieren Sie für jedes Duplikat seine Position, Drehung, Deckkraft usw. anhand der vom Benutzer festgelegten spezifischen Werte.
Beginnen wir damit, alle relevanten Informationen, die wir benötigen, aus den options
ziehen: die Anzahl der Duplizierungen, Startoptionen und Schrittoptionen. Wir können erneut die Destrukturierung verwenden (wie wir es zuvor mit Document
getan haben), um diese Eigenschaften aus options
zu ziehen:
function mosaic(options) { // ... // Destructure options: var { stepCount, startingOptions, stepOptions } = options; }
Lassen Sie uns als Nächstes unsere Eingaben bereinigen und sicherstellen, dass die Schrittzahl immer mindestens 1 beträgt:
function mosaic(options) { // ... // Destructure options: var { stepCount, startingOptions, stepOptions } = options; stepCount = Math.max(1, stepCount); }
Jetzt müssen wir sicherstellen, dass die Deckkraft, Drehung usw. der Vorlagenebene mit den gewünschten Startwerten des Benutzers übereinstimmen. Da das Anwenden der Benutzeroptionen auf eine Ebene etwas sein wird, das wir häufig tun werden, verschieben wir diese Arbeit in eine eigene Methode:
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; } };
Und da der Abstand nur zwischen den Duplikaten und nicht der Vorlagenebene angewendet werden muss, haben wir ein spezielles Flag, shouldAdjustSpacing
, hinzugefügt, das wir auf true
oder false
setzen können, je nachdem, ob wir Optionen auf eine Vorlagenebene anwenden oder nicht. Auf diese Weise können wir sicherstellen, dass Drehung und Deckkraft auf die Vorlage angewendet werden, jedoch keine Abstände.
Zurück in der mosaic
stellen wir nun sicher, dass die Startoptionen auf die Vorlagenebene angewendet werden:
function mosaic(options){ // ... // Configure template layer var layer = group.layers[0]; configureLayer(layer, startingOptions, false); }
Als nächstes müssen wir unsere Duplikate erstellen. Lassen Sie uns zunächst eine Variable erstellen, mit der wir nachverfolgen können, welche Optionen für das aktuelle Duplikat verfügbar sind:
function mosaic(options){ // ... var currentOptions; // ... }
Da wir die Startoptionen bereits auf die Vorlagenebene angewendet haben, müssen wir diese Optionen, die wir gerade angewendet haben, nehmen und die relativen Werte von stepOptions
hinzufügen, um die Optionen zu erhalten, die auf die nächste Ebene angewendet werden. Da wir dies in unserer Schleife auch noch mehrmals tun werden, verschieben wir diese Arbeit auch in eine bestimmte Methode, stepOptionsBy
:
function stepOptionsBy(start, step){ const newOptions = {}; for(let key in start){ newOptions[key] = start[key] + step[key]; } return newOptions; };
Danach müssen wir eine Schleife schreiben, die die vorherige Ebene dupliziert, die aktuellen Optionen darauf anwendet und dann die aktuellen Optionen versetzt (oder „steppt“), um die Optionen für das nächste Duplikat zu erhalten:

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; } }
Alles erledigt – wir haben erfolgreich den Kern dessen geschrieben, was unser Plugin tun soll! Jetzt müssen wir die Dinge so verdrahten, dass unser Mosaikcode aufgerufen wird, wenn der Benutzer tatsächlich auf die Schaltfläche „Übernehmen“ klickt.
Kehren wir zu ui.js
zurück und passen unseren Nachrichtenbehandlungscode an. Was wir tun müssen, ist, die JSON-Zeichenfolge von Optionen, die wir erhalten, zu analysieren, damit sie in ein Objekt umgewandelt werden, das wir tatsächlich verwenden können. Sobald wir diese Optionen haben, können wir damit die mosaic
aufrufen.
Zuerst parsen. Wir müssen unsere Nachrichtenbehandlungsfunktion aktualisieren, um die erhaltene JSON-Nachricht zu analysieren:
function createWebView(pageURL){ // ... const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const message = JSON.parse(wkMessage.body()); } }); }
Als Nächstes müssen wir dies an unsere mosaic
übergeben. Dies ist jedoch nicht wirklich etwas, was unser Code in ui.js
tun sollte – er soll sich hauptsächlich damit befassen, was notwendig ist, um schnittstellenbezogene Dinge auf dem Bildschirm anzuzeigen – und nicht selbst Mosaike erstellen. Um diese Verantwortlichkeiten getrennt zu halten, fügen wir createWebView
ein zweites Argument hinzu, das eine Funktion annimmt, und wir rufen diese Funktion auf, wenn wir Optionen von der Webschnittstelle erhalten.
Nennen wir dieses Argument onApplyMessage
:
function createWebView(pageURL, onApplyMessage){ // ... const scriptMessageHandler = new MochaJSDelegate({ "userContentController:didReceiveScriptMessage:": (_, wkMessage) => { const message = JSON.parse(wkMessage.body()); onApplyMessage(message); } }); }
Wir müssen auch unsere exportierte Methode, loadAndShow
, ändern, um auch dieses onApplyMessage
-Argument zu übernehmen und es an createWebView
:
function loadAndShow(baseURL, onApplyMessage){ // ... const webView = createWebView(pageURL, onApplyMessage); }
Gehen Sie schließlich zu main.js
. Wir müssen jetzt unsere mosaic
importieren und sie mit den Optionen aufrufen, die wir von der Benutzeroberfläche des Plugins erhalten:
const mosaic = require("./mosaic"); function onRun(context){ UI.loadAndShow(context.scriptURL, options => { mosaic(options); }); };
Wir sind fast fertig!
Wenn wir jedoch unseren Code jetzt ausführen und auf die Schaltfläche „Übernehmen“ in der Plugin-Oberfläche klicken, würde nichts passieren. Warum? Der Grund liegt in der Art und Weise, wie Sketch-Skripte ausgeführt werden: Standardmäßig „leben“ sie nur, bis das Ende Ihres Skripts erreicht ist, wonach Sketch es zerstört und alle verwendeten Ressourcen freigibt.
Dies ist ein Problem für uns, da es bedeutet, dass alles, was wir asynchron haben müssen (in diesem Fall, nachdem das Ende unseres Codes erreicht ist), wie das Empfangen von Nachrichten, nicht möglich ist, weil unser Skript zerstört wurde. Das bedeutet, dass wir keine unserer Nachrichten von der Webschnittstelle erhalten würden, da wir nicht in der Nähe sind, um sie zu empfangen und darauf zu antworten!
Es gibt eine Möglichkeit, Sketch mithilfe von Fibers
zu signalisieren, dass unser Skript über diesen Punkt hinaus am Leben bleiben muss. Indem wir eine Faser erstellen, teilen wir Sketch mit, dass etwas Asynchrones passiert und dass unser Skript in der Nähe bleiben muss. Sketch wird unser Skript dann nur dann zerstören, wenn es absolut notwendig ist (z. B. wenn der Benutzer Sketch schließt oder wenn das Mosaic-Plugin aktualisiert werden muss):
// ... 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); }); };
Voila! Lassen Sie uns jetzt unser Plugin ausprobieren. Geben Sie bei ausgewählter Ebene in Sketch einige Einstellungen ein und klicken Sie dann auf Anwenden:
Endgültige Verbesserungen
Nachdem wir nun den Großteil der Funktionalität unseres Plugins implementiert haben, können wir versuchen, ein wenig „herauszuzoomen“ und einen Blick auf das Gesamtbild zu werfen.
Verbesserung der Benutzererfahrung
Wenn Sie mit dem Plugin in seinem aktuellen Zustand herumgespielt haben, ist Ihnen vielleicht aufgefallen, dass einer der größten Reibungspunkte auftritt, wenn Sie versuchen, ein Mosaik zu bearbeiten. Sobald Sie eines erstellt haben, müssen Sie auf „Rückgängig“ klicken, die Optionen anpassen und dann auf „Übernehmen“ klicken (oder die Eingabetaste drücken). Es macht es auch schwieriger, ein Mosaik zu bearbeiten, nachdem Sie Ihr Dokument verlassen haben und später zu ihm zurückkehren, da Ihr Undo/Redo-Verlauf gelöscht wurde und Sie die doppelten Ebenen selbst manuell löschen müssen.
In einem idealeren Ablauf könnte der Benutzer einfach eine Mosaic-Gruppe auswählen, Optionen anpassen und das Mosaic-Update beobachten, bis er genau die Anordnung erhält, nach der er sucht. Um dies umzusetzen, müssen wir zwei Probleme lösen:
- Zuerst brauchen wir eine Möglichkeit, die Duplikate, aus denen ein Mosaik besteht, zu gruppieren. Sketch bietet das Konzept der Gruppen, mit denen wir dieses Problem lösen können.
- Zweitens brauchen wir eine Möglichkeit, den Unterschied zwischen einer normalen, von Benutzern erstellten Gruppe und einer Mosaic-Gruppe zu erkennen. Die API von Sketch gibt uns auch die Möglichkeit, Informationen zu einer bestimmten Ebene zu speichern, die wir als Way-Tag verwenden und später eine Gruppe als eine unserer „speziellen“ Mosaikgruppen identifizieren können.
Lassen Sie uns die Logik, die wir im vorherigen Abschnitt geschrieben haben, noch einmal durchgehen, um dies anzugehen. Unser ursprünglicher Code folgt den folgenden Schritten:
- Finden Sie das aktuelle Dokument.
- Suchen Sie die ausgewählte Ebene des aktuellen Dokuments.
- Duplizieren Sie die ausgewählte Ebene (wir nennen sie die Vorlagenebene ) x-mal.
- Optimieren Sie für jedes Duplikat seine Position, Drehung, Deckkraft usw. anhand der vom Benutzer festgelegten spezifischen Werte (Beträge).
Um unseren neuen Benutzerfluss zu ermöglichen, müssen wir diese Schritte wie folgt ändern:
- Holen Sie sich das aktuelle Dokument.
- Schnappen Sie sich die ausgewählte Ebene des aktuellen Dokuments.
- Bestimmen Sie, ob die ausgewählte Ebene eine Mosaikgruppe ist oder nicht.
- Wenn es sich um eine andere Ebene handelt, verwenden Sie sie als Vorlagenebene und fahren Sie mit Schritt 4 fort.
- Wenn es sich um eine Mosaikgruppe handelt, betrachten Sie die erste Ebene darin als Vorlagenebene und fahren Sie mit Schritt 5 fort.
- Wickeln Sie die Vorlagenebene in eine Gruppe und markieren Sie diese Gruppe als Mosaikgruppe.
- Entfernen Sie alle Ebenen aus der Gruppe mit Ausnahme der Vorlagenebene.
- Duplizieren Sie die Vorlagenebene x-mal.
- Optimieren Sie für jedes Duplikat seine Position, Drehung, Deckkraft usw. anhand der vom Benutzer festgelegten spezifischen Werte.
Wir haben drei neue Stufen. Für den ersten neuen Schritt, Schritt 3, erstellen wir eine Funktion namens findOrMakeSpecialGroupIfNeeded
, die die an sie übergebene Ebene untersucht, um festzustellen, ob es sich um eine Mosaic-Gruppe handelt oder nicht. Wenn ja, schicken wir es einfach zurück. Da der Benutzer möglicherweise eine tief in einer Mosaikgruppe verschachtelte Unterebene auswählen könnte, müssen wir auch die übergeordneten Ebenen der ausgewählten Ebene überprüfen, um festzustellen, ob sie auch zu unseren Mosaikgruppen gehören:
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; } };
Wenn wir keine Mosaic-Gruppe finden konnten, verpacken wir einfach die Ebene, die uns übergeben wurde, in eine Group
und markieren sie dann als Mosaic-Gruppe.
Zurück am Anfang der Datei müssen wir jetzt auch die Gruppenklasse herausziehen:
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; };
Jetzt müssen wir die Lücken (todo's) füllen. Zunächst einmal brauchen wir ein Mittel, um festzustellen, ob eine Gruppe eine der speziellen Gruppen ist, die zu uns gehört, oder nicht. Hier kommt uns das Settings
der Sketch-Bibliothek zu Hilfe. Wir können es verwenden, um benutzerdefinierte Informationen auf einer bestimmten Ebene zu speichern und sie auch zurückzulesen.
Sobald wir das Modul oben in der Datei importiert haben:
const Settings = require("sketch/settings");
Wir können dann zwei Schlüsselmethoden verwenden, die es bietet, setLayerSettingForKey
und layerSettingForKey
, um Daten von einer Ebene festzulegen und zu lesen:
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; };
Jetzt, da wir eine Methode haben, die das Umhüllen einer Ebene in eine Mosaikgruppe handhabt (oder, wenn es bereits eine mosaic
ist, sie einfach zurückgibt), können wir sie jetzt direkt nach unseren Sicherheitsprüfungen in unsere Hauptmosaikmethode einfügen:
function mosaic(options){ // ... safety checks ... // Group selection if needed: const group = findOrMakeSpecialGroupIfNeeded(selectedLayer); }
Als Nächstes fügen wir eine Schleife hinzu, um alle Ebenen aus der Gruppe zu entfernen, mit Ausnahme der Vorlagenebene (die die erste ist):
function mosaic(options) { // ... // Remove all layers except the first: while(group.layers.length > 1){ group.layers[group.layers.length - 1].remove(); } }
Zuletzt stellen wir sicher, dass die Größe der Gruppe an ihren neuen Inhalt angepasst ist, da der Benutzer möglicherweise ursprünglich eine Ebene ausgewählt hat, die in der alten Gruppe verschachtelt ist (eine Ebene, die wir möglicherweise entfernt haben).
Wir müssen auch sicherstellen, dass die aktuelle Auswahl auf unsere Mosaikgruppe selbst festgelegt wird. Dadurch wird sichergestellt, dass, wenn der Benutzer eine Reihe schneller Änderungen an derselben Mosaikgruppe vornimmt, diese nicht deselektiert wird. Fügen Sie nach dem Code, den wir bereits geschrieben haben, um eine Ebene zu duplizieren, Folgendes hinzu:
function mosaic(options) { // ... // Fit group to duplicates group.adjustToFit(); // Set selection to the group document.selectedLayers.clear(); group.selected = true; }
Probieren Sie das Plugin erneut aus. Sie sollten feststellen, dass das Bearbeiten eines Mosaiks jetzt viel reibungsloser ist!
Verbesserung der Schnittstelle
Eine andere Sache, die Sie vielleicht bemerken werden, ist die fehlende Synchronisation zwischen dem Anzeigefenster und der darin enthaltenen Schnittstelle, da beide gleichzeitig sichtbar werden. Dies liegt an der Tatsache, dass die Weboberfläche beim Anzeigen des Fensters nicht garantiert vollständig geladen ist, sodass sie manchmal danach „platzt“ oder „aufblitzt“.
Eine Möglichkeit, dies zu beheben, besteht darin, darauf zu warten, wann die Weboberfläche fertig geladen ist, und erst dann unser Fenster anzuzeigen. Es gibt eine Methode, webView:didFinishNavigation:
, die WKWebView aufruft, wenn die aktuelle Seite vollständig geladen ist. Wir können es verwenden, um genau die Benachrichtigung zu erhalten, nach der wir suchen.
Zurück in ui.js
erweitern wir die MochaJSDelegate
Instanz, die wir erstellt haben, um diese Methode zu implementieren, die wiederum das Argument onLoadFinish
, das wir an 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; };
Und zurück in der Methode loadAndShow
passen wir sie so an, dass das Fenster erst angezeigt wird, wenn die Webansicht geladen wurde:
function loadAndShow(baseURL, onApplyMessage){ // ... const window = createWindow(); const webView = createWebView(pageURL, onApplyMessage, () => { showWindow(window); }); window.contentView = webView; _window = window; };
Bingo! Jetzt wird unser Fenster nur angezeigt, wenn die Webansicht vollständig geladen ist, wodurch dieses lästige visuelle Flackern vermieden wird.
Fazit
Herzlichen Glückwunsch, Sie haben Ihr erstes Sketch-Plugin erstellt!
Wenn Sie Mosaic installieren und damit herumspielen möchten, können Sie das vollständige Plugin von GitHub herunterladen. Und bevor Sie losfahren, hier sind ein paar Ressourcen, die Ihnen für den Rest Ihrer Reise nützlich sein könnten:
- developer.sketchapp.com Die offizielle Ressource zur Entwicklung von Sketch-Plugins. Enthält mehrere nützliche Leitfäden sowie eine API-Referenz für die Sketch-JavaScript-Bibliothek.
- sketchplugins.com Eine fantastische und hilfreiche Community von Sketch-Plugin-Entwicklern. Großartig, um all Ihre brennenden Fragen beantwortet zu bekommen.
- github.com/sketchplugins/plugin-directory Offizielles, zentrales GitHub-Repository von Sketch-Plugins. Sie können Ihre Plugins hier einreichen und sie mit dem Rest der Sketch-Community teilen!