Сайт

nodes

contributes.nodes указывает на папку с определениями нод. Каждая нода хранится в своей папке с файлом schema.json.

  • my-extension/
    • package.json
    • nodes/
      • timer/
        • schema.json
        • timer_node.py
{ "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

Ничего объявлять не нужно — имя файла является соглашением. Как только файл создаётся, карточка ноды получает кнопку Документация, которая открывает соответствующую страницу во вкладке рядом с редактором.

Если нужно использовать другое имя файла или поделиться ожной страницой между семейством нод, объявите путь явно:

{ "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