retejs / rete

JavaScript framework for visual programming
https://retejs.org
MIT License
10.17k stars 653 forks source link

¿How to implement redux with retejs? #374

Closed ricaralan closed 5 years ago

ricaralan commented 5 years ago

First of all, AMAZING project!

rete v1.3.0 rete-react-render-plugin v0.2.0 react v16.9.0 redux v4.0.4 react-redux v7.1.1

I tried to connect retejs with redux but I couldn't connect it. Do you have any connection example?

Captura react components

My code is as follows:

Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store';
import App from './App';
import './index.css';

const storeApp = configureStore();

const rootElement = document.getElementById('root');

ReactDOM.render(
    <Provider store={storeApp}>
        <App />
    </Provider>
, rootElement);

App.js

import React from 'react';
import initReteEditor from "./editor";
import { connect } from "react-redux";
import { setNodeDetailAction } from './actions/rete-components';
import SelectedNodeDetail from './components/bot-flow-builder/detail-editor'

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            reteData: null,
            intervalsReteEvents: [],
            listeningReteEvents: false,
            lastHeardData: null,
        };

        this._startReteEditor = this._startReteEditor.bind(this);
    }

    componentDidUpdate() {
        if(this.state.reteData && this.state.reteData.editor) {
            this.listenReteEvents();
        }
    }

    async _startReteEditor(container) {
        this.setState({
            reteData: await initReteEditor({ container })
        });
    }

    setSelectedNode() {
        this.clearIntervalsReteEvents();
        if(this.props.node !== this.state.lastHeardData) {
            this.props.openNodeDetailAction({ node: this.state.lastHeardData });
            if(this.state.lastHeardData) {
                // TODO: Make functionality
            }
        }
    }

    clearIntervalsReteEvents() {
        this.state.intervalsReteEvents.map((id) => clearInterval(id));
        this.setState({
            intervalsReteEvents: []
        });
    }

    listenReteEvents() {
        if(!this.state.listeningReteEvents) {
            this.setState({
                listeningReteEvents: true
            });

            this.state.reteData.editor.on('translatenode nodeselect mousemove click', (data) => {
                if((data.id && data.name) || data.container) {
                    if(this.state.intervalsReteEvents.length) return;

                    this.setState({
                        lastHeardData: data.container ? null : data,
                        intervalsReteEvents: this.state.intervalsReteEvents.concat(
                            setInterval(this.setSelectedNode.bind(this), 200)
                        )
                    });
                } else {
                    if(this.state.intervalsReteEvents && this.state.intervalsReteEvents.length) {
                        this.clearIntervalsReteEvents();
                    }
                }
            });
        }
    }

    render() {
        return (
            <div className="App">
                <SelectedNodeDetail node={this.props.node} />
                <div style={{ textAlign: "left", width: "100vw", height: "100vh", backgroundColor: '#f1f1f1' }}>
                    <div ref={ this._startReteEditor  } />
                </div>
            </div>
        );
    }
}

const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
  setNodeDetailAction: (payload) => dispatch(openNodeDetailAction(payload)),
});

export default connect(mapStateToProps, mapDispatchToProps)(App);

editor.js

import Rete from "rete";
import ReactRenderPlugin from "rete-react-render-plugin";
import ConnectionPlugin from "rete-connection-plugin";
import ContextMenuPlugin from "rete-context-menu-plugin";
import AreaPlugin from "rete-area-plugin";
import ReteMessageComponent from './components/bot-flow-builder/steps/message';
import ReteActionComponent  from './components/bot-flow-builder/steps/action';
import ReteTriggerComponent from './components/bot-flow-builder/steps/trigger';

export default async function({ container }) {
  var components = [
    new ReteMessageComponent(),
    new ReteActionComponent (),
    new ReteTriggerComponent(),
  ];

  var editor = new Rete.NodeEditor("projectname@0.1.0", container);
  const { area } = editor.view;
  editor.use(ConnectionPlugin);
  editor.use(ReactRenderPlugin);
  editor.use(ContextMenuPlugin);

  var engine = new Rete.Engine("projectname@0.1.0");

  components.forEach(c => {
    editor.register(c);
    engine.register(c);
  });

  editor.on(
    "process nodecreated noderemoved connectioncreated connectionremoved",
    async () => {
      console.log("process");
      await engine.abort();
      await engine.process(editor.toJSON());
    }
  );

  editor.fromJSON({ ............. });

  editor.view.resize();
  AreaPlugin.zoomAt(editor);
  editor.trigger("process");

  return { editor, AreaPlugin };
}
Ni55aN commented 5 years ago

