jerosoler / Drawflow

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

Moving multiple nodes at the same time #322

Open Granduca opened 2 years ago

Granduca commented 2 years ago

Is it possible to move several nodes (with a specific tag or id) at the same time?

jerosoler commented 2 years ago

Only by javascript https://github.com/jerosoler/Drawflow/issues/112

The UX https://github.com/jerosoler/Drawflow/issues/50 not implemented.

Granduca commented 2 years ago

I solved the multiselect/move problem with this little simple but very powerful library Selectables. Watch the DEMO. alt+click and drag for select/deselect double click in an empty space for clear selection

jerosoler commented 2 years ago

WoW! 👍

dearsina commented 2 years ago

@Granduca thank you for pointing me in the direction of the Selectables library. I haven't started playing with it yet, but I did notice in your demo that when I select a few items, including the "Save log file" element, and start moving the elements around, that particular element doesn't move. Is that just me, or is there something in particular I should be keeping in mind when I start working with the Selectables library that perhaps you omitted for your demo?

Granduca commented 2 years ago

@Granduca thank you for pointing me in the direction of the Selectables library. I haven't started playing with it yet, but I did notice in your demo that when I select a few items, including the "Save log file" element, and start moving the elements around, that particular element doesn't move. Is that just me, or is there something in particular I should be keeping in mind when I start working with the Selectables library that perhaps you omitted for your demo?

There is no problem with the Selectables library here. I made my demo very quickly and maybe a little carelessly. Because of this, similar problems can be observed. However, in a personal project, everything works fine for me! I'm sure you can easily figure out how to implement this functionality for your project.

dearsina commented 2 years ago

Thank you for your help, I have now fully implemented a multi-select, multi-drag feature, that also allows you to hold the ctrl key to add or remove individual nodes. It works very well. There is one problem that I have yet to be able to resolve though, at zoom 1.0, everything works well. As soon as I start zooming in our out, the Selectables library struggles. Your demo suffers from the same. Any ideas on how I can get around it?

Granduca commented 2 years ago

To fix this problem you need to override the static method in the Selectables library:

const editor = new Drawflow("...");

//Add `editor.zoom` to that function:
let cross = function (a, b) {
    let aTop = offset(a).top, aLeft = offset(a).left, bTop = offset(b).top, bLeft = offset(b).left;
    return !(((aTop + a.offsetHeight) < (bTop)) || (aTop > (bTop + b.offsetHeight * editor.zoom)) || ((aLeft + a.offsetWidth) < bLeft) || (aLeft > (bLeft + b.offsetWidth * editor.zoom)));
};

If you want to leave the library itself unchanged, then you can do in your code like this:

var dr = new Selectables({
//your prefs
})

let rb = function () {
    return document.getElementById('s-rectBox');
};

let cross = function (a, b) {
    let aTop = offset(a).top, aLeft = offset(a).left, bTop = offset(b).top, bLeft = offset(b).left;
    return !(((aTop + a.offsetHeight) < (bTop)) || (aTop > (bTop + b.offsetHeight * editor.zoom)) || ((aLeft + a.offsetWidth) < bLeft) || (aLeft > (bLeft + b.offsetWidth * editor.zoom)));
};

let offset = function (el) {
    let r = el.getBoundingClientRect();
    return {top: r.top + document.body.scrollTop, left: r.left + document.body.scrollLeft}
};

dr.select = function (e) {
    let a = rb();
    if (!a) {
        return;
    }
    delete(dr.ipos);
    document.body.classList.remove('s-noselect');
    document.body.removeEventListener('mousemove', dr.rectDraw);
    window.removeEventListener('mouseup', dr.select);
    let s = dr.options.selectedClass;
    dr.foreach(dr.items, function (el) {
        if (cross(a, el) === true) {
            if (el.classList.contains(s)) {
                el.classList.remove(s);
                dr.options.onDeselect && dr.options.onDeselect(el);
            } else {
                el.classList.add(s);
                dr.options.onSelect && dr.options.onSelect(el);
            }
        }
        setTimeout(function () {
            el.removeEventListener('click', dr.suspend, true);
        }, 100);
    });
    a.parentNode.removeChild(a);
    dr.options.stop && dr.options.stop(e);
}
dearsina commented 2 years ago

