Comment créer un plugin de croquis avec JavaScript, HTML et CSS (Partie 2)

Publié: 2022-03-10
Résumé rapide ↬ Dans cette deuxième partie de notre tutoriel sur la construction de plugins Sketch, nous reprendrons là où nous nous sommes arrêtés avec la construction de notre interface utilisateur, puis nous passerons à la fonctionnalité clé de la génération de nos mosaïques de calques et optimiser le code final du plugin.

Comme mentionné dans la partie 1, ce didacticiel est destiné aux personnes qui connaissent et utilisent l'application Sketch et qui n'ont pas peur de jouer avec le code également. Pour en tirer le meilleur parti, vous devrez avoir au moins une expérience de base en écriture JavaScript (et, éventuellement, HTML/CSS).

Dans la partie précédente de ce didacticiel, nous avons découvert les fichiers de base qui composent un plugin et comment créer l'interface utilisateur du plugin. Dans cette deuxième et dernière partie, nous apprendrons comment connecter l'interface utilisateur au code principal du plugin et comment implémenter les principales fonctionnalités du plugin. Enfin, nous apprendrons également comment optimiser le code et le fonctionnement du plugin.

Construire l'interface utilisateur du plug-in : faire en sorte que notre interface Web et le code du plug-in Sketch se « parlent »

La prochaine chose que nous devons faire est de configurer la communication entre notre interface Web et le plugin Sketch.

Nous devons être en mesure d'envoyer un message de notre interface Web au plug-in Sketch lorsque vous cliquez sur le bouton "Appliquer" de notre interface Web. Ce message doit nous indiquer les paramètres saisis par l'utilisateur, tels que le nombre d'étapes, le nombre de rotations, le nombre de doublons à créer, etc.

WKWebView facilite un peu cette tâche : nous pouvons envoyer des messages à notre plugin Sketch à partir du code JavaScript de notre interface Web en utilisant l'API window.webkit.messageHandlers .

Du côté de notre code Sketch, nous pouvons utiliser une autre méthode, addScriptMessageHandler:name: (ou addScriptMessageHandler_name ) pour enregistrer un gestionnaire de messages qui sera appelé chaque fois qu'il recevra un message envoyé depuis notre interface Web de plug-in.

Commençons par nous assurer que nous pouvons recevoir des messages de notre interface utilisateur Web. Rendez-vous sur la fonction createWebView ui.js ajoutez ce qui suit :

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

Ici, nous utilisons la propriété userContentController de la vue Web pour ajouter un gestionnaire de messages que nous avons nommé "sketchPlugin". Ce "contrôleur de contenu utilisateur" est le pont qui garantit que les messages passent de notre vue Web.

Plus après saut! Continuez à lire ci-dessous ↓

Vous avez peut-être remarqué quelque chose d'étrange dans le code ci-dessus : l'objet que nous ajoutons en tant que gestionnaire de messages, ourMessageHandler , n'existe pas encore ! Malheureusement, nous ne pouvons pas simplement utiliser un objet ou une fonction JavaScript standard comme gestionnaire, car cette méthode attend un certain type d'objet natif.

Heureusement pour nous, nous pouvons contourner cette limitation en utilisant MochaJSDelegate , une mini-bibliothèque que j'ai écrite et qui permet de créer le type d'objet natif dont nous avons besoin en utilisant du JavaScript classique. Vous devrez le télécharger manuellement et l'enregistrer dans votre bundle de plugins sous Sketch/MochaJSDelegate.js .

Pour l'utiliser, nous devons d'abord l'importer dans ui.js . Ajoutez ce qui suit en haut du fichier :

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

Nous pouvons maintenant utiliser MochaJSDelegate pour créer le type de gestionnaire de messages addScriptMessageHandler:name: attend :

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

Le code que nous venons d'ajouter crée l'objet natif dont nous avons besoin. Il définit également une méthode sur cet objet nommée userContentController:didReceiveScriptMessage: — cette méthode est ensuite appelée avec le message que nous voulons comme deuxième argument. Étant donné que nous n'envoyons pas encore de messages, nous devrons revenir ici plus tard et ajouter du code pour analyser et gérer les messages que nous recevons.

Ensuite, nous devons ajouter du code à notre interface Web pour nous envoyer ces messages. Rendez-vous sur /Resources/web-ui/script.js . Vous constaterez que j'ai déjà écrit la plupart du code qui gère la récupération des valeurs des <inputs /> HTML dans lesquels l'utilisateur saisira ses options.

