RulesetEditor

Domain

Drag-and-drop visual rule chain editor. The blocks come from a catalog (typed blocks with params), each block has a form built from its schema, and the editor has undo, multi-select, groups, flows that run each other and validation.

With pre-loaded chain

Preview
TrueFalse
MQTT Ingress

Trigger

out
Temp > 85 °C

Filter

in
TrueFalse
Send Alert

Action

in
Log Reading

Action

in
100%
Code
<RulesetEditor
  initialNodes={nodes}
  initialEdges={edges}
/>

Block catalog, param forms, a group and a Run flow block

Preview
Sensing
TrueFalseErrorError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node go
Flow input
out
Boiler temperature

site/boiler/temp

out
Too hot?

msg.payload.temp > 90

in
yesno
Hold 2 min

2 min

in
out
Tell the operator

—

in
out
1
Log it

info: Boiler is fine

in
out
Log the error

error: Compare failed

in
out
Escalate

escalation

in
100%
Code
<RulesetEditor
  catalog={catalog}            // blocks with typed params, from data
  choices={{ brokers: [...] }} // lists for source fields
  initialNodes={nodes}
  initialEdges={edges}
  initialGroups={groups}
  active={chain.active}        // a missing required param is then an error
  onChange={(graph) => save(graph)}
/>

Arrange, mini map, box select and snap

Preview
TrueFalseErrorError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node go
Flow input
out
Boiler temperature

site/boiler/temp

out
Too hot?

msg.payload.temp > 90

in
yesno
Hold 2 min

2 min

in
out
Tell the operator

—

in
out
1
Log it

info: Boiler is fine

in
out
Log the error

error: Compare failed

in
out
Escalate

escalation

in
100%
Code
// The blocks are scattered: press "Arrange the blocks" (bottom right) for a layered layout, one undo step.
// Shift / Ctrl / ⌘ and drag on the empty canvas draws a selection frame; the frame button does it with one finger.
// Drag a block: it snaps to a 12 px grid and to the edges of other blocks (guide lines); hold Alt to move freely.
// The mini map shows above 30 blocks or when blocks leave the view; the binoculars button toggles it.
const ref = useRef<RulesetEditorRef>(null);
<RulesetEditor ref={ref} catalog={catalog} initialNodes={nodes} initialEdges={edges}
  onSelectionChange={(ids) => setSelected(ids)}
  nodeMenuItems={({ nodeIds }) => [{ kind: 'item', label: 'Open live data', onSelect: () => open(nodeIds) }]}
  toolbarExtra={<MyButton />} bannerSlot={<DraftBanner />} />
ref.current?.arrange(); ref.current?.select(['n1']); ref.current?.reveal('n4');

Read only

Preview
Sensing
TrueFalseErrorError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node goError path: where errors of this node go
Flow input
out
Boiler temperature

site/boiler/temp

out
Too hot?

msg.payload.temp > 90

in
yesno
Hold 2 min

2 min

in
out
Tell the operator

—

in
out
1
Log it

info: Boiler is fine

in
out
Log the error

error: Compare failed

in
out
Escalate

escalation

in
100%
Code
<RulesetEditor readOnly catalog={catalog} initialNodes={nodes} initialEdges={edges} />

Empty canvas (editable)

Preview

Drag nodes from the palette to start

Connect ● output ports to ○ input ports

100%
Code
<RulesetEditor />
Sourcemodules/app/RulesetEditor/RulesetEditor/index.tsx