@Granduca this is fantastic. I implemented the first option and it works like a charm. Thanks again for your guidance!

jerosoler commented 2 years ago

Multiple Select with ShiftKey native.

https://user-images.githubusercontent.com/30957047/169560995-c02d0fd7-b286-48d8-a1d6-1996e6b2d80b.mp4

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <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>
</head>
<div id="drawflow"></div>
  <style>
    #drawflow { 
      position: relative;
      text-align:initial;
      width: 100%;
      height: 800px;
      border: 1px solid red;
    }
    .drawflow svg {
        z-index: 1;
    }
    .drawflow .drawflow-node.GROUP {
        width: 500px;
        height: 500px;
        z-index: 0;
    }
    .drawflow .drawflow-node.GROUP.hover-drop {
        background: pink;
    }
</style>
<script>
    var id = document.getElementById("drawflow");
    const editor = new Drawflow(id);

    editor.start();    
    editor.addNode('aaa', 1, 1, 600, 200, 'aaa', {}, `aaa` );
    editor.addNode('bbb', 1, 1, 850, 200, 'bbb', {}, `bbb` );
    editor.addNode('ccc', 1, 1, 850, 370, 'ccc', {}, `ccc`);
    editor.addConnection(1, 2, 'output_1', 'input_1');
    editor.addConnection(2, 3, 'output_1', 'input_1');

    const nodesSelected = [];
    editor.on("clickEnd", (e) => {
        const shiftKey = e.shiftKey;
        if(shiftKey) {
            if(editor.node_selected !== null) {
                const nodeId = editor.node_selected.id.slice(5);
                const indexSelected = nodesSelected.indexOf(nodeId);
                if(indexSelected === -1) {
                    nodesSelected.push(nodeId);
                } else {
                        nodesSelected.splice(indexSelected,1);
                        const nodeUnselected = document.getElementById(`node-${nodeId}`);
                        nodeUnselected.classList.remove("selected");
                }
            } else {
                nodesSelected.forEach(eleN => {
                    const node = document.getElementById(`node-${eleN}`);
                    node.classList.remove("selected");
                });
                nodesSelected.splice(0, nodesSelected.length);
            }
            nodesSelected.forEach(eleN => {
                    const node = document.getElementById(`node-${eleN}`);
                    node.classList.add("selected");
            });
        } else {
            nodesSelected.forEach(eleN => {
                    const node = document.getElementById(`node-${eleN}`);
                    node.classList.remove("selected");
            });
            nodesSelected.splice(0, nodesSelected.length);
        }

    });

    let last_x = 0;
    let last_y = 0;
    editor.on("mouseMove", ({x,y}) => {
        if(editor.node_selected && editor.drag) {
            editor.node_selected.classList.add("selected");
            const nodeId = editor.node_selected.id.slice(5);
            const indexSelected = nodesSelected.indexOf(nodeId);
            if(indexSelected === -1) {
                nodesSelected.push(nodeId);
            }
            nodesSelected.forEach(eleN => {
                if(eleN != editor.node_selected.id.slice(5)) {
                const node = document.getElementById(`node-${eleN}`);
                var xnew = (last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom);
                var ynew = (last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom);

                node.style.top = (node.offsetTop - ynew) + "px";
                node.style.left = (node.offsetLeft - xnew) + "px";

                editor.drawflow.drawflow[editor.module].data[eleN].pos_x = (node.offsetLeft - xnew);
                editor.drawflow.drawflow[editor.module].data[eleN].pos_y = (node.offsetTop - ynew);
                editor.updateConnectionNodes(`node-${eleN}`);
                }
            });
        }
        last_x = x;
        last_y = y;
    }) 
</script>
</body>
</html>
koufounak commented 11 months ago

@jerosoler Hello, i'm using your library in my angular project and it's very useful. Isn't the multiselect + drag packaged in the library already? If it's not will it be in the future? Thank you.

jerosoler commented 11 months ago

Hello, @koufounak It would not be included in this library. I am preparing a new library. But it is still in development.

dearsina commented 11 months ago

@jerosoler Wait, what? New library? Tell me more! :-D