Ce qu'il nous reste à faire est d'ajouter le code qui envoie réellement les valeurs à notre code Sketch :

Recherchez la fonction d' apply et ajoutez ce qui suit à la fin :

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

Ici, nous utilisons l'API window.webkit.messageHandlers que nous avons mentionnée précédemment pour accéder au gestionnaire de messages que nous avons enregistré ci-dessus en tant que sketchPlugin . Envoyez-lui ensuite un message avec une chaîne JSON contenant les entrées de l'utilisateur.

Assurons-nous que tout est correctement configuré. Revenez à /Sketch/ui.js . Afin de nous assurer que nous recevons les messages comme prévu, nous allons modifier la méthode que nous avons définie précédemment afin qu'elle affiche une boîte de dialogue lorsque nous recevons un message :

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

Maintenant, lancez le plugin (vous devrez peut-être d'abord fermer toute fenêtre Mosaic existante que vous avez ouverte), entrez quelques valeurs, puis cliquez sur "Appliquer". Vous devriez voir une alerte comme celle ci-dessous - cela signifie que tout est correctement câblé et que notre message a été transmis avec succès ! Sinon, revenez sur les étapes précédentes et assurez-vous que tout a été fait comme décrit.

Image montrant la boîte de dialogue que vous devriez voir après avoir cliqué sur le bouton "appliquer" dans l'interface utilisateur du plugin.
La boîte de dialogue que vous devriez voir apparaître une fois que vous avez cliqué sur Appliquer. ( Grand aperçu )

Maintenant que nous sommes capables d'envoyer des messages de notre interface à notre plugin, nous pouvons passer à l'écriture du code qui fait réellement quelque chose d'utile avec ces informations : générer nos mosaïques de couches.

Génération des mosaïques de couches

Faisons le point sur ce qui est nécessaire pour y parvenir. Pour simplifier un peu les choses, ce que notre code doit faire est :

  1. Trouver le document actuel.
  2. Trouver le calque sélectionné du document actuel.
  3. Dupliquez le calque sélectionné (nous l'appellerons le calque de modèle ) x nombre de fois.
  4. Pour chaque doublon, ajustez sa position, sa rotation, son opacité, etc., en fonction des valeurs spécifiques (quantités) définies par l'utilisateur.

Maintenant que nous avons un plan raisonnable, continuons à écrire. Conformément à notre modèle de modularisation de notre code, créons un nouveau fichier, mosaic.js dans le dossier Sketch/ , et ajoutons-y le code suivant :

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

Nous utiliserons cette fonction comme seule exportation de ce module car elle simplifie l'utilisation de l'API une fois que nous l'avons importée - nous pouvons simplement appeler mosaic() avec les options que nous obtenons de l'interface Web.

Les deux premières étapes que nous devons suivre consistent à obtenir le document actuel, puis son calque sélectionné. L'API Sketch possède une bibliothèque intégrée pour la manipulation de documents à laquelle nous pouvons accéder en important le module sketch/dom . Nous n'avons besoin que de l'objet Document pour le moment, nous allons donc le retirer explicitement. En haut du fichier, ajoutez :

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

L'objet Document a une méthode spécifiquement pour accéder au document actuel que nous pouvons utiliser, appelée getSelectedDocument() . Une fois que nous avons l'instance de document actuelle, nous pouvons accéder à toutes les couches que l'utilisateur a sélectionnées via la propriété selectedLayers du document. Dans notre cas, cependant, nous ne nous soucions que des sélections à un seul calque, nous ne saisirons donc que le premier calque sélectionné par l'utilisateur :

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

Remarque : vous vous attendiez peut-être à ce que selectedLayers lui-même soit un tableau, mais ce n'est pas le cas. Au lieu de cela, il s'agit d'une instance de la classe Selection . Il y a une raison à cela : la classe Selection contient un tas de méthodes d'assistance utiles pour manipuler la sélection comme clear, map, reduce et forEach. Il expose le tableau de couches réel via la propriété layer .

Ajoutons également des commentaires d'avertissement au cas où l'utilisateur oublie d'ouvrir un document ou de sélectionner quelque chose :

 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;

Maintenant que nous avons écrit le code des étapes 1 et 2 (trouver le document actuel et le calque sélectionné), nous devons aborder les étapes 3 et 4 :

  • Dupliquez le calque de modèle x nombre de fois.
  • Pour chaque doublon, ajustez sa position, sa rotation, son opacité, etc., en fonction des valeurs spécifiques définies par l'utilisateur.

Commençons par extraire toutes les informations pertinentes dont nous avons besoin des options : le nombre de fois à dupliquer, les options de démarrage et les options d'étape. Nous pouvons à nouveau utiliser la déstructuration (comme nous l'avons fait précédemment avec Document ) pour extraire ces propriétés des options :

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

Ensuite, assainissons nos entrées et assurons-nous que le nombre de pas est toujours au moins égal à 1 :

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

Nous devons maintenant nous assurer que l'opacité, la rotation, etc. du calque de modèle correspondent toutes aux valeurs de départ souhaitées par l'utilisateur. Étant donné que l'application des options de l'utilisateur à un calque va être quelque chose que nous ferons beaucoup, nous allons déplacer ce travail dans sa propre méthode :

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

Et comme l'espacement ne doit être appliqué qu'entre les doublons et non le calque de modèle, nous avons ajouté un indicateur spécifique, shouldAdjustSpacing , que nous pouvons définir sur true ou false selon que nous appliquons des options à un calque de modèle ou ne pas. De cette façon, nous pouvons nous assurer que la rotation et l'opacité seront appliquées au modèle, mais pas l'espacement.

De retour dans la méthode de la mosaic , assurons-nous maintenant que les options de départ sont appliquées au calque du modèle :

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

Ensuite, nous devons créer nos doublons. Commençons par créer une variable que nous pouvons utiliser pour suivre les options du doublon actuel :

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

Puisque nous avons déjà appliqué les options de départ au calque de modèle, nous devons prendre les options que nous venons d'appliquer et ajouter les valeurs relatives de stepOptions afin d'obtenir les options à appliquer au calque suivant. Comme nous allons également le faire plusieurs fois dans notre boucle, nous allons également déplacer ce travail dans une méthode spécifique, stepOptionsBy :

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

Après cela, nous devons écrire une boucle qui duplique le calque précédent, lui applique les options actuelles, puis décale (ou « étape ») les options actuelles afin d'obtenir les options pour le prochain doublon :

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

Tout est fait - nous avons écrit avec succès le cœur de ce que notre plugin est censé faire ! Maintenant, nous devons câbler les choses de sorte que lorsque l'utilisateur clique réellement sur le bouton "Appliquer", notre code mosaïque est appelé.

Revenons à ui.js et ajustons notre code de gestion des messages. Ce que nous devrons faire est d'analyser la chaîne JSON d'options que nous obtenons afin qu'elles soient transformées en un objet que nous pouvons réellement utiliser. Une fois que nous avons ces options, nous pouvons alors appeler la fonction de mosaic avec elles.

Tout d'abord, l'analyse. Nous devrons mettre à jour notre fonction de gestion des messages pour analyser le message JSON que nous obtenons :

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

Ensuite, nous devrons transmettre cela à notre fonction de mosaic . Cependant, ce n'est pas vraiment quelque chose que notre code dans ui.js devrait faire - il est censé être principalement concerné par ce qui est nécessaire pour afficher des éléments liés à l'interface à l'écran - et non par la création de mosaïques elle-même. Pour séparer ces responsabilités, nous ajouterons un deuxième argument à createWebView qui prend une fonction, et nous appellerons cette fonction chaque fois que nous recevrons des options de l'interface Web.

Nommons cet argument onApplyMessage :

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

Nous devrons également modifier notre méthode exportée, loadAndShow , pour prendre également cet argument onApplyMessage et le transmettre à createWebView :

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

Enfin, rendez-vous sur main.js . Nous devons maintenant importer notre fonction mosaic et l'appeler avec les options que nous recevons de l'interface utilisateur du plugin :

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

Nous avons presque terminé !

Cependant, si nous exécutions notre code maintenant et cliquions sur le bouton "Appliquer" dans l'interface du plugin, rien ne se passerait. Pourquoi? La raison est due à la façon dont les scripts Sketch sont exécutés : par défaut, ils ne "vivent" que jusqu'à ce que le bas de votre script soit atteint, après quoi Sketch le détruit et libère toutes les ressources qu'il utilisait.

C'est un problème pour nous car cela signifie que tout ce dont nous avons besoin de se produire de manière asynchrone (dans ce cas, c'est après que le bas de notre code est atteint), comme la réception de messages, ne peut pas, car notre script a été détruit. Cela signifie que nous ne recevrons aucun de nos messages de l'interface Web puisque nous ne sommes pas là pour les recevoir et y répondre !

