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

в 6:51, , рубрики: Cocoa, CocoaScript, javascript, sketch, sketchapp, sketchplugin, плагины sketch

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

Приветствую друзья! Меня зовут Антон, я развиваю сайт 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.symbolMaster().name();

                    //only change the name of layers that don't match the symbol name
                    if(layerName != symbolName){
                        //set the layer name to the symbol name
                        layer.setName(symbolName);
                        symbolCount = symbolCount + 1;

                    }
                }
            }
        }
    }
    var alertMessage = symbolCount + " symbol layer name changed.";
    alert("Symbol Layer Names Reset!", alertMessage);
}

Подытожим пример 3, тут мы научились:

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

Вывод

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

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

Часть 5 — Класс MSLayer

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

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

Чтобы лучше понять терминологию, есть два типа данных, доступных через класс:

  1. Атрибуты — именованное свойство объекта (например, высота, ширина или название)
  2. Методы — функции, связанные с классами, обычно используется для изменения атрибутов (таких как изменение высоты, ширины или названия)

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

Получение атрибутов слоя

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

Предположим, что вы использовали массив выделения, чтобы получить переменную под названием layer:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
        }
    }
};

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

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

Вот несколько атрибутов, которые доступны в классе MSLayer:

Класс (тип слоя):

var layerClass = layer.class();

Фрейм (размер и положение, относительно его артборда, сущность MSRect):

var layerFrame = layer.frame();

var layerWidth = layerFrame.width();
 var layerHeight = layerFrame.height();
 var layerXpos = layerFrame.x();
 var layerYpos = layerFrame.y();

Стиль (границы, заливки, тени, внутренние тени, сущность MSStyle):

var layerStyle = layer.style();

//получение массива цветов заливки
 var layerFills = layer.style().fills();

//получение каждого цвета заливки
 for(var z = 0; z < layerFills.count(); z++){
    var fillColor = layerFills[z].colorGeneric();
 }

Название слоя:

var layerName = layer.name();

Видимость:

var layerIsVisible = layer.isVisible();

Статус блокировки:

var layerIsLocked = layer.isLocked();

Отражение (горизонтальное или вертикальное):

//горизонтальное
 var layerIsFlippedHorizontal = layer.isFlippedHorizontal();

//вертикальное
 var layerIsFlippedVertical = layer.isFlippedVertical();

Поворот:

var layerRotation = layer.rotation();

Родительская группа (страница, артборд или группа):

var layerParent = layer.parentGroup();

Статус выделения:

var layerIsSelected = layer.isSelected();

Absolute Rect (глобальный размер и положение в целом документе, сущность MSAbsoluteRect):

var layerAbsoluteRect = layer.absoluteRect();

var layerAbsoluteWidth = layerAbsoluteRect.width();
 var layerAbsoluteHeight = layerAbsoluteRect.height();
 var layerAbsoluteXpos = layerAbsoluteRect.x();
 var layerAbsoluteYpos = layerAbsoluteRect.y();

CSSAttributeString:

var layerCSSAttributeString = layer.CSSAttributeString();

CSSAttributes:

var layerCSSAttributes = layer.CSSAttributes();

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

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            var layerClass = layer.class();
            var layerFrame = layer.frame();
            var layerStyle = layer.style();
            var layerName = layer.name();
            var layerIsVisible = layer.isVisible();
            var layerIsLocked = layer.isLocked();
            var layerIsFlippedHorizontal = layer.isFlippedHorizontal();
            var layerIsVertical = layer.isFlippedVertical();
            var layerRotation = layer.rotation();
            var layerParent = layer.parentGroup();
            var layerIsSelected = layer.isSelected();
            var layerAbsoluteRect = layer.absoluteRect();
            var layerUserInfo = layer.userInfo();
            var layerCSSAttributeString = layer.CSSAttributeString();
            var layerCSSAttributes = layer.CSSAttributes();
        }
    }
};

Затем вы можете использовать консоль для логирования любых переменных, которых хотели бы видеть:

log(“Layer Rotation: “ + layerRotation);

Использование методов

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

setName

var newLayerName = "New Layer Name";
 layer.setName(newLayerName);

setIsVisible

//показать слой
 layer.setIsVisible(true);
 //спрятать слой
 layer.setIsVisible(false);
 //переключить
 layer.setIsVisible(!layer.isVisible())

