jerosoler / Drawflow

Simple flow library 🖥️🖱️
https://jerosoler.github.io/Drawflow/
MIT License
4.8k stars 745 forks source link

CTRL+Z or +Y #31

Open hamedf62 opened 4 years ago

hamedf62 commented 4 years ago

dear @jerosoler

it would be good idea to add CTRL+Z and CTRL+Y in next versions to accelerate editing.

specially if you want to correct wrongly added node or connection have to delete it manually.

** for delete a node in Mac have to push down both fn+Del to do. i thought its possible to use only Del key.

thank you

jerosoler commented 4 years ago

Hi.

The undo / redo function.

I don't know if he would have to come with the library.

Or develop by yourself. I study it.

hamedf62 commented 4 years ago

I thought it would be possible if create and save events logs, and parse it back when ctrl+z pushed.

Good luck 🙏

ishpreetkaurwebner commented 3 years ago

Yes, it is needed undo redo feature.

ishpreetkaurwebner commented 3 years ago

Hello @jerosoler , can I know whether you have plans to start work on it? Whether you will add undo-redo features in this amazing library?

jerosoler commented 3 years ago

Hello,

I have not started working. But here is a guide on how to do it. Since it is a function you can have many options.

Here I have done a little test removing and creating connections.

It seems to work well.

Since it can be implemented thanks to events.

HTML

<button onclick="undo()">Undo</button>
  <button onclick="redo()">Redo</button>

Javascript

    const history = [];
    let historyPosition = -1;
    let fixHistory = false;

    function undo() {
      if(historyPosition < 0) {
        console.log("Finish");
        return;
      }
      const redoElement = history[historyPosition];
      const redoElementName = Object.keys( redoElement );

      switch(redoElementName[0]){
        case 'connectionCreated':
          var d = history[historyPosition]["connectionCreated"][0];    
          fixHistory = true;
          editor.removeSingleConnection(d.output_id, d.input_id, d.output_class, d.input_class);
        break;
        case 'connectionRemoved':
          var d = history[historyPosition]["connectionRemoved"][0];    
          fixHistory = true;
          editor.addConnection(d.output_id, d.input_id, d.output_class, d.input_class);
        break;
      }
      historyPosition--;
    }

    function redo() {
      if(historyPosition === history.length-1) {
        console.log("Last");
        return;
      }
      historyPosition++;
      const redoElement = history[historyPosition];
      const redoElementName = Object.keys( redoElement );

      switch(redoElementName[0]){
        case 'connectionCreated':
          var d = history[historyPosition]["connectionCreated"][0];    
          fixHistory = true;
          editor.addConnection(d.output_id, d.input_id, d.output_class, d.input_class);
        break;

        case 'connectionRemoved':
          var d = history[historyPosition]["connectionRemoved"][0];    
          fixHistory = true;
          editor.removeSingleConnection(d.output_id, d.input_id, d.output_class, d.input_class);
        break;
      }
    }

    editor.on('connectionCreated', function({ output_id, input_id, output_class, input_class }) {
      if(!fixHistory) {
        history.push({ connectionCreated: [{ output_id, input_id, output_class, input_class }] });
        historyPosition++;
      } else {
        fixHistory = false;
      }
    })

    editor.on('connectionRemoved', function({ output_id, input_id, output_class, input_class }) {
      if(!fixHistory) {
        history.push({ connectionRemoved: [{ output_id, input_id, output_class, input_class }] });
        historyPosition++;
      } else {
        fixHistory = false;
      }
    })

Jero

ishpreetkaurwebner commented 3 years ago

Thanks a lot @jerosoler , I will give a try to the example code you have provided above.

iamnikhilrohan commented 3 years ago

hey @jerosoler , can we do similar functionality with nodes? i.e redo and undo with nodes. if yes, can we tell me how?

gunasekharravilla commented 3 years ago

How to do the same for nodes and node values

gustavoaferreira97 commented 3 years ago

Hello @jerosoler,

I'm working on undo and redo actions but I used an approach a little bit different than the example. I'm trying to achieve this by Export() and Import() methods. When I add a Node by a button o screen it works well because I can Export() flow before the node creation.

But I'm trying to figure out how can I Export() flow before the events occurs.

I tried: image

But the events trigger after all action done. Is there anyway I can execute some code before the flow execute his actions?

Thank you and congrats about this library

jerosoler commented 3 years ago

I understand that if you are saving the values. It already has the old value.

All changes can be detected using a javascript proxy. Example:

const editor = new Drawflow(id);
    const handler = {
        get: function(target, property, receiver) {
            console.log(JSON.stringify(target));
            return Reflect.get(...arguments)
        },
    }
    editor.drawflow = new Proxy(editor.drawflow, handler);
    editor.start();

I've been playing around with this idea a bit, an example could be:

    var id = document.getElementById("drawflow");
    const editor = new Drawflow(id);

    const history = [];
    let historyStatus = null;
    let historyActive = false;
    let historyStop = false;

    function undo() {
        if(historyStatus == 1) {
            return;
        }
        if(historyStatus === null) {
            historyStatus = history.length;
        }
        if(historyStatus === history.length) {
            historyActive = true;
            history.push(editor.export())
            history.splice(-1); // Fix editor export two events;
        }
        historyStatus = historyStatus-1;
        historyActive = true;
        editor.import(JSON.parse(history[historyStatus]));
        editor.drawflow = new Proxy(editor.drawflow, handler);

    }

    function redo() {
        if(historyStatus !== null && historyStatus < history.length-1) {
            historyStatus = historyStatus+1;
            historyActive = true;
            editor.import(JSON.parse(history[historyStatus]));
            editor.drawflow = new Proxy(editor.drawflow, handler);

        }
    }

    editor.on("click", () => {
        historyStop = true;
    })

    editor.on("mouseUp", () => {
        historyStop = false;
        historyActive = true;
        history.push(editor.export())
        history.splice(-1);
        if(history.at(-2) === history.at(-1)) {
            console.log("delete");
            history.splice(-1);
        } else {
            /// ??? DELETE History at restore point????
            historyStatus =  history.length-1
        }

    })

    const handler = {
        get: function(target, property, receiver) {

                console.log(JSON.stringify(target));
                if(!historyActive && !historyStop) {
                    history.push(JSON.stringify(target));
                }
                historyActive = false;
            return Reflect.get(...arguments)
        },
    }
    editor.drawflow = new Proxy(editor.drawflow, handler);

    editor.start();
    history.splice(-2); // Fix history 

    editor.addNode('aaa', 0, 1, 100, 300, 'aaa', {}, 'aaa' );
    editor.addNode('bbb', 1, 0, 200, 400, 'bbb', {}, 'bbb' );
    editor.addNode('ccc', 1, 0, 500, 500, 'ccc', {}, 'ccc' );
gustavoaferreira97 commented 3 years ago

Thank you a lot @jerosoler!!

aswinak799 commented 3 months ago

could you add this functionality along with the library