mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	[groups] add basic group functionality to editor
This commit is contained in:
		| @@ -177,6 +177,7 @@ module.exports = function(grunt) { | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/group.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", | ||||
|   | ||||
| @@ -61,6 +61,7 @@ | ||||
|         "shift-down": "core:step-selection-down", | ||||
|         "shift-left": "core:step-selection-left", | ||||
|         "ctrl-shift-j": "core:show-previous-tab", | ||||
|         "ctrl-shift-k": "core:show-next-tab" | ||||
|         "ctrl-shift-k": "core:show-next-tab", | ||||
|         "ctrl-shift-g": "core:group-selection" | ||||
|      } | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,9 @@ RED.nodes = (function() { | ||||
|     var subflows = {}; | ||||
|     var loadedFlowVersion = null; | ||||
|  | ||||
|     var groups = {}; | ||||
|     var groupsByZ = {}; | ||||
|  | ||||
|     var initialLoad; | ||||
|  | ||||
|     var dirty = false; | ||||
| @@ -1444,6 +1447,14 @@ RED.nodes = (function() { | ||||
|         // var loadedFlowVersion = null; | ||||
|     } | ||||
|  | ||||
|     function addGroup(group) { | ||||
|         groupsByZ[group.z] = groupsByZ[group.z] || []; | ||||
|         groupsByZ[group.z].push(group); | ||||
|         groups[group.id] = group; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     return { | ||||
|         init: function() { | ||||
|             RED.events.on("registry:node-type-added",function(type) { | ||||
| @@ -1539,6 +1550,10 @@ RED.nodes = (function() { | ||||
|         subflow: getSubflow, | ||||
|         subflowContains: subflowContains, | ||||
|  | ||||
|         addGroup: addGroup, | ||||
|         group: function(id) { return groups[id] }, | ||||
|         groups: function(z) { return groupsByZ[z] }, | ||||
|  | ||||
|         eachNode: function(cb) { | ||||
|             for (var n=0;n<nodes.length;n++) { | ||||
|                 if (cb(nodes[n]) === false) { | ||||
|   | ||||
| @@ -524,6 +524,7 @@ var RED = (function() { | ||||
|         } | ||||
|  | ||||
|         RED.subflow.init(); | ||||
|         RED.group.init(); | ||||
|         RED.clipboard.init(); | ||||
|         RED.search.init(); | ||||
|         RED.actionList.init(); | ||||
|   | ||||
| @@ -514,7 +514,9 @@ RED.editor = (function() { | ||||
|         for (var i=editStack.length-1;i<editStack.length;i++) { | ||||
|             var node = editStack[i]; | ||||
|             label = node.type; | ||||
|             if (node.type === '_expression') { | ||||
|             if (node.type === 'group') { | ||||
|                 label = RED._("subflow.editGroup",{name:RED.utils.sanitize(node.name||node.id)}); | ||||
|             } else if (node.type === '_expression') { | ||||
|                 label = RED._("expressionEditor.title"); | ||||
|             } else if (node.type === '_js') { | ||||
|                 label = RED._("jsEditor.title"); | ||||
| @@ -2453,6 +2455,249 @@ RED.editor = (function() { | ||||
|         RED.tray.show(trayOptions); | ||||
|     } | ||||
|  | ||||
|     function showEditGroupDialog(group) { | ||||
|         var editing_node = group; | ||||
|         editStack.push(group); | ||||
|         RED.view.state(RED.state.EDITING); | ||||
|         var nodeInfoEditor; | ||||
|         var finishedBuilding = false; | ||||
|         var trayOptions = { | ||||
|             title: getEditStackTitle(), | ||||
|             buttons: [ | ||||
|                 { | ||||
|                     id: "node-dialog-cancel", | ||||
|                     text: RED._("common.label.cancel"), | ||||
|                     click: function() { | ||||
|                         RED.tray.close(); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     id: "node-dialog-ok", | ||||
|                     class: "primary", | ||||
|                     text: RED._("common.label.done"), | ||||
|                     click: function() { | ||||
|                         var changes = {}; | ||||
|                         var changed = false; | ||||
|                         var wasDirty = RED.nodes.dirty(); | ||||
|                         var d; | ||||
|                         var outputMap; | ||||
|  | ||||
|                         if (editing_node._def.oneditsave) { | ||||
|                             var oldValues = {}; | ||||
|                             for (d in editing_node._def.defaults) { | ||||
|                                 if (editing_node._def.defaults.hasOwnProperty(d)) { | ||||
|                                     if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") { | ||||
|                                         oldValues[d] = editing_node[d]; | ||||
|                                     } else { | ||||
|                                         oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             try { | ||||
|                                 var rc = editing_node._def.oneditsave.call(editing_node); | ||||
|                                 if (rc === true) { | ||||
|                                     changed = true; | ||||
|                                 } | ||||
|                             } catch(err) { | ||||
|                                 console.log("oneditsave",editing_node.id,editing_node.type,err.toString()); | ||||
|                             } | ||||
|  | ||||
|                             for (d in editing_node._def.defaults) { | ||||
|                                 if (editing_node._def.defaults.hasOwnProperty(d)) { | ||||
|                                     if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") { | ||||
|                                         if (oldValues[d] !== editing_node[d]) { | ||||
|                                             changes[d] = oldValues[d]; | ||||
|                                             changed = true; | ||||
|                                         } | ||||
|                                     } else { | ||||
|                                         if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) { | ||||
|                                             changes[d] = oldValues[d]; | ||||
|                                             changed = true; | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         var newValue; | ||||
|                         if (editing_node._def.defaults) { | ||||
|                             for (d in editing_node._def.defaults) { | ||||
|                                 if (editing_node._def.defaults.hasOwnProperty(d)) { | ||||
|                                     var input = $("#node-input-"+d); | ||||
|                                     if (input.attr('type') === "checkbox") { | ||||
|                                         newValue = input.prop('checked'); | ||||
|                                     } else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") { | ||||
|                                         // An empty select-multiple box returns null. | ||||
|                                         // Need to treat that as an empty array. | ||||
|                                         newValue = input.val(); | ||||
|                                         if (newValue == null) { | ||||
|                                             newValue = []; | ||||
|                                         } | ||||
|                                     } else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") { | ||||
|                                         newValue = input.text(); | ||||
|                                     } else { | ||||
|                                         newValue = input.val(); | ||||
|                                     } | ||||
|                                     if (newValue != null) { | ||||
|                                         if (editing_node._def.defaults[d].type) { | ||||
|                                             if (newValue == "_ADD_") { | ||||
|                                                 newValue = ""; | ||||
|                                             } | ||||
|                                         } | ||||
|                                         if (editing_node[d] != newValue) { | ||||
|                                             if (editing_node._def.defaults[d].type) { | ||||
|                                                 // Change to a related config node | ||||
|                                                 var configNode = RED.nodes.node(editing_node[d]); | ||||
|                                                 if (configNode) { | ||||
|                                                     var users = configNode.users; | ||||
|                                                     users.splice(users.indexOf(editing_node),1); | ||||
|                                                 } | ||||
|                                                 configNode = RED.nodes.node(newValue); | ||||
|                                                 if (configNode) { | ||||
|                                                     configNode.users.push(editing_node); | ||||
|                                                 } | ||||
|                                             } | ||||
|                                             changes[d] = editing_node[d]; | ||||
|                                             editing_node[d] = newValue; | ||||
|                                             changed = true; | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         var oldInfo = editing_node.info; | ||||
|                         if (nodeInfoEditor) { | ||||
|                             var newInfo = nodeInfoEditor.getValue(); | ||||
|                             if (!!oldInfo) { | ||||
|                                 // Has existing info property | ||||
|                                 if (newInfo.trim() === "") { | ||||
|                                     // New value is blank - remove the property | ||||
|                                     changed = true; | ||||
|                                     changes.info = oldInfo; | ||||
|                                     delete editing_node.info; | ||||
|                                 } else if (newInfo !== oldInfo) { | ||||
|                                     // New value is different | ||||
|                                     changed = true; | ||||
|                                     changes.info = oldInfo; | ||||
|                                     editing_node.info = newInfo; | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 // No existing info | ||||
|                                 if (newInfo.trim() !== "") { | ||||
|                                     // New value is not blank | ||||
|                                     changed = true; | ||||
|                                     changes.info = undefined; | ||||
|                                     editing_node.info = newInfo; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         if (changed) { | ||||
|                             var wasChanged = editing_node.changed; | ||||
|                             editing_node.changed = true; | ||||
|                             RED.nodes.dirty(true); | ||||
|                             var historyEvent = { | ||||
|                                 t:'edit', | ||||
|                                 node:editing_node, | ||||
|                                 changes:changes, | ||||
|                                 dirty:wasDirty, | ||||
|                                 changed:wasChanged | ||||
|                             }; | ||||
|                             RED.history.push(historyEvent); | ||||
|                         } | ||||
|                         editing_node.dirty = true; | ||||
|                         RED.tray.close(); | ||||
|                         RED.view.redraw(true);  | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             resize: function(size) { | ||||
|                 editTrayWidthCache['group'] = size.width; | ||||
|                 $(".red-ui-tray-content").height(size.height - 50); | ||||
|                 // var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40); | ||||
|                 // if (editing_node && editing_node._def.oneditresize) { | ||||
|                 //     try { | ||||
|                 //         editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()}); | ||||
|                 //     } catch(err) { | ||||
|                 //         console.log("oneditresize",editing_node.id,editing_node.type,err.toString()); | ||||
|                 //     } | ||||
|                 // } | ||||
|             }, | ||||
|             open: function(tray, done) { | ||||
|                 var trayFooter = tray.find(".red-ui-tray-footer"); | ||||
|                 var trayFooterLeft = $("<div/>", { | ||||
|                     class: "red-ui-tray-footer-left" | ||||
|                 }).appendTo(trayFooter) | ||||
|                 var trayBody = tray.find('.red-ui-tray-body'); | ||||
|                 trayBody.parent().css('overflow','hidden'); | ||||
|  | ||||
|                 var editorTabEl = $('<ul></ul>').appendTo(trayBody); | ||||
|                 var editorContent = $('<div></div>').appendTo(trayBody); | ||||
|  | ||||
|                 var editorTabs = RED.tabs.create({ | ||||
|                     element:editorTabEl, | ||||
|                     onchange:function(tab) { | ||||
|                         editorContent.children().hide(); | ||||
|                         if (tab.onchange) { | ||||
|                             tab.onchange.call(tab); | ||||
|                         } | ||||
|                         tab.content.show(); | ||||
|                         if (finishedBuilding) { | ||||
|                             RED.tray.resize(); | ||||
|                         } | ||||
|                     }, | ||||
|                     collapsible: true, | ||||
|                     menu: false | ||||
|                 }); | ||||
|  | ||||
|                 var nodePropertiesTab = { | ||||
|                     id: "editor-tab-properties", | ||||
|                     label: RED._("editor-tab.properties"), | ||||
|                     name: RED._("editor-tab.properties"), | ||||
|                     content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), | ||||
|                     iconClass: "fa fa-cog" | ||||
|                 }; | ||||
|                 buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group); | ||||
|  | ||||
|                 editorTabs.addTab(nodePropertiesTab); | ||||
|  | ||||
|                 var descriptionTab = { | ||||
|                     id: "editor-tab-description", | ||||
|                     label: RED._("editor-tab.description"), | ||||
|                     name: RED._("editor-tab.description"), | ||||
|                     content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), | ||||
|                     iconClass: "fa fa-file-text-o", | ||||
|                     onchange: function() { | ||||
|                         nodeInfoEditor.focus(); | ||||
|                     } | ||||
|                 }; | ||||
|                 editorTabs.addTab(descriptionTab); | ||||
|                 nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node); | ||||
|                 prepareEditDialog(group,group._def,"node-input", function() { | ||||
|                     trayBody.i18n(); | ||||
|                     finishedBuilding = true; | ||||
|                     done(); | ||||
|                 }); | ||||
|             }, | ||||
|             close: function() { | ||||
|                 if (RED.view.state() != RED.state.IMPORT_DRAGGING) { | ||||
|                     RED.view.state(RED.state.DEFAULT); | ||||
|                 } | ||||
|                 nodeInfoEditor.destroy(); | ||||
|                 nodeInfoEditor = null; | ||||
|                 editStack.pop(); | ||||
|                 editing_node = null; | ||||
|             }, | ||||
|             show: function() { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (editTrayWidthCache.hasOwnProperty('group')) { | ||||
|             trayOptions.width = editTrayWidthCache['group']; | ||||
|         } | ||||
|         RED.tray.show(trayOptions); | ||||
|     } | ||||
|  | ||||
|     function showTypeEditor(type, options) { | ||||
|         if (customEditTypes.hasOwnProperty(type)) { | ||||
|             if (editStack.length > 0) { | ||||
| @@ -2576,6 +2821,7 @@ RED.editor = (function() { | ||||
|         edit: showEditDialog, | ||||
|         editConfig: showEditConfigNodeDialog, | ||||
|         editSubflow: showEditSubflowDialog, | ||||
|         editGroup: showEditGroupDialog, | ||||
|         editJavaScript: function(options) { showTypeEditor("_js",options) }, | ||||
|         editExpression: function(options) { showTypeEditor("_expression", options) }, | ||||
|         editJSON: function(options) { showTypeEditor("_json", options) }, | ||||
|   | ||||
| @@ -25,5 +25,7 @@ RED.state = { | ||||
|     IMPORT_DRAGGING: 8, | ||||
|     QUICK_JOINING: 9, | ||||
|     PANNING: 10, | ||||
|     SELECTING_NODE: 11 | ||||
|     SELECTING_NODE: 11, | ||||
|     GROUP_DRAGGING: 12, | ||||
|     GROUP_RESIZE: 13 | ||||
| } | ||||
|   | ||||
| @@ -21,12 +21,15 @@ | ||||
|   *           \- <g>.red-ui-workspace-chart-event-layer "eventLayer" | ||||
|   *                |- <rect>.red-ui-workspace-chart-background | ||||
|   *                |- <g>.red-ui-workspace-chart-grid "gridLayer" | ||||
|   *                |- <g> "groupLayer" | ||||
|   *                |- <g> "linkLayer" | ||||
|   *                |- <g> "dragGroupLayer" | ||||
|   *                \- <g> "nodeLayer" | ||||
|   */ | ||||
|  | ||||
| RED.view = (function() { | ||||
|     var DEBUG_EVENTS = false; | ||||
|     2 | ||||
|     var space_width = 5000, | ||||
|         space_height = 5000, | ||||
|         lineCurveScale = 0.75, | ||||
| @@ -48,22 +51,29 @@ RED.view = (function() { | ||||
|     var activeSpliceLink; | ||||
|     var spliceActive = false; | ||||
|     var spliceTimer; | ||||
|     var groupHoverTimer; | ||||
|  | ||||
|     var activeSubflow = null; | ||||
|     var activeNodes = []; | ||||
|     var activeLinks = []; | ||||
|     var activeFlowLinks = []; | ||||
|     var activeLinkNodes = {}; | ||||
|     var activeGroup = null; | ||||
|     var activeHoverGroup = null; | ||||
|     var activeGroups = []; | ||||
|     var dirtyGroups = {}; | ||||
|  | ||||
|     var selected_link = null, | ||||
|         mousedown_link = null, | ||||
|         mousedown_node = null, | ||||
|         mousedown_group = null, | ||||
|         mousedown_port_type = null, | ||||
|         mousedown_port_index = 0, | ||||
|         mouseup_node = null, | ||||
|         mouse_offset = [0,0], | ||||
|         mouse_position = null, | ||||
|         mouse_mode = 0, | ||||
|         mousedown_group_handle = null; | ||||
|         moving_set = [], | ||||
|         lasso = null, | ||||
|         ghostNode = null, | ||||
| @@ -75,7 +85,8 @@ RED.view = (function() { | ||||
|         scroll_position = [], | ||||
|         quickAddActive = false, | ||||
|         quickAddLink = null, | ||||
|         showAllLinkPorts = -1; | ||||
|         showAllLinkPorts = -1, | ||||
|         groupNodeSelectPrimed = false; | ||||
|  | ||||
|     var selectNodesOptions; | ||||
|  | ||||
| @@ -101,6 +112,7 @@ RED.view = (function() { | ||||
|     var linkLayer; | ||||
|     var dragGroupLayer; | ||||
|     var nodeLayer; | ||||
|     var groupLayer; | ||||
|     var drag_lines; | ||||
|  | ||||
|     function init() { | ||||
| @@ -253,6 +265,7 @@ RED.view = (function() { | ||||
|         gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); | ||||
|         updateGrid(); | ||||
|  | ||||
|         groupLayer = eventLayer.append("g"); | ||||
|         linkLayer = eventLayer.append("g"); | ||||
|         dragGroupLayer = eventLayer.append("g"); | ||||
|         nodeLayer = eventLayer.append("g"); | ||||
| @@ -517,6 +530,8 @@ RED.view = (function() { | ||||
|             source:{z:activeWorkspace}, | ||||
|             target:{z:activeWorkspace} | ||||
|         }); | ||||
|  | ||||
|         activeGroups = RED.nodes.groups(activeWorkspace)||[]; | ||||
|     } | ||||
|  | ||||
|     function generateLinkPath(origX,origY, destX, destY, sc) { | ||||
| @@ -671,6 +686,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function canvasMouseDown() { | ||||
| if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } | ||||
|         var point; | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             d3.event.stopPropagation(); | ||||
| @@ -684,7 +700,7 @@ RED.view = (function() { | ||||
|             scroll_position = [chart.scrollLeft(),chart.scrollTop()]; | ||||
|             return; | ||||
|         } | ||||
|         if (!mousedown_node && !mousedown_link) { | ||||
|         if (!mousedown_node && !mousedown_link && !mousedown_group) { | ||||
|             selected_link = null; | ||||
|             updateSelection(); | ||||
|         } | ||||
| @@ -697,7 +713,13 @@ RED.view = (function() { | ||||
|         if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { | ||||
|             if (d3.event.metaKey || d3.event.ctrlKey) { | ||||
|                 d3.event.stopPropagation(); | ||||
|                 showQuickAddDialog(d3.mouse(this)); | ||||
|                 clearSelection(); | ||||
|                 point = d3.mouse(this); | ||||
|                 var clickedGroup = getGroupAt(point[0],point[1]); | ||||
|                 if (drag_lines.length > 0) { | ||||
|                     clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) | ||||
|                 } | ||||
|                 showQuickAddDialog(point, null, clickedGroup); | ||||
|             } | ||||
|         } | ||||
|         if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { | ||||
| @@ -718,7 +740,15 @@ RED.view = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function showQuickAddDialog(point,spliceLink) { | ||||
|     function showQuickAddDialog(point, spliceLink, targetGroup) { | ||||
|         if (targetGroup && !targetGroup.active) { | ||||
|             targetGroup.active = true; | ||||
|             targetGroup.dirty = true; | ||||
|             selectGroup(targetGroup,false); | ||||
|             activeGroup = targetGroup; | ||||
|             RED.view.redraw(); | ||||
|         } | ||||
|  | ||||
|         var ox = point[0]; | ||||
|         var oy = point[1]; | ||||
|  | ||||
| @@ -946,6 +976,11 @@ RED.view = (function() { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 if (targetGroup) { | ||||
|                     RED.group.addToGroup(targetGroup, nn); | ||||
|  | ||||
|                 } | ||||
|  | ||||
|                 if (spliceLink) { | ||||
|                     resetMouseVars(); | ||||
|                     // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog | ||||
| @@ -972,6 +1007,12 @@ RED.view = (function() { | ||||
|                 // auto select dropped node - so info shows (if visible) | ||||
|                 clearSelection(); | ||||
|                 nn.selected = true; | ||||
|                 if (targetGroup) { | ||||
|                     selectGroup(targetGroup,false); | ||||
|                     targetGroup.active = true | ||||
|                     targetGroup.dirty = true; | ||||
|                     activeGroup = targetGroup; | ||||
|                 } | ||||
|                 moving_set.push({n:nn}); | ||||
|                 updateActiveNodes(); | ||||
|                 updateSelection(); | ||||
| @@ -1076,12 +1117,23 @@ RED.view = (function() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { | ||||
|         if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         var mousePos; | ||||
|         if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { | ||||
|         if (mouse_mode === RED.state.GROUP_RESIZE) { | ||||
|             mousePos = mouse_position; | ||||
|             var nx = mousePos[0] + mousedown_group.dx; | ||||
|             var ny = mousePos[1] + mousedown_group.dy; | ||||
|             switch(mousedown_group.activeHandle) { | ||||
|                 case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; | ||||
|                 case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; | ||||
|                 case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; | ||||
|                 case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; | ||||
|             } | ||||
|             mousedown_group.dirty = true; | ||||
|         } else if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { | ||||
|             // update drag line | ||||
|             if (drag_lines.length === 0 && mousedown_port_type !== null) { | ||||
|                 if (d3.event.shiftKey) { | ||||
| @@ -1201,6 +1253,13 @@ RED.view = (function() { | ||||
|                     node.n.y -= (maxY - space_height); | ||||
|                 } | ||||
|             } | ||||
|             if (mousedown_group) { | ||||
|                 mousedown_group.pos.x0 = mousePos[0] + mousedown_group.dx0; | ||||
|                 mousedown_group.pos.y0 = mousePos[1] + mousedown_group.dy0; | ||||
|                 mousedown_group.pos.x1 = mousePos[0] + mousedown_group.dx1; | ||||
|                 mousedown_group.pos.y1 = mousePos[1] + mousedown_group.dy1; | ||||
|                 mousedown_group.dirty = true; | ||||
|             } | ||||
|             if (snapGrid != d3.event.shiftKey && moving_set.length > 0) { | ||||
|                 var gridOffset = [0,0]; | ||||
|                 node = moving_set[0]; | ||||
| @@ -1265,6 +1324,29 @@ RED.view = (function() { | ||||
|                         },100); | ||||
|                     } | ||||
|                 } | ||||
|                 if (!node.n.g && activeGroups) { | ||||
|                     if (!groupHoverTimer) { | ||||
|                         groupHoverTimer = setTimeout(function() { | ||||
|                             activeHoverGroup = null; | ||||
|                             for (var i=0;i<activeGroups.length;i++) { | ||||
|                                 var g = activeGroups[i]; | ||||
|                                 if ( !activeHoverGroup && | ||||
|                                     node.n.x >= g.pos.x0 && node.n.x <= g.pos.x1 && | ||||
|                                     node.n.y >= g.pos.y0 && node.n.y <= g.pos.y1 | ||||
|                                 ) { | ||||
|                                     g.dirty = !g.hovered; | ||||
|                                     g.hovered = true; | ||||
|                                     activeHoverGroup = g; | ||||
|                                 } else { | ||||
|                                     // Mark dirty if it is selected | ||||
|                                     g.dirty = g.hovered; | ||||
|                                     g.hovered = false; | ||||
|                                 } | ||||
|                             } | ||||
|                             groupHoverTimer = null; | ||||
|                         },50); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|  | ||||
| @@ -1275,6 +1357,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function canvasMouseUp() { | ||||
| if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } | ||||
|         var i; | ||||
|         var historyEvent; | ||||
|         if (mouse_mode === RED.state.PANNING) { | ||||
| @@ -1311,15 +1394,22 @@ RED.view = (function() { | ||||
|             var y = parseInt(lasso.attr("y")); | ||||
|             var x2 = x+parseInt(lasso.attr("width")); | ||||
|             var y2 = y+parseInt(lasso.attr("height")); | ||||
|             if (!d3.event.ctrlKey) { | ||||
|             if (!d3.event.shiftKey) { | ||||
|                 clearSelection(); | ||||
|             } | ||||
|             RED.nodes.eachNode(function(n) { | ||||
|             activeNodes.forEach(function(n) { | ||||
|                 if (n.z == RED.workspaces.active() && !n.selected) { | ||||
|                     n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); | ||||
|                     if (n.selected) { | ||||
|                         n.dirty = true; | ||||
|                         moving_set.push({n:n}); | ||||
|                     if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { | ||||
|                         if (n.g) { | ||||
|                             var group = RED.nodes.group(n.g); | ||||
|                             if (!group.selected) { | ||||
|                                 selectGroup(RED.nodes.group(n.g),true); | ||||
|                             } | ||||
|                         } else { | ||||
|                             n.selected = true; | ||||
|                             n.dirty = true; | ||||
|                             moving_set.push({n:n}); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
| @@ -1355,6 +1445,20 @@ RED.view = (function() { | ||||
|         } | ||||
|         if (mouse_mode == RED.state.MOVING_ACTIVE) { | ||||
|             if (moving_set.length > 0) { | ||||
|                 if (activeHoverGroup) { | ||||
|                     for (var j=0;j<moving_set.length;j++) { | ||||
|                         var n = moving_set[j]; | ||||
|                         RED.group.addToGroup(activeHoverGroup,n.n); | ||||
|                     } | ||||
|                     activeHoverGroup.hovered = false; | ||||
|                     activeHoverGroup.dirty = true; | ||||
|                     activeHoverGroup.active = true; | ||||
|                     activeGroup = activeHoverGroup; | ||||
|                     activeGroup.selected = true; | ||||
|  | ||||
|                     activeHoverGroup = null; | ||||
|                 } | ||||
|  | ||||
|                 var ns = []; | ||||
|                 for (var j=0;j<moving_set.length;j++) { | ||||
|                     var n = moving_set[j]; | ||||
| @@ -1391,7 +1495,23 @@ RED.view = (function() { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { | ||||
|         //     if (mousedown_node.gSelected) { | ||||
|         //         delete mousedown_node.gSelected | ||||
|         //     } else { | ||||
|         //         if (!d3.event.ctrlKey && !d3.event.metaKey) { | ||||
|         //             clearSelection(); | ||||
|         //         } | ||||
|         //         RED.nodes.group(mousedown_node.g).selected = true; | ||||
|         //         mousedown_node.selected = true; | ||||
|         //         mousedown_node.dirty = true; | ||||
|         //         moving_set.push({n:mousedown_node}); | ||||
|         //     } | ||||
|         // } | ||||
|         if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { | ||||
|             // if (mousedown_node) { | ||||
|             //     delete mousedown_node.gSelected; | ||||
|             // } | ||||
|             for (i=0;i<moving_set.length;i++) { | ||||
|                 delete moving_set[i].ox; | ||||
|                 delete moving_set[i].oy; | ||||
| @@ -1437,20 +1557,28 @@ RED.view = (function() { | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) { | ||||
|             return; | ||||
|         } | ||||
|         RED.nodes.eachNode(function(n) { | ||||
|             if (n.z == RED.workspaces.active()) { | ||||
|                 if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|                     if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 if (!n.selected) { | ||||
|                     n.selected = true; | ||||
|                     n.dirty = true; | ||||
|                     moving_set.push({n:n}); | ||||
|  | ||||
|         activeGroups.forEach(function(g) { | ||||
|             selectGroup(g, true); | ||||
|             if (!g.selected) { | ||||
|                 g.selected = true; | ||||
|                 g.dirty = true; | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         activeNodes.forEach(function(n) { | ||||
|             if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|                 if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             if (!n.g && !n.selected) { | ||||
|                 n.selected = true; | ||||
|                 n.dirty = true; | ||||
|                 moving_set.push({n:n}); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) { | ||||
|             activeSubflow.in.forEach(function(n) { | ||||
|                 if (!n.selected) { | ||||
| @@ -1483,6 +1611,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function clearSelection() { | ||||
| if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } | ||||
|         for (var i=0;i<moving_set.length;i++) { | ||||
|             var n = moving_set[i]; | ||||
|             n.n.dirty = true; | ||||
| @@ -1490,6 +1619,15 @@ RED.view = (function() { | ||||
|         } | ||||
|         moving_set = []; | ||||
|         selected_link = null; | ||||
|         if (activeGroup) { | ||||
|             activeGroup.active = false | ||||
|             activeGroup.dirty = true; | ||||
|             activeGroup = null; | ||||
|         } | ||||
|         activeGroups.forEach(function(g) { | ||||
|             g.selected = false; | ||||
|             g.dirty = true; | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     var lastSelection = null; | ||||
| @@ -1505,6 +1643,7 @@ RED.view = (function() { | ||||
|             if (selected_link != null) { | ||||
|                 selection.link = selected_link; | ||||
|             } | ||||
|             selection.groups = activeGroups.filter(function(g) { return g.selected }) | ||||
|             activeLinks = RED.nodes.filterLinks({ | ||||
|                 source:{z:activeWorkspace}, | ||||
|                 target:{z:activeWorkspace} | ||||
| @@ -1588,7 +1727,7 @@ RED.view = (function() { | ||||
|             selection.flows = workspaceSelection; | ||||
|         } | ||||
|         var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { | ||||
|             if (key === 'nodes' || key === 'flows') { | ||||
|             if (key === 'nodes' || key === 'flows' || key === 'groups') { | ||||
|                 return value.map(function(n) { return n.id }) | ||||
|             } else if (key === 'link') { | ||||
|                 return value.source.id+":"+value.sourcePort+":"+value.target.id; | ||||
| @@ -1895,6 +2034,8 @@ RED.view = (function() { | ||||
|  | ||||
|     function resetMouseVars() { | ||||
|         mousedown_node = null; | ||||
|         mousedown_group = null; | ||||
|         mousedown_group_handle = null; | ||||
|         mouseup_node = null; | ||||
|         mousedown_link = null; | ||||
|         mouse_mode = 0; | ||||
| @@ -1906,6 +2047,10 @@ RED.view = (function() { | ||||
|             clearTimeout(spliceTimer); | ||||
|             spliceTimer = null; | ||||
|         } | ||||
|         if (groupHoverTimer) { | ||||
|             clearTimeout(groupHoverTimer); | ||||
|             groupHoverTimer = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function disableQuickJoinEventHandler(evt) { | ||||
| @@ -1919,6 +2064,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function portMouseDown(d,portType,portIndex) { | ||||
| if (DEBUG_EVENTS) { console.warn("portMouseDown", mouse_mode,d); } | ||||
|         //console.log(d,portType,portIndex); | ||||
|         // disable zoom | ||||
|         //eventLayer.call(d3.behavior.zoom().on("zoom"), null); | ||||
| @@ -1946,6 +2092,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function portMouseUp(d,portType,portIndex) { | ||||
| if (DEBUG_EVENTS) { console.warn("portMouseUp", mouse_mode,d); } | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
| @@ -2280,6 +2427,7 @@ RED.view = (function() { | ||||
|     } | ||||
|  | ||||
|     function nodeMouseUp(d) { | ||||
| if (DEBUG_EVENTS) { console.warn("nodeMouseUp", mouse_mode,d); } | ||||
|         if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
| @@ -2295,6 +2443,27 @@ RED.view = (function() { | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
|         } | ||||
|         if (mouse_mode === RED.state.MOVING) { | ||||
|             // Moving primed, but not active. | ||||
|             if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) { | ||||
|                 clearSelection(); | ||||
|                 activeGroup = RED.nodes.group(d.g); | ||||
|                 activeGroup.active = true; | ||||
|                 activeGroup.dirty = true; | ||||
|  | ||||
|                 selectGroup(RED.nodes.group(d.g), false); | ||||
|                 mousedown_node.selected = true; | ||||
|                 moving_set.push({n:mousedown_node}); | ||||
|                 var mouse = d3.touches(this)[0]||d3.mouse(this); | ||||
|                 mouse[0] += d.x-d.w/2; | ||||
|                 mouse[1] += d.y-d.h/2; | ||||
|                 prepareDrag(mouse); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         groupNodeSelectPrimed = false; | ||||
|  | ||||
|         var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) | ||||
|         var wasJoining = false; | ||||
|         if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { | ||||
| @@ -2316,7 +2485,23 @@ RED.view = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function prepareDrag(mouse) { | ||||
|         mouse_mode = RED.state.MOVING; | ||||
|         // Called when moving_set should be prepared to be dragged | ||||
|         for (i=0;i<moving_set.length;i++) { | ||||
|             moving_set[i].ox = moving_set[i].n.x; | ||||
|             moving_set[i].oy = moving_set[i].n.y; | ||||
|             moving_set[i].dx = moving_set[i].n.x-mouse[0]; | ||||
|             moving_set[i].dy = moving_set[i].n.y-mouse[1]; | ||||
|         } | ||||
|         mouse_offset = d3.mouse(document.body); | ||||
|         if (isNaN(mouse_offset[0])) { | ||||
|             mouse_offset = d3.touches(document.body)[0]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function nodeMouseDown(d) { | ||||
| if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } | ||||
|         focusView(); | ||||
|         if (d3.event.button === 1) { | ||||
|             return; | ||||
| @@ -2385,7 +2570,9 @@ RED.view = (function() { | ||||
|             // } | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         mousedown_node = d; | ||||
|  | ||||
|         var now = Date.now(); | ||||
|         clickElapsed = now-clickTime; | ||||
|         clickTime = now; | ||||
| @@ -2393,11 +2580,76 @@ RED.view = (function() { | ||||
|         dblClickPrimed = (lastClickNode == mousedown_node && | ||||
|             d3.event.button === 0 && | ||||
|             !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey); | ||||
|         lastClickNode = mousedown_node; | ||||
|             lastClickNode = mousedown_node; | ||||
|  | ||||
|         var i; | ||||
|  | ||||
|         if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) { | ||||
|         if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) { | ||||
|             var nodeGroup = RED.nodes.group(d.g); | ||||
|  | ||||
|             if (nodeGroup !== activeGroup && (d3.event.ctrlKey || d3.event.metaKey)) { | ||||
|                 // Clicked on a node in a non-active group with ctrl pressed | ||||
|                 //  - exit active group | ||||
|                 //  - toggle the select state of the group | ||||
|                 exitActiveGroup(); | ||||
|                 groupNodeSelectPrimed = true; | ||||
|                 if (nodeGroup.selected) { | ||||
|                     deselectGroup(nodeGroup); | ||||
|                 } else { | ||||
|                     selectGroup(nodeGroup,true); | ||||
|                 } | ||||
|             } else if (nodeGroup === activeGroup ) { | ||||
|                 // Clicked on a node in the active group | ||||
|                 if (!d3.event.ctrlKey && !d3.event.metaKey) { | ||||
|                     // Ctrl not pressed so clear selection | ||||
|                     deselectGroup(nodeGroup); | ||||
|                     selectGroup(nodeGroup,false); | ||||
|                 } | ||||
|                 // Select this node | ||||
|                 mousedown_node.selected = true; | ||||
|                 moving_set.push({n:mousedown_node}); | ||||
|             } else { | ||||
|                 // Clicked on a node in a group | ||||
|                 //  - if this group is not selected, clear current selection | ||||
|                 //    and select this group | ||||
|                 //  - if this group is not the active group, exit the active group | ||||
|                 //    and select the group | ||||
|                 //  - if this group is the active group, keep it active and | ||||
|                 //    change node selection | ||||
|  | ||||
|                 // Set groupNodeSelectPrimed to true as this is a (de)select of the | ||||
|                 // group and NOT meant to trigger going into the group - see nodeMouseUp | ||||
|                 groupNodeSelectPrimed = !nodeGroup.selected; | ||||
|                 var ag = activeGroup; | ||||
|                 if (!nodeGroup.selected) { | ||||
|                     clearSelection(); | ||||
|                 } | ||||
|                 if (ag) { | ||||
|                     if (ag !== nodeGroup) { | ||||
|                         ag.active = false; | ||||
|                         ag.dirty = true; | ||||
|                     } else { | ||||
|                         activeGroup = nodeGroup; | ||||
|                         activeGroup.active = true; | ||||
|                     } | ||||
|                 } else { | ||||
|                     dblClickPrimed = false; | ||||
|                 } | ||||
|                 selectGroup(nodeGroup, !activeGroup); | ||||
|                 if (activeGroup) { | ||||
|                     mousedown_node.selected = true; | ||||
|                     moving_set.push({n:mousedown_node}); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (d3.event.button != 2) { | ||||
|                 var mouse = d3.touches(this)[0]||d3.mouse(this); | ||||
|                 mouse[0] += d.x-d.w/2; | ||||
|                 mouse[1] += d.y-d.h/2; | ||||
|                 prepareDrag(mouse); | ||||
|             } | ||||
|         } else if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) { | ||||
|             mousedown_node.selected = false; | ||||
|             for (i=0;i<moving_set.length;i+=1) { | ||||
|                 if (moving_set[i].n === mousedown_node) { | ||||
| @@ -2406,6 +2658,21 @@ RED.view = (function() { | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|  | ||||
|             // if (d.g && !RED.nodes.group(d.g).selected) { | ||||
|             //     selectGroup(RED.nodes.group(d.g), false); | ||||
|             // } | ||||
|  | ||||
|  | ||||
|             // if (!d.selected && d.g) { | ||||
|             //     if (!RED.nodes.group(d.g).selected) {// && !RED.nodes.group(d.g).selected) { | ||||
|             //         clearSelection(); | ||||
|             //         selectGroup(RED.nodes.group(d.g)); | ||||
|             //         d.selected = true; | ||||
|             //         console.log(d.id,"Setting selected") | ||||
|             //         d.gSelected = true; | ||||
|             //     } | ||||
|             // } else | ||||
|             if (d3.event.shiftKey) { | ||||
|                 clearSelection(); | ||||
|                 var cnodes = RED.nodes.getAllFlowNodes(mousedown_node); | ||||
| @@ -2417,6 +2684,8 @@ RED.view = (function() { | ||||
|             } else if (!d.selected) { | ||||
|                 if (!d3.event.ctrlKey && !d3.event.metaKey) { | ||||
|                     clearSelection(); | ||||
|                 } else { | ||||
|                     exitActiveGroup(); | ||||
|                 } | ||||
|                 mousedown_node.selected = true; | ||||
|                 moving_set.push({n:mousedown_node}); | ||||
| @@ -2427,16 +2696,7 @@ RED.view = (function() { | ||||
|                 var mouse = d3.touches(this)[0]||d3.mouse(this); | ||||
|                 mouse[0] += d.x-d.w/2; | ||||
|                 mouse[1] += d.y-d.h/2; | ||||
|                 for (i=0;i<moving_set.length;i++) { | ||||
|                     moving_set[i].ox = moving_set[i].n.x; | ||||
|                     moving_set[i].oy = moving_set[i].n.y; | ||||
|                     moving_set[i].dx = moving_set[i].n.x-mouse[0]; | ||||
|                     moving_set[i].dy = moving_set[i].n.y-mouse[1]; | ||||
|                 } | ||||
|                 mouse_offset = d3.mouse(document.body); | ||||
|                 if (isNaN(mouse_offset[0])) { | ||||
|                     mouse_offset = d3.touches(document.body)[0]; | ||||
|                 } | ||||
|                 prepareDrag(mouse); | ||||
|             } | ||||
|         } | ||||
|         d.dirty = true; | ||||
| @@ -2445,6 +2705,164 @@ RED.view = (function() { | ||||
|         d3.event.stopPropagation(); | ||||
|     } | ||||
|  | ||||
|     function groupMouseUp(g) { | ||||
|         if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < 750) { | ||||
|             mouse_mode = RED.state.DEFAULT; | ||||
|             RED.editor.editGroup(g); | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     function groupMouseDown(g) { | ||||
|         var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); | ||||
|         if (! (mouse[0] < g.pos.x0+10 || mouse[0] > g.pos.x1-10 || mouse[1] < g.pos.y0+10 || mouse[1] > g.pos.y1-10) ) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         focusView(); | ||||
|         if (d3.event.button === 1) { | ||||
|             return; | ||||
|         } | ||||
|         if (mouse_mode == RED.state.IMPORT_DRAGGING) { | ||||
|             RED.keyboard.remove("escape"); | ||||
|             console.log("Dragged a node into the group") | ||||
|         } else if (mouse_mode == RED.state.QUICK_JOINING) { | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
|         } else if (mouse_mode === RED.state.SELECTING_NODE) { | ||||
|             d3.event.stopPropagation(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         mousedown_group = g; | ||||
|  | ||||
|         var now = Date.now(); | ||||
|         clickElapsed = now-clickTime; | ||||
|         clickTime = now; | ||||
|  | ||||
|         dblClickPrimed = ( | ||||
|             lastClickNode == g && | ||||
|             d3.event.button === 0 && | ||||
|             !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey | ||||
|         ); | ||||
|         lastClickNode = g; | ||||
|  | ||||
|         if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { | ||||
|             if (g === activeGroup) { | ||||
|                 exitActiveGroup(); | ||||
|             } | ||||
|             deselectGroup(g); | ||||
|             d3.event.stopPropagation(); | ||||
|         } else { | ||||
|             if (!g.selected) { | ||||
|                 if (!d3.event.ctrlKey && !d3.event.metaKey) { | ||||
|                     clearSelection(); | ||||
|                 } | ||||
|                 selectGroup(g,true);//!wasSelected); | ||||
|             } else { | ||||
|                 exitActiveGroup(); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (d3.event.button != 2) { | ||||
|                 var d = g.nodes[0]; | ||||
|                 prepareDrag(mouse); | ||||
|                 mousedown_group.dx0 = mousedown_group.pos.x0 - mouse[0]; | ||||
|                 mousedown_group.dy0 = mousedown_group.pos.y0 - mouse[1]; | ||||
|                 mousedown_group.dx1 = mousedown_group.pos.x1 - mouse[0]; | ||||
|                 mousedown_group.dy1 = mousedown_group.pos.y1 - mouse[1]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         updateSelection(); | ||||
|         redraw(); | ||||
|         d3.event.stopPropagation(); | ||||
|     } | ||||
|  | ||||
|     function selectGroup(g, includeNodes) { | ||||
|         if (!g.selected) { | ||||
|             g.selected = true; | ||||
|             g.dirty = true; | ||||
|         } | ||||
|         if (includeNodes) { | ||||
|             var currentSet = new Set(moving_set.map(function(n) { return n.n })); | ||||
|             g.nodes.forEach(function(n) { | ||||
|                 if (!currentSet.has(n)) { | ||||
|                     moving_set.push({n:n}) | ||||
|                     // n.selected = true; | ||||
|                 } | ||||
|                 n.dirty = true; | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|     function exitActiveGroup() { | ||||
|         if (activeGroup) { | ||||
|             activeGroup.active = false; | ||||
|             activeGroup.dirty = true; | ||||
|             deselectGroup(activeGroup); | ||||
|             selectGroup(activeGroup,true); | ||||
|             activeGroup = null; | ||||
|         } | ||||
|     } | ||||
|     function deselectGroup(g) { | ||||
|         if (g.selected) { | ||||
|             g.selected = false; | ||||
|             g.dirty = true; | ||||
|         } | ||||
|         var nodeSet = new Set(g.nodes); | ||||
|         for (var i = moving_set.length-1; i >= 0; i -= 1) { | ||||
|             if (nodeSet.has(moving_set[i].n)) { | ||||
|                 moving_set[i].n.selected = false; | ||||
|                 moving_set[i].n.dirty = true; | ||||
|                 moving_set.splice(i,1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     function getGroupAt(x,y) { | ||||
|         for (var i=0;i<activeGroups.length;i++) { | ||||
|             var g = activeGroups[i]; | ||||
|             if (x >= g.pos.x0 && x <= g.pos.x1 && y >= g.pos.y0 && y <= g.pos.y1) { | ||||
|                 return g; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     function groupHandleMouseDown(group, groupEl, handle,handleIndex) { | ||||
|         d3.event.stopPropagation(); | ||||
|         console.log("GHANDLE MD"); | ||||
|         if (d3.event.button != 2) { | ||||
|             mousedown_group = group; | ||||
|             group.activeHandle = handleIndex; | ||||
|             var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); | ||||
|             switch(handleIndex) { | ||||
|                 case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; | ||||
|                 case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; | ||||
|                 case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; | ||||
|                 case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; | ||||
|             } | ||||
|             group.dx = group.ox - mouse[0]; | ||||
|             group.dy = group.oy - mouse[1]; | ||||
|             console.log("START",group.ox, group.oy); | ||||
|             mouse_offset = d3.mouse(document.body); | ||||
|             if (isNaN(mouse_offset[0])) { | ||||
|                 mouse_offset = d3.touches(document.body)[0]; | ||||
|             } | ||||
|             mouse_mode = RED.state.GROUP_RESIZE; | ||||
|         } | ||||
|     } | ||||
|     function groupHandleMouseUp(group,groupEl,handle,handleIndex) { | ||||
|         console.log("GHANDLE MU"); | ||||
|         d3.event.stopPropagation(); | ||||
|         delete group.ox; | ||||
|         delete group.oy; | ||||
|         delete group.dx; | ||||
|         delete group.dy; | ||||
|         resetMouseVars(); | ||||
|         mouse_mode = RED.state.DEFAULT; | ||||
|     } | ||||
|  | ||||
|     function isButtonEnabled(d) { | ||||
|         var buttonEnabled = true; | ||||
|         var ws = RED.nodes.workspace(RED.workspaces.active()); | ||||
| @@ -3294,6 +3712,19 @@ RED.view = (function() { | ||||
|                         } | ||||
|  | ||||
|                         d.dirty = false; | ||||
|  | ||||
|                         if (d.g) { | ||||
|                             if (!dirtyGroups[d.g]) { | ||||
|                                 dirtyGroups[d.g] = RED.nodes.group(d.g); | ||||
|                             } | ||||
|                             var group = dirtyGroups[d.g]; | ||||
|                             group.pos = { | ||||
|                                 x0: Math.min(group.pos.x0,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)), | ||||
|                                 y0: Math.min(group.pos.y0,d.y-d.h/2-25), | ||||
|                                 x1: Math.max(group.pos.x1,d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0)), | ||||
|                                 y1: Math.max(group.pos.y1,d.y+d.h/2+25) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|             }); | ||||
|  | ||||
| @@ -3324,7 +3755,9 @@ RED.view = (function() { | ||||
|                         d3.event.stopPropagation(); | ||||
|                         if (d3.event.metaKey || d3.event.ctrlKey) { | ||||
|                             l.classed("red-ui-flow-link-splice",true); | ||||
|                             showQuickAddDialog(d3.mouse(this), selected_link); | ||||
|                             var point = d3.mouse(this); | ||||
|                             var clickedGroup = getGroupAt(point[0],point[1]); | ||||
|                             showQuickAddDialog(point, selected_link, clickedGroup); | ||||
|                         } | ||||
|                     }) | ||||
|                     .on("touchstart",function(d) { | ||||
| @@ -3509,6 +3942,112 @@ RED.view = (function() { | ||||
|  | ||||
|             }) | ||||
|  | ||||
|  | ||||
|             var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); | ||||
|             group.exit().remove(); | ||||
|             var groupEnter = group.enter().insert("svg:g") | ||||
|                 .attr("class", "red-ui-flow-group") | ||||
|  | ||||
|             groupEnter.each(function(d,i) { | ||||
|                 var g = d3.select(this); | ||||
|  | ||||
|                 // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-0",true).style({ | ||||
|                 //     "fill":"white", | ||||
|                 //     "stroke": "#ff7f0e", | ||||
|                 //     "stroke-width": 2 | ||||
|                 // }).attr("d","m -5 6 v -2 q 0 -9 9 -9 h 2 v 11 z") | ||||
|                 // | ||||
|                 // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-1",true).style({ | ||||
|                 //     "fill": "white", | ||||
|                 //     "stroke": "#ff7f0e", | ||||
|                 //     "stroke-width": 2 | ||||
|                 // }).attr("d","m -6 -5 h 2 q 9 0 9 9 v 2 h -11 z") | ||||
|                 // | ||||
|                 // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-2",true).style({ | ||||
|                 //     "fill": "white", | ||||
|                 //     "stroke": "#ff7f0e", | ||||
|                 //     "stroke-width": 2 | ||||
|                 // }).attr("d","m 5 -6 v 2 q 0 9 -9 9 h -2 v -11 z") | ||||
|                 // | ||||
|                 // g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-3",true).style({ | ||||
|                 //     "fill": "white", | ||||
|                 //     "stroke": "#ff7f0e", | ||||
|                 //     "stroke-width": 2 | ||||
|                 // }).attr("d","m 6 5 h -2 q -9 0 -9 -9 v -2 h 11 z") | ||||
|  | ||||
|  | ||||
|  | ||||
|                 g.append('rect').classed("red-ui-flow-group-outline",true) | ||||
|                     .attr('rx',1).attr('ry',1).style({ | ||||
|                         "fill":"none", | ||||
|                         "stroke": "#ff7f0e", | ||||
|                         "stroke-opacity": 0, | ||||
|                         "stroke-width": 15 | ||||
|                     }) | ||||
|                 g.append('rect').classed("red-ui-flow-group-body",true) | ||||
|                     .attr('rx',1).attr('ry',1).style({ | ||||
|                         "fill":d.fill||"none", | ||||
|                         "stroke": d.stroke||"none", | ||||
|                         "stroke-width": 2 | ||||
|                     }) | ||||
|  | ||||
|  | ||||
|                 d.dirty = true; | ||||
|                 g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) | ||||
|  | ||||
|             }); | ||||
|             group.each(function(d,i) { | ||||
|                 if (d.dirty || dirtyGroups[d.id]) { | ||||
|                     var g = d3.select(this); | ||||
|                     var minX = Number.POSITIVE_INFINITY; | ||||
|                     var minY = Number.POSITIVE_INFINITY; | ||||
|                     var maxX = 0; | ||||
|                     var maxY = 0; | ||||
|                     d.nodes.forEach(function(n) { | ||||
|                         minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); | ||||
|                         minY = Math.min(minY,n.y-n.h/2-25); | ||||
|                         maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); | ||||
|                         maxY = Math.max(maxY,n.y+n.h/2+25); | ||||
|                     }); | ||||
|  | ||||
|                     d.pos = { | ||||
|                         x0: minX, y0: minY, | ||||
|                         x1: maxX, y1: maxY | ||||
|                     } | ||||
|  | ||||
|                     g.attr("transform","translate("+d.pos.x0+","+d.pos.y0+")"); | ||||
|                     g.selectAll(".red-ui-flow-group-outline") | ||||
|                         .attr("width",d.pos.x1-d.pos.x0) | ||||
|                         .attr("height",d.pos.y1-d.pos.y0) | ||||
|                         .style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0}); | ||||
|  | ||||
|                     g.selectAll(".red-ui-flow-group-body") | ||||
|                         .attr("width",d.pos.x1-d.pos.x0) | ||||
|                         .attr("height",d.pos.y1-d.pos.y0) | ||||
|                         .style("stroke",function(d) { /*if (d.selected) { return "#ff7f0e" } */return d.style.stroke || "none"}) | ||||
|                         .style("stroke-dasharray", function(d) { return (d.active||d.hovered)?"10 4":"none"}) | ||||
|                         .style("fill", function(d) { return d.style.fill || "none"}) | ||||
|                         .style("fill-opacity", 0.1) | ||||
|  | ||||
|                     // g.selectAll(".red-ui-flow-group-handle-0") | ||||
|                     //     .style("opacity",function(d) { return d.selected?1:0}) | ||||
|                     // g.selectAll(".red-ui-flow-group-handle-1") | ||||
|                     //     .attr("transform","translate("+(maxX-minX)+",0)") | ||||
|                     //     .style("opacity",function(d) { return d.selected?1:0}) | ||||
|                     // g.selectAll(".red-ui-flow-group-handle-2") | ||||
|                     //     .attr("transform","translate("+(maxX-minX)+","+(maxY-minY)+")") | ||||
|                     //     .style("opacity",function(d) { return d.selected?1:0}) | ||||
|                     // g.selectAll(".red-ui-flow-group-handle-3") | ||||
|                     //     .attr("transform","translate(0,"+(maxY-minY)+")") | ||||
|                     //     .style("opacity",function(d) { return d.selected?1:0}) | ||||
|  | ||||
|  | ||||
|                     delete dirtyGroups[d.id]; | ||||
|                     delete d.dirty; | ||||
|                 } | ||||
|             }) | ||||
|  | ||||
|  | ||||
|         } else { | ||||
|             // JOINING - unselect any selected links | ||||
|             linkLayer.selectAll(".red-ui-flow-link-selected").data( | ||||
| @@ -3785,10 +4324,17 @@ RED.view = (function() { | ||||
|                         selectedNode.dirty = true; | ||||
|                         moving_set = [{n:selectedNode}]; | ||||
|                     } | ||||
|                 } else if (selection) { | ||||
|                     if (selection.groups) { | ||||
|                         updateActiveNodes(); | ||||
|                         selection.groups.forEach(function(g) { | ||||
|                             selectGroup(g,true); | ||||
|                         }) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             updateSelection(); | ||||
|             redraw(); | ||||
|             redraw(true); | ||||
|         }, | ||||
|         selection: function() { | ||||
|             var selection = {}; | ||||
| @@ -3798,6 +4344,10 @@ RED.view = (function() { | ||||
|             if (selected_link != null) { | ||||
|                 selection.link = selected_link; | ||||
|             } | ||||
|             selection.groups = activeGroups.filter(function(g) { return g.selected }) | ||||
|             if (selection.groups.length === 0) { | ||||
|                 delete selection.groups; | ||||
|             } | ||||
|             return selection; | ||||
|         }, | ||||
|         scale: function() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user