Плагины редактора Пазлов

Редактор пазлов «Вердж3Д» предоставляет возможность загрузки собственных пользовательских пазлов, что позволяет расширить функциональность редактора за счет функций, о которых вы всегда мечтали.

Содержание

Установка плагинов

Плагин — это обычная папка с файлами. Она должна быть помещена в папку puzzles/plugins дистрибутива «Вердж3Д» для того, чтобы быть распознанной редактором пазлов. Этого будет достаточно! После перезагрузки страницы редактора все установленные плагины должны появиться в нижней части панели инструментов редактора сразу после всех стандартных категорий пазлов.

Plugin categories in the toolbox

Для разработчиков пазлов
Этот полезный плагин содержит примеры некоторых типичных блоков пазлов: ExamplePlugin.zip. Просто распакуйте его в папку puzzles/plugins, а затем ознакомьтесь с категорией пазлов "Example Plugin".

Обзор файлов плагинов

Типичный плагин представляет собой каталог с файлом init.plug, содержащим общие настройки с большим количеством файлов *.block, каждый из которых определяет один блок пазла. Если ваш текстовый редактор поддерживает подсветку синтаксиса, то режим ХТМЛ должен хорошо работать для обоих форматов файлов.

Формат файла init.plug

init.plug - это обязательный файл плагина, который используется для задания общих настроек. В этом файле вы можете определить, как выглядит запись панели инструментов. Также вы можете добавить туда некоторый предварительный код «ЯваСкрипт» для ваших пазлов. Вот простой пример того, что вы можете увидеть в файле init.plug:

<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> </category> <script> function code() { return `console.log('Powered by My Awesome Plugin!');`; } </script>

Category

Часть category представляет собой XML-дерево, которое определяет, как плагин и его блоки пазлов будут отображаться в панели инструментов редактора пазлов. Хотя она и необязательна, но если вы не добавите ее в файл init.plug, то плагин вообще не будет загружен.

Существует несколько вариантов, которые можно настроить через category:

Имя панели инструментов

Указывается через атрибут name: <category name="My Awesome Plugin"></category>

Цвет панели инструментов

Указывается с помощью атрибута color: <category name="My Awesome Plugin" color="green"></category> Цвет может быть задан в одном из следующих форматов:

Доступные пазлы

Чтобы сделать блок пазла доступным в категории инструментов плагина, он должен быть указан через элемент block и его атрибут type. Атрибут type может ссылаться на такие пазлы, как:

Чтобы узнать тип определенного блока пазла, можно воспользоваться опцией "Print Puzzle XML Tree" из контекстного меню пазла:

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

Опция меню "Print Puzzle XML Tree" обеспечивает простой способ добавления в ваш плагин сразу целой группы блоков пазлов. Это полезно для создания своего рода "snippets", состоящих из связанных блоков пазлов, и помещения их в ваш плагин, что делает его немного похожим на Библиотеку пазлов.

Следующий пример объясняет, как это сделать: :

Порядок пазлов

Блоки пазлов появляются на панели инструментов в том порядке, который вы определили в файле init.plug: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <!-- <block type="testPuzzle"></block> --> <block type="anotherPuzzle"></block> </category>
Обратите внимание, что закомментированный блок пазла "testPuzzle" не отображается в панели инструментов.

Текстовые метки

Вы можете добавить текстовые метки в категорию инструментов с помощью элемента label:

<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>

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

Элементы label также могут быть в некоторой степени стилизованы. Они поддерживают атрибут web-class, который предназначен для назначения пользовательского ЦСС-класса элементу label. Правила ЦСС для этого класса описаны в разделе init.plug's script. Этот подход проиллюстрирован в следующем примере:

<category name="Example Plugin" color="#a52a2a"> <label text="Example Plugin v1.0 by Soft8Soft" web-class="example-plugin__label"></label> </category> <script> const styleElem = document.createElement('style'); styleElem.innerHTML = ` .example-plugin__label .blocklyFlyoutLabelText { fill: #a52a2a; font-style: italic; font-weight: bold; text-decoration: underline; } `; document.head.appendChild(styleElem); </script>

