nodes
contributes.nodes указывает на папку с определениями нод. Каждая нода хранится в своей папке с файлом schema.json.
- my-extension/
- package.json
- nodes/
- timer/
- schema.json
- timer_node.py
- timer/
{ "contributes": { "nodes": "nodes" } }
Определение ноды
{
"type": "TimerNode",
"kind": "class",
"metadata": { "displayName": "Timer", "icon": "Timer" },
"ports": {
"inputs": [
{ "type": "signal", "key": "execute" },
{ "type": "data", "key": "seconds", "dataType": "number", "defaultValue": 1, "required": true }
],
"outputs": [
{ "type": "signal", "key": "next" },
{ "type": "data", "key": "elapsed", "dataType": "number" }
]
},
"code": { "path": "timer_node.py" }
}
Поля
| Поле | Обяз. | Описание |
| --- | --- | --- |
| type | да | Уникальный id типа ноды. |
| kind | | base или class. |
| metadata | да | displayName (обязательно), description и icon. |
| ports | | inputs и outputs (data / signal). Data port может объявить schema — что лежит внутри его значения. |
| parameters | | Параметры ноды с kind: "class": displayName, control, defaultValue, required и поля зависимостей. |
| code | | Путь к реализации ноды. |
| ui | | Рендерер карточки: path к JSX/TSX-файлу от корня расширения или встроенный type. |
| docs | | Путь к странице документации. По умолчанию README.md рядом со схемой. |
| capabilities | | draggable, selectable, connectable, deletable. |
| appearance | | color ноды. |
| constraints | | Ограничить конкретными notations / orchestrators. |
| dimensions | | width и height. |
| menu | | Действия ноды в контекстном меню и шапке карточки. См. Меню ноды. |
Меню ноды
menu добавляет действия к ноде. Они появляются в её контекстном меню (правый клик), а элементы с control также отображаются в заголовке карточки.
{
"menu": {
"items": [
{
"id": "run-timer",
"command": "runTimer",
"label": "Запустить таймер",
"icon": "Play",
"control": { "type": "button", "tooltip": "Запустить таймер" }
}
]
}
}
Меню, не привязанные к типу ноды, добавляются через contributes.menus.
Команда
command — это короткий идентификатор команды из contributes.commands, без префикса расширения - его Fragmenta добавляет сама. Объявите команду в package.json и зарегистрируйте её обработчик в activate:
{
"contributes": {
"commands": [{ "command": "runTimer", "title": "Запустить таймер" }]
}
}
import type { ExtensionContext } from '@fragmenta/extension'
export function activate(context: ExtensionContext) {
context.subscriptions.push(
sdk.commands.registerCommand('runTimer', (nodeId?: string) => {
// запустить таймер
}),
)
}
Аргументы зависят от того, откуда вызвана команда: контекстное меню передаёт id ноды, кнопка в заголовке ничего не передаёт, а select передаёт выбранное value. Подробнее — sdk.commands.
Где выводятся пункты
display на уровне menu задаёт размещение для всех элементов, display на отдельном элементе переопределяет его.
| Значение | Контекстное меню | Шапка карточки |
| --- | --- | --- |
| auto (по умолчанию) | да | если есть control |
| menu | да | нет |
| renderer | нет | если есть control |
Заголовок поддерживает три типа control: button (необязательные label и tooltip), select (options и текущее value) и dropdown-menu (вложенные items). Все они принимают icon и color. label элемента — это его текст в контекстном меню; кнопка в заголовке показывает control.label, а без него — только иконку, если label нет.
Свой рендерер
Если карточка рендерится вашим собственным компонентом из ui.path, вы размещаете меню самостоятельно с помощью NodeCardMenus. Пустой <NodeCardHeader /> уже рендерит имя ноды и меню — он эквивалентен примеру ниже. Как только вы передаёте свои собственные дочерние элементы, добавьте NodeCardMenus явно. Контекстное меню от рендерера не зависит.
Компоненты карточки импортируются из модуля node:
{ "ui": { "path": "nodes/timer/timer.jsx" } }
import { NodeCard, NodeCardHeader, NodeCardHeaderGroup, NodeCardName, NodeCardMenus } from 'node'
export default function TimerNode() {
return (
<NodeCard>
<NodeCardHeader>
<NodeCardHeaderGroup>
<NodeCardName />
</NodeCardHeaderGroup>
<NodeCardMenus />
</NodeCardHeader>
</NodeCard>
)
}
Документация ноды
Пользователи видят новую ноду как чёрный ящик: у неё есть только имя, иконка и несколько портов. Есть два поля, в которых можно объяснить, что она делает.
Однострочное описание
metadata.description отображается в карточке ноды при наведении на ноду в библиотеке Assets, а также в заголовке вкладки документации.
{
"metadata": {
"displayName": "Timer",
"description": "Ждёт заданное время и отправляет сигнал дальше"
}
}
Ограничьтесь одним предложением, чтобы его можно было прочитать за полсекунды перед тем, как перетаскивать ноду в нодовый редактор. Более подробное описание следует разместить на странице документации.
Страница документации
Положите README.md рядом со schema.json:
- nodes/
- timer/
- schema.json
- timer_node.py
- README.md
- timer/
Ничего объявлять не нужно — имя файла является соглашением. Как только файл создаётся, карточка ноды получает кнопку Документация, которая открывает соответствующую страницу во вкладке рядом с редактором.
Если нужно использовать другое имя файла или поделиться ожной страницой между семейством нод, объявите путь явно:
{ "docs": { "path": "guide.md" } }
Путь считается от папки ноды, поэтому "../shared/loops.md" тоже работает.
Страница рендерится как обычный Markdown — выражения MDX не вычисляются, поэтому { и < в тексте можно использовать. Содержимое читается при первом открытии и кэшируется до перерегистрации расширения.
Без такого файла у ноды нет кнопки Документация, и карточка заканчивается на метаданных.
extensions/example-nodes покрывают все три случая: Timer с README.md, Sum с guide.md через docs.path и Random без документации.
Что содержит выход ноды
dataType указывает, переносит ли порт object или array, а schema описывает, что находится внутри. Без этого поля выражения могут предложить один уровень и остановиться — например, после {{Fetch Orders.orders[0]. предлагать будет нечего.
Короткая форма — это сама структура: объект из полей, массив как массив из одного элемента, скаляр, записанный как имя его типа.
{
"type": "data",
"key": "orders",
"dataType": "array",
"schema": [
{
"id": "string",
"total": "number",
"customer": { "id": "string", "contacts": { "email": "string" } },
"items": [{ "sku": "string", "price": { "amount": "number", "currency": "string" } }]
}
]
}
Это объявление включает пошаговое автодополнение для {{Fetch Orders.orders[0].customer.contacts.email}}.
При регистрации node проверяются два правила:
schemaдолжна соответствоватьdataTypeузла. ЕслиdataType—array, схема должна быть одноэлементным массивом, описывающим элементы массива. ЕслиdataType—object, схема должна быть объектом, свойства которого описывают поля объекта.- Вложенность схемы ограничена 8 уровнями. Это не позволяет слишком глубокой схеме перегрузить выпадающий список подсказок. Даже если узел не объявляет схему, он всё равно предлагает один уровень подсказок: его ключи верхнего уровня появляются, но вложенные ключи под ними — нет.
Массив записываются как [{ … }], а не { "items": … }, намеренно: ключ-маркер внутри объекта неотличим от настоящего поля с тем же именем, и заказ с полем items схлопнулся бы в лишний уровень.
Параметры, которые зависят друг от друга
Параметр может быть скрыт, отключён или сделан обязательным в зависимости от того, что содержат соседние поля. Условие — это предложение when — тот же язык выражений, который редактор использует для контекстных ключей:
{
"parameters": {
"database": {
"displayName": "Database",
"control": { "kind": "enum" },
"options": { "provider": "list-databases" }
},
"table": {
"displayName": "Table",
"control": { "kind": "enum" },
"dependsOn": ["database"],
"visibleWhen": "database",
"options": { "provider": "list-tables" },
"resetWhenMissing": true
}
}
}
| Поле | Что значит |
| --- | --- |
| dependsOn | Ключи, изменение которых перечитывает этот параметр — его варианты запрашиваются заново. |
| visibleWhen | Параметр отображается только пока условие истинно. |
| disabledWhen | Поле остаётся видимым, но его нельзя редактировать. |
| requiredWhen | Делает параметр условно обязательным в дополнение к статическому required. |
| options | { "provider": "<key>" } — список приходит от провайдера вариантов расширения, а не из схемы. |
| resetWhenMissing | Когда текущее значение больше не входит в число опций, оно сбрасывается на defaultValue. |
Нода, которая ничего из этого не объявила, рисуется так же, как раньше: дескриптора для выполнения нет, поэтому ничего не выполняется..
Полная схема
Схема: node