Міграція - Rete.js

Міграція

Rete.js v1

Поточна версія фреймворку містить численні критичні зміни порівняно з її попередницею.

Давайте почнемо з вивчення відмінностей між v1 і v2 як з точки зору розробника, так і з точки зору користувача:

Контекстv1v2Посилання
TypeScriptЧасткова підтримкаTypeScript-first
Швидкий стартCodepen прикладиDevKit, Codesandbox приклади
АрхітектураНа основі подійMiddleware-подібні сигнали
Інструментиrete-clirete-cli, rete-kit, rete-qa
Тестуваннямодульне тестуванняблок + E2E тестування
UI
Порядок вузлівфіксований порядоквисувати вперед обрані вузли
Вибірвбудований лише для вузліврозширений вибір + спеціальні елементи
Контролинемає вбудованих елементів керуваннявбудований класичний контроль входу
Упорядкування вузлівобмеженийна основі elkjs
Код
Створення вузлаКомпонентний підхідна ваш розсуд
Editor/Engine ідентифікаториобов'язковий, необхідний для імпорту/експортуна ваш розсуд
Ідентифікатор вузлаінкрементальний десятковийунікальний
Імпорт/експортВбудований, обмеженийна ваш розсуд
ВалідаціяПеревірка на основі сокетівна ваш розсуд
Dataflow обробкаобмежено (без рекурсії)DataflowEngine з динамічною вибіркою
Control flow обробкасимулюється плагіном Task з обмеженнямиControlFlowEngine
Модуліrete-module-pluginна ваш розсуд
Плагін з'єднаньвідповідає як за рендеринг, так і за взаємодіювідповідає лише за взаємодію

Підключення плагінів

Підключіть плагін, імпортувавши його за замовчуванням.

Другий параметр використовується для передачі опцій/параметрів плагіна:

ts
// v1
import HistoryPlugin from 'rete-history-plugin';

editor.use(HistoryPlugin, { keyboard: true });

Усі плагіни реалізовані як класи та можуть бути розширені, забезпечуючи гнучку кастомізацію без зміни ядра.

ts
// v2
import { HistoryPlugin, HistoryExtensions, Presets } from 'rete-history-plugin'

const history = new HistoryPlugin<Schemes>()

history.addPreset(Presets.classic.setup())

HistoryExtensions.keyboard(history)

area.use(history)

Створення вузлів

У версії v1 вузли генеруються за допомогою компонентів, зареєстрованих у редакторі, що дозволяє створювати численні екземпляри вузлів, що належать до одного типу компонентів.

ts
// v1
class NumComponent extends Rete.Component {
  constructor(){
    super("Number");
  }

  builder(node) {
    node.addControl(new NumControl('num'))
    node.addOutput(new Rete.Output('num', "Number", numSocket))

    return node
  }
}

const numComponent = new NumComponent()
editor.register(numComponent);

const node = await numComponent.createNode({ num: 2 });

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

ts
// v2
const node = new ClassicPreset.Node('Number')

node.addControl('num', new NumControl('num'))
node.addOutput('num', new ClassicPreset.Output(numSocket, "Number"));

await editor.addNode(node)

Збереження даних у вузлі

Дані можна зберегти за допомогою методу putData. Очікується, що дані мають бути дійсним JSON форматом, оскільки їх можна використовувати для імпорту/експорту.

ts
// v1
node.putData('myData', 'data')
control.putData('myData', 'data') // де контрол є частиною вузла

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

ts
// v2
class MyNode extends ClassicPreset.Node {
  myData = 'data'
}

Імпорт/експорт

Через обмеження, згадані раніше, редактор можна легко експортувати та імпортувати.

ts
// v1
const data = editor.toJSON();
await editor.fromJSON(data);

Поточна версія містить переглянутий підхід, який вимагає імплементації, як показано в Імпорт/експорт.

Вибір вузлів

Вибір елементів є функцією, вбудованою в редактор

ts
// v1
editor.selected.list

editor.selected.add(node, accumulate)

Недоліком цієї реалізації є її нездатність підтримувати щось, крім вибору вузла.

Вибір вузлів (та інших елементів) виглядає так:

ts
// v2
const selector = AreaExtensions.selector()
const accumulating = AreaExtensions.accumulateOnCtrl()