Il existe un moyen de signaler à Sketch que nous avons besoin que notre script reste en vie au-delà de ce point, en utilisant Fibers . En créant une fibre, nous disons à Sketch que quelque chose d'asynchrone se passe et qu'il doit conserver notre script. Sketch ne détruira alors notre script qu'en cas d'absolue nécessité (comme lorsque l'utilisateur ferme Sketch ou lorsque le plugin Mosaic doit être mis à jour) :

 // ... 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 ! Essayons maintenant notre plugin. Avec un calque sélectionné dans Sketch, entrez quelques paramètres, puis cliquez sur Appliquer :

Essayons maintenant notre plugin - avec un calque sélectionné dans Sketch, entrez quelques paramètres, puis cliquez sur "Appliquer".

Améliorations finales

Maintenant que nous avons implémenté la majorité des fonctionnalités de notre plugin, nous pouvons essayer de "zoomer" un peu et jeter un coup d'œil à la situation dans son ensemble.

Améliorer l'expérience de l'utilisateur

Si vous avez joué avec le plugin dans son état actuel, vous avez peut-être remarqué que l'un des plus gros points de friction apparaît lorsque vous essayez de modifier une mosaïque. Une fois que vous en avez créé un, vous devez appuyer sur Annuler, ajuster les options, puis cliquer sur "Appliquer" (ou appuyer sur Entrée). Cela rend également plus difficile la modification d'une mosaïque après avoir quitté votre document et y être revenu plus tard, car votre historique d'annulation/rétablissement aura été effacé, vous laissant supprimer manuellement les calques en double vous-même.

Dans un flux plus idéal, l'utilisateur peut simplement sélectionner un groupe Mosaic, ajuster les options et regarder la mise à jour de Mosaic jusqu'à ce qu'il obtienne l'arrangement exact qu'il recherche. Pour implémenter cela, nous avons deux problèmes à résoudre :

  1. Tout d'abord, nous aurons besoin d'un moyen de regrouper les doublons qui composent une mosaïque. Sketch fournit le concept de groupes, que nous pouvons utiliser pour résoudre ce problème.
  2. Deuxièmement, nous aurons besoin d'un moyen de faire la différence entre un groupe normal créé par l'utilisateur et un groupe Mosaic. L'API de Sketch nous donne également un moyen de stocker des informations sur n'importe quel calque donné, que nous pouvons utiliser comme balise de chemin et identifier plus tard un groupe comme l'un de nos groupes Mosaic "spéciaux".

Revoyons la logique que nous avons écrite dans la section précédente pour résoudre ce problème. Notre code d'origine suit les étapes suivantes :

  1. Trouver le document actuel.
  2. Trouver le calque sélectionné du document actuel.
  3. Dupliquez le calque sélectionné (nous l'appellerons le calque de modèle ) x nombre de fois.
  4. Pour chaque doublon, ajustez sa position, sa rotation, son opacité, etc., en fonction des valeurs spécifiques (quantités) définies par l'utilisateur.

Afin de rendre notre nouveau flux d'utilisateurs possible, nous devons modifier ces étapes pour :

  1. Saisissez le document actuel.
  2. Saisissez le calque sélectionné du document actuel.
  3. Déterminez si le calque sélectionné est un groupe Mosaic ou non.
    • S'il s'agit d'un autre calque, utilisez-le comme calque de modèle et passez à l'étape 4.
    • S'il s'agit d'un groupe Mosaic, considérez le premier calque qu'il contient comme calque de modèle et passez à l'étape 5.
  4. Enveloppez le calque de modèle dans un groupe et marquez ce groupe en tant que groupe Mosaic.
  5. Supprimez tous les calques de l'intérieur du groupe, à l'exception du calque de modèle.
  6. Dupliquez le calque de modèle x nombre de fois.
  7. Pour chaque doublon, ajustez sa position, sa rotation, son opacité, etc., en fonction des valeurs spécifiques définies par l'utilisateur.

Nous avons trois nouvelles étapes. Pour la première nouvelle étape, l'étape 3, nous allons créer une fonction nommée findOrMakeSpecialGroupIfNeeded qui examinera la couche qui lui est transmise pour déterminer s'il s'agit ou non d'un groupe Mosaic. Si c'est le cas, nous vous le rendrons simplement. Étant donné que l'utilisateur peut potentiellement sélectionner une sous-couche imbriquée profondément dans un groupe Mosaic, nous devrons également vérifier les parents de la couche sélectionnée pour savoir s'ils font également partie de nos groupes 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; } };

Si nous n'avons pas pu trouver de groupe Mosaic, nous allons simplement envelopper le calque qui nous a été transmis dans un Group , puis le marquer en tant que groupe Mosaic.

De retour en haut du fichier, nous devrons également extraire la classe 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; };

Maintenant, nous devons combler les lacunes (todo's). Pour commencer, nous avons besoin d'un moyen d'identifier si oui ou non un groupe est l'un des groupes spéciaux qui nous appartiennent ou non. Ici, le module Settings de la bibliothèque Sketch vient à notre rescousse. Nous pouvons l'utiliser pour stocker des informations personnalisées sur une couche particulière, et également pour les relire.

Une fois que nous avons importé le module en haut du fichier :

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

Nous pouvons ensuite utiliser deux méthodes clés fournies, setLayerSettingForKey et layerSettingForKey , pour définir et lire les données d'une couche :

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

Maintenant que nous avons une méthode qui gère l'enveloppement d'un calque dans un groupe de mosaïques (ou, s'il s'agit déjà d'un groupe de mosaïques, le renvoie simplement), nous pouvons maintenant le connecter à notre méthode de mosaic principale juste après nos vérifications de sécurité :

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

Ensuite, nous allons ajouter une boucle pour supprimer tous les calques du groupe, à l'exception du calque de modèle (qui est le premier) :

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

Enfin, nous nous assurerons que la taille du groupe est adaptée à son nouveau contenu puisque l'utilisateur peut avoir sélectionné à l'origine un calque imbriqué dans l'ancien groupe (un calque que nous avons peut-être supprimé).

Nous devrons également nous assurer de définir la sélection actuelle sur notre groupe de mosaïques lui-même. Cela garantira que si l'utilisateur apporte un ensemble de modifications rapides au même groupe de mosaïques, il ne sera pas désélectionné. Après le code que nous avons déjà écrit pour dupliquer un calque, ajoutez :

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

Essayez à nouveau le plugin. Vous devriez constater que l'édition d'une mosaïque est beaucoup plus fluide maintenant !

Améliorer l'interface

Une autre chose que vous remarquerez peut-être est le manque de synchronisation entre la fenêtre d'affichage et l'interface à l'intérieur de celle-ci, les deux devenant visibles en même temps. Cela est dû au fait que lorsque nous affichons la fenêtre, l'interface Web n'est pas garantie d'avoir fini de se charger, donc parfois elle "saute" ou "clignote" par la suite.

Une façon de résoudre ce problème consiste à écouter lorsque l'interface Web a fini de se charger, puis à afficher notre fenêtre. Il existe une méthode, webView:didFinishNavigation: , que WKWebView appellera lorsque la page en cours aura fini de se charger. Nous pouvons l'utiliser pour obtenir exactement la notification que nous recherchons.

De retour dans ui.js , nous allons étendre l'instance MochaJSDelegate que nous avons créée pour implémenter cette méthode, qui appellera à son tour l'argument onLoadFinish que nous passerons à 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; };

Et de retour dans la méthode loadAndShow , nous l'ajusterons pour qu'elle n'affiche la fenêtre qu'une fois la vue Web chargée :

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

Bingo ! Désormais, notre fenêtre ne s'affiche que lorsque la vue Web a fini de se charger, évitant ainsi ce scintillement visuel gênant.

Conclusion

Félicitations, vous avez créé votre premier plugin Sketch !

Si vous souhaitez installer et jouer avec Mosaic, vous pouvez télécharger le plugin complet depuis GitHub. Et avant de partir, voici quelques ressources qui pourraient vous être utiles pendant le reste de votre voyage :

  • developer.sketchapp.com La ressource officielle concernant le développement du plugin Sketch. Contient plusieurs guides utiles, ainsi qu'une référence API pour la bibliothèque Sketch JavaScript.
  • sketchplugins.com Une communauté fantastique et utile de développeurs de plugins Sketch. Idéal pour obtenir des réponses à toutes vos questions brûlantes.
  • github.com/sketchplugins/plugin-directory Dépôt GitHub officiel et central des plugins Sketch. Vous pouvez soumettre vos plugins ici et les partager avec le reste de la communauté Sketch !