Jak zbudować wtyczkę szkicu za pomocą JavaScript, HTML i CSS (część 2)

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ W tej drugiej części naszego samouczka poświęconego tworzeniu wtyczek Sketch, zaczniemy od miejsca, w którym zakończyliśmy budowanie naszego interfejsu użytkownika, a następnie przejdziemy do kluczowej funkcji generowania naszych mozaik warstw i optymalizacja końcowego kodu wtyczki.

Jak wspomniano w części 1, ten samouczek jest przeznaczony dla osób, które znają i używają aplikacji Sketch, a także nie boją się bawić kodem. Aby czerpać z tego największe korzyści, będziesz musiał mieć przynajmniej podstawowe doświadczenie w pisaniu JavaScript (i opcjonalnie HTML/CSS).

W poprzedniej części tego samouczka poznaliśmy podstawowe pliki, które składają się na wtyczkę oraz jak stworzyć interfejs użytkownika wtyczki. W tej drugiej i ostatniej części dowiemy się, jak połączyć interfejs użytkownika z podstawowym kodem wtyczki i jak zaimplementować główne funkcje wtyczki. Na koniec dowiemy się również, jak zoptymalizować kod i sposób działania wtyczki.

Budowanie interfejsu użytkownika wtyczki: tworzenie naszego interfejsu internetowego i kodu wtyczki szkicu „rozmawiają” ze sobą

Następną rzeczą, którą musimy zrobić, to skonfigurować komunikację między naszym interfejsem internetowym a wtyczką Sketch.

Musimy mieć możliwość wysłania wiadomości z naszego interfejsu internetowego do wtyczki Sketch po kliknięciu przycisku „Zastosuj” w naszym interfejsie internetowym. Ta wiadomość musi nam powiedzieć, jakie ustawienia wprowadził użytkownik — na przykład liczbę kroków, wielkość rotacji, liczbę duplikatów do utworzenia itd.

WKWebView nieco ułatwia nam to zadanie: możemy wysyłać wiadomości do naszej wtyczki Sketch z kodu JavaScript naszego interfejsu internetowego za pomocą interfejsu API window.webkit.messageHandlers .

Po stronie naszego kodu szkicu możemy użyć innej metody, addScriptMessageHandler:name: (lub addScriptMessageHandler_name ), aby zarejestrować obsługę wiadomości, która będzie wywoływana za każdym razem, gdy otrzyma wiadomość wysłaną z naszego interfejsu sieciowego wtyczki.

Zacznijmy od upewnienia się, że możemy odbierać wiadomości z naszego internetowego interfejsu użytkownika. Przejdź do funkcji createWebView ui.js dodaj następujące elementy:

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

Tutaj używamy właściwości userContentController widoku internetowego, aby dodać procedurę obsługi wiadomości, którą nazwaliśmy „sketchPlugin”. Ten „kontroler treści użytkownika” jest pomostem, który zapewnia przekazywanie wiadomości z naszego widoku internetowego.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Być może zauważyłeś coś dziwnego w powyższym kodzie: obiekt, który dodajemy jako moduł obsługi wiadomości, ourMessageHandler , jeszcze nie istnieje! Niestety, nie możemy po prostu użyć zwykłego obiektu lub funkcji JavaScript jako funkcji obsługi, ponieważ ta metoda oczekuje pewnego rodzaju natywnego obiektu.

Na szczęście dla nas możemy obejść to ograniczenie, używając MochaJSDelegate , mini-biblioteki, którą napisałem, która umożliwia tworzenie natywnego obiektu, którego potrzebujemy, przy użyciu zwykłego JavaScriptu. Musisz ręcznie pobrać i zapisać go w pakiecie wtyczek w Sketch/MochaJSDelegate.js .

Aby z niego skorzystać, musimy go najpierw zaimportować do ui.js . Dodaj następujące informacje na górze pliku:

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

Teraz możemy użyć MochaJSDelegate do utworzenia typu obsługi wiadomości 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; };

