З’єднання - Rete.js

З’єднання

Цей гайд базується на гайді Базовий редактор. Рекомендується переглянути його для повного розуміння цього гайду.

ВалідаціяЧат-бот

Цей гайд містить більш детальне вивчення функціональних можливостей плагіна rete-connection-plugin, що забезпечує взаємодію користувача зі з’єднаннями.

Коли користувач натискає на сокет, з’єднання, що слідує за курсором, називається псевдоз’єднанням, яке є об’єктом із додатковою властивістю isPseudo: true.

Пресети

Можливо, ви вже бачили таке використання попередньо встановлених налаштувань, яке дозволяє користувачам додавати з’єднання, клацаючи/натискаючи вхідний/вихідний роз’єм і клацаючи/відпускаючи вихідний/вхідний роз’єм:

ts
connection.addPreset(ConnectionPresets.classic.setup())

який еквівалентний наведеному нижче коду:

ts
import { ClassicFlow } from 'rete-connection-plugin'

connection.addPreset(() => new ClassicFlow())

Якщо вхідне гніздо вже підключено, клацання або натискання на ньому призведе до видалення підключення та заміни його на псевдоз’єднання.

Кастомний пресет

Якщо ви віддаєте перевагу альтернативному методу додавання підключень, ви можете скористатися BidirectFlow. У цьому режимі додавання вузла здійснюється клацанням миші на вхідному/вихідному сокеті та перетягуванням псевдоз’єднання на сокет протилежного типу.

ts
import { ClassicFlow } from 'rete-connection-plugin'

connection.addPreset(() => new BidirectFlow())

Крім того, використовуючи дані початкового сокета, ви можете вибрати певний флоу або взагалі вимкнути взаємодію з певним сокетом

ts
connection.addPreset(({ nodeId, side, key }) => {
  if (isReadonly(nodeId, side, key)) return undefined
  if (usesBidirect(nodeId, side, key)) return new BidirectFlow()
  return new ClassicFlow()
})

Якщо існуючі флоу не відповідають вашим потребам, ви можете реалізувати власне рішення, посилаючись на вихідний код існуючих.

Розширена поведінка

Покращення поведінки існуючих пресетів може включати відстеження подій connectionpick і connectiondrop

ts
connection.addPipe(context => {
  if (context.type === 'connectionpick') { // коли користувач натискає на сокет
    const { socket } = context.data
  }
  if (context.type === 'connectiondrop') { // коли користувач натискає на сокет або будь-яку область
    const { socket, initial, created } = context.data
  }
  return context
})

Подію connectionpick можна запобігти

ts
connection.addPipe(context => {
  if (context.type === 'connectionpick') {
    if (readonly) return
  }
  return context
})

З’єднання, створене користувачем

За замовчуванням, коли користувач створює з’єднання за допомогою UI, плагін додає з’єднання як об’єкт, а не екземпляр класу, наприклад ClassicPreset.Connection. Якщо ви хочете налаштувати процес додавання цих з’єднань, укажіть опцію makeConnection у ClassicFlow або BidirectFlow.

ts
import { getSourceTarget } from 'rete-connection-plugin'

connection.addPreset(() => new ClassicFlow({
  makeConnection(from, to, context) {
    const [source, target] = getSourceTarget(from, to) || [null, null];
    const { editor } = context;

    if (source && target) {
      editor.addConnection(
        new MyConnection(
          editor.getNode(source.nodeId),
          source.key,
          editor.getNode(target.nodeId),
          target.key
        )
      );
      return true; // переконайтеся, що підключення було успішно додано
    }
  }
}))

Крім того, використання getSourceTarget є важливим у цьому випадку, оскільки параметри from і to несуть дані про початковий і кінцевий сокети, які не обов’язково можуть збігатися з вихідним і вхідним сокетами.

Налаштування початкової/кінцевої позиції з’єднання

Кожне з’єднання пов’язано з початковою та кінцевою точками, безпосередньо з’єднаними з сокетом (за винятком псевдоз’єднань, де кінцева точка залежить від позиції курсору). За замовчуванням початковою точкою є права сторона вихідного сокету, а початковою – ліва сторона вхідного сокету. Зміна розміру сокета спричинить зміщення початкової/кінцевої точки з’єднання.

Щоб налаштувати початкову та кінцеву позицію з’єднання, ви можете вказати getDOMSocketPosition з цими координатами зсуву відносно центру сокета.Цей метод використовується за замовчуванням, коли параметр socketPositionWatcher не вказано.

ts
import { getDOMSocketPosition } from 'rete-render-utils'

render.addPreset(Presets.classic.setup({
  socketPositionWatcher: getDOMSocketPosition({
    offset({ x, y }, nodeId, side, key) {

      return {
        x: x + 10 * (side === 'input' ? -1 : 1),
        y: y
      }
    },
  })
}))

У цьому сценарії обчислення позиції сокета покладається на елементи DOM. Бувають випадки, коли такий підхід може виявитися неефективним через продуктивність або інші нюанси реалізації (наприклад, у прикладі LOD, де потрібне відображення з’єднань, навіть якщо фактичні сокети відсутні в DOM).

Щоб використовувати ваш підхід до розрахунку для початкової/кінцевої позиції з’єднання, ви можете розширити абстрактний клас BaseSocketPosition та імплементувати метод calculatePosition.

ts
import { BaseSocketPosition } from 'rete-render-utils'

type Position = { x: number, y: number }
type Side = 'input' | 'output'

export class ComputedSocketPosition<S extends Schemes, K> extends BaseSocketPosition<S, K> {
  async calculatePosition(nodeId: string, side: Side, key: string): Promise<Position | null> {
    if (!this.area) return null

    return {
      x: side === 'input' ? 0 : getNodeWith(nodeId)
      y: 0
    }
  }
}

render.addPreset(Presets.classic.setup({
  socketPositionWatcher: new ComputedSocketPosition()
}))

де calculatePosition має повернути позицію відносно позиції вузла, або null, якщо її неможливо обчислити