const nodeSelector = AreaExtensions.selectableNodes(area, selector, { accumulating });

editor.getNodes().filter(node => node.selected)
nodeSelector.select(add.id)

Прослуховування подій

Типовий спосіб прослуховування подій, які можна попередити

ts
// v1
editor.on('nodecreate', node => {
 return node.canCreate
});

* - незміння
** - переміщені до іншого пакету
*** - видалено

Події пакету rete

  • nodecreate *
  • nodecreated *
  • noderemove *
  • noderemoved *
  • connectioncreate *
  • connectioncreated *
  • connectionremove *
  • connectionremoved *
  • translatenode ***
  • nodetranslate **
  • nodetranslated **
  • nodedraged ***
  • nodedragged **
  • selectnode ***
  • multiselectnode ***
  • nodeselect ***
  • nodeselected ***
  • rendernode ** (renamed to 'render')
  • rendersocket ** (renamed to 'render')
  • rendercontrol ** (renamed to 'render')
  • renderconnection ** (renamed to 'render')
  • updateconnection ***
  • keydown ***
  • keyup ***
  • translate **
  • translated **
  • zoom **
  • zoomed **
  • click ** (renamed to 'nodepicked')
  • mousemove *** (renamed to 'pointermove')
  • contextmenu **
  • import ***
  • export ***
  • process ***
  • clear **

Події пакету rete-connection-plugin

  • connectionpath **
  • connectiondrop *
  • connectionpick *
  • resetconnection ***

У поточній версії використовується особливий тип реалізації сигналу, який включає сигнали як об’єкти. Крім того, пайпи використовуються або для маніпулювання цими об’єктами, або для запобігання поширенню сигналу.

ts
// v2
editor.addPipe(context => {
  if (context.type === 'nodecreate') return
  return context
})

Події пакету rete

  • nodecreate
  • nodecreated
  • noderemove
  • noderemoved
  • connectioncreate
  • connectioncreated
  • connectionremove
  • connectionremoved
  • clear
  • clearcancelled
  • cleared

Події пакету rete-area-plugin

  • nodepicked
  • nodedragged
  • nodetranslate
  • nodetranslated
  • contextmenu
  • pointerdown
  • pointermove
  • pointerup
  • noderesize
  • noderesized
  • render
  • unmount
  • reordered
  • translate
  • translated
  • zoom
  • zoomed
  • resized

Події пакету rete-connection-plugin

  • connectionpick
  • connectiondrop

Події пакету rete-angular-plugin

  • connectionpath

Події пакету rete-vue-plugin

  • connectionpath

Події пакету rete-react-plugin

  • connectionpath

Валідація з’єднань

Існує вбудована валідація з'єднань на основі сумісності сокетів

ts
// v1
const anyTypeSocket = new Rete.Socket('Any type');

numSocket.combineWith(anyTypeSocket);

Цей підхід простий, але має деякі обмеження.

Валідацію з'єднань можна реалізувати незалежно, що забезпечує більшу гнучкість

ts
// v2
editor.addPipe(context => {
  if (context.type === 'connectioncreate') {
    if (canCreateConnection(context.data)) return false
  }
  return context
})

Двигун (dataflow)

Компонент із визначеним worker методом має бути зареєстрований

ts
// v1
const engine = new Rete.Engine('[email protected]');

engine.register(myComponent);

Визначте метод worker компонента

ts
// v1
worker(node, inputs, outputs){
  outputs['num'] = node.data.num;
}

Ініціювати обробку

ts
// v1
await engine.process(data);

Створіть екземпляр DataflowEngine, щоб підключитися його до редактора. На відміну від першої версії, немає необхідності передавати data з вузлами та підключеннями.

ts
// v2
import { DataflowEngine } from 'rete-engine'

const engine = new DataflowEngine<Schemes>()

editor.use(engine)

Приклад методу вузла

ts
// v2
data(inputs) {
  const { left, right } = inputs

  return { sum: left[0] + right[0] }
}

Розпочати обробку

ts
// v2
engine.fetch(node.id)

Плагін завдань (control flow)

Цей підхід реалізовано за допомогою rete-task-plugin і на основі Rete.Engine. Тому він має вищезгадані обмеження

