jerosoler / Drawflow

Simple flow library πŸ–₯οΈπŸ–±οΈ
https://jerosoler.github.io/Drawflow/
MIT License
4.65k stars 722 forks source link

undo/redo function. #646

Closed cewebdesigner closed 1 year ago

cewebdesigner commented 1 year ago

I'm implementing this solution, and I noticed that when I use editor.import json, this undo and redo solution doesn't work, it only works if I remove code from editor.import

var id = document.getElementById("drawflow");
    const editor = new Drawflow(id);
    editor.reroute = true;
    editor.reroute_fix_curvature = true;
    editor.force_first_input = true;
    /*editor.zoom_value = 0.05;
    editor.zoom = 0.8;*/

    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);

    const dataToImport = {"drawflow":{"Home":{"data":{"0":{"id":0,"name":"inicial","data":{"tipo":"inicial","segundodigitacao":"0"},"class":"inicial","html":"\n        <div class=\"title\"><i class=\"fas fa-play-circle text-success\"></i> Passo Inicial</div>\n        <input type=\"hidden\" name=\"tipomensagem\" df-temp-tipo id=\"tipomensagem\" class=\"tipomensagem\" value=\"inicial\">\n        <input type=\"hidden\" name=\"segundodigitacao\" id=\"segundodigitacao\" class=\"segundodigitacao\" value=\"0\" />","typenode":false,"inputs":{},"outputs":{"output_1":{"connections":[]}},"pos_x":100,"pos_y":79}}}}};

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

    editor.import = function(data) {
      this.clear();
      this.drawflow = JSON.parse(JSON.stringify(data));
      this.load();
      this.dispatch('import', 'import');
    }

    editor.on("import", () => {
      Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(id => {
        const links =  document.querySelectorAll(`#node-${id} .drawflow_content_node .link`);
        console.log(links);
        links.forEach((item) => {
            const target = document.querySelector(`#node-${id} .outputs .${item.classList[1]}`);
            if(target != null) {
                const pos = item.getBoundingClientRect();
                const targetPos = target.getBoundingClientRect();
                target.style.top = `${pos.y - targetPos.y}px`;
                target.style.left = `${pos.x - targetPos.x}px`;
            }
        })        

      })
    })

    editor.import(dataToImport);

    // Events!
    editor.on('nodeCreated', (id) => {
        const links =  document.querySelectorAll(`#node-${id} .drawflow_content_node .link`);
        const zoom = editor.zoom;
        links.forEach((item) => {
            const target = document.querySelector(`#node-${id} .outputs .${item.classList[1]}`);
            if(target != null) {
                const pos = item.getBoundingClientRect();
                const targetPos = target.getBoundingClientRect();
                target.style.top = `${(pos.y - targetPos.y) / zoom}px`;
                target.style.left = `${(pos.x - targetPos.x) / zoom}px`;
            }
        })
    })

31

jerosoler commented 1 year ago

Complete example of #31

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow/dist/drawflow.min.css"
    />
    <script src="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow/dist/drawflow.min.js"></script>
    <style>
      #drawflow {
        position: relative;
        width: 100%;
        height: 800px;
        border: 1px solid red;
      }
      </style>
  </head>
  <body>
    <div>
        <div id="drawflow"></div>  
        <button onclick="undo()">Undo</button>
        <button onclick="redo()">Redo</button>
    </div>

    <script>

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' );
</script> 
  </body>
</html>

Likewise as discussed in #31. It's an idea.

cewebdesigner commented 1 year ago

so i have this example however it does not work when i import json

when I remove the json import, then yes it works

jerosoler commented 1 year ago