Kod, który właśnie dodaliśmy, tworzy potrzebny nam obiekt natywny. Definiuje również metodę na tym obiekcie o nazwie userContentController:didReceiveScriptMessage: — ta metoda jest następnie wywoływana z komunikatem, który chcemy jako drugi argument. Ponieważ w rzeczywistości nie wysyłamy jeszcze żadnych wiadomości, będziemy musieli wrócić tutaj później i dodać trochę kodu, aby faktycznie przeanalizować i obsłużyć otrzymywane wiadomości.

Następnie musimy dodać kod do naszego interfejsu internetowego, aby wysyłać nam te wiadomości. Przejdź do /Resources/web-ui/script.js . Przekonasz się, że napisałem już większość kodu, który obsługuje pobieranie wartości z <inputs /> HTML, do których użytkownik wprowadzi swoje opcje.

Pozostało nam jeszcze dodać kod, który faktycznie wysyła wartości do naszego kodu szkicu:

Znajdź funkcję apply i dodaj na jej końcu:

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

Tutaj używamy interfejsu API window.webkit.messageHandlers , o którym wspomnieliśmy wcześniej, aby uzyskać dostęp do programu obsługi wiadomości, który zarejestrowaliśmy powyżej jako sketchPlugin . Następnie wyślij do niego wiadomość z ciągiem JSON zawierającym dane wejściowe użytkownika.

Upewnijmy się, że wszystko jest poprawnie skonfigurowane. Wróć do /Sketch/ui.js . Aby upewnić się, że otrzymujemy wiadomości zgodnie z oczekiwaniami, zmodyfikujemy metodę, którą zdefiniowaliśmy wcześniej, tak aby wyświetlała okno dialogowe po otrzymaniu wiadomości:

 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" ); // ... };

Teraz uruchom wtyczkę (być może będziesz musiał najpierw zamknąć otwarte okno Mosaic), wprowadź kilka wartości, a następnie kliknij „Zastosuj”. Powinieneś zobaczyć alert podobny do poniższego — oznacza to, że wszystko jest prawidłowo podłączone, a nasza wiadomość została pomyślnie przesłana! Jeśli nie, wróć do poprzednich kroków i upewnij się, że wszystko zostało zrobione zgodnie z opisem.

Obraz przedstawiający okno dialogowe, które powinieneś zobaczyć po kliknięciu przycisku „Zastosuj” w interfejsie wtyczki.
Okno dialogowe, które powinieneś zobaczyć, pojawi się po kliknięciu przycisku Zastosuj. (duży podgląd)

Teraz, gdy jesteśmy w stanie wysyłać wiadomości z naszego interfejsu do naszej wtyczki, możemy przejść do pisania kodu, który faktycznie robi coś pożytecznego z tymi informacjami: generuje nasze mozaiki warstw.

Generowanie mozaiki warstwowej

Zróbmy bilans tego, co jest konieczne, aby tak się stało. Upraszczając trochę, to, co nasz kod musi zrobić, to:

  1. Znajdź bieżący dokument.
  2. Znajdź wybraną warstwę bieżącego dokumentu.
  3. Powiel wybraną warstwę (nazwiemy ją warstwą szablonu ) x ilość razy.
  4. Dla każdego duplikatu dostosuj jego pozycję, rotację, krycie itp. o określone wartości (ilości) ustawione przez użytkownika.

Teraz, gdy mamy rozsądny plan, kontynuujmy pisanie. Trzymając się naszego wzorca modularyzacji naszego kodu, utwórzmy nowy plik mosaic.js w folderze Sketch/ i dodajmy do niego następujący kod:

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

Użyjemy tej funkcji jako jedynego eksportu tego modułu, ponieważ ułatwia to użycie API po zaimportowaniu — możemy po prostu wywołać mosaic() z dowolnymi opcjami, które otrzymamy z interfejsu internetowego.