А вот результат применения пользовательских настроек ЦСС:

При выборе имени ЦСС-класса для атрибута web-class рекомендуется учитывать особенную ЦСС-подстановку, например, использовать префикс, уникальный для вашего плагина пазлов, т.е. в примере выше это "example-plugin" часть класса "example-plugin__label". Таким образом, вы с меньшей вероятностью случайно нарушите какие-либо классы ЦСС, уже используемые на странице.

Разделители

Разделители можно использовать для изменения расстояния между блоками пазла. Вы можете добавить разделители в категорию инструментов с помощью элемента sep. Атрибут gap задает ширину расстояния в пикселях. По умолчанию расстояние между блоками (если не используется sep) равно 24 пикселям. <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <sep gap="0"></sep> <block type="doSomethingCool"></block> <sep gap="80"></sep> <block type="anotherPuzzle"></block> </category>

Блоки пазла, доступные на вкладке "init"

По умолчанию блоки пазла доступны только на вкладках main и на созданных пользователем, но не на init. Это связано с тем, что код, генерируемый вкладкой init, выполняется до загрузки и полной инициализации приложения «Вердж3Д». Это означает, что пазлы, предназначенные для работы с 3d-сценой, 3d-объектами, материалами и т.д., не подходят для использования внутри init и могут вызвать сбой приложения. Однако пазлы, которые не требуют наличия 3d-сцены (например, те, которые предварительно загружают ресурсы или настраивают пользовательский интерфейс) не должны иметь таких проблем и могут быть разрешены в init.

Для того чтобы пазл появился на панели инструментов вкладки init, вам необходимо установить для атрибута allow-init значение true в элементе block пазлов (это также работает с элементами label и sep): <category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0" allow-init="true"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle" allow-init="true"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>
Обратите внимание, что элементы block и label без allow-init не отображаются на панели инструментов.

Значения ввода и полей по умолчанию

Если блок пазла имеет входы для блока (слоты для подключения других блоков пазла) и/или поля ввода (неблочные элементы пользовательского интерфейса, такие как селекторы, галочки, текстовые поля и т.д.), то вы можете указать их блоки-заполнители и / или значения по умолчанию. Эта функция служит двум целям: она даёт пользователям подсказку о том, что можно вставить во входной слот, а также делает использование пазла немного более удобным.

Допустим, у вашего блока пазла есть вход с именем "myNumber". Вот как вы можете добавить блок-заполнитель типа math_number подключенный к этому слоту: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <block type="math_number"></block> </value> </block> </category> А вот как это будет выглядеть:


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

Блоки Shadow определяются почти так же, как и обычные блоки-заполнители, с той лишь разницей, что элемент block заменяется аналогичным элементом shadow: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <shadow type="math_number"></shadow> </value> </block> </category> И это будет выглядеть следующим образом:


Блоки пазла могут иметь входные данные команд, которые обычно заключают в себе серию дочерних блоков пазла. Предположим, что в вашем блоке пазла есть ввод команд под названием "myStatement". Затем вы можете добавить к этому входу пару блоков-заполнителей следующим образом: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="myStatement"> <block type="addHTMLElement"> <next> <block type="setHTMLElemAttribute"></block> </next> </block> </statement> </block> </category> Используемый здесь элемент statement ссылается на вход "myStatement" через атрибут name attribute. Кроме того, в него добавлены некоторые блоки-заполнители. Также здесь используется элемент next для цепочки блоков-заполнителей. Результат этой настройки показан ниже:


Если в вашем блоке пазле есть поле для галочки с именем "myCheckbox", то вы можете задать его состояние по умолчанию (true - включен, false - выключен) следующим образом: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <field name="myCheckbox">true</field> </block> </category> And here's the result:


