[Перевод] Руководство новичка по разработке плагинов для графического редактора Sketch

2321cb7489b44f0db4f988dbdb3755b9.png

Приветствую друзья! Меня зовут Антон, я развиваю сайт sketchapp.me посвященный графическому редактору Sketch. Очень часто мне на почту приходят вопросы о тонкостях разработки плагинов для Sketch. Я не разработчик и не специалист в создании плагинов, поэтому я решил сделать перевод самого подробного руководства по созданию плагинов от Mike Mariano.

Часть 1 — С чего начать?


Вы хотите начать писать Sketch-плагины и не знаете, с чего начать? Продолжайте читать, так как этот пост как раз для вас!

Освоить базу не так просто. Есть масса примеров уже существующих плагинов, но очень сложно понять, с чего нужно начать. В помощь вам я собрал всю нужную информацию, которую только удалось отыскать, в одном месте.

Конечно, это не мануал по написанию плагинов для продвинутых, так как я сам не разработчик. Я UI/UX-дизайнер, которому иногда приходится кодить на довольно неплохом уровне (по крайней мере, я так думаю). Тру-программисты откровенно плачут, видя мой код, но я думаю, что как раз такой код хорошо понятен новичкам.

Если вы — инженер, который ищет более сложные примеры, вам будет полезен этот пост http://james.ooo/sketch-plugin-development, а также официальный сайт Sketch-разработчиков: http://developer.sketchapp.com/

Зачем писать плагин?


Плагины идеальны для автоматизации повторяющихся задач, они значительно упрощают работу и продакшн. Есть все шансы, что уже существует плагин под ваши нужды, так что сначала поищите готовое решение перед написанием своего. Очень вероятно, что кто-то уже справился с задачей лучше вас. Но если у вас уникальный рабочий процесс создания UI (как у меня с дизайном игровых интерфейсов в Unity), скорее всего вам потребуется кастомное решение.

Начало


Перед тем, как вы приступите к коду, установите все нужные программы и закладки.
  1. Установите текстовый редактор, если у вас его еще нет. (Я использую Atom, но есть много других отличных редакторов, таких как Sublime или Textmate).
  2. Откройте Консоль для дебага, и добавьте ее в свою панель Dock, вы часто будете ею пользоваться.>1-8tsgn6bxwkezgshwyip8lw
  3. Консоль используется вашей машиной для ВСЕГО дебага, так что создайте новый фильтр журнала запросов Sketch: File > New System Query Log

Скопируйте эти настройки и нажмите Ok.

1-n8clsi0_jhe-4jsfx-txga

Фильтр Sketch появится в колонке слева.

1-p0oatbbn78yfdssskqrigg

4. Сделайте закладку папки Sketch Plugins для быстрого доступа, добавив ее в Избранное (Favorites) в окне Finder.

К этой папке вы тоже будете часто обращаться, так что желательно иметь ее под рукой.

/Library/Application support/com.bohemiancoding.sketch3/Plugins

1-xermiwsh60b0osekwabugw

Вот и все, вы готовы к написанию своего первого плагина!

Создание плагина за 10 простых шагов


Плагины Sketch — это папки с расширением .sketchplugin, которыми легко обмениваться с другими пользователями.

В этом примере мы создадим базовый скрипт для получения названия страницы. Вам не нужно знать программирование, чтобы реализовать эту задачу, но она поможет понять базовые принципы. В последующих постах я буду документировать разные скрипты для получения разных данных из Sketch и их изменения. Этот пример самый простой.

Плагины Sketch пишутся на CocoaScript, который представляет собой смесь Objective-C/Cocoa и JavaScript. Я неплохо знаком с Javascript, так что тут сложностей не возникло. Не скажу, что я в CocoaScript, как рыба в воде, но моих знаний по JavaScript было достаточно, чтобы разобраться.

Итак, начнем!

1. Создайте новую папку в каталоге Sketch Plugins и назовите ее MyPlugin.sketchplugin

1-dw5qn1na_zd8dgzm6s141w
(как только вы добавите расширение .sketchplugin, двойной клик на ней запустит установку плагина вместо открытия папки. Чтобы открыть саму папку, кликните правой кнопкой мышки на плагине и выберите опцию Show Package Contents).

2. Внутри папки создайте еще одну папку и назовите ее Contents

3. Внутри Contents создайте папку Sketch