setIsLocked

//заблокировать слой
 layer.setIsLocked(true);
 //разблокировать слой
 layer.setIsLocked(false);
 //переключить
 layer.setIsLocked(!layer.isLocked());

setRotation

var newLayerRotation = 180;
 layer.setRotation(newLayerRotation);

setIsFlippedHorizontal

//отразить по горизонтали
 layer.setIsFlippedHorizontal(true);
 //сбросить
 layer.setIsFlippedHorizontal(false);
 //переключить
 layer.setIsFlippedHorizontal(!layer.isFlippedHorizontal());

setIsFlippedVertical

//отразить по вертикали
 layer.setIsFlippedVertical(true);
 //сбросить
 layer.setIsFlippedVertical(false);
 //переключить
 layer.setIsFlippedVertical(!layer.isFlippedVertical());

setIsSelected

//выделить
 layer.setIsSelected(true);
 //снять выделение
 layer.setIsSelected(false);
 //переключить
 layer.setIsSelected(!layer.isSelected());

дублирование

layer.duplicate();

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

Например, если вы хотите разблокировать все слои, вы сначала можете проверить, заблокированы ли они, затем можете их разблокировать:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerIsLocked = layer.isLocked();
            
            if(layerIsLocked == true){
              layer.setIsLocked(false);
            }
            
        }
    }
};

Или если бы вы хотели добавить префикс к названию слоя:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerName = layer.name();
            var layerPrefix = "prefix_";
            
            layer.setName(layerPrefix + layerName);
            
        }
    }
}

И еще много всего, опции действительно бесконечны!

Методы атрибутов

Атрибуты, которые являются сущностями других классов, таких как фрейм или стиль, также имеют свои методы.

Например, чтобы изменить ширину слоя до 100, вам понадобится использовать setWidth на переменной фрейма:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerFrame = layer.frame();
            var newLayerWidth = 100;
            
            layerFrame.setWidth(newLayerWidth);
            
        }
    }
}

Если вы хотите добавить на слой заливку, вы можете использовать addStylePartOfType(0) на переменной стиля:

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
          
            var layer = selection[i];
            var layerStyle = layer.style();
            
            layerStyle.addStylePartOfType(0);
            
        }
    }
}

Определение типа подкласса слоя Finding the Layer Subclass Type

Это все вы можете делать с любым MSLayer. Но что если, например, вы хотите считать строку текста из текстового поля? Вам нужно знать сначала, является ли этот объект текстовым полем, иначе код выбросит вам ошибку о несуществовании String.

Не все подклассы описаны в документации, но так как мы можем выяснить класс слоя из атрибута класса, мы можем легко с этим справиться. И чтобы еще больше упростить себе задачу, специально для этого я написал плагин!

Скачайте и установите плагин Show Layer Type Plugin здесь

Затем скачайте и откройте этот файл Sketch для тестирования.

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

1-4_g6dwvalei6aasionoktw

Выделите все слои и запустите плагин Show Layer Type. В консоли вы увидите такой вывод:

8/28/16 9:07:18.993 AM Show Layer Type (Sketch Plugin)[46515]: Background is a: MSShapeGroup
 8/28/16 9:07:18.997 AM Show Layer Type (Sketch Plugin)[46515]: Line is a: MSShapeGroup
 8/28/16 9:07:18.999 AM Show Layer Type (Sketch Plugin)[46515]: Author is a: MSTextLayer
 8/28/16 9:07:18.999 AM Show Layer Type (Sketch Plugin)[46515]: Icon is a: MSBitmapLayer
 8/28/16 9:07:19.000 AM Show Layer Type (Sketch Plugin)[46515]: Oval is a: MSSymbolInstance
 8/28/16 9:07:19.001 AM Show Layer Type (Sketch Plugin)[46515]: Title is a: MSTextLayer
 8/28/16 9:07:19.002 AM Show Layer Type (Sketch Plugin)[46515]: Line is a: MSShapeGroup

Если вы перейдете на страницу Symbols и выберите символ, снова запустите плагин, то увидите следующее:

8/28/16 9:10:08.600 AM Show Layer Type (Sketch Plugin)[46515]: Oval is a: MSSymbolMaster

Если вы выделите артборд и запустите плагин, вывод будет таким:

8/28/16 9:10:48.226 AM Show Layer Type (Sketch Plugin)[46515]: Artboard 1 is a: MSArtboardGroup