Pierwsze dwa kroki, które musimy wykonać, to pobranie bieżącego dokumentu, a następnie jego wybranej warstwy. Sketch API ma wbudowaną bibliotekę do manipulacji dokumentami, do której możemy uzyskać dostęp, importując moduł sketch/dom . W tej chwili potrzebujemy tylko obiektu Document , więc wyciągniemy go wyraźnie. U góry pliku dodaj:

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

Obiekt Document ma metodę dostępną do bieżącego dokumentu, której możemy użyć, o nazwie getSelectedDocument() . Gdy mamy już aktualną instancję dokumentu, możemy uzyskać dostęp do dowolnych warstw wybranych przez użytkownika za pomocą właściwości selectedLayers dokumentu. Jednak w naszym przypadku interesują nas tylko selekcje jednowarstwowe, więc pobierzemy tylko pierwszą warstwę wybraną przez użytkownika:

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

Uwaga: Być może spodziewałeś się, że selectedLayers będzie tablicą, ale tak nie jest. Zamiast tego jest instancją klasy Selection . Jest ku temu powód: klasa Selection zawiera kilka przydatnych metod pomocniczych do manipulowania zaznaczeniem, takich jak clear, map, reduction i forEach. Uwidacznia rzeczywistą tablicę warstw za pomocą właściwości layer .

Dodajmy również informację zwrotną ostrzegawczą na wypadek, gdyby użytkownik zapomniał otworzyć dokument lub coś wybrać:

 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;

Teraz, gdy napisaliśmy kod dla kroków 1 i 2 (odnajdywanie bieżącego dokumentu i wybranej warstwy), musimy zająć się krokami 3 i 4:

  • Powiel warstwę szablonu x razy.
  • Dla każdego duplikatu dostosuj jego położenie, obrót, krycie itp. o określone wartości ustawione przez użytkownika.

Zacznijmy od wyciągnięcia z options wszystkich istotnych informacji, których potrzebujemy: liczby powtórzeń, opcji początkowych i opcji kroków. Możemy ponownie użyć destrukturyzacji (tak jak zrobiliśmy wcześniej z Document ), aby wyciągnąć te właściwości z options :

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

Następnie oczyśćmy nasze dane wejściowe i upewnijmy się, że liczba kroków zawsze wynosi co najmniej 1:

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

Teraz musimy upewnić się, że krycie warstwy szablonu, rotacja itp. są zgodne z żądanymi wartościami początkowymi użytkownika. Ponieważ stosowanie opcji użytkownika do warstwy będzie czymś, co będziemy robić często, przeniesiemy tę pracę do własnej metody:

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

A ponieważ odstępy muszą być stosowane tylko pomiędzy duplikatami, a nie warstwą szablonu, dodaliśmy specjalną flagę, shouldAdjustSpacing , którą możemy ustawić na true lub false w zależności od tego, czy stosujemy opcje do warstwy szablonu, czy nie. W ten sposób możemy zapewnić, że do szablonu zostanie zastosowany obrót i krycie, ale nie odstępy.

Wracając do metody mosaic , upewnijmy się teraz, że opcje początkowe zostały zastosowane do warstwy szablonu:

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

Następnie musimy stworzyć nasze duplikaty. Najpierw utwórzmy zmienną, której możemy użyć do śledzenia opcji dla bieżącego duplikatu:

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

Ponieważ już zastosowaliśmy początkowe opcje do warstwy szablonu, musimy wziąć te opcje, które właśnie zastosowaliśmy, i dodać względne wartości stepOptions , aby uzyskać opcje do zastosowania do następnej warstwy. Ponieważ będziemy to robić jeszcze kilka razy w naszej pętli, przeniesiemy również tę pracę do określonej metody stepOptionsBy :

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

Następnie musimy napisać pętlę, która powiela poprzednią warstwę, zastosuje do niej bieżące opcje, a następnie przesunie (lub „kroki”) bieżące opcje, aby uzyskać opcje dla następnego duplikatu:

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