Конечная структура папки плагина будет выглядеть так:

1-mnhkmqkpjyg0taofttwtgw

Внутри папки Sketch вы и будете создавать сам плагин, который состоит минимум из 2 файлов — манифеста и скрипта.

1-8l57idwssf5tnot0x408tq

Манифест описывает плагин и может содержать разные горячие клавиши и дополнительные скрипты, он всегда называется manifest.json.

В скрипте содержится сам код плагина, и на него ссылается манифест. Имя можно изменять на свое усмотрение, но оно должно совпадать в обоих файлах.

4. В текстовом редакторе создайте новый файл под названием manifest.json и сохраните его в MyPlugin.sketchplugin > Contents > Sketch

5. Скопируйте и вставьте этот код в manifest.json, и сохраните.

{
    "name" : "My Plugin",
    "identifier" : "my.plugin",
    "version" : "1.0",
    "description" : "My First Sketch Plugin",
    "authorEmail" : "your@email.com",
    "author" : "Your Name",

    "commands" : [
    {
      "script" : "MyScript.js",
      "handler" : "onRun",
      "shortcut" : "command shift y",
      "name" : "Get Page Names",
      "identifier" : "my.plugin.pagenames"
    }
  ]
}

Теперь MyPlugin появится в вашем меню плагинов Sketch. Вы можете изменить название плагина или горячую клавишу его вызова, и это отразится в меню Plugins в Sketch. Важно выбрать такую горячую клавишу запуска, которая не назначена для других установленных приложений или плагинов, иначе она не будет работать.

Теперь создадим MyScript.js, на который ссылается manifest. Убедитесь, что название файла совпадает с названием в файле manifest!

6. Вернитесь в текстовый редактор и создайте новый файл под названием MyScript.js, и также сохраните его в папку MyPlugin.sketchplugin > Contents > Sketch folder

7. Скопируйте и вставьте этот код в MyScript.js

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  
}

Я детальнее поясню этот код в последующих частях. А пока что опирайтесь на комментарии в строках.

8. Перейдите в Sketch и откройте новый файл

9. В меню Plugins выберите MyPlugin > Get Page Names

1-uelcrvjw2vyxzgjdib58ya

10. Перейдите в консоль и внизу лога вы должны увидеть название страницы

10:54:42 PM Get Page Names (Sketch Plugin): Page 1

Попробуйте изменить название страницы в Sketch-файле и перезапустите плагин. Лог должен показывать новое название. Добавьте еще одну страницу и переименуйте ее, а затем запустите плагин, консоль теперь покажет названия обеих страниц.

Вот и все!


Я работаю в Sketch всего несколько недель, и уже впечатлен его мощью и возможностями кастомизации. Вы можете скачать текущую версию плагина здесь.

Часть 2 — Пользовательские уведомления

Я открыл для себя отличный текстовый редактор Atom, на который и переключился. Не знаю, почему до сих пор не пользовался им, но теперь я попрощался со своим старым-добрым TextMate!

В предыдущей главе мы работали над плагином, который показывал названия всех страниц вашего документа в Консоли. Теперь изучим другие способы отображения информации. Они нужны, если вы хотите уведомлять пользователей (и себя) о разных вещах, например, о том, что плагин отработал, или что пользователю необходимо ввести какие-то данные. Также вам не придется постоянно переключаться в консоль, чтобы протестировать свои скрипты.

Есть два дополнительных способа уведомления пользователей внутри Sketch:

  1. Сообщение (Message) — короткое ненавязчивое сообщение, которое отображается внизу приложения, прячется через короткий промежуток времени
  2. Окно оповещения (Alert Box) — стандартное всплывающее окно, которое либо запрашивает от пользователя ввод данных, либо требует какое-то действие для продолжения.

Для каждого способа есть свои случаи применения, и обычно вам не придется перегружать плагин (и пользователей) множеством сообщений. Поэкспериментируйте с обоими, чтобы понять, какой метод лучше для вашего собственного плагина.

Вернемся к скрипту плагина из первого примера и переделаем его.

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  
}

Если рассматривать этот фрагмент кода, pageName ссылается на название каждой страницы в массиве страниц документа. Это контейнер с данными, содержащий всю информацию о странице. Обычно для доступа к этим массивам используются цикл for, которые итерируют через каждый объект, чтобы извлечь или назначить определенные значения.