ts
// v1
import TaskPlugin from 'rete-task-plugin';

editor.use(TaskPlugin);

Конструктор компонента має визначення виходів, призначених для потоку керування або потоку даних

ts
// v1
this.task = {
    outputs: { exec: 'option', data: 'output' },
    init(task) {
        task.run('any data');
        task.reset();
    }
}

Визначте метод worker, який повертає дані та визначає закриті порти виводу для потоку керування

ts
// v1
worker(node, inputs, data) {
    this.closed = ['exec'];
    return { data }
}

Використовується пакет rete-engine, який має окрему реалізацію двигуна для керування потоком

ts
// v2
import { ControlFlowEngine } from 'rete-engine'

const engine = new ControlFlowEngine<Schemes>()

editor.use(engine)

За замовчуванням усі порти налаштовані на передачу контролю, але ви можете призначити тільки певні порти для цього

ts
// v2
const engine = new ControlFlowEngine<Schemes>(() => {
  return {
    inputs: () => ["exec"],
    outputs: () => ["exec"]
  };
});

Методом вузла служить наступне:

ts
// v2
execute(input: 'exec', forward: (output: 'exec') => void) {
  forward('exec')
}

На відміну від попередньої версії, цей підхід повністю відокремлений від потоку даних. Тим не менш, його можна використовувати в поєднанні з DataflowEngine.

ts
// v2
async execute(input: 'exec', forward: (output: 'exec') => void) {
  const inputs = await dataflow.fetchInputs(this.id)

  forward('exec')
}

Рендер плагіни

Для демонстрації ми вирішили використати rete-react-render-plugin

ts
// v1
import ReactRenderPlugin from 'rete-react-render-plugin';

editor.use(ReactRenderPlugin)
ts
// v2
import { ReactPlugin } from 'rete-react-plugin'

const reactPlugin = new ReactPlugin<Schemes, AreaExtra>()

area.use(reactPlugin)

Кастомні вузли та контроли

Наступний код використовується для визначення компонентів, необхідних для певних вузлів і контролів

ts
// v1
class AddComponent extends Rete.Component {
  constructor() {
    super("Add");
    this.data.component = MyNode;
  }
}

class MyControl extends Rete.Control {
  constructor(emitter, key, name) {
    super(key);
    this.render = 'react';
    this.component = MyReactControl;
    this.props = { emitter, name };
  }
}

Крім того, компонент можна вказати для всіх вузлів

ts
// v1
editor.use(ReactRenderPlugin, { component: MyNode });

У цій версії компоненти, які потрібно візуалізувати, визначені в класичному пресеті, який підключений

ts
// v2
reactPlugin.addPreset(ReactPresets.classic.setup({ customize: {
  node(data) {
    return MyNode
  },
  control() {
    return MyReactControl
  }
}}))

Цей підхід забезпечує більшу гнучкість, дозволяючи визначати додаткові умови в обробниках

Пересування вузлів

Отримайте вид вузла та виконайте його метод translate

ts
// v1
editor.view.nodes.get(node).translate(x, y)

Екземпляр плагіна містить метод translate, якому потрібен лише ідентифікатор вузла.

ts
// v2
await area.translate(node.id, { x, y })

Упорядкування вузлів

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

ts
// v1
import AutoArrangePlugin from 'rete-auto-arrange-plugin';

editor.use(AutoArrangePlugin, {});

editor.trigger('arrange');

Плагін використовує розширену функціональність пакета elkjs.

ts
// v2
import { AutoArrangePlugin, Presets as ArrangePresets } from "rete-auto-arrange-plugin";

const arrange = new AutoArrangePlugin<Schemes>();

arrange.addPreset(ArrangePresets.classic.setup());

area.use(arrange);

await arrange.layout()

Пристосувати в’юпорт

Для методу zoomAt потрібен екземпляр редактора, який відповідає за візуалізацію

ts
// v1
import AreaPlugin from "rete-area-plugin";

AreaPlugin.zoomAt(editor);

Для цілей візуалізації в цій версії потрібен екземпляр AreaPlugin.

ts
// v2
import { AreaExtensions } from "rete-area-plugin";

AreaExtensions.zoomAt(area, editor.getNodes());