Wszystko gotowe — pomyślnie napisaliśmy rdzeń tego, co ma robić nasza wtyczka! Teraz musimy wszystko połączyć, aby kiedy użytkownik faktycznie kliknie przycisk „Zastosuj”, nasz kod mozaiki zostanie wywołany.

Wróćmy do ui.js i dostosujmy nasz kod obsługi wiadomości. To, co musimy zrobić, to przeanalizować ciąg JSON opcji, które otrzymujemy, aby zostały przekształcone w obiekt, którego możemy faktycznie użyć. Gdy mamy te opcje, możemy za ich pomocą wywołać funkcję mosaic .

Najpierw parsowanie. Musimy zaktualizować naszą funkcję obsługi wiadomości, aby przeanalizować otrzymaną wiadomość JSON:

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

Następnie musimy przekazać to do naszej funkcji mosaic . Jednak tak naprawdę nie jest to coś, co powinien robić nasz kod w ui.js — ma zajmować się przede wszystkim tym, co jest konieczne do wyświetlania na ekranie rzeczy związanych z interfejsem — a nie tworzeniem samego mozaiki. Aby oddzielić te obowiązki, dodamy drugi argument do createWebView , który przyjmuje funkcję i będziemy ją wywoływać za każdym razem, gdy otrzymamy opcje z interfejsu internetowego.

Nazwijmy ten argument onApplyMessage :

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

Musimy również zmodyfikować naszą wyeksportowaną metodę loadAndShow , aby przyjąć również ten argument onApplyMessage i przekazać go do createWebView :

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

Na koniec przejdź do main.js . Teraz musimy zaimportować naszą funkcję mosaic i wywołać ją opcjami, które otrzymujemy z interfejsu użytkownika wtyczki:

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

Prawie skończyliśmy!

Jeśli jednak uruchomimy teraz nasz kod i klikniemy przycisk „Zastosuj” w interfejsie wtyczki, nic by się nie stało. Czemu? Powodem jest sposób uruchamiania skryptów Sketch: domyślnie „żyją” tylko do momentu osiągnięcia dolnej części skryptu, po czym Sketch niszczy go i uwalnia wszelkie zasoby, z których korzystał.

Jest to dla nas problem, ponieważ oznacza to, że wszystko, co musimy mieć asynchronicznie (w tym przypadku, po osiągnięciu końca naszego kodu), na przykład otrzymywanie wiadomości, nie może, ponieważ nasz skrypt został zniszczony. Oznacza to, że nie otrzymalibyśmy żadnych naszych wiadomości z interfejsu internetowego, ponieważ nie ma nas w pobliżu, aby je otrzymywać i odpowiadać!

Jest sposób na zasygnalizowanie Sketchowi, że potrzebujemy naszego skryptu do przetrwania poza tym punktem, używając Fibers . Tworząc Fiber, mówimy Sketchowi, że dzieje się coś asynchronicznego i że musi zachować nasz skrypt. Sketch zniszczy wtedy nasz skrypt tylko wtedy, gdy jest to absolutnie konieczne (np. gdy użytkownik zamyka Sketch lub gdy wtyczka Mosaic wymaga aktualizacji):

 // ... 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! Wypróbujmy teraz naszą wtyczkę. Po wybraniu warstwy w Szkicu wprowadź ustawienia, a następnie kliknij Zastosuj:

Wypróbujmy teraz naszą wtyczkę — po wybraniu warstwy w Szkicu wprowadź ustawienia, a następnie kliknij „Zastosuj”.

Ostateczne ulepszenia

Teraz, gdy mamy zaimplementowaną większość funkcjonalności naszej wtyczki, możemy spróbować „oddalić” nieco i spojrzeć na szerszy obraz.

Poprawa doświadczenia użytkownika