В этой строке переменная pageName отправляется в Консоль.

log(pageName);

Теперь добавим сообщение вниз скрипта, который будет отображать сообщение внутри Sketch, когда скрипт отработает.

Для этого мы добавим строку кода после скобки цикла for:

doc.showMessage("MyPlugin Finished!”);

Doc — это переменная вверху скрипта, которая ссылается на документ, а showMessage — это встроенная функция, в которой мы можем передавать переменную или строку (String) для отображения сообщения.

Вот как выглядит ее добавление после цикла for в контексте всего скрипта:

var onRun = function(context) {
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  //show a message at the bottom of Sketch
  doc.showMessage("MyPlugin Finished!");
}

Запустите MyPlugin через меню Plugins, чтобы увидеть результат. Внизу окна Sketch вы должны увидеть:

1-z9cmbfc9syuokwrzdsairq

Этот тип сообщений полезен, если вам не хочется постоянно переключаться в консоль, или если есть что-то, что вы бы хотели показать пользователю, но что не требует его участия.

Если вы хотите более выделяющегося сообщения, которое требует от пользователя какого-то действия, лучше использовать alert box. Чтобы добавить его, допишите строку кода в начало скрипта, и еще одну после отображения предыдущих сообщений.

Первая строка добавляется вверх, над объявлением переменной doc, чтобы обратиться к самому приложению:

var app = [NSApplication sharedApplication];

Вторая строка дает доступ к новой переменной для отправки сообщений в приложение.
[app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];

И вот как выглядит конечный код с новыми строками:
var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
  }
  //show a message at the bottom of Sketch
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  [app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];
}

Запустите MyPlugin с меню Plugin, и вы должны увидеть что-то такое:

1-1aqb4gdfoh7dxzdugdqdzq

Меняйте сообщения (те, что в кавычках) на что угодно и перезапустите плагин. Полная кастомизация!

Это самое начало отладки и отображения данных внутри Sketch. Вы можете попробовать изменить скрипты сообщений/уведомлений, добавить дополнительные скрипты для показа разных данных — переменных или счетчиков массивов.

Чтобы показать количество страниц в документе, вы можете создать сообщение с отображением pages.count ().

[app displayDialog:”This document has " + pages.count() + " pages.” withTitle:”Alert Box Title”];

Вот таким будет результат:

1-ruhpur9ob2ns-kknochjyq

Мы разобрались с пользовательскими уведомлениями! Сначала подготовили среду для разработки, затем разобрались, как дебажить и отображать сообщения внутри Sketch. Далее мы сфокусируемся больше на самом коде, поиске конкретных данных и способах их обработки.

Текущую версию плагина можно скачать здесь.

Часть 3 — Написание кода для многоразового использования


Настало время развернуть мониторы вертикально, потому что мы приступаем к программированию!

В этой части мы раскроем базовые концепты написания кода, создание функций для многократного вызова, а также линкование двух скриптов вместе. Если вы уже освоились в написании скриптов, можете просто проскроллить вниз до конца этой части, скачать исходники и переделать их на свой лад. Если же вы новичок в этом деле, или вам нужно освежить знания, продолжайте читать!

Базовые концепты написания кода


Есть код, который мы уже написали, и он включает в себя переменные, массивы, функции и циклы for. Эти элементы, а также операторы if/else, и есть фундаментальные кирпичики всего, что я делаю. Как только я освоил эти элементы (особенно, цикл loop), мне стало гораздо проще понимать скрипты. Я пишу свои скрипты на Javascript и Objective-C, но эти концепты довольно универсальны для любого языка программирования. Вам просто нужно выработать определенный способ написания кода.

Я бегло поясню эти понятия:

Переменная — это ссылка на какой-то тип информации. Это может быть как строка (кусок текста), так и число или булевское значение (true или false). Это горячая клавиша доступа к значению, и очень удобно использовать именно ее вместо того, чтобы печатать его руками снова и снова.

var myName = "Mike”;
 var thisYear = 2016;
 var givesHighFives = true;

Если я задам для myName значение «Mike», тогда я просто могу писать везде в скрипте myName, и эта переменная будет ссылаться на «Mike». Но если я захочу, чтобы значением myName стал «Shawn», мне нужно будет изменить его только один раз в объявлении переменной, и каждая ее сущность в коде также изменится.