Используя блоки-заполнители и значения полей по умолчанию, вы можете создавать сложные составные пазлы аналогичные тем, которые можно добавить в библиотеку пазлов:
Код в init.plug для сложного пазла на картинке выше может выглядеть следующим образом: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="STATEMENT_0"> <block type="whenClicked"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">Cube</field> </block> </value> </block> </statement> <statement name="STATEMENT_1"> <block type="loadScene"> <value name="URL"> <block type="text"> <field name="TEXT">my_scene.gltf</field> </block> </value> </block> </statement> <statement name="STATEMENT_2"> <block type="show"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">something</field> </block> </value> </block> </statement> </block> </category>

Проверьте параметр контекстного меню "Print Puzzle XML Tree". Это поможет найти XML структуру (конфигурацию входов и полей) интересующего вас блока пазла.

Подкатегории панели интрументов

Категория в панели инструментов редактора может иметь подкатегории, которые, в свою очередь, также могут иметь подкатегории, и так далее... Эта функция полезна, если вы хотите организовать пазлы вашего плагина в древовидную структуру.

Этого можно достичь с помощью встроенного элемента category: <category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <category name="1" color="red"> <category name="1.1" color="silver"> <block type="anotherPuzzle"></block> </category> </category> <category name="2" color="blue"> <block type="doSomethingCool"></block> </category> </category>
Любая category может содержать одновременно элемент category и block (хотя это не обязательно). Таким образом, она действует как родитель для подкатегорий и также предоставляет выбор блоков пазла.

Скрипт

Элемент <script> является необязательной частью init.plug. Его можно использовать для добавления кода инициализации для ваших пазлов. Иногда вам может потребоваться выполнить тяжелые вычисления и кэшировать некоторые данные, прежде чем можно будет использовать какой-либо из ваших пазлов, вот где на помощь приходит <script>

Если вы определите функцию code() внутри <script> она будет использоваться для генерации кода, который выполняется один раз перед любым пазлом. Функция code() должна возвращать строку, содержащую код «ЯваСкрипт». <script> function code() { // this line will be executed before any puzzles return `console.log('Powered by My Awesome Plugin!');`; } </script>

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

Формат файла .block

Файлы плагина с расширением .block используются для описания отдельных блоков пазла, а именно: как может выглядеть блок и какой код он должен генерировать при добавлении в рабочую область. Плагин может вообще не иметь файлов .block. Это может быть полезно, если вы хотите создать панель инструментов только с stock блоками пазла (даже включая более сложные настройки блоков).

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

Вот минимальный пример файла .block: <template color="green"> <dummy> <label>myPuzzle</label> </dummy> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script> Здесь у нас есть элемент <template>, который определяет внешний вид блока пазла. А также есть элемент <script>, внутри которого находится функция code(). Функция code() возвращает строку, содержащую код, который будет сгенерирован на месте этого пазла.

Итак, исходя из приведенного выше примера, мы можем полагать, что наш простой блок пазла будет зеленого цвета и будет иметь текстовую метку"myPuzzle":


И если добавить его в рабочую область, он должен вывести следующее сообщение в консоль браузера:

Шаблон блока

Внешний вид блока пазла можно задать двумя способами: через <template> XML элемент и через функцию template(). Первый вариант более прост и удобен в использовании. Например, вот как может выглядеть <template> типичного пазла:

<template color="green" inline="true" output="Dictionary" tooltip="This is my first puzzle!" help="https://soft8soft.com" > <dummy name="myDummyInput"> <label>enable</label> <checkbox name="myCheckbox">true</checkbox> </dummy> <value name="myValueInput"> <label>input value</label> </value> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script>

Другой подход заключается в использовании функции template(). Это функция, которую вы можете определить внутри элемента <script>. Её также можно использовать для настройки внешнего вида пазла, но на этот раз через Blockly JavaScript API, а не через элементы и атрибуты XML. Он получает параметр block, который является копией Blockly.BlockSvg.

Тот же блок, что и в примере выше, можно переписать с помощью функции template() следующим образом:

<script> function template(block) { block.setColor('green'); block.setInputsInline(true); block.setOutput(true, 'Dictionary'); block.setTooltip('This is a test puzzle!'); block.setHelpUrl('https://soft8soft.com'); block.appendDummyInput('myDummyInput') .appendField('enable') .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); block.appendValueInput('myValueInput') .appendField('input value'); } function code(block) { return `console.log('This is my first puzzle!');`; } </script>