Jeśli bawiłeś się wtyczką w jej obecnym stanie, być może zauważyłeś, że jeden z największych punktów tarcia pojawia się podczas próby edycji mozaiki. Po utworzeniu musisz nacisnąć cofnij, dostosować opcje, a następnie kliknąć „Zastosuj” (lub nacisnąć Enter). Utrudnia to również edycję Mozaiki po opuszczeniu dokumentu i powrocie do niego później, ponieważ historia cofania/ponawiania zostanie wymazana, co oznacza, że ​​możesz ręcznie usunąć zduplikowane warstwy samodzielnie.

W bardziej idealnym przepływie użytkownik może po prostu wybrać grupę Mosaic, dostosować opcje i oglądać aktualizację Mosaic, aż uzyska dokładny układ, którego szuka. Aby to zrealizować, mamy do rozwiązania dwa problemy:

  1. Najpierw potrzebujemy sposobu na pogrupowanie duplikatów, które tworzą mozaikę. Sketch dostarcza koncepcji grup, które możemy wykorzystać do rozwiązania tego problemu.
  2. Po drugie, będziemy potrzebować sposobu na odróżnienie normalnej grupy utworzonej przez użytkownika od grupy Mosaic. API Sketch daje nam również sposób na przechowywanie informacji na dowolnej danej warstwie, które możemy użyć jako znacznika drogi, a później zidentyfikować grupę jako jedną z naszych „specjalnych” grup Mosaic.

Wróćmy do logiki, którą napisaliśmy w poprzedniej sekcji, aby rozwiązać ten problem. Nasz oryginalny kod składa się z następujących kroków:

  1. Znajdź bieżący dokument.
  2. Znajdź wybraną warstwę bieżącego dokumentu.
  3. Powiel wybraną warstwę (nazwiemy ją warstwą szablonu ) x ilość razy.
  4. Dla każdego duplikatu dostosuj jego pozycję, rotację, krycie itp. o określone wartości (ilości) ustawione przez użytkownika.

Aby nasz nowy przepływ użytkowników był możliwy, musimy zmienić te kroki na:

  1. Chwyć bieżący dokument.
  2. Chwyć wybraną warstwę bieżącego dokumentu.
  3. Określ, czy wybrana warstwa jest grupą Mosaic, czy nie.
    • Jeśli jest to inna warstwa, użyj jej jako warstwy szablonu i przejdź do kroku 4.
    • Jeśli jest to grupa Mosaic, potraktuj pierwszą warstwę jako warstwę szablonu i przejdź do kroku 5.
  4. Owiń warstwę szablonu wewnątrz grupy i oznacz tę grupę jako grupę Mosaic.
  5. Usuń wszystkie warstwy z wewnątrz grupy z wyjątkiem warstwy szablonu.
  6. Powiel warstwę szablonu x razy.
  7. Dla każdego duplikatu dostosuj jego położenie, obrót, krycie itp. o określone wartości ustawione przez użytkownika.

Mamy trzy nowe kroki. Dla pierwszego nowego kroku, kroku 3, utworzymy funkcję o nazwie findOrMakeSpecialGroupIfNeeded , która będzie sprawdzać przekazaną do niej warstwę, aby określić, czy jest to grupa Mosaic. Jeśli tak, po prostu go zwrócimy. Ponieważ użytkownik mógłby potencjalnie wybrać podwarstwę zagnieżdżoną głęboko w grupie Mosaic, musimy również sprawdzić rodziców wybranej warstwy, aby stwierdzić, czy należą oni również do jednej z naszych grup 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; } };

Jeśli nie mogliśmy znaleźć grupy Mosaic, po prostu zawijamy przekazaną nam warstwę wewnątrz Group , a następnie oznaczamy ją jako grupę Mosaic.

Wracając na górę pliku, musimy teraz również wyciągnąć klasę 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; };