Массив — это тоже переменная, но обычно он содержит несколько однотипных значений. Как если бы у вас был массив дней недели, вы бы увидели стринговое значение всех дней внутри такого массива.

var daysOfTheWeek = {"Sunday”, "Monday”, "Tuesday”, Wednesday”, "Thursday”, "Friday”, "Saturday”};

Вы также можете написать его так, будет проще увидеть количество элементов массива, которые начинаются с 0:
var daysOfTheWeek[];
 daysOfTheWeek[0] = "Sunday”;
 daysOfTheWeek[1] = "Monday”;
 daysOfTheWeek[2] = "Tuesday”;
 daysOfTheWeek[3] = "Wednesday”;
 daysOfTheWeek[4] = "Thursday”;
 daysOfTheWeek[5] = "Friday”;
 daysOfTheWeek[6] = "Saturday”;

Функция — это многократно используемый фрагмент кода, который выполняет определенную задачу. Обычно вы их задаете, если нужно делать что-то снова и снова. Вы можете передавать в нее переменные, заставлять их возвращать значение или держать их пустыми.
var eyesOpen = true;
 function closeEyes(){
 eyesOpen = false;
 }
 function openEyes(){
 eyesOpen = true;
 }

В примере выше каждый раз, когда вы хотите закрыть глаза, вы можете вызывать функцию closeEyes (), затем для их открытия вызывать openEyes ().

Оператор if/else делает именно то, что говорит, т.е. проверяет какое-то условие, производит определенное действие, если условие выполняется, в противном случае выполняет что-то другое.

if(daysOfTheWeek == "Sunday”){
 watchGameOfThrones();
 }else if(daysOfTheWeek == "Wednesday”){
 watchMrRobot();
 }else{
 watchTheNews();
 }

Цикл for используется для повтора определенного блока кода известное количество раз. Например, в массиве daysOfTheWeek, если бы вы хотели вывести значения массива в список, вы бы воспользовались для этого циклом for.

Без цикла вы бы писали так:

log(daysOfTheWeek[0]);
 log(daysOfTheWeek[1]);
 log(daysOfTheWeek[2]);
 log(daysOfTheWeek[3]);
 log(daysOfTheWeek[4]);
 log(daysOfTheWeek[5]);
 log(daysOfTheWeek[6]);

А с циклом вам бы пришлось написать всего лишь:
for(var i = 0; i < daysOfTheWeek.count(); i++){
 log(daysOfTheWeek[i];
 }

Гораздо проще, правда? Для цикла мы задали начальное значение i, равное 0 и считали, пока i не достигнет количества значений в массиве daysOfTheWeek, т.е. 7. То есть начиная с i=0, цикл логирует значение daysOfTheWeek, потом добавляет к i 1, и все повторяется. Всего это проделывается 7 раз, пока не достигается полный набор значений в массиве.

Создание функции окна оповещения (Alert Box)


Вернемся к кодуMyPlugin. В предыдущей части мы создали окно оповещения, которое отображалось после отработки скрипта.
[app displayDialog:”This is an alert box!” withTitle:”Alert Box Title”];

Выглядит как что-то, что будет использоваться каждый раз, когда нам понадобится показать сообщения пользователю, и не особо хочется каждый раз прописывать всю строку кода для каждого такого сообщения. Это особенно актуально, если отображаемое сообщение — это комбинация переменных и строк, код может быть довольно длинный.

Отличный повод сделать повторно используемую функцию, и для этого нужно прописать:

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

Каждый раз, когда мы вызываем alert () и передаем заголовок и сообщение, будет отображаться окно оповещения.

Добавьте эту функцию в конец скрипта, после скобок функции onRun:

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  [app displayDialog:"This document has " + pages.count() + " pages." withTitle:"Alert Box Title"];
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  [app displayDialog:"This is an alert box!" withTitle:"Alert Box Title"];
  
}

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

И теперь мы можем изменить код, написанный ранее, заменив им исходную ссылку на переменную, так как теперь она прописана в функции. Скрипт теперь выглядит так:

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  alert("Number of Pages", "This document has " + pages.count() + " pages.");
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  alert("Plugin Finished!", "This is a message saying the Plugin is finished.")
  
}

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

Запустите плагин, и вы будете видеть такой же результат, как раньше, но теперь ваш код ссылается на функцию. Ее можно вызвать в любом месте скрипта, и не нужно будет переписывать код снова и снова. Но что если бы у нас был другой скрипт, в котором нужно использовать эту функцию?