Этот подход более гибкий, но требует знания соответствующих APIs. Он особенно полезен, если вам нужно выполнить какую-то нетривиальную настройку, которую невозможно осуществить с помощью элемента <template>. Более того, вы можете использовать и <template> и template(). одновременно.

В этом разделе приведены примеры как для <template> (XML) так и для template() (JS).

Обратите внимание, что этот раздел - лишь краткий обзор того, как создать пользовательский блок пазла. Для получения более подробной информации об общей настройке, ознакомьтесь с документацией Google Blockly о настраиваемых блоках и полях.

Цвет блока

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

Может быть задан с помощью атрибута color: <template color="green"></template>
Можно настроить с помощью block.setColor: <script> function template(block) { block.setColor('green'); } </script>

Цвета должны быть в одном из описанных здесь форматов: цветовой формат.

Всплывающая подсказка блока

Можно добавить всплывающую подсказку, которая появляется при наведении курсора на блок. Подсказка полезна для предоставления пользователю простого описания того, для чего предназначен пазл, как он работает, какие есть советы по использованию и т.д...

Можно установить с помощью атрибута tooltip: <template tooltip="This is my first puzzle!"></template>
Можно настроить с помощью block.setTooltip: <script> function template(block) { block.setTooltip('This is my first puzzle!'); } </script>

Справочная ссылка блока

Если tooltip недостаточно для описания вашего пазла, вы также можете добавить ссылку на сайт с более подробной документацией. Эта ссылка будет использоваться для пункта Help в контекстном меню пазла (щелкните правой кнопкой мыши на пазле):

Может быть задан с помощью атрибута help: <template help="https://www.soft8soft.com/"></template>
Можно настроить с помощью block.setHelpUrl: <script> function template(block) { block.setHelpUrl('https://www.soft8soft.com/'); } </script>

Добавление входа

Блоки пазла могут содержать слоты ввода для подключения других блоков. Также они могут содержать неблочные элементы пользовательского интерфейса, такие как галочки или текстовые поля. Существует 3 различных типа входов: value inputs,statement inputs и dummy inputs.

Разницу между этими типами входов можно увидеть на рисунке ниже:

