Міграція - 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());