Ссылка на скрипты из других скриптов


И вот тут уместно создать библиотеку. Иногда у вас есть общая функция, которую не стоит привязывать к какому-то одному скрипту. Как в случае с функцией alert (), или с функцией округления числа до ближайшего целого. Это общие функции, которые, скорее всего, будут использоваться в разных скриптах. Поэтому вместо копипаста функции в каждый файл вы можете создать общий файл, и вставлять его по необходимости.

1. Сохраните новый JavaScript-файл в ту же папку, в которой хранится MyScript.js, и назовите его common.js. Теперь в папке будет 3 файла:

1-c9uz7wbojajdje_xvd0ziw

2. Вырежьте функцию alert из MyScript.js и вставьте ее в common.js и сохраните изменения.

В common.js должен быть только этот код:

function alert(title, message){
  var app = [NSApplication sharedApplication];
  [app displayDialog:message withTitle:title];
}

3. Чтобы запустить этот скрипт из MyScript.js, добавьте эту строку кода вверху скрипта и сохраните:

@import 'common.js'

Весь скрипт с новой строкой вверху и удаленной функцией alert () выглядит так:
@import 'common.js'

var onRun = function(context) {
  
  //reference the Application
  var app = [NSApplication sharedApplication];
  
  //reference the Sketch Document
  var doc = context.document;
  
  //reference all the pages in the document in an array
  var pages = [doc pages];
  alert("Number of Pages", "This document has " + pages.count() + " pages.");
  
  //loop through the pages of the document
  for (var i = 0; i < pages.count(); i++){
    
    //reference each page
    var page = pages[i];
    
    //get the name of the page
    var pageName = [page name];
    
    //show the page name in the console
    log(pageName);
    
  }
  
  //show a message in app
  doc.showMessage("MyPlugin Finished!");
  
  //send an alert message to the application
  alert("Plugin Finished!", "This is a message saying the Plugin is finished.")
  
}

Запустите плагин еще раз, он должен работать, как и раньше, но теперь вы запускаете функцию из общего скрипта! Оптимизация завершена!

Теперь у вас есть основы написания и понимания более сложных плагинов. Далее мы изучим доступ к данным документа Sketch через его переменные и массивы, способы прохождения по этим массивам для извлечения и назначения нужных нам значений.

Вы можете скачать текущую версию плагина здесь.

Часть 4 — Примеры из реального мира


Мы уже научились, как организовывать среду для написания и отладки кода, как отображать разные виды пользовательских уведомлений, а также освоили базовые понятия написания кода. Кажется, у нас есть все, чтобы начать писать собственные плагины.

К счастью, Bohemian Coding имеют документацию по своим классам:
http://developer.sketchapp.com/reference/class/

К сожалению, как пишется вначале этой страницы, она все еще «в стадии разработки». Так что не все еще описано, и не так много примеров, что с этим всем делать.

Лично я лучше всего учусь на примерах. Понимание кода может вызывать большие трудности, но когда вы видите, как он используется применительно к вашим знаниям и целям, становится гораздо проще. Я приведу три примера, которые нашел, пока работал со Sketch, надеюсь, вам они окажутся полезными!

Пример 1: Переключение высоты и ширины артборда.


Странно, но в стандартном функционале Sketch нет смены ориентации артборда с портретной на альбомную и наоборот. Я поискал, и не нашел ни одного плагина, который решает эту проблему, так что самое время создать наш собственный!

Для ясности давайте определимся, что мы будем делать. Это помогает определить цель, способы ее достичь, а также выявить крайние случаи, с которыми придется столкнуться. Это почти как создание UX-сценария, правда?

Для этого плагина мы напишем скрипт, который выполняет следующее:

  • Сначала убеждается, что что-то выделено
  • Затем убеждается, что выделенный объект — это артборд
  • Затем берет исходную высоту и ширину выбранного артборда и сохраняет их в переменные
  • Затем задает новую высоту на основе его старой ширины, и новую ширину на основе старой высоты
  • В конечном итоге, уведомляет пользователя, что скрипт отработал

Добавление еще одного скрипта в MyPlugin


Вместо добавления новой порции кода в MyScript.js, давайте создадим новый скрипт под названием RotateArtboard.js, и сохраните его в папку MyPlugin.sketchplugin.