Используйте элементы <value>, <statement> и <dummy> для добавления входов. Входы Value и statement должны иметь имя (для этого используйте их атрибут name). Входам Dummy обычно не нужны имена, они являются просто областями для полей. fields. <template> <value name="myInput"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>
Для добавления входов используйте способы block.appendValueInput, block.appendStatementInput и block.appendDummyInput. Входы Value и statement должны иметь имя (через атрибут name). Входы Dummy обычно не нуждаются в именах, они являются просто контейнерами для fields. <script> function template(block) { block.appendValueInput('myValue'); block.appendStatementInput('myStatement'); block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>

Расположение входов

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

Может быть задан с помощью атрибута inline, который может быть либо true либо false. falseдля вертикального расположения, а true - для горизонтального. <template inline="true"></template>
Может быть установлен через block.setInputsInline метод. Метод получает параметр, который может быть либо true либо false. false - для вертикального расположения и true - для горизонтального варианта. <script> function template(block) { block.setInputsInline(true); } </script>

Добавление полей

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

Есть несколько особенностей, которые объединяют все поля:

Давайте посмотрим, как добавить различные поля в пазл.

Выравнивание полей

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

Чтобы изменить выравнивание поля для определенного ввода, используйте атрибут align у соответствующего input element. Действующими значениями этого атрибута являются: left, center и right. <template> <dummy align="left"></dummy> <value align="center" name="myValueInput"></value> <statement align="right" name="myStatementInput"></statement> </template>
Чтобы изменить выравнивание поля для определенного ввода, используйте метод setAlign. Его первый параметр должен быть Blockly.ALIGN_LEFT, Blockly.ALIGN_CENTER или Blockly.ALIGN_RIGHT. <script> function template(block) { block.appendDummyInput() .setAlign(Blockly.ALIGN_LEFT); block.appendValueInput('myValueInput') .setAlign(Blockly.ALIGN_CENTER); block.appendStatementInput('myStatementInput') .setAlign(Blockly.ALIGN_RIGHT); } </script>

Блочные соединения

Блоки пазлов могут иметь входные, утверждающие и выходные соединения. Входные соединения добавляются автоматически для каждого созданного входного слота и служат для подключения дочерних блоков. Более подробную информацию о создании входов можно найти здесь: adding inputs. Соединения определений (предыдущих и следующих) и выходов используются для соединения блоков с родительским блоком или блоками-потомками.

Previous и next добавляют соединения сверху и снизу блока пазла соответственно, так что блок может быть соединен снизу/сверху с другими блоками, имеющими соответствующее соединение.

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

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

Вот как все эти связи могут быть добавлены в блок:

Связь previous оператора можно включить, установив атрибут prev в значение true: <template prev="true"></template>
Связь с previous оператором может быть включена вызовом block.setPreviousStatement с true в качестве первого параметра: <script> function template(block) { block.setPreviousStatement(true); } </script>
Связь с оператором next можно включить, установив атрибут next в значение true: <template next="true"></template>
Подключение next оператора можно включить, вызвав block.setNextStatement с true в качестве первого параметра: <script> function template(block) { block.setNextStatement(true); } </script>
Соединение output можно включить, установив атрибут output в пустую строку: <template output=""></template>
Соединение output может быть включено вызовом block.setOutput с true в качестве первого параметра: <script> function template(block) { block.setOutput(true); } </script>

Проверка типов входов и выходов

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

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

Для получения общей информации о выходных и других соединениях см. раздел соединения блоков.

Тип вывода может быть указан через атрибут output шаблона <template>. Типы, которые может принимать конкретный вход, определяются атрибутом type входа. <!-- the block's output type is 'String' --> <template output="String"> <!-- this input accepts only blocks of type 'Number' --> <value name="myInput" type="Number"></value> </template>
Тип вывода может быть назначен блоку вызовом метода block.setOutput передавая в качестве первого параметра true, а в качестве второго - желаемый тип. Типы, которые может принимать определенный вход, устанавливаются вызовом метода setCheck. <script> function template(block) { // этот вход принимает только блоки типа 'Number' block.appendValueInput('myInput') .setCheck('Number'); // тип вывода блока - 'String' block.setOutput(true, 'String'); } </script>

В приведенном примере блок имеет вход, который может принимать блоки только типа "Number" или неопределенного типа (если тип не был установлен через setOutput). Блок также имеет тип выхода "String", что означает, что он может быть подключен только к входу, имеющему тип "String" или неопределенный тип (если тип не был установлен через setCheck).

Входы и выходы также могут иметь более одного типа:

Для того чтобы иметь несколько типов ввода/вывода, заполните соответствующие атрибуты type и output значениями нескольких типов, разделенными пробелом: <!-- тип вывода этого блока - 'String' или 'Animation' --> <template output="String Animation"> <!-- этот вход принимает только блоки типа 'Number' или 'Object3D' --> <value name="myInput" type="Number Object3D"></value> </template>
Чтобы иметь несколько типов ввода/вывода, предоставьте массив типов в setCheck и/или setOutput. <script> function template(block) { // этот вход принимает только блоки типа 'Number' или 'Object3D' block.appendValueInput('myInput') .setCheck(['Number', 'Object3D']); // тип вывода этого блока - 'String' или 'Animation' block.setOutput(true, ['String', 'Animation']); } </script>

В стандартных пазлах «Вердж3Д» используется несколько определенных типов ввода/вывода, которые вы можете заимствовать и для своих пазлов:

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

Функция code

Функция code() используется для предоставления кода «ЯваСкрипт», который должен быть сгенерирован для пазла, если он добавлен в рабочее пространство. Как правило, это место, где вы определяете логику пазла и реализуете большинство ее функций.

Ожидается, что функция вернет строку, содержащую js-код. Принцип ее работы похож на работу init.plug's code function, за исключением того, что в данном случае код будет добавлен и использован столько раз, сколько блоков пазла будет добавлено в рабочее пространство. Функция code() получает параметр block - это тот же экземпляр Blockly.BlockSvg который используется в template().

Давайте посмотрим, что можно сделать с помощью функции code().

Генерация базового кода

Очень простая вещь, которую может сделать code() - это вернуть строку с парой строк кода «ЯваСкрипт», который будет добавлен в результирующий файл visual_logic.js.
Например, следующий код открывает стандартный диалог оповещения браузера: function code(block) { return `alert('Test');`; } И здесь мы просто возвращаем значение 1: function code(block) { return `1`; } - этот пример не имеет особого смысла, если только блок не имеет выходного соединения. В этом случае доступ к возвращаемому значению можно получить из родительского блока, у которого этот блок подключен к одному из входов.

А теперь более сложный пример: function code(block) { const fun = function() { app.scene.traverse(function(obj) { obj.material = new v3d.MeshBasicMaterial({ color: new v3d.Color(Math.random(), Math.random(), Math.random()) }); }); } return `(${fun})();`; } - здесь все объекты получают новый материал со случайно сгенерированным цветом.

Борьба с разрастанием кода

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

Для решения этой проблемы можно использовать специальный метод, доступный внутри функции code(). Он называется Plug.provide(). Давайте продемонстрируем его использование на следующем примере: function code(block) { const fun = Plug.provide('myFunction', function(a, b, c) { console.log(a, b, c); }); return `${fun}(1, 2, 3);`; } Здесь у нас есть функция "myFunction", определенная через Plug.provide(), что означает, что независимо от того, сколько раз пазл используется в рабочем пространстве, "myFunction" будет скопирована в visual_logic.js только один раз. Кроме того, значение, которое фактически возвращается из code() - это ${fun}(1, 2, 3);, что, по сути, является вызовом функции "myFunction(1, 2, 3);", которая будет вставлена в visual_logic.js для каждого такого пазла, используемого на рабочем пространстве. И это именно то, чего мы хотим от нашего пазла, потому что "myFunction" нужно объявить только один раз, и после этого ее можно вызывать много раз.

Первым параметром в Plug.provide() должен быть уникальный идентификатор функции. Возвращаемая переменная fun - это имя предоставленной функции (обычно оно почти совпадает со значением, переданным в первом параметре, но может быть и другим, так как редактору пазлов необходимо убедиться, что имя корректно и не имеет коллизий с другими функциями/переменными, используемыми на рабочем пространстве). Это имя (вместо исходного "myFunction") должно быть использовано для вызова предоставленной функции - именно так это делается в части под оператором возврата.

Доступ к входам и полям

Если блок пазла имеет поля или слоты ввода определенные в функции template(), то вы, скорее всего, захотите, чтобы они влияли на то, что генерируется в функции code() function. Например, флажок может включать или выключать одну из функций пазла.

Методы API, используемые для доступа к входам значений, входам операторов и полям are namely: Blockly.JavaScript.valueToCode, Blockly.JavaScript.statementToCode и block.getFieldValue.

Давайте создадим блок пазла, в котором есть и входы, и поля. Вот полное содержимое файла .block: <template color="green"> <dummy> <label>myPuzzle</label> </dummy> <value name="myValue"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template> <script> function wrapFn(contents) { return `function() {${contents}}`; } function code(block) { const myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`; const myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement')); const myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE'; const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); }); return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`; } </script> В этом примере блок определяет ввод значения под названием "myValue", ввод утверждения "myStatement" и поле флажка "myCheckbox". Мы получаем их значения с помощью API, описанного выше, но прежде чем передать их в "myFunction", они претерпевают некоторые заметные изменения: var myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`; - Входной слот может не иметь блоков, подключенных к нему, поэтому мы просто гарантируем, что в этом случае мы получим пустую строку, добавив часть *|| ''* в конце. function wrapFn(contents) { return `function() {${contents}}`; } ... var myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement')); - слот ввода оператора обычно содержит группу операторов. Удобно обернуть их в функцию (см., что делает wrapFn), чтобы передать объект функции в качестве параметра и затем рассматривать его как обратный вызов. var myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE'; - здесь значение флажка просто сравнивается с "TRUE" для получения булевого результата.

В итоге все значения можно передать в "myFunction" следующим образом: return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`; Теперь вы можете использовать их по своему усмотрению: const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); });