but I couldn't connect it

What doesn't work? Are there any errors in the console?

ricaralan commented 5 years ago

Thanks for answering @Ni55aN. I get the following error when I try to connect a custom node.

Could not find "store" in the context of "Connect(CustomMessageNode)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(CustomMessageNode) in connect options

With the following code

import React from "react";
import { Node, Socket } from "rete-react-render-plugin";
import classnames from 'classnames';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAlignLeft, faTrash, faClone } from '@fortawesome/free-solid-svg-icons';
import { connect } from "react-redux";
import './style.css';

library.add(faAlignLeft);
library.add(faTrash);
library.add(faClone);

class CustomMessageNode extends Node {
  render() {
    const { node, bindSocket }          = this.props;
    const { outputs, inputs, selected } = this.state;

    return (
      <div className={`node-container message ${selected}`}>
        <div className="top-controls">
            <div className="top-controls-item">
                <FontAwesomeIcon icon="clone" />
            </div>
            <div className="top-controls-item">
                <FontAwesomeIcon icon="trash" />
            </div>
        </div>
        <div className="title">
            {inputs.filter(input => input.key === 'COMPONENT_SOCKET_IN').map(input => (
                <div className={classnames('socket-component-container top-unique-in',{
                    used: input.connections.length,
                })} key={input.key}>
                    <Socket
                      type="input"
                      socket={input.socket}
                      io={input}
                      innerRef={bindSocket}
                    />
                </div>
            ))}
            {node.name}
        </div>
        <div className="body">
            <div className="message-item empty-message">
                <FontAwesomeIcon icon="align-left" />
                <br/>
                Da clic para agregar mensaje
            </div>
        </div>
        <div className="footer">
             {outputs.filter(output => output.key === 'COMPONENT_SOCKET_OUT').map(output => (
                <div className={classnames('socket-component-container bottom-unique-out',{
                    used: output.connections.length,
                })} key={output.key}>
                    <Socket
                      type="input"
                      socket={output.socket}
                      io={output}
                      innerRef={bindSocket}
                    />
                </div>
            ))}
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
});

export default connect(mapStateToProps, mapDispatchToProps)(CustomMessageNode);

Captura de pantalla 2019-09-30 a la(s) 03 07 02

ricaralan commented 5 years ago

I think the error originates here.

https://github.com/retejs/rete/blob/master/src/editor.ts#L23

The containers are of type HTMLElement and not ReactElement so it does not insert the nodes within the context of the provider.

https://github.com/retejs/rete/blob/master/src/view/index.ts#L34

Ni55aN commented 5 years ago

The containers are of type HTMLElement and not ReactElement so it does not insert the nodes within the context of the provider.

It is expected since the core knows nothing about React. Try to pass the store directly to your custom component

ricaralan commented 5 years ago

I already did it and I get the error that I specified here

https://github.com/retejs/rete/issues/374#issuecomment-536452628

Thanks for answering @Ni55aN. I get the following error when I try to connect a custom node.

Could not find "store" in the context of "Connect(CustomMessageNode)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(CustomMessageNode) in connect options

With the following code

import React from "react";
import { Node, Socket } from "rete-react-render-plugin";
import { connect } from "react-redux";

class CustomMessageNode extends Node { ....... }

const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
});

export default connect(mapStateToProps, mapDispatchToProps)(CustomMessageNode);
Ni55aN commented 5 years ago

I already did it

I only see that you are trying to "connect" the store, but store is missing in context.

Try something like this

editor.use(ReactRenderPlugin, {
  component: props =>(<Provider store={storeApp}>
        <CustomMessageNode {...props}/>
    </Provider>);
});
ricaralan commented 5 years ago

@Ni55aN you're right, that's how it works.

thank you.