Добавьте этот код в RotateArtboard.js. Каждый основной скрипт должен быть в функции onRun, так что этот скрипт всегда является хорошей базой для старта:

@import 'common.js'
var onRun = function(context) {
  //reference the Sketch Document
  var doc = context.document;
}

Вы увидите, что мы импортируем файл common.js, чтобы использовать ту же функцию alert, которую мы уже создали.

На данный момент в папке плагина должны быть следующие файлы:

1-zq4ryj_udiwcr-biqkupkg

Теперь откройте manifest.json, чтобы добавить еще один скрипт в наш манифест.

Скопируйте фрагмент кода со всеми командами MyScript.js, добавьте запятую после блока MyScript, и вставьте перед закрывающейся скобкой commands, вот так:

{
    "name" : "My Plugin",
    "identifier" : "my.plugin",
    "version" : "1.0",
    "description" : "My First Sketch Plugin",
    "authorEmail" : "your@email.com",
    "author" : "Your Name",

    "commands" : [
    {
      "script" : "MyScript.js",
      "handler" : "onRun",
      "shortcut" : "command shift y",
      "name" : "Get Page Names",
      "identifier" : "my.plugin.pagenames"
    },
    {
      "script" : "RotateArtboard.js",
      "handler" : "onRun",
      "shortcut" : "command shift u",
      "name" : "Rotate Artboard",
      "identifier" : "my.plugin.rotateartboard"
    }
  ],
}

Теперь вы видите новый скрипт в меню MyPlugin (можете менять горячие клавиши, кстати):

1-hv9u76wafmq1rnjitphdg

Вернемся к коду!

Чтобы узнать, что выделено, используем следующее:

var selection = context.selection;