Если вы сгруппируете все слои на артборде, затем запустите плагин, увидите следующее:

8/28/16 9:11:24.234 AM Show Layer Type (Sketch Plugin)[46515]: Group is a: MSLayerGroup

Вот и все типы подклассов слоев! Возможно, есть и другие, но это все, которые мне удалось найти (документированные я сопроводил ссылками).

MSShapeGroup
MSBitmapLayer
MSTextLayer
MSSymbolInstance
MSSymbolMaster
MSArtboardGroup
MSLayerGroup

Теперь мы можем дифференцировать код в зависимости от того, какой этот класс, используя операторы if/else.

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;

    if(selection.count() == 0){
        doc.showMessage("Please select something.");
    }else{
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            var layerClass = layer.class();
            
            if(layerClass == "MSShapeGroup"){
              //do something
            } else if (layerClass == "MSBitmapLayer"){
              //do something
            } else if (layerClass == "MSTextLayer"){
              //do something
            } else if (layerClass == "MSSymbolInstance"){
              //do something
            } else if (layerClass == "MSSymbolMaster"){
              //do something
            } else if (layerClass == "MSArtboardGroup"){
              //do something
            } else if (layerClass == "MSLayerGroup"){
              //do something
            }
        }
    }
};

Каждый из этих подклассов располагает гораздо большим количеством атрибутов и методов доступа, которые мы рассмотрим в других постах. Изучите документацию по подклассам layer, чтобы быть готовым к старту!

Выводы

В этой части мы научились, как:

  • Просматривать атрибуты слоя
  • Изменять атрибуты слоя с помощью методов
  • Получать доступ к атрибутам атрибутов и методов
  • Проверять тип класса слоя и различать их в коде

Часть 6 — Экспорт данных

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

Есть много сторонних инструментов-мостиков между дизайном и разработкой (Zeplin, Avocode, Sketch Measure, Sympli и др.), но эти инструменты предоставляют лишь справочное руководство для разработчиков. Было бы куда полезнее, если приложения для дизайна и разработки работали вместе напрямую. Представьте: вы делаете дизайн чего-то в Sketch, нажимаете “экспортировать”, считываете свой проект по разработке и воссоздаете макет. Это избавит от необходимости в попиксельных спецификациях, и даже сэкономит время инженера на воссоздании дизайна в коде.

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

Плагины Sketch — это инструменты, соединяющие дизайн с разработкой

Чтобы соединить дизайн и разработку, нужно найти общую основу двумя этими сферами. Так как нет родных способов их соединить, что мы можем использовать такое, что понятно обоим? Ответ — XML или JSON. С помощью плагинов Sketch может экспортировать данные в оба формата, и, скорее всего, среда разработки, в которой вы работаете, может их считывать. Так зачем слать разработчику пиксельные спецификации, из которых придется копировать значения вручную, если можно отослать их в файле XML и JSON и импортировать автоматически?

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

  1. Выделяем слой
  2. Создаем модальное окно для выбора папки пользователем и сохранения этого местоположения в переменной.
  3. Из выделенного поля создаются переменные для хранения базовой информации (Название, координата X, координата Y, высота и ширина).
  4. Сохраняем все эти переменные либо в XML, либо в JSON-объект.
  5. И, наконец, сохраняем этот объект в файл XML или JSON в предварительно выбранную папку.

Экспорт XML

Для начала мы начнем с создания нашего плагина. Создайте плагин под названием Export Layer to XML с основным скриптом под названием ExportLayerToXML.js. Также нужно добавить наш файл common.js для использования кастомного окна уведомлений, который создавали в предыдущих частях.

Мы уже знаем, как определить, выделен ли слой:

@import 'common.js'

var onRun = function(context) {
  var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
      //do something
    }
};

В операторе else мы создадим панель, где пользователь будет выбирать, в какую папку сохранить файл:

//allow xml to be written to the folder
var fileTypes = [NSArray arrayWithObjects:@"xml", nil];

//create select folder window
var panel = [NSOpenPanel openPanel];
[panel setCanChooseDirectories:true];
[panel setCanCreateDirectories:true];
[panel setAllowedFileTypes:fileTypes];

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