Following the previous example, try this.

    ...
    editor.addNode('bbb', 1, 0, 200, 400, 'bbb', {}, 'bbb' );
    editor.addNode('ccc', 1, 0, 500, 500, 'ccc', {}, 'ccc' );

    historyActive =  true;
    history.push(JSON.stringify(editor.export()));
    history.splice(-1);
    const dataToImport = {"drawflow":{"Home":{"data":{"1":{"id":1,"name":"welcome","data":{},"class":"welcome","html":"\n    <div>\n      <div class=\"title-box\">πŸ‘ Welcome!!</div>\n      <div class=\"box\">\n        <p>Simple flow library <b>demo</b>\n        <a href=\"https://github.com/jerosoler/Drawflow\" target=\"_blank\">Drawflow</a> by <b>Jero Soler</b></p><br>\n\n        <p>Multiple input / outputs<br>\n           Data sync nodes<br>\n           Import / export<br>\n           Modules support<br>\n           Simple use<br>\n           Type: Fixed or Edit<br>\n           Events: view console<br>\n           Pure Javascript<br>\n        </p>\n        <br>\n        <p><b><u>Shortkeys:</u></b></p>\n        <p>🎹 <b>Delete</b> for remove selected<br>\n        πŸ’  Mouse Left Click == Move<br>\n        ❌ Mouse Right == Delete Option<br>\n        πŸ” Ctrl + Wheel == Zoom<br>\n        πŸ“± Mobile support<br>\n        ...</p>\n      </div>\n    </div>\n    ","typenode": false, "inputs":{},"outputs":{},"pos_x":50,"pos_y":50},"2":{"id":2,"name":"slack","data":{},"class":"slack","html":"\n          <div>\n            <div class=\"title-box\"><i class=\"fab fa-slack\"></i> Slack chat message</div>\n          </div>\n          ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"7","input":"output_1"}]}},"outputs":{},"pos_x":1028,"pos_y":87},"3":{"id":3,"name":"telegram","data":{"channel":"channel_2"},"class":"telegram","html":"\n          <div>\n            <div class=\"title-box\"><i class=\"fab fa-telegram-plane\"></i> Telegram bot</div>\n            <div class=\"box\">\n              <p>Send to telegram</p>\n              <p>select channel</p>\n              <select df-channel>\n                <option value=\"channel_1\">Channel 1</option>\n                <option value=\"channel_2\">Channel 2</option>\n                <option value=\"channel_3\">Channel 3</option>\n                <option value=\"channel_4\">Channel 4</option>\n              </select>\n            </div>\n          </div>\n          ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"7","input":"output_1"}]}},"outputs":{},"pos_x":1032,"pos_y":184},"4":{"id":4,"name":"email","data":{},"class":"email","html":"\n            <div>\n              <div class=\"title-box\"><i class=\"fas fa-at\"></i> Send Email </div>\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"5","input":"output_1"}]}},"outputs":{},"pos_x":1033,"pos_y":439},"5":{"id":5,"name":"template","data":{"template":"Write your template"},"class":"template","html":"\n            <div>\n              <div class=\"title-box\"><i class=\"fas fa-code\"></i> Template</div>\n              <div class=\"box\">\n                Ger Vars\n                <textarea df-template></textarea>\n                Output template with vars\n              </div>\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"6","input":"output_1"}]}},"outputs":{"output_1":{"connections":[{"node":"4","output":"input_1"},{"node":"11","output":"input_1"}]}},"pos_x":607,"pos_y":304},"6":{"id":6,"name":"github","data":{"name":"https://github.com/jerosoler/Drawflow"},"class":"github","html":"\n          <div>\n            <div class=\"title-box\"><i class=\"fab fa-github \"></i> Github Stars</div>\n            <div class=\"box\">\n              <p>Enter repository url</p>\n            <input type=\"text\" df-name>\n            </div>\n          </div>\n          ","typenode": false, "inputs":{},"outputs":{"output_1":{"connections":[{"node":"5","output":"input_1"}]}},"pos_x":341,"pos_y":191},"7":{"id":7,"name":"facebook","data":{},"class":"facebook","html":"\n        <div>\n          <div class=\"title-box\"><i class=\"fab fa-facebook\"></i> Facebook Message</div>\n        </div>\n        ","typenode": false, "inputs":{},"outputs":{"output_1":{"connections":[{"node":"2","output":"input_1"},{"node":"3","output":"input_1"},{"node":"11","output":"input_1"}]}},"pos_x":347,"pos_y":87},"11":{"id":11,"name":"log","data":{},"class":"log","html":"\n            <div>\n              <div class=\"title-box\"><i class=\"fas fa-file-signature\"></i> Save log file </div>\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"5","input":"output_1"},{"node":"7","input":"output_1"}]}},"outputs":{},"pos_x":1031,"pos_y":363}}},"Other":{"data":{"8":{"id":8,"name":"personalized","data":{},"class":"personalized","html":"\n            <div>\n              Personalized\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"12","input":"output_1"},{"node":"12","input":"output_2"},{"node":"12","input":"output_3"},{"node":"12","input":"output_4"}]}},"outputs":{"output_1":{"connections":[{"node":"9","output":"input_1"}]}},"pos_x":764,"pos_y":227},"9":{"id":9,"name":"dbclick","data":{"name":"Hello World!!"},"class":"dbclick","html":"\n            <div>\n            <div class=\"title-box\"><i class=\"fas fa-mouse\"></i> Db Click</div>\n              <div class=\"box dbclickbox\" ondblclick=\"showpopup(event)\">\n                Db Click here\n                <div class=\"modal\" style=\"display:none\">\n                  <div class=\"modal-content\">\n                    <span class=\"close\" onclick=\"closemodal(event)\">&times;</span>\n                    Change your variable {name} !\n                    <input type=\"text\" df-name>\n                  </div>\n\n                </div>\n              </div>\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[{"node":"8","input":"output_1"}]}},"outputs":{"output_1":{"connections":[{"node":"12","output":"input_2"}]}},"pos_x":209,"pos_y":38},"12":{"id":12,"name":"multiple","data":{},"class":"multiple","html":"\n            <div>\n              <div class=\"box\">\n                Multiple!\n              </div>\n            </div>\n            ","typenode": false, "inputs":{"input_1":{"connections":[]},"input_2":{"connections":[{"node":"9","input":"output_1"}]},"input_3":{"connections":[]}},"outputs":{"output_1":{"connections":[{"node":"8","output":"input_1"}]},"output_2":{"connections":[{"node":"8","output":"input_1"}]},"output_3":{"connections":[{"node":"8","output":"input_1"}]},"output_4":{"connections":[{"node":"8","output":"input_1"}]}},"pos_x":179,"pos_y":272}}}}}
    editor.import(dataToImport);
    editor.drawflow = new Proxy(editor.drawflow, handler);
    historyActive = false;