Этот код создает переменную типа массив со всеми выделенными слоями. Теперь мы можем проверить этот массив, чтобы узнать, есть ли что-нибудь внутри него. Если там 0, тогда ничего не выделено, и мы скажем пользователю, что нужно что-то выделить.
if(selection.count() == 0){
 doc.showMessage("Please select something.”);
 }

Но если счетчик не 0, тогда внутри массива что-то есть, как минимум, один слой должен быть выделен. Теперь мы можем пройтись циклом по выделенным слоям, чтобы определить, есть ли среди них артборды (MSArtboardGroup):
if(selection.count() == 0){
 doc.showMessage("Please select something.”);
} else {
 for(var i = 0; i < selection.count(); i++){
  if(selection[i].class() == "MSArtboardGroup"){
     //do something
  }
 }
}

Вы можете использовать эту же технику для проверки, является ли выделение текстовым слоем (MSTextLayer), группой (MSGroupLayer), фигурой (MSShapeGroup / не задокументированная), импортированным изображением (MSBitmapLayer) или символом (MSSymbolInstance / не задокументированная).

Чтобы получить высоту и ширину артборда, сначала нужно получить его фрейм. И уже из фрейма можно извлечь значения, и назначить новые.

Создайте переменную под названием artboard на основе элемента массива в цикле for, и так мы сможем получить текущие размеры. Использование цикла for позволяет работать с несколькими выделенными артбордами одновременно.

var artboard = selection[i];
 var artboardFrame = artboard.frame();
 var artboardWidth = artboardFrame.width();
 var artboardHeight = artboardFrame.height();

Теперь создайте две новые переменные со значениями, которые нам нужны, т.е. поменяйте местами ширину и высоту:
var newArtboardWidth = artboardHeight;
 var newArtboardHeight = artboardWidth;

Затем воспользуемся artboardFrame, чтобы задать высоту и ширину новыми переменными:
artboardFrame.setWidth(newArtboardWidth);
 artboardFrame.setHeight(newArtboardHeight);

И, наконец, мы воспользуемся нашим окном уведомлений, чтобы сообщить пользователю об отработке скрипта и отобразить новые значения:
var alertMessage = "New Height: "+newArtboardHeight+ " | New Width: "+newArtboardWidth;
 alert("Artboard Rotated!”, alertMessage)

Вот весь скрипт с комментариями:
@import 'common.js'

var onRun = function(context) {
    
    //reference the sketch document
    var doc = context.document;
    //reference what is selected
    var selection = context.selection;

    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        //loop through the selected layers
        for(var i = 0; i < selection.count(); i++){

            //checks to see if the layer is an artboard
            if(selection[i].class() == "MSArtboardGroup"){

                //reference the selection
                var artboard = selection[i];

                //get the artboard frame for dimensions
                var artboardFrame = artboard.frame();
                //get the width
                var artboardWidth = artboardFrame.width();
                //get the height
                var artboardHeight = artboardFrame.height();

                //set a new width variable to the old height
                var newArtboardWidth = artboardHeight;
                //set a new height variable to the old width
                var newArtboardHeight = artboardWidth;

                //set the artboard frame with the new dimensions
                artboardFrame.setWidth(newArtboardWidth);
                artboardFrame.setHeight(newArtboardHeight);

                //send an alert message with the new values
                var alertMessage = "New Height: "+newArtboardHeight+ " | New Width: "+newArtboardWidth;
                alert("Artboard Rotated!", alertMessage);
            }else{
                doc.showMessage("Please select an artboard.");
            }
        }
    }
}

Подытожим пример 1:

  • Проверяем, выделено ли что-то
  • Определяем тип слоя выделенного, и делаем кое-что, если это тот тип, который нам нужен
  • Извлекаем исходный фрейм нашего выделенного артборда, высоту и ширину, и сохраняем это в переменных
  • Создаем новые переменные c нужными нам значениями
  • Задаем для фрейма выделенного артборда эти значения

Пример 2: Выделение слоев и их переименование на имя символа


Вот еще одна проблема из реального мира, которую я обнаружил, работая с символами. Если я добавляю новый символ из своей библиотеки, имя слоя изменяется на имя символа. Если я переключаюсь на другой символ, название слоя все равно остается без изменений. Иногда я хочу задать свое название слоя, а иногда просто хочу переключить его на название нового символа. Вот еще один идеальный повод написать плагин!

Для этого примера, скачайте файл Sketch для справок.

В файле Sketch на первом артборде вы увидите символ под названием Square, он находится по центру. Также в библиотеке есть еще один символ под названием Circle.

Выделите Square, и используя опции справа измените Square на Circle

1-ow6hbt63d3_y9d7hzdkd0a

Теперь посмотрите на слои слева — слой все еще называется Square! Я думаю, что мог бы изменить название вручную, но это не прикольно, особенно если изменить нужно множество сущностей.

Сценарий для этого плагина такой:

  • Сначала убедиться, что что-то выделено
  • Затем убедиться, что выделен именно символ
  • Получить название символа
  • Проверить, совпадает ли название символа с названием слоя
  • Если нет, изменить название слоя на название символа
  • И, наконец, уведомить пользователя, когда скрипт отработает

Вы заметите, что у нового и предыдущего скрипта много общего — та же проверка на выделение, и определение типа выделенных объектов. Так что мы можем воспользоваться готовыми наработками, но все же это довольно специфический случай, так что будем создавать отдельный скрипт.

Следуя первым шагам из предыдущего примера, давайте добавим еще один скрипт в MyPlugin.sketchplugin, создав новый файл под названием SetLayerNameToSymbolName.js и добавим его в manifest.json. Чтобы сэкономить время, просто используйте RotateArtboard.js и «Save As». Мы будем использовать этот скрипт очень часто, так что начнем отсюда и сделаем несколько изменений.

аша папка сейчас содержит такие файлы:

1-wswvgjzkj299_nctanol8g

В этом скрипте, вместо проверки, является ли выделение MSArtboardGroup (артбордом), мы проверим, является ли оно MSSymbolInstance (символом). Если является, мы выясним название symbolMaster и сравним его с названием слоя.

И вот вам новый скрипт с парой измененных строк:

@import 'common.js'

var onRun = function(context) {

    //reference the sketch document
    var doc = context.document;
    //reference what is selected
    var selection = context.selection;

    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        //loop through the selected layers
        for(var i = 0; i < selection.count(); i++){

            //checks to see if the layer is a Symbol
            if(selection[i].class() == "MSSymbolInstance"){

                //reference the selection
                var layer = selection[i];
                //get the original layer name
                var layerName = layer.name();
                //get the name of the symbol on the layer
                var symbolName = layer.symbolMaster().name();

                //check if layer name is not already symbol name
                if(layerName != symbolName){
                    //set the layer name to the symbol name
                    layer.setName(symbolName);
                    
                    var alertMessage = "Layer Name Changed from: "+layerName+ " to: "+symbolName;
                    alert("Layer Name Changed!", alertMessage);

                }else{
                    doc.showMessage("Layer name is already Symbol Name.");
                }

            }else{
                doc.showMessage("Please select a Symbol Layer.");
            }
        }
    }
}

Подытожим пример 2

В этом примере мы усвоили:

  • Проверки, является ли выделение артбордом или символом, выполняются идентично.
  • Как получить название symbolMaster и название слоя
  • Как сравнить два названия и настроить название слоя, если они отличаются.

Пример 3: Задание названий символов в качестве названий слоев


Предыдущий скрипт работает идеально, но иногда мне не хочется выделять каждый слой вручную. Иногда после разбрасывания кучи символов по документу я просто хочу привести все названия слоев в соответствие с названиями символов без выделения слоя. Для этого понадобится изменить следующее:
  • Сначала получить все страницы документа
  • Затем получить все артборды на каждой странице
  • Затем получить все слои на каждом артборде
  • Затем проверить, является ли какой-то из этих слоев слоем символа
  • Затем проверить, не совпадает ли название слоя с названием символа
  • Если не совпадает, заменить название слоя и уведомить пользователя

Так как мы теперь не учитываем, что сейчас выделено, нам придется изменить скрипт, чтобы он прошелся по данным страниц документа. Затем мы воспользуемся рядом вложенных петель for, которые будут итерировать по массиву артбордов, затем по массиву слоев, и наконец, дойдут до каждого слоя. Цикл for внутри цикла for, которая внутри цикла for.

Наверное, для этой задачи мы создадим еще один новый скрипт. Начните с нового файла под названием SetAllLayerNamesToSymbolNames.js и также добавьте его в manifest.json, как мы делали раньше.

Теперь в папке MyPlugin.sketchplugin будет столько файлов:

1-rhsu-brcrkmcsyvbno0ckw

И плагин в вашем списке меню, вместе с предыдущими примерами должен отображать новую опцию, как только вы добавите новый файл в манифест:

1-zl8p9tmk09ocpqo0ujd7gw

Чтобы извлечь данные страниц документа, используйте следующий код:

var pages = [doc pages];

Чтобы получить артборды страниц, используйте цикл for:
var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
}