//create variable to check if clicked
var clicked = [panel runModal];
//check if clicked
if (clicked == NSFileHandlingPanelOKButton) {
  
  var isDirectory = true;
  //get the folder path
  var firstURL = [[panel URLs] objectAtIndex:0];
  //format it to a string
  var file_path = [NSString stringWithFormat:@"%@", firstURL];
  
  //remove the file:// path from string
  if (0 === file_path.indexOf("file://")) {
    file_path = file_path.substring(7);
  }
}

Когда все будет сделано, пройдемся циклом по выделенным слоям и вызовем функцию, которая записывает данные в XML-файл. Получившийся код будет выглядеть так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"xml", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        //create variable to check if clicked
        var clicked = [panel runModal];
        //check if clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportXML(layer, file_path);
        }
    }
};

Теперь для функции exportXML передадим 2 значения: выделенный слой и file_path.

Сначала мы настроим XML:

//initialize the root xml element
var root = [NSXMLElement elementWithName:@"document"];
//initialize the xml object with the root element
var xmlObj = [[NSXMLDocument document] initWithRootElement:root];

Затем из переданного слоя извлечем нужные переменные:

//create the variables
var layerName = layer.name();
var layerFrame = layer.absoluteRect();
var layerXpos = String(layerFrame.x());
var layerYpos = String(layerFrame.y());
var layerHeight = String(layerFrame.height());
var layerWidth = String(layerFrame.width());

Сохраним эти переменные в объект XML:

//create the first child element and add it to the root
var layerElement = [NSXMLElement elementWithName:@"layer"];
[root addChild:layerElement];

//add elements based on variables to the first child
var layerNameElement = [NSXMLElement elementWithName:@"name" stringValue:layerName];
[layerElement addChild:layerNameElement];

var layerXPosElement = [NSXMLElement elementWithName:@"xPos" stringValue:layerXpos];
[layerElement addChild:layerXPosElement];

var layerYPosElement = [NSXMLElement elementWithName:@"yPox" stringValue:layerYpos];
[layerElement addChild:layerYPosElement];

var layerHeightElement = [NSXMLElement elementWithName:@"height" stringValue:layerHeight];
[layerElement addChild:layerHeightElement];

var layerWidthElement = [NSXMLElement elementWithName:@"width" stringValue:layerWidth];
[layerElement addChild:layerWidthElement];

И наконец, запишем XML-файл по переданному пути файла и уведомим пользователя, когда все будет сделано:

//create the xml file
var xmlData = [xmlObj XMLDataWithOptions:NSXMLNodePrettyPrint];

//name the xml file the name of the layer and save it to the folder
[xmlData writeToFile:file_path+layerName+".xml"];
var alertMessage = layerName+".xml saved to: " + file_path;

alert("Layer XML Exported!", alertMessage);

Скрипт целиком выглядит так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"xml", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        //create variable to check if clicked
        var clicked = [panel runModal];
        //check if clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportXML(layer, file_path);
        }
    }
};

function exportXML(layer, file_path){

  //initialize the root xml element
  var root = [NSXMLElement elementWithName:@"document"];
  //initialize the xml object with the root element
  var xmlObj = [[NSXMLDocument document] initWithRootElement:root];

  //create the variables
  var layerName = layer.name();
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  //create the first child element and add it to the root
  var layerElement = [NSXMLElement elementWithName:@"layer"];
  [root addChild:layerElement];

  //add elements based on variables to the first child
  var layerNameElement = [NSXMLElement elementWithName:@"name" stringValue:layerName];
  [layerElement addChild:layerNameElement];

  var layerXPosElement = [NSXMLElement elementWithName:@"xPos" stringValue:layerXpos];
  [layerElement addChild:layerXPosElement];

  var layerYPosElement = [NSXMLElement elementWithName:@"yPos" stringValue:layerYpos];
  [layerElement addChild:layerYPosElement];

  var layerHeightElement = [NSXMLElement elementWithName:@"height" stringValue:layerHeight];
  [layerElement addChild:layerHeightElement];

  var layerWidthElement = [NSXMLElement elementWithName:@"width" stringValue:layerWidth];
  [layerElement addChild:layerWidthElement];

  //create the xml file
  var xmlData = [xmlObj XMLDataWithOptions:NSXMLNodePrettyPrint];

  //name the xml file the name of the layer and save it to the folder
  [xmlData writeToFile:file_path+layerName+".xml"];
  var alertMessage = layerName+".xml saved to: " + file_path;

  alert("Layer XML Exported!", alertMessage);

}