Ошибки плагинов и отдельных блоков

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

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

PluginError(PLUGIN_NAME) ...
BlockError(PLUGIN_NAME/BLOCK_NAME) ...
Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

- они ссылаются на конкретный плагин и конкретный блок, который вызвал ошибку.

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

Недопустимый пазл на панели инструментов и на рабочей области

Вот список наиболее распространенных ошибок плагинов и блоков:


BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."

Это означает, что в соответствующем файле .block присутствуют ошибки XML, препятствующие его разбору. Например, отсутствие завершающего тега <script> приводит к такой ошибке: <script> function template(block) {} function code(block) {}


BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

Ошибка возникает в том случае, если код в элементе <script> соответствующего файла .block содержит ошибку «ЯваСкрипт» вида, указанного в сообщении об ошибке.


BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."

Блок, на который ссылается эта ошибка, имеет дочерний блок, подключенный к нему. Однако дочерний блок не имеет ни выхода, ни предыдущего соединения, и поэтому не может быть использован таким образом. Такая ситуация может произойти как с блоками пазлами внутри категории инструментов плагина из-за того, как они настроены в init.plug, так и с блоками, реально используемыми на рабочем пространстве.


BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "Error: Connection checks failed. Input "PARENT_INPUT_NAME" connection on "PLUGIN_NAME/BLOCK_NAME" block (id="BLOCK_ID") expected TYPE_PARENT, found TYPE_CHILD"

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


BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"

Эта ошибка означает, что файл init.plug плагина ссылается на блок, в котором не указан атрибут типа что недопустимо. Например, это не сработает: <category name="My Awesome Plugin" color="green"> <block></block> </category>


BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Unknown block type: PLUGIN_NAME/BLOCK_NAME"

Это сообщение об ошибке обычно появляется после одной из ошибок "error parsing .block file" и просто указывает на то, что упомянутый блок пазла не был правильно загружен и инициализирован из-за первоначальной ошибки.


BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...

Соответствующий файл .block либо имеет неправильно определенный элемент <template> либо содержит ошибки «ЯваСкрипт» внутри своей функции template().


BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...

Соответствующий файл .block содержит ошибки «ЯваСкрипт» внутри своей функции code().


PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."

Это означает, что в файле init.plug плагина присутствуют XML-ошибки, препятствующие его разбору. Например, отсутствие завершающего тега <category> приводит к такой ошибке: <category name="MyAwesomePlugin" color="green"> <block type="myPuzzle"></block>


PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

Ошибка возникает в том случае, если код в элементе <script> соответствующего файла init.plug содержит ошибку «ЯваСкрипт» вида, указанного в сообщении об ошибке.


PluginError(PLUGIN_NAME): error calling code() function ...

Файл init.plug плагина содержит ошибки «ЯваСкрипт» в функции code().


Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

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


Как распространять плагины

Как только плагин будет разработан и протестирован, не стесняйтесь поделиться им:

Проблемы с пазлами?

Обратитесь за помощью на форуме!