Teraz musimy uzupełnić luki (todo's). Na początek potrzebujemy środków pozwalających określić, czy dana grupa jest jedną ze specjalnych grup należących do nas, czy nie. Tutaj z pomocą przychodzi nam moduł Settings biblioteki Sketch. Możemy go używać do przechowywania niestandardowych informacji na określonej warstwie, a także do ich odczytywania.

Po zaimportowaniu modułu na górze pliku:

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

Następnie możemy użyć dwóch kluczowych metod, które zapewnia, setLayerSettingForKey i layerSettingForKey , aby ustawić i odczytać dane z warstwy:

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

Teraz, gdy mamy metodę, która obsługuje zawijanie warstwy w grupie mozaik (lub, jeśli już jest to grupa mozaik, po prostu ją zwraca), możemy teraz podłączyć ją do naszej głównej metody mosaic tuż po naszych kontrolach bezpieczeństwa:

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

Następnie dodamy pętlę, aby usunąć wszystkie warstwy z grupy z wyjątkiem warstwy szablonu (która jest pierwszą):

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

Na koniec upewnimy się, że rozmiar grupy jest dopasowany do jej nowej zawartości, ponieważ użytkownik mógł pierwotnie wybrać warstwę zagnieżdżoną w starej grupie (warstwę, którą mogliśmy usunąć).

Musimy również upewnić się, że bieżący wybór został ustawiony na samą naszą grupę mozaik. Zapewni to, że jeśli użytkownik dokona kilku szybkich zmian w tej samej grupie mozaiki, nie zostanie ona odznaczona. Po kodzie, który już napisaliśmy, aby zduplikować warstwę, dodaj:

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

Wypróbuj wtyczkę ponownie. Powinieneś zauważyć, że edycja mozaiki jest teraz znacznie płynniejsza!

Ulepszanie interfejsu

Inną rzeczą, którą możesz zauważyć, jest brak synchronizacji między oknem wyświetlacza a interfejsem w nim, ponieważ oba stają się widoczne w tym samym czasie. Wynika to z faktu, że kiedy wyświetlamy okno, nie ma gwarancji, że interfejs sieciowy został załadowany, więc czasami „wyskakuje” lub „miga” później.

Jednym ze sposobów, aby to naprawić, jest nasłuchiwanie, kiedy interfejs sieciowy zakończył ładowanie, a dopiero potem wyświetlenie naszego okna. Istnieje metoda webView:didFinishNavigation: , którą WKWebView wywoła po zakończeniu ładowania bieżącej strony. Możemy go użyć, aby uzyskać dokładnie to powiadomienie, którego szukamy.

Wracając do ui.js , rozszerzymy instancję MochaJSDelegate , którą stworzyliśmy, aby zaimplementować tę metodę, która z kolei wywoła argument onLoadFinish , który przekażemy do 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; };

Wracając do metody loadAndShow , dostosujemy ją tak, aby wyświetlało okno dopiero po załadowaniu widoku internetowego:

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

Bingo! Teraz nasze okno wyświetla się tylko po zakończeniu ładowania widoku internetowego, unikając denerwującego wizualnego migotania.

Wniosek

Gratulacje, zbudowałeś swoją pierwszą wtyczkę Sketch!

Jeśli chcesz zainstalować i bawić się Mosaic, możesz pobrać pełną wtyczkę z GitHub. A zanim wyruszysz, oto kilka zasobów, które mogą się przydać podczas dalszej podróży:

  • developer.sketchapp.com Oficjalny zasób dotyczący tworzenia wtyczek Sketch. Zawiera kilka przydatnych przewodników, a także odniesienie do interfejsu API biblioteki Sketch JavaScript.
  • sketchplugins.com Fantastyczna i pomocna społeczność twórców wtyczek Sketch. Świetne, aby uzyskać odpowiedzi na wszystkie palące pytania.
  • github.com/sketchplugins/plugin-directory Oficjalne, centralne repozytorium wtyczek Sketch na GitHubie. Tutaj możesz przesłać swoje wtyczki i podzielić się nimi z resztą społeczności Sketch!