Скачайте плагин или просмотрите его на GitHub, протестируйте. Если вы создадите прямоугольник на артборде и экспортируете в XML, получится примерно так:

<document>
 <layer>
 <name>Rectangle</name>
 <xPos>550</xPos>
 <yPos>258</yPos>
 <height>234</height>
 <width>235</width>
 </layer>
</document>

Экспорт JSON

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

function exportJSON(layer, file_path){

  //initialize the layer array
  var layerArray = [];

  //create the variables
  var layerName = String(layer.name());
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  // add the strings to the array
  layerArray.push({
      name: layerName,
      xPos: layerXpos,
      yPos: layerYpos,
      height: layerHeight,
      width: layerWidth,
  });
  
  // Create the JSON object from the layer array
  var jsonObj = { "layer": layerArray };
  // Convert the object to a json string
  var file = NSString.stringWithString(JSON.stringify(jsonObj, null, "t"));
  // Save the file
  [file writeToFile:file_path+layerName+".json" atomically:true encoding:NSUTF8StringEncoding error:null];

  var alertMessage = layerName+".json saved to: " + file_path;
  alert("Layer JSON Exported!", alertMessage);

}

Скрипт целиком выглядит так:

@import 'common.js'

var onRun = function(context) {
    var doc = context.document;
    var selection = context.selection;
    
    //make sure something is selected
    if(selection.count() == 0){
        doc.showMessage("Please select a layer.");
    }else{
        //allow xml to be written to the folder
        var fileTypes = [NSArray arrayWithObjects:@"json", nil];
        
        //create select folder window
        var panel = [NSOpenPanel openPanel];
        [panel setCanChooseDirectories:true];
        [panel setCanCreateDirectories:true];
        [panel setAllowedFileTypes:fileTypes];
        
        var clicked = [panel runModal];
        //check if Ok has been clicked
        if (clicked == NSFileHandlingPanelOKButton) {
            var isDirectory = true;
            //get the folder path
            var firstURL = [[panel URLs] objectAtIndex:0];
            //format it to a string
            var file_path = [NSString stringWithFormat:@"%@", firstURL];
            
            //remove the file:// path from string
            if (0 === file_path.indexOf("file://")) {
                file_path = file_path.substring(7);
            }
        }
        //loop through the selected layers and export the XML
        for(var i = 0; i < selection.count(); i++){
            var layer = selection[i];
            exportJSON(layer, file_path);
        }
    }
};

function exportJSON(layer, file_path){

  //initialize the layer array
  var layerArray = [];

  //create the variables
  var layerName = String(layer.name());
  var layerFrame = layer.absoluteRect();
  var layerXpos = String(layerFrame.x());
  var layerYpos = String(layerFrame.y());
  var layerHeight = String(layerFrame.height());
  var layerWidth = String(layerFrame.width());

  // add the strings to the array
  layerArray.push({
      name: layerName,
      xPos: layerXpos,
      yPos: layerYpos,
      height: layerHeight,
      width: layerWidth,
  });

    // Create the JSON object from the layer array
  var jsonObj = { "layer": layerArray };
  // Convert the object to a json string
  var file = NSString.stringWithString(JSON.stringify(jsonObj, null, "t"));
  // Save the file
  [file writeToFile:file_path+layerName+".json" atomically:true encoding:NSUTF8StringEncoding error:null];

  var alertMessage = layerName+".json saved to: " + file_path;
  alert("Layer JSON Exported!", alertMessage);

}

Вы можете скачать плагин здесь или посмотреть его на GitHub. Если протестируете скрипт, получите JSON-файл с таким содержимым:

{“layer”:[{“name”:”Rectangle”,”xPos”:”550",”yPos”:”258",”height”:”234",”width”:”235"}]}

Заключение

По итогу этой части мы научились:

  • Создавать модальное окно для сохранения файла
  • Создавать объекты XML и JSON для хранения переменных
  • Сохранять файлы XML и JSON в определенную папку.

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

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

Удачи вам в создании собственных плагинов, делитесь достижениями под этим постом.

Обо всех найденных ошибках, неточностях перевода и прочих подобных вещах просьба сообщать в личку.

Автор: Varhal

Источник

Поделиться новостью

* - обязательные к заполнению поля