Чтобы получить слои артборда, используйте цикл for внутри цикла for:

var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
  
  for (var z = 0; z < artboards.count(); z++){
    var artboard = artboards[z];
    var layers = [artboard layers];
    
  }
}   

Далее, чтобы извлечь каждый отдельный слой, вам нужно пройтись по слоям внутри цикла for, и затем проверить, что это за слой:

var pages = [doc pages];

for (var i = 0; i < pages.count(); i++){
  var page = pages[i];
  var artboards = [page artboards];
  
  for (var z = 0; z < artboards.count(); z++){
    var artboard = artboards[z];
    var layers = [artboard layers];
    
    for(var k = 0; k < layers.count(); k++){
      var layer = layers[k];
      
      if(layer.class() == "MSSymbolInstance"){
        //do something
        }
    }
  }
}

Как только мы установили, что это слой символа, мы можем проверить его название и сравнить с названием слоя. Если они отличаются, меняем название слоя на название символа, и все!

Вот как выглядит весь скрипт, для простоты понимания почти каждая строка прокомментирована:

@import 'common.js'

var onRun = function(context) {
    //reference the sketch document
    var doc = context.document;
    //reference the pages array in the document
    var pages = [doc pages];
    //create a variable to hold how many symbol layers we changed
    var symbolCount = 0;

    //loop through the pages array
    for (var i = 0; i < pages.count(); i++){
        //reference each page
        var page = pages[i];
        //reference the artboards array of each page
        var artboards = [page artboards];

        //loop through the artboards of each page
        for (var z = 0; z < artboards.count(); z++){
            //reference each artboard of each page
            var artboard = artboards[z];
            //reference the layers array of each artboard
            var layers = [artboard layers];

            //loop through the layers array
            for(var k = 0; k < layers.count(); k++){
                //reference each layer of each artboard
                var layer = layers[k];

                //check to see if the layer is a Symbol
                if(layer.class() == "MSSymbolInstance"){

                    //get the original layer name
                    var layerName = layer.name();
                    //get the name of the symbol on the layer
                    var symbolName = layer
    
            

© Habrahabr.ru