diff options
author | Timoney, Daniel (dt5972) <dtimoney@att.com> | 2017-02-15 10:37:53 -0500 |
---|---|---|
committer | Timoney, Daniel (dt5972) <dtimoney@att.com> | 2017-02-15 10:40:37 -0500 |
commit | 324ee36fe31763e507b422ab0a88e4230045e205 (patch) | |
tree | d0b04520f6657601c918ce63fd27575977624187 /dgbuilder/public/red/ui | |
parent | f0c97e8db427481e28c0a16b789bc73801b35e47 (diff) |
Initial commit for OpenECOMP SDN-C OA&M
Change-Id: I7ab579fd0d206bf356f36d52dcdf4f71f1fa2680
Signed-off-by: Timoney, Daniel (dt5972) <dtimoney@att.com>
Former-commit-id: 2a9f0edd09581f907e62ec4689b5ac94dd5382ba
Diffstat (limited to 'dgbuilder/public/red/ui')
-rw-r--r-- | dgbuilder/public/red/ui/editor.js | 665 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/keyboard.js | 68 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/library.js | 370 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/menu.js | 122 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/notifications.js | 59 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/palette.js | 230 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/sidebar.js | 154 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/state.js | 26 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/tab-config.js | 84 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/tab-info.js | 90 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/tabs.js | 127 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/touch/radialMenu.js | 184 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/view.js | 2053 | ||||
-rw-r--r-- | dgbuilder/public/red/ui/view.js.orig | 1639 |
14 files changed, 5871 insertions, 0 deletions
diff --git a/dgbuilder/public/red/ui/editor.js b/dgbuilder/public/red/ui/editor.js new file mode 100644 index 00000000..c5f7986e --- /dev/null +++ b/dgbuilder/public/red/ui/editor.js @@ -0,0 +1,665 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.editor = (function() { + var editing_node = null; + // TODO: should IMPORT/EXPORT get their own dialogs? + + function getCredentialsURL(nodeType, nodeID) { + var dashedType = nodeType.replace(/\s+/g, '-'); + return 'credentials/' + dashedType + "/" + nodeID; + } + + /** + * Validate a node + * @param node - the node being validated + * @returns {boolean} whether the node is valid. Sets node.dirty if needed + */ + function validateNode(node) { + var oldValue = node.valid; + node.valid = validateNodeProperties(node, node._def.defaults, node); + if (node._def._creds) { + node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds); + } + if (oldValue != node.valid) { + node.dirty = true; + } + } + + /** + * Validate a node's properties for the given set of property definitions + * @param node - the node being validated + * @param definition - the node property definitions (either def.defaults or def.creds) + * @param properties - the node property values to validate + * @returns {boolean} whether the node's properties are valid + */ + function validateNodeProperties(node, definition, properties) { + var isValid = true; + for (var prop in definition) { + if (definition.hasOwnProperty(prop)) { + if (!validateNodeProperty(node, definition, prop, properties[prop])) { + isValid = false; + } + } + } + return isValid; + } + + /** + * Validate a individual node property + * @param node - the node being validated + * @param definition - the node property definitions (either def.defaults or def.creds) + * @param property - the property name being validated + * @param value - the property value being validated + * @returns {boolean} whether the node proprty is valid + */ + function validateNodeProperty(node,definition,property,value) { + var valid = true; + if ("required" in definition[property] && definition[property].required) { + valid = value !== ""; + } + if (valid && "validate" in definition[property]) { + valid = definition[property].validate.call(node,value); + } + if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) { + if (!value || value == "_ADD_") { + valid = false; + } else { + var v = RED.nodes.node(value).valid; + valid = (v==null || v); + } + } + return valid; + } + + /** + * Called when the node's properties have changed. + * Marks the node as dirty and needing a size check. + * Removes any links to non-existant outputs. + * @param node - the node that has been updated + * @returns {array} the links that were removed due to this update + */ + function updateNodeProperties(node) { + node.resize = true; + node.dirty = true; + var removedLinks = []; + if (node.outputs < node.ports.length) { + while (node.outputs < node.ports.length) { + node.ports.pop(); + } + RED.nodes.eachLink(function(l) { + if (l.source === node && l.sourcePort >= node.outputs) { + removedLinks.push(l); + } + }); + for (var l=0;l<removedLinks.length;l++) { + RED.nodes.removeLink(removedLinks[l]); + } + } else if (node.outputs > node.ports.length) { + while (node.outputs > node.ports.length) { + node.ports.push(node.ports.length); + } + } + return removedLinks; + } + + + + $( "#dialog" ).dialog({ + modal: true, + autoOpen: false, + closeOnEscape: false, + width: 500, + buttons: [ + { + id: "node-dialog-ok", + text: "Ok", + click: function() { + if (editing_node) { + var changes = {}; + var changed = false; + var wasDirty = RED.view.dirty(); + var d; + + 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; + } + } + } + var rc = editing_node._def.oneditsave.call(editing_node); + if (rc === true) { + changed = true; + } + + 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; + } + } + } + } + + + } + + if (editing_node._def.defaults) { + for (d in editing_node._def.defaults) { + if (editing_node._def.defaults.hasOwnProperty(d)) { + var input = $("#node-input-"+d); + var newValue; + if (input.attr('type') === "checkbox") { + newValue = input.prop('checked'); + } else { + newValue = input.val(); + } + if (newValue != null) { + if (editing_node[d] != newValue) { + if (editing_node._def.defaults[d].type) { + if (newValue == "_ADD_") { + newValue = ""; + } + // 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; + } + } + } + } + } + if (editing_node._def.credentials) { + var prefix = 'node-input'; + var credDefinition = editing_node._def.credentials; + var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix); + changed = changed || credsChanged; + } + + + var removedLinks = updateNodeProperties(editing_node); + if (changed) { + var wasChanged = editing_node.changed; + editing_node.changed = true; + RED.view.dirty(true); + RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged}); + } + editing_node.dirty = true; + validateNode(editing_node); + RED.view.redraw(); + } else if (RED.view.state() == RED.state.EXPORT) { + if (/library/.test($( "#dialog" ).dialog("option","title"))) { + //TODO: move this to RED.library + var flowName = $("#node-input-filename").val(); + if (!/^\s*$/.test(flowName)) { + $.post('library/flows/'+flowName,$("#node-input-filename").attr('nodes'),function() { + RED.library.loadFlowLibrary(); + RED.notify("Saved nodes","success"); + }); + } + } + } else if (RED.view.state() == RED.state.IMPORT) { + RED.view.importNodes($("#node-input-import").val()); + } + $( this ).dialog( "close" ); + } + }, + { + id: "node-dialog-cancel", + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + resize: function(e,ui) { + if (editing_node) { + $(this).dialog('option',"sizeCache-"+editing_node.type,ui.size); + } + }, + open: function(e) { + RED.keyboard.disable(); + if (editing_node) { + var size = $(this).dialog('option','sizeCache-'+editing_node.type); + if (size) { + $(this).dialog('option','width',size.width); + $(this).dialog('option','height',size.height); + } + } + }, + close: function(e) { + RED.keyboard.enable(); + + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { + RED.view.state(RED.state.DEFAULT); + } + $( this ).dialog('option','height','auto'); + $( this ).dialog('option','width','500'); + if (editing_node) { + RED.sidebar.info.refresh(editing_node); + } + RED.sidebar.config.refresh(); + editing_node = null; + } + }); + + /** + * Create a config-node select box for this property + * @param node - the node being edited + * @param property - the name of the field + * @param type - the type of the config-node + */ + function prepareConfigNodeSelect(node,property,type) { + var input = $("#node-input-"+property); + var node_def = RED.nodes.getType(type); + + input.replaceWith('<select style="width: 60%;" id="node-input-'+property+'"></select>'); + updateConfigNodeSelect(property,type,node[property]); + var select = $("#node-input-"+property); + select.after(' <a id="node-input-lookup-'+property+'" class="btn"><i class="fa fa-pencil"></i></a>'); + $('#node-input-lookup-'+property).click(function(e) { + showEditConfigNodeDialog(property,type,select.find(":selected").val()); + e.preventDefault(); + }); + var label = ""; + var configNode = RED.nodes.node(node[property]); + if (configNode && node_def.label) { + if (typeof node_def.label == "function") { + label = node_def.label.call(configNode); + } else { + label = node_def.label; + } + } + input.val(label); + } + + /** + * Populate the editor dialog input field for this property + * @param node - the node being edited + * @param property - the name of the field + * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) + */ + function preparePropertyEditor(node,property,prefix) { + var input = $("#"+prefix+"-"+property); + if (input.attr('type') === "checkbox") { + input.prop('checked',node[property]); + } else { + var val = node[property]; + if (val == null) { + val = ""; + } + input.val(val); + } + } + + /** + * Add an on-change handler to revalidate a node field + * @param node - the node being edited + * @param definition - the definition of the node + * @param property - the name of the field + * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) + */ + function attachPropertyChangeHandler(node,definition,property,prefix) { + $("#"+prefix+"-"+property).change(function() { + if (!validateNodeProperty(node, definition, property,this.value)) { + $(this).addClass("input-error"); + } else { + $(this).removeClass("input-error"); + } + }); + } + + /** + * Assign the value to each credential field + * @param node + * @param credDef + * @param credData + * @param prefix + */ + function populateCredentialsInputs(node, credDef, credData, prefix) { + var cred; + for (cred in credDef) { + if (credDef.hasOwnProperty(cred)) { + if (credDef[cred].type == 'password') { + if (credData[cred]) { + $('#' + prefix + '-' + cred).val(credData[cred]); + } else if (credData['has_' + cred]) { + $('#' + prefix + '-' + cred).val('__PWRD__'); + } + else { + $('#' + prefix + '-' + cred).val(''); + } + } else { + preparePropertyEditor(credData, cred, prefix); + } + attachPropertyChangeHandler(node, credDef, cred, prefix); + } + } + for (cred in credDef) { + if (credDef.hasOwnProperty(cred)) { + $("#" + prefix + "-" + cred).change(); + } + } + } + + /** + * Update the node credentials from the edit form + * @param node - the node containing the credentials + * @param credDefinition - definition of the credentials + * @param prefix - prefix of the input fields + * @return {boolean} whether anything has changed + */ + function updateNodeCredentials(node, credDefinition, prefix) { + var changed = false; + if(!node.credentials) { + node.credentials = {_:{}}; + } + + for (var cred in credDefinition) { + if (credDefinition.hasOwnProperty(cred)) { + var input = $("#" + prefix + '-' + cred); + var value = input.val(); + if (credDefinition[cred].type == 'password') { + node.credentials['has_' + cred] = (value !== ""); + if (value == '__PWRD__') { + continue; + } + changed = true; + + } + node.credentials[cred] = value; + if (value != node.credentials._[cred]) { + changed = true; + } + } + } + return changed; + } + + /** + * Prepare all of the editor dialog fields + * @param node - the node being edited + * @param definition - the node definition + * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) + */ + function prepareEditDialog(node,definition,prefix) { + for (var d in definition.defaults) { + if (definition.defaults.hasOwnProperty(d)) { + if (definition.defaults[d].type) { + prepareConfigNodeSelect(node,d,definition.defaults[d].type); + } else { + preparePropertyEditor(node,d,prefix); + } + attachPropertyChangeHandler(node,definition.defaults,d,prefix); + } + } + var completePrepare = function() { + if (definition.oneditprepare) { + definition.oneditprepare.call(node); + } + for (var d in definition.defaults) { + if (definition.defaults.hasOwnProperty(d)) { + $("#"+prefix+"-"+d).change(); + } + } + } + + if (definition.credentials) { + if (node.credentials) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + completePrepare(); + } else { + $.getJSON(getCredentialsURL(node.type, node.id), function (data) { + node.credentials = data; + node.credentials._ = $.extend(true,{},data); + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + completePrepare(); + }); + } + } else { + completePrepare(); + } + } + + function showEditDialog(node) { + editing_node = node; + RED.view.state(RED.state.EDITING); + $("#dialog-form").html($("script[data-template-name='"+node.type+"']").html()); + $('<input type="text" style="display: none;" />').appendTo("#dialog-form"); + prepareEditDialog(node,node._def,"node-input"); + $( "#dialog" ).dialog("option","title","Edit "+node.type+" node").dialog( "open" ); + } + + function showEditConfigNodeDialog(name,type,id) { + var adding = (id == "_ADD_"); + var node_def = RED.nodes.getType(type); + + var configNode = RED.nodes.node(id); + if (configNode == null) { + configNode = { + id: (1+Math.random()*4294967295).toString(16), + _def: node_def, + type: type + } + for (var d in node_def.defaults) { + if (node_def.defaults[d].value) { + configNode[d] = node_def.defaults[d].value; + } + } + } + + $("#dialog-config-form").html($("script[data-template-name='"+type+"']").html()); + prepareEditDialog(configNode,node_def,"node-config-input"); + + var buttons = $( "#node-config-dialog" ).dialog("option","buttons"); + if (adding) { + if (buttons.length == 3) { + buttons = buttons.splice(1); + } + buttons[0].text = "Add"; + $("#node-config-dialog-user-count").html("").hide(); + } else { + if (buttons.length == 2) { + buttons.unshift({ + class: 'leftButton', + text: "Delete", + click: function() { + var configProperty = $(this).dialog('option','node-property'); + var configId = $(this).dialog('option','node-id'); + var configType = $(this).dialog('option','node-type'); + var configNode = RED.nodes.node(configId); + var configTypeDef = RED.nodes.getType(configType); + + if (configTypeDef.ondelete) { + configTypeDef.ondelete.call(RED.nodes.node(configId)); + } + RED.nodes.remove(configId); + for (var i=0;i<configNode.users.length;i++) { + var user = configNode.users[i]; + for (var d in user._def.defaults) { + if (user._def.defaults.hasOwnProperty(d) && user[d] == configId) { + user[d] = ""; + } + } + validateNode(user); + } + updateConfigNodeSelect(configProperty,configType,""); + RED.view.dirty(true); + $( this ).dialog( "close" ); + RED.view.redraw(); + } + }); + } + buttons[1].text = "Update"; + $("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show(); + } + $( "#node-config-dialog" ).dialog("option","buttons",buttons); + + $( "#node-config-dialog" ) + .dialog("option","node-adding",adding) + .dialog("option","node-property",name) + .dialog("option","node-id",configNode.id) + .dialog("option","node-type",type) + .dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node") + .dialog( "open" ); + } + + function updateConfigNodeSelect(name,type,value) { + var select = $("#node-input-"+name); + var node_def = RED.nodes.getType(type); + select.children().remove(); + RED.nodes.eachConfig(function(config) { + if (config.type == type) { + var label = ""; + if (typeof node_def.label == "function") { + label = node_def.label.call(config); + } else { + label = node_def.label; + } + select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>'); + } + }); + + select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>'); + window.setTimeout(function() { select.change();},50); + } + + $( "#node-config-dialog" ).dialog({ + modal: true, + autoOpen: false, + width: 500, + closeOnEscape: false, + buttons: [ + { + id: "node-config-dialog-ok", + text: "Ok", + click: function() { + var configProperty = $(this).dialog('option','node-property'); + var configId = $(this).dialog('option','node-id'); + var configType = $(this).dialog('option','node-type'); + var configAdding = $(this).dialog('option','node-adding'); + var configTypeDef = RED.nodes.getType(configType); + var configNode; + var d; + + if (configAdding) { + configNode = {type:configType,id:configId,users:[]}; + for (d in configTypeDef.defaults) { + if (configTypeDef.defaults.hasOwnProperty(d)) { + configNode[d] = $("#node-config-input-"+d).val(); + } + } + configNode.label = configTypeDef.label; + configNode._def = configTypeDef; + RED.nodes.add(configNode); + updateConfigNodeSelect(configProperty,configType,configNode.id); + } else { + configNode = RED.nodes.node(configId); + for (d in configTypeDef.defaults) { + if (configTypeDef.defaults.hasOwnProperty(d)) { + var input = $("#node-config-input-"+d); + if (input.attr('type') === "checkbox") { + configNode[d] = input.prop('checked'); + } else { + configNode[d] = input.val(); + } + } + } + updateConfigNodeSelect(configProperty,configType,configId); + } + if (configTypeDef.credentials) { + updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input"); + } + if (configTypeDef.oneditsave) { + configTypeDef.oneditsave.call(RED.nodes.node(configId)); + } + validateNode(configNode); + + RED.view.dirty(true); + $(this).dialog("close"); + + } + }, + { + id: "node-config-dialog-cancel", + text: "Cancel", + click: function() { + var configType = $(this).dialog('option','node-type'); + var configId = $(this).dialog('option','node-id'); + var configAdding = $(this).dialog('option','node-adding'); + var configTypeDef = RED.nodes.getType(configType); + + if (configTypeDef.oneditcancel) { + // TODO: what to pass as this to call + if (configTypeDef.oneditcancel) { + var cn = RED.nodes.node(configId); + if (cn) { + configTypeDef.oneditcancel.call(cn,false); + } else { + configTypeDef.oneditcancel.call({id:configId},true); + } + } + } + $( this ).dialog( "close" ); + } + } + ], + resize: function(e,ui) { + }, + open: function(e) { + if (RED.view.state() != RED.state.EDITING) { + RED.keyboard.disable(); + } + }, + close: function(e) { + $("#dialog-config-form").html(""); + if (RED.view.state() != RED.state.EDITING) { + RED.keyboard.enable(); + } + RED.sidebar.config.refresh(); + } + }); + + + return { + edit: showEditDialog, + editConfig: showEditConfigNodeDialog, + validateNode: validateNode, + updateNodeProperties: updateNodeProperties // TODO: only exposed for edit-undo + } +})(); diff --git a/dgbuilder/public/red/ui/keyboard.js b/dgbuilder/public/red/ui/keyboard.js new file mode 100644 index 00000000..3bc28c4d --- /dev/null +++ b/dgbuilder/public/red/ui/keyboard.js @@ -0,0 +1,68 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.keyboard = (function() { + + var active = true; + var handlers = {}; + + d3.select(window).on("keydown",function() { + if (!active) { return; } + var handler = handlers[d3.event.keyCode]; + if (handler && handler.ondown) { + if (!handler.modifiers || + ((!handler.modifiers.shift || d3.event.shiftKey) && + (!handler.modifiers.ctrl || d3.event.ctrlKey ) && + (!handler.modifiers.alt || d3.event.altKey ) )) { + handler.ondown(); + } + } + }); + d3.select(window).on("keyup",function() { + if (!active) { return; } + var handler = handlers[d3.event.keyCode]; + if (handler && handler.onup) { + if (!handler.modifiers || + ((!handler.modifiers.shift || d3.event.shiftKey) && + (!handler.modifiers.ctrl || d3.event.ctrlKey ) && + (!handler.modifiers.alt || d3.event.altKey ) )) { + handler.onup(); + } + } + }); + function addHandler(key,modifiers,ondown,onup) { + var mod = modifiers; + var cbdown = ondown; + var cbup = onup; + + if (typeof modifiers == "function") { + mod = {}; + cbdown = modifiers; + cbup = ondown; + } + handlers[key] = {modifiers:mod, ondown:cbdown, onup:cbup}; + } + function removeHandler(key) { + delete handlers[key]; + } + + return { + add: addHandler, + remove: removeHandler, + disable: function(){ active = false;}, + enable: function(){ active = true; } + } + +})(); diff --git a/dgbuilder/public/red/ui/library.js b/dgbuilder/public/red/ui/library.js new file mode 100644 index 00000000..0c803bf0 --- /dev/null +++ b/dgbuilder/public/red/ui/library.js @@ -0,0 +1,370 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.library = (function() { + + + function loadFlowLibrary() { + $.getJSON("library/flows",function(data) { + //console.log(data); + + var buildMenu = function(data,root) { + var i; + var li; + var a; + var ul = document.createElement("ul"); + ul.id = "btn-import-library-submenu"; + ul.className = "dropdown-menu"; + if (data.d) { + for (i in data.d) { + if (data.d.hasOwnProperty(i)) { + li = document.createElement("li"); + li.className = "dropdown-submenu pull-left"; + a = document.createElement("a"); + a.href="#"; + a.innerHTML = i; + li.appendChild(a); + li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i)); + ul.appendChild(li); + } + } + } + if (data.f) { + for (i in data.f) { + if (data.f.hasOwnProperty(i)) { + li = document.createElement("li"); + a = document.createElement("a"); + a.href="#"; + a.innerHTML = data.f[i]; + a.flowName = root+(root!==""?"/":"")+data.f[i]; + a.onclick = function() { + $.get('library/flows/'+this.flowName, function(data) { + RED.view.importNodes(data); + }); + }; + li.appendChild(a); + ul.appendChild(li); + } + } + } + return ul; + }; + var menu = buildMenu(data,""); + //TODO: need an api in RED.menu for this + $("#btn-import-library-submenu").replaceWith(menu); + }); + } + loadFlowLibrary(); + + + + function createUI(options) { + var libraryData = {}; + var selectedLibraryItem = null; + var libraryEditor = null; + + function buildFileListItem(item) { + var li = document.createElement("li"); + li.onmouseover = function(e) { $(this).addClass("list-hover"); }; + li.onmouseout = function(e) { $(this).removeClass("list-hover"); }; + return li; + } + + function buildFileList(root,data) { + var ul = document.createElement("ul"); + var li; + for (var i=0;i<data.length;i++) { + var v = data[i]; + if (typeof v === "string") { + // directory + li = buildFileListItem(v); + li.onclick = (function () { + var dirName = v; + return function(e) { + var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>'); + $("a",bcli).click(function(e) { + $(this).parent().nextAll().remove(); + $.getJSON("library/"+options.url+root+dirName,function(data) { + $("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data)); + }); + e.stopPropagation(); + }); + var bc = $("#node-dialog-library-breadcrumbs"); + $(".active",bc).removeClass("active"); + bc.append(bcli); + $.getJSON("library/"+options.url+root+dirName,function(data) { + $("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data)); + }); + } + })(); + li.innerHTML = '<i class="fa fa-folder"></i> '+v+"</i>"; + ul.appendChild(li); + } else { + // file + li = buildFileListItem(v); + li.innerHTML = v.name; + li.onclick = (function() { + var item = v; + return function(e) { + $(".list-selected",ul).removeClass("list-selected"); + $(this).addClass("list-selected"); + $.get("library/"+options.url+root+item.fn, function(data) { + selectedLibraryItem = item; + libraryEditor.setText(data); + }); + } + })(); + ul.appendChild(li); + } + } + return ul; + } + +/* +//Commented this portion as is not used by the DGBuilder application + $('#node-input-name').addClass('input-append-left').css("width","65%").after( + '<div class="btn-group" style="margin-left: 0px;">'+ + '<button id="node-input-'+options.type+'-lookup" class="btn input-append-right" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></button>'+ + '<ul class="dropdown-menu pull-right" role="menu">'+ + '<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">Open Library...</a></li>'+ + '<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">Save to Library...</a></li>'+ + '</ul></div>' + ); + + + + $('#node-input-'+options.type+'-menu-open-library').click(function(e) { + $("#node-select-library").children().remove(); + var bc = $("#node-dialog-library-breadcrumbs"); + bc.children().first().nextAll().remove(); + libraryEditor.setText(''); + + $.getJSON("library/"+options.url,function(data) { + $("#node-select-library").append(buildFileList("/",data)); + $("#node-dialog-library-breadcrumbs a").click(function(e) { + $(this).parent().nextAll().remove(); + $("#node-select-library").children().first().replaceWith(buildFileList("/",data)); + e.stopPropagation(); + }); + $( "#node-dialog-library-lookup" ).dialog( "open" ); + }); + + e.preventDefault(); + }); + + $('#node-input-'+options.type+'-menu-save-library').click(function(e) { + //var found = false; + var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,""); + + //var buildPathList = function(data,root) { + // var paths = []; + // if (data.d) { + // for (var i in data.d) { + // var dn = root+(root==""?"":"/")+i; + // var d = { + // label:dn, + // files:[] + // }; + // for (var f in data.d[i].f) { + // d.files.push(data.d[i].f[f].fn.split("/").slice(-1)[0]); + // } + // paths.push(d); + // paths = paths.concat(buildPathList(data.d[i],root+(root==""?"":"/")+i)); + // } + // } + // return paths; + //}; + $("#node-dialog-library-save-folder").attr("value",""); + + var filename = name.replace(/[^\w-]/g,"-"); + if (filename === "") { + filename = "unnamed-"+options.type; + } + $("#node-dialog-library-save-filename").attr("value",filename+".js"); + + //var paths = buildPathList(libraryData,""); + //$("#node-dialog-library-save-folder").autocomplete({ + // minLength: 0, + // source: paths, + // select: function( event, ui ) { + // $("#node-dialog-library-save-filename").autocomplete({ + // minLength: 0, + // source: ui.item.files + // }); + // } + //}); + + $( "#node-dialog-library-save" ).dialog( "open" ); + e.preventDefault(); + }); + require(["orion/editor/edit"], function(edit) { + libraryEditor = edit({ + parent:document.getElementById('node-select-library-text'), + lang:"js", + readonly: true + }); + }); + + + $( "#node-dialog-library-lookup" ).dialog({ + title: options.type+" library", + modal: true, + autoOpen: false, + width: 800, + height: 450, + buttons: [ + { + text: "Ok", + click: function() { + if (selectedLibraryItem) { + for (var i=0;i<options.fields.length;i++) { + var field = options.fields[i]; + $("#node-input-"+field).val(selectedLibraryItem[field]); + } + options.editor.setText(libraryEditor.getText()); + } + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function(e) { + var form = $("form",this); + form.height(form.parent().height()-30); + $("#node-select-library-text").height("100%"); + $(".form-row:last-child",form).children().height(form.height()-60); + }, + resize: function(e) { + var form = $("form",this); + form.height(form.parent().height()-30); + $(".form-row:last-child",form).children().height(form.height()-60); + } + }); + + */ + function saveToLibrary(overwrite) { + var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,""); + if (name === "") { + name = "Unnamed "+options.type; + } + var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,""); + var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,""); + if (filename === "" || !/.+\.js$/.test(filename)) { + RED.notify("Invalid filename","warning"); + return; + } + var fullpath = pathname+(pathname===""?"":"/")+filename; + if (!overwrite) { + //var pathnameParts = pathname.split("/"); + //var exists = false; + //var ds = libraryData; + //for (var pnp in pathnameParts) { + // if (ds.d && pathnameParts[pnp] in ds.d) { + // ds = ds.d[pathnameParts[pnp]]; + // } else { + // ds = null; + // break; + // } + //} + //if (ds && ds.f) { + // for (var f in ds.f) { + // if (ds.f[f].fn == fullpath) { + // exists = true; + // break; + // } + // } + //} + //if (exists) { + // $("#node-dialog-library-save-type").html(options.type); + // $("#node-dialog-library-save-name").html(fullpath); + // $("#node-dialog-library-save-confirm").dialog( "open" ); + // return; + //} + } + var queryArgs = []; + for (var i=0;i<options.fields.length;i++) { + var field = options.fields[i]; + if (field == "name") { + queryArgs.push("name="+encodeURIComponent(name)); + } else { + queryArgs.push(encodeURIComponent(field)+"="+encodeURIComponent($("#node-input-"+field).val())); + } + } + var queryString = queryArgs.join("&"); + + var text = options.editor.getText(); + $.post("library/"+options.url+'/'+fullpath+"?"+queryString,text,function() { + RED.notify("Saved "+options.type,"success"); + }); + } + $( "#node-dialog-library-save-confirm" ).dialog({ + title: "Save to library", + modal: true, + autoOpen: false, + width: 530, + height: 230, + buttons: [ + { + text: "Ok", + click: function() { + saveToLibrary(true); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }); + $( "#node-dialog-library-save" ).dialog({ + title: "Save to library", + modal: true, + autoOpen: false, + width: 530, + height: 230, + buttons: [ + { + text: "Ok", + click: function() { + saveToLibrary(false); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }); + + } + + return { + create: createUI, + loadFlowLibrary: loadFlowLibrary + } +})(); + + diff --git a/dgbuilder/public/red/ui/menu.js b/dgbuilder/public/red/ui/menu.js new file mode 100644 index 00000000..07ea5fd2 --- /dev/null +++ b/dgbuilder/public/red/ui/menu.js @@ -0,0 +1,122 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + + +RED.menu = (function() { + + var menuItems = {}; + + function createMenuItem(opt) { + var item; + if (opt === null) { + item = $('<li class="divider"></li>'); + } else { + item = $('<li></li>'); + var link = $('<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">'+ + (opt.toggle?'<i class="fa fa-check pull-right"></i>':'')+ + (opt.icon?'<i class="'+opt.icon+'"></i> ':'')+ + opt.label+ + '</a>').appendTo(item); + + menuItems[opt.id] = opt; + + if (opt.onselect) { + link.click(function() { + if ($(this).parent().hasClass("disabled")) { + return; + } + if (opt.toggle) { + setSelected(opt.id,!isSelected(opt.id)); + } else { + opt.onselect.call(opt); + } + }) + } else if (opt.href) { + link.attr("target","_blank").attr("href",opt.href); + } + if (opt.options) { + item.addClass("dropdown-submenu pull-left"); + var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item); + + for (var i=0;i<opt.options.length;i++) { + createMenuItem(opt.options[i]).appendTo(submenu); + } + } + if (opt.disabled) { + item.addClass("disabled"); + } + } + + + return item; + + } + function createMenu(options) { + + var button = $("#"+options.id); + + var topMenu = $("<ul/>",{class:"dropdown-menu"}).insertAfter(button); + + for (var i=0;i<options.options.length;i++) { + var opt = options.options[i]; + createMenuItem(opt).appendTo(topMenu); + } + } + + function isSelected(id) { + return $("#"+id).hasClass("active"); + } + function setSelected(id,state) { + if (isSelected(id) == state) { + return; + } + var opt = menuItems[id]; + if (state) { + $("#"+id).addClass("active"); + } else { + $("#"+id).removeClass("active"); + } + if (opt.onselect) { + opt.onselect.call(opt,state); + } + } + + function setDisabled(id,state) { + if (state) { + $("#"+id).parent().addClass("disabled"); + } else { + $("#"+id).parent().removeClass("disabled"); + } + } + + function addItem(id,opt) { + createMenuItem(opt).appendTo("#"+id+"-submenu"); + } + function removeItem(id) { + $("#"+id).parent().remove(); + } + + return { + init: createMenu, + setSelected: setSelected, + isSelected: isSelected, + setDisabled: setDisabled, + addItem: addItem, + removeItem: removeItem + //TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary + } +})(); diff --git a/dgbuilder/public/red/ui/notifications.js b/dgbuilder/public/red/ui/notifications.js new file mode 100644 index 00000000..c9436c68 --- /dev/null +++ b/dgbuilder/public/red/ui/notifications.js @@ -0,0 +1,59 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.notify = (function() { + var currentNotifications = []; + var c = 0; + return function(msg,type,fixed,timeout) { + if (currentNotifications.length > 4) { + var ll = currentNotifications.length; + for (var i = 0;ll > 4 && i<currentNotifications.length;i+=1) { + var notifiction = currentNotifications[i]; + if (!notifiction.fixed) { + window.clearTimeout(notifiction.timeoutid); + notifiction.close(); + ll -= 1; + } + } + } + var n = document.createElement("div"); + n.id="red-notification-"+c; + n.className = "alert"; + n.fixed = fixed; + if (type) { + n.className = "alert alert-"+type; + } + n.style.display = "none"; + n.innerHTML = msg; + $("#notifications").append(n); + $(n).slideDown(300); + n.close = (function() { + var nn = n; + return function() { + currentNotifications.splice(currentNotifications.indexOf(nn),1); + $(nn).slideUp(300, function() { + nn.parentNode.removeChild(nn); + }); + }; + })(); + if (!fixed) { + n.timeoutid = window.setTimeout(n.close,timeout||3000); + } + currentNotifications.push(n); + c+=1; + return n; + } +})(); + diff --git a/dgbuilder/public/red/ui/palette.js b/dgbuilder/public/red/ui/palette.js new file mode 100644 index 00000000..f0524e55 --- /dev/null +++ b/dgbuilder/public/red/ui/palette.js @@ -0,0 +1,230 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +RED.palette = (function() { + + var exclusion = ['config','unknown','deprecated']; + //var core = ['input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced']; + var core = ['DGEmain','DGEoutcome','DGEreturn','DGElogic']; + function createCategoryContainer(category){ + var escapedCategory = category.replace(" ","_"); + $("#palette-container").append('<div class="palette-category">'+ + '<div id="header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+category.replace("_"," ")+'</span></div>'+ + '<div class="palette-content" id="palette-base-category-'+category+'">'+ + '<div id="palette-'+category+'-input"></div>'+ + '<div id="palette-'+category+'-output"></div>'+ + '<div id="palette-'+category+'-function"></div>'+ + '</div>'+ + '</div>'); + + $("#header-"+category).on('click', function(e) { + $(this).next().slideToggle(); + $(this).children("i").toggleClass("expanded"); + }); + } + + core.forEach(createCategoryContainer); + + function addNodeType(nt,def) { + + var nodeTypeId = nt.replace(" ","_"); + + if ($("#palette_node_"+nodeTypeId).length) { + return; + } + + if (exclusion.indexOf(def.category)===-1) { + + var category = def.category.replace(" ","_"); + var rootCategory = category.split("-")[0]; + + var d = document.createElement("div"); + d.id = "palette_node_"+nodeTypeId; + d.type = nt; + + // calculate width of label text + $.fn.textWidth = function(text, font) { + if (!$.fn.textWidth.fakeEl) { + $.fn.textWidth.fakeEl = $('<span>').hide().appendTo(document.body); + } + $.fn.textWidth.fakeEl.text(text || this.val() || this.text()).css('font', font || this.css('font')); + return $.fn.textWidth.fakeEl.width(); + }; + + var label; + + if (typeof def.paletteLabel === "undefined") { + label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1]; + } else { + label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||""; + } + + var pixels = $.fn.textWidth(label, '13px helvetica'); + var nodeWidth = 90; + var labelWidth = nodeWidth - 10; + var numLines = Math.ceil(pixels / nodeWidth); + var multiLine = numLines > 1; + + // styles matching with style.css + var nodeHeight = 25; + var lineHeight = 16; + var portHeight = 10; + var multiLineNodeHeight = lineHeight * numLines + (nodeHeight - lineHeight); + + d.innerHTML = '<div class="palette_label"'+ + (multiLine ? 'style="line-height: '+ + lineHeight + 'px; margin-top: 5px"' : '')+ + '>'+label+"</div>"; + d.className="palette_node"; + if (def.icon) { + d.style.backgroundImage = "url(icons/"+def.icon+")"; + if (multiLine) { + d.style.backgroundSize = "18px 27px"; + } + if (def.align == "right") { + d.style.backgroundPosition = "95% 50%"; + } else if (def.inputs > 0) { + d.style.backgroundPosition = "10% 50%"; + } + } + + d.style.backgroundColor = def.color; + d.style.height = multiLineNodeHeight + "px"; + + if (def.outputs > 0) { + var portOut = document.createElement("div"); + portOut.className = "palette_port palette_port_output"; + if (multiLine) { + portOut.style.top = ((multiLineNodeHeight - portHeight) / 2) + "px"; + } + d.appendChild(portOut); + } + + if (def.inputs > 0) { + var portIn = document.createElement("div"); + portIn.className = "palette_port"; + if (multiLine) { + portIn.style.top = ((multiLineNodeHeight - portHeight) / 2) + "px"; + } + d.appendChild(portIn); + } + + if ($("#palette-base-category-"+rootCategory).length === 0) { + createCategoryContainer(rootCategory); + } + + if ($("#palette-"+category).length === 0) { + $("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>'); + } + + $("#palette-"+category).append(d); + d.onmousedown = function(e) { e.preventDefault(); }; + + var popOverContent; + try { + popOverContent = $("<p><b>"+label+"</b></p>"+($("script[data-help-name|='"+nt+"']").html().trim()||"<p>no information available</p>")).slice(0,2); + } catch(err) { + // Malformed HTML may cause errors. TODO: need to understand what can break + console.log("Error generating pop-over label for '"+nt+"'."); + console.log(err.toString()); + popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>"; + } + $(d).popover({ + title:d.type, + placement:"right", + trigger: "hover", + delay: { show: 750, hide: 50 }, + html: true, + container:'body', + content: popOverContent + }); + $(d).click(function() { + var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>"; + $("#tab-info").html(help); + }); + $(d).draggable({ + helper: 'clone', + appendTo: 'body', + revert: true, + revertDuration: 50 + }); + } + } + + function removeNodeType(nt) { + var nodeTypeId = nt.replace(" ","_"); + $("#palette_node_"+nodeTypeId).remove(); + } + function hideNodeType(nt) { + var nodeTypeId = nt.replace(" ","_"); + $("#palette_node_"+nodeTypeId).hide(); + } + + function showNodeType(nt) { + var nodeTypeId = nt.replace(" ","_"); + $("#palette_node_"+nodeTypeId).show(); + } + + function filterChange() { + var val = $("#palette-search-input").val(); + if (val === "") { + $("#palette-search-clear").hide(); + } else { + $("#palette-search-clear").show(); + } + + var re = new RegExp(val); + $(".palette_node").each(function(i,el) { + if (val === "" || re.test(el.id)) { + $(this).show(); + } else { + $(this).hide(); + } + }); + } + + $("#palette-search-input").focus(function(e) { + RED.keyboard.disable(); + }); + $("#palette-search-input").blur(function(e) { + RED.keyboard.enable(); + }); + + $("#palette-search-clear").on("click",function(e) { + e.preventDefault(); + $("#palette-search-input").val(""); + filterChange(); + $("#palette-search-input").focus(); + }); + + $("#palette-search-input").val(""); + $("#palette-search-input").on("keyup",function() { + filterChange(); + }); + + $("#palette-search-input").on("focus",function() { + $("body").one("mousedown",function() { + $("#palette-search-input").blur(); + }); + }); + + return { + add:addNodeType, + remove:removeNodeType, + hide:hideNodeType, + show:showNodeType + }; +})(); diff --git a/dgbuilder/public/red/ui/sidebar.js b/dgbuilder/public/red/ui/sidebar.js new file mode 100644 index 00000000..f55e516c --- /dev/null +++ b/dgbuilder/public/red/ui/sidebar.js @@ -0,0 +1,154 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.sidebar = (function() { + + //$('#sidebar').tabs(); + var sidebar_tabs = RED.tabs.create({ + id:"sidebar-tabs", + onchange:function(tab) { + $("#sidebar-content").children().hide(); + $("#"+tab.id).show(); + }, + onremove: function(tab) { + $("#"+tab.id).remove(); + } + }); + function addTab(title,content,closeable) { + $("#sidebar-content").append(content); + $(content).hide(); + sidebar_tabs.addTab({id:"tab-"+title,label:title,closeable:closeable}); + //content.style.position = "absolute"; + //$('#sidebar').tabs("refresh"); + } + + function removeTab(title) { + sidebar_tabs.removeTab("tab-"+title); + } + + var sidebarSeparator = {}; + $("#sidebar-separator").draggable({ + axis: "x", + start:function(event,ui) { + sidebarSeparator.closing = false; + sidebarSeparator.opening = false; + var winWidth = $(window).width(); + sidebarSeparator.start = ui.position.left; + sidebarSeparator.chartWidth = $("#workspace").width(); + sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2; + + + if (!RED.menu.isSelected("btn-sidebar")) { + sidebarSeparator.opening = true; + var newChartRight = 15; + $("#sidebar").addClass("closing"); + $("#workspace").css("right",newChartRight); + $("#chart-zoom-controls").css("right",newChartRight+20); + $("#sidebar").width(0); + RED.menu.setSelected("btn-sidebar",true); + RED.view.resize(); + } + + + sidebarSeparator.width = $("#sidebar").width(); + }, + drag: function(event,ui) { + var d = ui.position.left-sidebarSeparator.start; + var newSidebarWidth = sidebarSeparator.width-d; + if (sidebarSeparator.opening) { + newSidebarWidth -= 13; + } + + if (newSidebarWidth > 150) { + if (sidebarSeparator.chartWidth+d < 200) { + ui.position.left = 200+sidebarSeparator.start-sidebarSeparator.chartWidth; + d = ui.position.left-sidebarSeparator.start; + newSidebarWidth = sidebarSeparator.width-d; + } + } + + if (newSidebarWidth < 150) { + if (!sidebarSeparator.closing) { + $("#sidebar").addClass("closing"); + sidebarSeparator.closing = true; + } + if (!sidebarSeparator.opening) { + newSidebarWidth = 150; + ui.position.left = sidebarSeparator.width-(150 - sidebarSeparator.start); + d = ui.position.left-sidebarSeparator.start; + } + } else if (newSidebarWidth > 150 && (sidebarSeparator.closing || sidebarSeparator.opening)) { + sidebarSeparator.closing = false; + $("#sidebar").removeClass("closing"); + } + + var newChartRight = sidebarSeparator.chartRight-d; + $("#workspace").css("right",newChartRight); + $("#chart-zoom-controls").css("right",newChartRight+20); + $("#sidebar").width(newSidebarWidth); + + sidebar_tabs.resize(); + RED.view.resize(); + + }, + stop:function(event,ui) { + RED.view.resize(); + if (sidebarSeparator.closing) { + $("#sidebar").removeClass("closing"); + RED.menu.setSelected("btn-sidebar",false); + if ($("#sidebar").width() < 180) { + $("#sidebar").width(180); + $("#workspace").css("right",208); + $("#chart-zoom-controls").css("right",228); + } + } + $("#sidebar-separator").css("left","auto"); + $("#sidebar-separator").css("right",($("#sidebar").width()+13)+"px"); + } + }); + + function toggleSidebar(state) { + if (!state) { + $("#main-container").addClass("sidebar-closed"); + } else { + $("#main-container").removeClass("sidebar-closed"); + } + } + + function showSidebar(id) { + RED.menu.setSelected("btn-sidebar",true); + sidebar_tabs.activateTab("tab-"+id); + } + + function containsTab(id) { + return sidebar_tabs.contains("tab-"+id); + } + + + $(function() { + RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();}); + showSidebar("info"); + }); + + + return { + addTab: addTab, + removeTab: removeTab, + show: showSidebar, + containsTab: containsTab, + toggleSidebar: toggleSidebar + } + +})(); diff --git a/dgbuilder/public/red/ui/state.js b/dgbuilder/public/red/ui/state.js new file mode 100644 index 00000000..419b04b8 --- /dev/null +++ b/dgbuilder/public/red/ui/state.js @@ -0,0 +1,26 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.state = { + DEFAULT: 0, + MOVING: 1, + JOINING: 2, + MOVING_ACTIVE: 3, + ADDING: 4, + EDITING: 5, + EXPORT: 6, + IMPORT: 7, + IMPORT_DRAGGING: 8 +} diff --git a/dgbuilder/public/red/ui/tab-config.js b/dgbuilder/public/red/ui/tab-config.js new file mode 100644 index 00000000..6ef6ba00 --- /dev/null +++ b/dgbuilder/public/red/ui/tab-config.js @@ -0,0 +1,84 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.sidebar.config = (function() { + + var content = document.createElement("div"); + content.id = "tab-config"; + content.style.paddingTop = "4px"; + content.style.paddingLeft = "4px"; + content.style.paddingRight = "4px"; + + var list = $("<ul>",{class:"tab-config-list"}).appendTo(content); + + function show() { + if (!RED.sidebar.containsTab("config")) { + RED.sidebar.addTab("config",content,true); + } + refresh(); + RED.sidebar.show("config"); + } + + function refresh() { + list.empty(); + RED.nodes.eachConfig(function(node) { + var li = list.find("#tab-config-list-type-"+node.type); + if (li.length === 0) { + li = $("<li>",{id:"tab-config-list-type-"+node.type}).appendTo(list); + $('<div class="tab-config-list-type">'+node.type+'</div>').appendTo(li); + } + var label = ""; + if (typeof node._def.label == "function") { + label = node._def.label.call(node); + } else { + label = node._def.label; + } + label = label || " "; + + var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li); + entry.on('dblclick',function(e) { + RED.editor.editConfig("", node.type, node.id); + }); + + var userArray = node.users.map(function(n) { return n.id }); + entry.on('mouseover',function(e) { + RED.nodes.eachNode(function(node) { + if( userArray.indexOf(node.id) != -1) { + node.highlighted = true; + node.dirty = true; + } + }); + RED.view.redraw(); + }); + + entry.on('mouseout',function(e) { + RED.nodes.eachNode(function(node) { + if(node.highlighted) { + node.highlighted = false; + node.dirty = true; + } + }); + RED.view.redraw(); + }); + + $('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry); + $('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry); + }); + } + return { + show:show, + refresh:refresh + } +})(); diff --git a/dgbuilder/public/red/ui/tab-info.js b/dgbuilder/public/red/ui/tab-info.js new file mode 100644 index 00000000..bb8eb044 --- /dev/null +++ b/dgbuilder/public/red/ui/tab-info.js @@ -0,0 +1,90 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.sidebar.info = (function() { + + var content = document.createElement("div"); + content.id = "tab-info"; + content.style.paddingTop = "4px"; + content.style.paddingLeft = "4px"; + content.style.paddingRight = "4px"; + + RED.sidebar.addTab("info",content); + + function jsonFilter(key,value) { + if (key === "") { + return value; + } + var t = typeof value; + if ($.isArray(value)) { + return "[array:"+value.length+"]"; + } else if (t === "object") { + return "[object]" + } else if (t === "string") { + if (value.length > 30) { + return value.substring(0,30)+" ..."; + } + } + return value; + } + + function refresh(node) { + var table = '<table class="node-info"><tbody>'; + + table += '<tr class="blank"><td colspan="2">Node</td></tr>'; + table += "<tr><td>Type</td><td> "+node.type+"</td></tr>"; + table += "<tr><td>ID</td><td> "+node.id+"</td></tr>"; + table += '<tr class="blank"><td colspan="2">Properties</td></tr>'; + for (var n in node._def.defaults) { + if (node._def.defaults.hasOwnProperty(n)) { + var val = node[n]||""; + var type = typeof val; + if (type === "string") { + if (val.length > 30) { + val = val.substring(0,30)+" ..."; + } + val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); + } else if (type === "number") { + val = val.toString(); + } else if ($.isArray(val)) { + val = "[<br/>"; + for (var i=0;i<Math.min(node[n].length,10);i++) { + var vv = JSON.stringify(node[n][i],jsonFilter," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); + val += " "+i+": "+vv+"<br/>"; + } + if (node[n].length > 10) { + val += " ... "+node[n].length+" items<br/>"; + } + val += "]"; + } else { + val = JSON.stringify(val,jsonFilter," "); + val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); + } + + table += "<tr><td> "+n+"</td><td>"+val+"</td></tr>"; + } + } + table += "</tbody></table><br/>"; + table += '<div class="node-help">'+($("script[data-help-name|='"+node.type+"']").html()||"")+"</div>"; + $("#tab-info").html(table); + } + + return { + refresh:refresh, + clear: function() { + $("#tab-info").html(""); + } + } +})(); diff --git a/dgbuilder/public/red/ui/tabs.js b/dgbuilder/public/red/ui/tabs.js new file mode 100644 index 00000000..aa207583 --- /dev/null +++ b/dgbuilder/public/red/ui/tabs.js @@ -0,0 +1,127 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + + +RED.tabs = (function() { + + + function createTabs(options) { + var tabs = {}; + + var ul = $("#"+options.id) + ul.addClass("red-ui-tabs"); + ul.children().first().addClass("active"); + ul.children().addClass("red-ui-tab"); + + function onTabClick() { + activateTab($(this)); + return false; + } + + function onTabDblClick() { + if (options.ondblclick) { + options.ondblclick(tabs[$(this).attr('href').slice(1)]); + } + return false; + } + + function activateTab(link) { + if (typeof link === "string") { + link = ul.find("a[href='#"+link+"']"); + } + if (!link.parent().hasClass("active")) { + ul.children().removeClass("active"); + link.parent().addClass("active"); + if (options.onchange) { + options.onchange(tabs[link.attr('href').slice(1)]); + } + } + } + + function updateTabWidths() { + var tabs = ul.find("li.red-ui-tab"); + var width = ul.width(); + var tabCount = tabs.size(); + var tabWidth = (width-6-(tabCount*7))/tabCount; + var pct = 100*tabWidth/width; + tabs.css({width:pct+"%"}); + } + + ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick); + updateTabWidths(); + + + function removeTab(id) { + var li = ul.find("a[href='#"+id+"']").parent(); + if (li.hasClass("active")) { + var tab = li.prev(); + if (tab.size() === 0) { + tab = li.next(); + } + activateTab(tab.find("a")); + } + li.remove(); + if (options.onremove) { + options.onremove(tabs[id]); + } + delete tabs[id]; + updateTabWidths(); + } + + return { + addTab: function(tab) { + tabs[tab.id] = tab; + var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul); + var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li); + link.html(tab.label); + + link.on("click",onTabClick); + link.on("dblclick",onTabDblClick); + if (tab.closeable) { + var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li); + closeLink.html('<i class="fa fa-times" />'); + + closeLink.on("click",function(event) { + removeTab(tab.id); + }); + } + updateTabWidths(); + if (options.onadd) { + options.onadd(tab); + } + link.attr("title",tab.label); + if (ul.find("li.red-ui-tab").size() == 1) { + activateTab(link); + } + }, + removeTab: removeTab, + activateTab: activateTab, + resize: updateTabWidths, + count: function() { + return ul.find("li.red-ui-tab").size(); + }, + contains: function(id) { + return ul.find("a[href='#"+id+"']").length > 0; + } + + } + } + + return { + create: createTabs + } +})(); diff --git a/dgbuilder/public/red/ui/touch/radialMenu.js b/dgbuilder/public/red/ui/touch/radialMenu.js new file mode 100644 index 00000000..ffb6a71c --- /dev/null +++ b/dgbuilder/public/red/ui/touch/radialMenu.js @@ -0,0 +1,184 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +RED.touch = RED.touch||{}; +RED.touch.radialMenu = (function() { + + + var touchMenu = null; + var isActive = false; + var isOutside = false; + var activeOption = null; + + + function createRadial(obj,pos,options) { + isActive = true; + try { + var w = $("body").width(); + var h = $("body").height(); + + touchMenu = d3.select("body").append("div") + .style({ + position:"absolute", + top: 0, + left:0, + bottom:0, + right:0, + "z-index": 1000 + }) + .on('touchstart',function() { + hide(); + d3.event.preventDefault(); + }); + + + + + var menu = touchMenu.append("div") + .style({ + position: "absolute", + top: (pos[1]-80)+"px", + left:(pos[0]-80)+"px", + "border-radius": "80px", + width: "160px", + height: "160px", + background: "rgba(255,255,255,0.6)", + border: "1px solid #666" + }); + + var menuOpts = []; + var createMenuOpt = function(x,y,opt) { + opt.el = menu.append("div") + .style({ + position: "absolute", + top: (y+80-25)+"px", + left:(x+80-25)+"px", + "border-radius": "20px", + width: "50px", + height: "50px", + background: "#fff", + border: "2px solid #666", + "text-align": "center", + "line-height":"50px" + }); + + opt.el.html(opt.name); + + if (opt.disabled) { + opt.el.style({"border-color":"#ccc",color:"#ccc"}); + } + opt.x = x; + opt.y = y; + menuOpts.push(opt); + + opt.el.on('touchstart',function() { + opt.el.style("background","#999"); + d3.event.preventDefault(); + d3.event.stopPropagation(); + }); + opt.el.on('touchend',function() { + hide(); + opt.onselect(); + d3.event.preventDefault(); + d3.event.stopPropagation(); + }); + } + + var n = options.length; + var dang = Math.max(Math.PI/(n-1),Math.PI/4); + var ang = Math.PI; + for (var i=0;i<n;i++) { + var x = Math.floor(Math.cos(ang)*80); + var y = Math.floor(Math.sin(ang)*80); + if (options[i].name) { + createMenuOpt(x,y,options[i]); + } + ang += dang; + } + + + var hide = function() { + isActive = false; + activeOption = null; + touchMenu.remove(); + touchMenu = null; + } + + obj.on('touchend.radial',function() { + obj.on('touchend.radial',null); + obj.on('touchmenu.radial',null); + + if (activeOption) { + try { + activeOption.onselect(); + } catch(err) { + RED._debug(err); + } + hide(); + } else if (isOutside) { + hide(); + } + }); + + + + obj.on('touchmove.radial',function() { + try { + var touch0 = d3.event.touches.item(0); + var p = [touch0.pageX - pos[0],touch0.pageY-pos[1]]; + for (var i=0;i<menuOpts.length;i++) { + var opt = menuOpts[i]; + if (!opt.disabled) { + if (p[0]>opt.x-30 && p[0]<opt.x+30 && p[1]>opt.y-30 && p[1]<opt.y+30) { + if (opt !== activeOption) { + opt.el.style("background","#999"); + activeOption = opt; + } + } else if (opt === activeOption) { + opt.el.style("background","#fff"); + activeOption = null; + } else { + opt.el.style("background","#fff"); + } + } + } + if (!activeOption) { + var d = Math.abs((p[0]*p[0])+(p[1]*p[1])); + isOutside = (d > 80*80); + } + + } catch(err) { + RED._debug(err); + } + + + }); + + } catch(err) { + RED._debug(err); + } + } + + + return { + show: createRadial, + active: function() { + return isActive; + } + } + +})(); + diff --git a/dgbuilder/public/red/ui/view.js b/dgbuilder/public/red/ui/view.js new file mode 100644 index 00000000..0109d1ed --- /dev/null +++ b/dgbuilder/public/red/ui/view.js @@ -0,0 +1,2053 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + +RED.view = (function() { + /* increasing the width and height from 5000 to 7500*/ + var space_width = 7500, + space_height = 7500, + lineCurveScale = 0.75, + scaleFactor = 1, + node_width = 100, + node_height = 30; + + var touchLongPressTimeout = 1000, + startTouchDistance = 0, + startTouchCenter = [], + moveTouchCenter = [], + touchStartTime = 0; + + + var activeWorkspace = 0; + var workspaceScrollPositions = {}; + + var selected_link = null, + mousedown_link = null, + mousedown_node = null, + mousedown_port_type = null, + mousedown_port_index = 0, + mouseup_node = null, + mouse_offset = [0,0], + mouse_position = null, + mouse_mode = 0, + moving_set = [], + dirty = false, + lasso = null, + showStatus = false, + showNumbers = false, + showNodePalette = true, + lastClickNode = null, + dblClickPrimed = null, + clickTime = 0, + clickElapsed = 0; + + var clipboard = ""; + + var status_colours = { + "red": "#c00", + "green": "#5a8", + "yellow": "#F9DF31", + "blue": "#53A3F3", + "grey": "#d3d3d3" + } + + var outer = d3.select("#chart") + .append("svg:svg") + .attr("width", space_width) + .attr("height", space_height) + .attr("pointer-events", "all") + .style("cursor","crosshair"); + + var vis = outer + .append('svg:g') + .on("dblclick.zoom", null) + .append('svg:g') + .on("mousemove", canvasMouseMove) + .on("mousedown", canvasMouseDown) + .on("mouseup", canvasMouseUp) + .on("touchend", function() { + clearTimeout(touchStartTime); + touchStartTime = null; + if (RED.touch.radialMenu.active()) { + return; + } + if (lasso) { + outer_background.attr("fill","#fff"); + } + canvasMouseUp.call(this); + }) + .on("touchcancel", canvasMouseUp) + .on("touchstart", function() { + var touch0; + if (d3.event.touches.length>1) { + clearTimeout(touchStartTime); + touchStartTime = null; + d3.event.preventDefault(); + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0['pageY']-touch1['pageY']; + var b = touch0['pageX']-touch1['pageX']; + + var offset = $("#chart").offset(); + var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + startTouchCenter = [ + (touch1['pageX']+(b/2)-offset.left+scrollPos[0])/scaleFactor, + (touch1['pageY']+(a/2)-offset.top+scrollPos[1])/scaleFactor + ]; + moveTouchCenter = [ + touch1['pageX']+(b/2), + touch1['pageY']+(a/2) + ] + startTouchDistance = Math.sqrt((a*a)+(b*b)); + } else { + var obj = d3.select(document.body); + touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + var point = d3.touches(this)[0]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + //lasso = vis.append('rect') + // .attr("ox",point[0]) + // .attr("oy",point[1]) + // .attr("rx",2) + // .attr("ry",2) + // .attr("x",point[0]) + // .attr("y",point[1]) + // .attr("width",0) + // .attr("height",0) + // .attr("class","lasso"); + //outer_background.attr("fill","#e3e3f3"); + },touchLongPressTimeout); + } + }) + .on("touchmove", function(){ + if (RED.touch.radialMenu.active()) { + d3.event.preventDefault(); + return; + } + var touch0; + if (d3.event.touches.length<2) { + if (touchStartTime) { + touch0 = d3.event.touches.item(0); + var dx = (touch0.pageX-startTouchCenter[0]); + var dy = (touch0.pageY-startTouchCenter[1]); + var d = Math.abs(dx*dx+dy*dy); + if (d > 64) { + clearTimeout(touchStartTime); + touchStartTime = null; + } + } else if (lasso) { + d3.event.preventDefault(); + } + canvasMouseMove.call(this); + } else { + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0['pageY']-touch1['pageY']; + var b = touch0['pageX']-touch1['pageX']; + var offset = $("#chart").offset(); + var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + var moveTouchDistance = Math.sqrt((a*a)+(b*b)); + var touchCenter = [ + touch1['pageX']+(b/2), + touch1['pageY']+(a/2) + ]; + + if (!isNaN(moveTouchDistance)) { + oldScaleFactor = scaleFactor; + scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); + + var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% + startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), + startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) + ]; + + startTouchDistance = moveTouchDistance; + moveTouchCenter = touchCenter; + + $("#chart").scrollLeft(scrollPos[0]+deltaTouchCenter[0]); + $("#chart").scrollTop(scrollPos[1]+deltaTouchCenter[1]); + redraw(); + } + } + }); + + var outer_background = vis.append('svg:rect') + .attr('width', space_width) + .attr('height', space_height) + .attr('fill','#fff'); + + //var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]); + //var grid = vis.append('g'); + // + //grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter() + // .append("line") + // .attr( + // { + // "class":"horizontal", + // "x1" : 0, + // "x2" : 2000, + // "y1" : function(d){ return gridScale(d);}, + // "y2" : function(d){ return gridScale(d);}, + // "fill" : "none", + // "shape-rendering" : "crispEdges", + // "stroke" : "#eee", + // "stroke-width" : "1px" + // }); + //grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter() + // .append("line") + // .attr( + // { + // "class":"vertical", + // "y1" : 0, + // "y2" : 2000, + // "x1" : function(d){ return gridScale(d);}, + // "x2" : function(d){ return gridScale(d);}, + // "fill" : "none", + // "shape-rendering" : "crispEdges", + // "stroke" : "#eee", + // "stroke-width" : "1px" + // }); + + + var drag_line = vis.append("svg:path").attr("class", "drag_line"); + + var workspace_tabs = RED.tabs.create({ + id: "workspace-tabs", + onchange: function(tab) { + if (tab.type == "subflow") { + $("#workspace-toolbar").show(); + } else { + $("#workspace-toolbar").hide(); + } + var chart = $("#chart"); + if (activeWorkspace !== 0) { + workspaceScrollPositions[activeWorkspace] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + } + var scrollStartLeft = chart.scrollLeft(); + var scrollStartTop = chart.scrollTop(); + + activeWorkspace = tab.id; + if (workspaceScrollPositions[activeWorkspace]) { + chart.scrollLeft(workspaceScrollPositions[activeWorkspace].left); + chart.scrollTop(workspaceScrollPositions[activeWorkspace].top); + } else { + chart.scrollLeft(0); + chart.scrollTop(0); + } + var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; + var scrollDeltaTop = chart.scrollTop() - scrollStartTop; + if (mouse_position != null) { + mouse_position[0] += scrollDeltaLeft; + mouse_position[1] += scrollDeltaTop; + } + + clearSelection(); + RED.nodes.eachNode(function(n) { + n.dirty = true; + }); + redraw(); + }, + ondblclick: function(tab) { + showRenameWorkspaceDialog(tab.id); + }, + onadd: function(tab) { + RED.menu.addItem("btn-workspace-menu",{ + id:"btn-workspace-menu-"+tab.id.replace(".","-"), + label:tab.label, + onselect:function() { + workspace_tabs.activateTab(tab.id); + } + }); + RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); + }, + onremove: function(tab) { + RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); + RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-")); + } + }); + + var workspaceIndex = 0; + + function addWorkspace() { + var tabId = RED.nodes.id(); + do { + workspaceIndex += 1; + } while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0); + + var ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex}; + RED.nodes.addWorkspace(ws); + workspace_tabs.addTab(ws); + workspace_tabs.activateTab(tabId); + RED.history.push({t:'add',workspaces:[ws],dirty:dirty}); + RED.view.dirty(true); + } + $(function() { + $('#btn-workspace-add-tab').on("click",addWorkspace); + $('#btn-workspace-add').on("click",addWorkspace); + $('#btn-workspace-edit').on("click",function() { + showRenameWorkspaceDialog(activeWorkspace); + }); + $('#btn-workspace-delete').on("click",function() { + deleteWorkspace(activeWorkspace); + }); + }); + + function deleteWorkspace(id) { + if (workspace_tabs.count() == 1) { + return; + } + var ws = RED.nodes.workspace(id); + $( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws); + $( "#node-dialog-delete-workspace-name" ).text(ws.label); + $( "#node-dialog-delete-workspace" ).dialog('open'); + } + + function canvasMouseDown() { + if (!mousedown_node && !mousedown_link) { + selected_link = null; + updateSelection(); + } + if (mouse_mode === 0) { + if (lasso) { + lasso.remove(); + lasso = null; + } + + if (!touchStartTime) { + var point = d3.mouse(this); + lasso = vis.append('rect') + .attr("ox",point[0]) + .attr("oy",point[1]) + .attr("rx",2) + .attr("ry",2) + .attr("x",point[0]) + .attr("y",point[1]) + .attr("width",0) + .attr("height",0) + .attr("class","lasso"); + d3.event.preventDefault(); + } + } + } + + function canvasMouseMove() { + mouse_position = d3.touches(this)[0]||d3.mouse(this); + + // Prevent touch scrolling... + //if (d3.touches(this)[0]) { + // d3.event.preventDefault(); + //} + + // TODO: auto scroll the container + //var point = d3.mouse(this); + //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } + //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); + + if (lasso) { + var ox = parseInt(lasso.attr("ox")); + var oy = parseInt(lasso.attr("oy")); + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var w; + var h; + if (mouse_position[0] < ox) { + x = mouse_position[0]; + w = ox-x; + } else { + w = mouse_position[0]-x; + } + if (mouse_position[1] < oy) { + y = mouse_position[1]; + h = oy-y; + } else { + h = mouse_position[1]-y; + } + lasso + .attr("x",x) + .attr("y",y) + .attr("width",w) + .attr("height",h) + ; + return; + } + + if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + return; + } + + var mousePos; + if (mouse_mode == RED.state.JOINING) { + // update drag line + drag_line.attr("class", "drag_line"); + mousePos = mouse_position; + var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1; + var sourcePort = mousedown_port_index; + var portY = -((numOutputs-1)/2)*13 +13*sourcePort; + + var sc = (mousedown_port_type === 0)?1:-1; + + var dy = mousePos[1]-(mousedown_node.y+portY); + var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2); + var delta = Math.sqrt(dy*dy+dx*dx); + var scale = lineCurveScale; + var scaleY = 0; + + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + } + if (dx*sc < 0) { + scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + if (Math.abs(dy) < 3*node_height) { + scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + } + } + + drag_line.attr("d", + "M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+ + " C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+ + (mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+ + mousePos[0]+" "+mousePos[1] + ); + d3.event.preventDefault(); + } else if (mouse_mode == RED.state.MOVING) { + //console.log("node mouse moving"); + mousePos = mouse_position; + var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); + if (d > 2) { + mouse_mode = RED.state.MOVING_ACTIVE; + clickElapsed = 0; + } + } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) { + //console.log("node mouse moving active or IMPORT_DRAGGING"); + mousePos = mouse_position; + var node; + var i; + var minX = 0; + var minY = 0; + for (var n = 0; n<moving_set.length; n++) { + node = moving_set[n]; + if (d3.event.shiftKey) { + node.n.ox = node.n.x; + node.n.oy = node.n.y; + } + node.n.x = mousePos[0]+node.dx; + node.n.y = mousePos[1]+node.dy; + node.n.dirty = true; + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + } + if (minX !== 0 || minY !== 0) { + for (i = 0; i<moving_set.length; i++) { + node = moving_set[i]; + node.n.x -= minX; + node.n.y -= minY; + } + } + if (d3.event.shiftKey && moving_set.length > 0) { + var gridOffset = [0,0]; + node = moving_set[0]; + gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2); + gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20)); + if (gridOffset[0] !== 0 || gridOffset[1] !== 0) { + for (i = 0; i<moving_set.length; i++) { + node = moving_set[i]; + node.n.x -= gridOffset[0]; + node.n.y -= gridOffset[1]; + if (node.n.x == node.n.ox && node.n.y == node.n.oy) { + node.dirty = false; + } + } + } + } + } + redraw(); + } + + function canvasMouseUp() { + if (mousedown_node && mouse_mode == RED.state.JOINING) { + drag_line.attr("class", "drag_line_hidden"); + } + if (lasso) { + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var x2 = x+parseInt(lasso.attr("width")); + var y2 = y+parseInt(lasso.attr("height")); + if (!d3.event.ctrlKey) { + clearSelection(); + } + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace && !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}); + } + } + }); + updateSelection(); + lasso.remove(); + lasso = null; + } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) { + clearSelection(); + updateSelection(); + } + if (mouse_mode == RED.state.MOVING_ACTIVE) { + //console.log("node moved active."); + //CSS setting view dirty if the node was moved + //RED.view.dirty(true); + if (moving_set.length > 0) { + var ns = []; + for (var j=0;j<moving_set.length;j++) { + ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy}); + } + RED.history.push({t:'move',nodes:ns,dirty:dirty}); + } + } + if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { + //console.log("node moving or MOVING_ACTIVE."); + for (var i=0;i<moving_set.length;i++) { + delete moving_set[i].ox; + delete moving_set[i].oy; + } + } + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove(/* ESCAPE */ 27); + setDirty(true); + } + redraw(); + // clear mouse event vars + resetMouseVars(); + } + + $('#btn-zoom-out').click(function() {zoomOut();}); + $('#btn-zoom-zero').click(function() {zoomZero();}); + $('#btn-zoom-in').click(function() {zoomIn();}); + $("#chart").on('DOMMouseScroll mousewheel', function (evt) { + if ( evt.altKey ) { + evt.preventDefault(); + evt.stopPropagation(); + var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; + if (move <= 0) { zoomOut(); } + else { zoomIn(); } + } + }); + $("#chart").droppable({ + accept:".palette_node", + drop: function( event, ui ) { + d3.event = event; + var selected_tool = ui.draggable[0].type; + var mousePos = d3.touches(this)[0]||d3.mouse(this); + mousePos[1] += this.scrollTop; + mousePos[0] += this.scrollLeft; + mousePos[1] /= scaleFactor; + mousePos[0] /= scaleFactor; + + var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:activeWorkspace}; + + nn.type = selected_tool; + nn._def = RED.nodes.getType(nn.type); + nn.outputs = nn._def.outputs; + nn.changed = true; + + for (var d in nn._def.defaults) { + if (nn._def.defaults.hasOwnProperty(d)) { + nn[d] = nn._def.defaults[d].value; + } + } + + if (nn._def.onadd) { + nn._def.onadd.call(nn); + } + + nn.h = Math.max(node_height,(nn.outputs||0) * 15); + RED.history.push({t:'add',nodes:[nn.id],dirty:dirty}); + RED.nodes.add(nn); + RED.editor.validateNode(nn); + setDirty(true); + // auto select dropped node - so info shows (if visible) + clearSelection(); + nn.selected = true; + moving_set.push({n:nn}); + updateSelection(); + redraw(); + + if (nn._def.autoedit) { + RED.editor.edit(nn); + } + } + }); + + function zoomIn() { + if (scaleFactor < 2) { + scaleFactor += 0.1; + redraw(); + } + } + function zoomOut() { + if (scaleFactor > 0.3) { + scaleFactor -= 0.1; + redraw(); + } + } + function zoomZero() { + scaleFactor = 1; + redraw(); + } + + function selectAll() { + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace) { + if (!n.selected) { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } + } + }); + selected_link = null; + updateSelection(); + redraw(); + } + + function clearSelection() { + for (var i=0;i<moving_set.length;i++) { + var n = moving_set[i]; + n.n.dirty = true; + n.n.selected = false; + } + moving_set = []; + selected_link = null; + } + + function updateSelection() { + if (moving_set.length === 0) { + RED.menu.setDisabled("btn-export-menu",true); + RED.menu.setDisabled("btn-export-clipboard",true); + RED.menu.setDisabled("btn-export-library",true); + } else { + RED.menu.setDisabled("btn-export-menu",false); + RED.menu.setDisabled("btn-export-clipboard",false); + RED.menu.setDisabled("btn-export-library",false); + } + if (moving_set.length === 0 && selected_link == null) { + //RED.keyboard.remove(/* backspace */ 8); + RED.keyboard.remove(/* delete */ 46); + RED.keyboard.remove(/* c */ 67); + RED.keyboard.remove(/* x */ 88); + } else { + //RED.keyboard.add(/* backspace */ 8,function(){deleteSelection();d3.event.preventDefault();}); + RED.keyboard.add(/* delete */ 46,function(){deleteSelection();d3.event.preventDefault();}); + RED.keyboard.add(/* c */ 67,{ctrl:true},function(){copySelection();d3.event.preventDefault();}); + RED.keyboard.add(/* x */ 88,{ctrl:true},function(){copySelection();deleteSelection();d3.event.preventDefault();}); + } + if (moving_set.length === 0) { + RED.keyboard.remove(/* up */ 38); + RED.keyboard.remove(/* down */ 40); + RED.keyboard.remove(/* left */ 37); + RED.keyboard.remove(/* right*/ 39); + } else { + RED.keyboard.add(/* up */ 38, function() { if(d3.event.shiftKey){moveSelection( 0,-20)}else{moveSelection( 0,-1);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* down */ 40, function() { if(d3.event.shiftKey){moveSelection( 0, 20)}else{moveSelection( 0, 1);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove); + } + if (moving_set.length == 1) { + RED.sidebar.info.refresh(moving_set[0].n); + } else { + RED.sidebar.info.clear(); + } + } + function endKeyboardMove() { + //console.log("end keyboard move."); + //CSS setting view dirty if the node was moved + //RED.view.dirty(true); + var ns = []; + for (var i=0;i<moving_set.length;i++) { + ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy}); + delete moving_set[i].ox; + delete moving_set[i].oy; + } + RED.history.push({t:'move',nodes:ns,dirty:dirty}); + } + function moveSelection(dx,dy) { + var minX = 0; + var minY = 0; + var node; + + for (var i=0;i<moving_set.length;i++) { + node = moving_set[i]; + if (node.ox == null && node.oy == null) { + node.ox = node.n.x; + node.oy = node.n.y; + } + node.n.x += dx; + node.n.y += dy; + node.n.dirty = true; + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + } + + if (minX !== 0 || minY !== 0) { + for (var n = 0; n<moving_set.length; n++) { + node = moving_set[n]; + node.n.x -= minX; + node.n.y -= minY; + } + } + + redraw(); + } + function deleteSelection() { + var removedNodes = []; + var removedLinks = []; + var startDirty = dirty; + if (moving_set.length > 0) { + for (var i=0;i<moving_set.length;i++) { + var node = moving_set[i].n; + node.selected = false; + if (node.x < 0) { + node.x = 25 + } + var rmlinks = RED.nodes.remove(node.id); + removedNodes.push(node); + removedLinks = removedLinks.concat(rmlinks); + } + moving_set = []; + setDirty(true); + } + if (selected_link) { + RED.nodes.removeLink(selected_link); + removedLinks.push(selected_link); + setDirty(true); + } + RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,dirty:startDirty}); + + selected_link = null; + updateSelection(); + redraw(); + } + + function copySelection() { + if (moving_set.length > 0) { + var nns = []; + for (var n=0;n<moving_set.length;n++) { + var node = moving_set[n].n; + nns.push(RED.nodes.convertNode(node)); + } + clipboard = JSON.stringify(nns); + RED.notify(moving_set.length+" node"+(moving_set.length>1?"s":"")+" copied"); + } + } + + + function calculateTextWidth(str) { + var sp = document.createElement("span"); + sp.className = "node_label"; + sp.style.position = "absolute"; + sp.style.top = "-1000px"; + sp.innerHTML = (str||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); + document.body.appendChild(sp); + var w = sp.offsetWidth; + document.body.removeChild(sp); + return 50+w; + } + + function resetMouseVars() { + mousedown_node = null; + mouseup_node = null; + mousedown_link = null; + mouse_mode = 0; + mousedown_port_type = 0; + } + + function portMouseDown(d,portType,portIndex) { + // disable zoom + //vis.call(d3.behavior.zoom().on("zoom"), null); + mousedown_node = d; + selected_link = null; + mouse_mode = RED.state.JOINING; + mousedown_port_type = portType; + mousedown_port_index = portIndex || 0; + document.body.style.cursor = "crosshair"; + d3.event.preventDefault(); + } + + function portMouseUp(d,portType,portIndex) { + document.body.style.cursor = ""; + if (mouse_mode == RED.state.JOINING && mousedown_node) { + if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) { + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace) { + var hw = n.w/2; + var hh = n.h/2; + if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] && + n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) { + mouseup_node = n; + portType = mouseup_node._def.inputs>0?1:0; + portIndex = 0; + } + } + }); + } else { + mouseup_node = d; + } + if (portType == mousedown_port_type || mouseup_node === mousedown_node) { + drag_line.attr("class", "drag_line_hidden"); + resetMouseVars(); + return; + } + var src,dst,src_port; + if (mousedown_port_type === 0) { + src = mousedown_node; + src_port = mousedown_port_index; + dst = mouseup_node; + } else if (mousedown_port_type == 1) { + src = mouseup_node; + dst = mousedown_node; + src_port = portIndex; + } + + var existingLink = false; + RED.nodes.eachLink(function(d) { + existingLink = existingLink || (d.source === src && d.target === dst && d.sourcePort == src_port); + }); + if (!existingLink) { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + RED.history.push({t:'add',links:[link],dirty:dirty}); + setDirty(true); + } + selected_link = null; + redraw(); + } + } + + function nodeMouseUp(d) { + if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < 750) { + RED.editor.edit(d); + clickElapsed = 0; + d3.event.stopPropagation(); + return; + } + portMouseUp(d, d._def.inputs > 0 ? 1 : 0, 0); + } + + function nodeMouseDown(d) { + //var touch0 = d3.event; + //var pos = [touch0.pageX,touch0.pageY]; + //RED.touch.radialMenu.show(d3.select(this),pos); + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove(/* ESCAPE */ 27); + updateSelection(); + setDirty(true); + redraw(); + resetMouseVars(); + d3.event.stopPropagation(); + return; + } + mousedown_node = d; + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = (lastClickNode == mousedown_node); + lastClickNode = mousedown_node; + + var i; + + if (d.selected && d3.event.ctrlKey) { + d.selected = false; + for (i=0;i<moving_set.length;i+=1) { + if (moving_set[i].n === d) { + moving_set.splice(i,1); + break; + } + } + } else { + if (d3.event.shiftKey) { + clearSelection(); + var cnodes = RED.nodes.getAllFlowNodes(mousedown_node); + for (var n=0;n<cnodes.length;n++) { + cnodes[n].selected = true; + cnodes[n].dirty = true; + moving_set.push({n:cnodes[n]}); + } + } else if (!d.selected) { + if (!d3.event.ctrlKey) { + clearSelection(); + } + mousedown_node.selected = true; + moving_set.push({n:mousedown_node}); + } + selected_link = null; + if (d3.event.button != 2) { + mouse_mode = RED.state.MOVING; + 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]; + } + } + } + d.dirty = true; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function nodeButtonClicked(d) { + if (d._def.button.toggle) { + d[d._def.button.toggle] = !d[d._def.button.toggle]; + d.dirty = true; + } + if (d._def.button.onclick) { + d._def.button.onclick.call(d); + } + if (d.dirty) { + redraw(); + } + d3.event.preventDefault(); + } + + function showTouchMenu(obj,pos) { + var mdn = mousedown_node; + var options = []; + options.push({name:"delete",disabled:(moving_set.length===0),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}}); + options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}}); + options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"select",onselect:function() {selectAll();}}); + options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); + + RED.touch.radialMenu.show(obj,pos,options); + resetMouseVars(); + } + function redraw() { + vis.attr("transform","scale("+scaleFactor+")"); + outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); + + if (mouse_mode != RED.state.JOINING) { + // Don't bother redrawing nodes if we're drawing links + + var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id}); + node.exit().remove(); + + var nodeEnter = node.enter().insert("svg:g").attr("class", "node nodegroup"); + nodeEnter.each(function(d,i) { + var node = d3.select(this); + node.attr("id",d.id); + var l = d._def.label; + l = (typeof l === "function" ? l.call(d) : l)||""; + d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) ); + d.h = Math.max(node_height,(d.outputs||0) * 15); + + if (d._def.badge) { + var badge = node.append("svg:g").attr("class","node_badge_group"); + var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); + badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr('text-anchor','end').text(d._def.badge()); + if (d._def.onbadgeclick) { + badgeRect.attr("cursor","pointer") + .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); + } + } + + if (d._def.button) { + var nodeButtonGroup = node.append('svg:g') + .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) + .attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); }); + nodeButtonGroup.append('rect') + .attr("rx",8) + .attr("ry",8) + .attr("width",32) + .attr("height",node_height-4) + .attr("fill","#eee");//function(d) { return d._def.color;}) + nodeButtonGroup.append('rect') + .attr("x",function(d) { return d._def.align == "right"? 10:5}) + .attr("y",4) + .attr("rx",5) + .attr("ry",5) + .attr("width",16) + .attr("height",node_height-12) + .attr("fill",function(d) { return d._def.color;}) + .attr("cursor","pointer") + .on("mousedown",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) + .on("mouseup",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}}) + .on("mouseover",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);}}) + .on("mouseout",function(d) {if (!lasso) { + var op = 1; + if (d._def.button.toggle) { + op = d[d._def.button.toggle]?1:0.2; + } + d3.select(this).attr("fill-opacity",op); + }}) + .on("click",nodeButtonClicked) + .on("touchstart",nodeButtonClicked) + } + + var mainRect = node.append("rect") + .attr("class", "node") + .classed("node_unknown",function(d) { return d.type == "unknown"; }) + .attr("rx", 6) + .attr("ry", 6) + .attr("fill",function(d) { return d._def.color;}) + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",function(d) { + var obj = d3.select(this); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + touchStartTime = setTimeout(function() { + showTouchMenu(obj,pos); + },touchLongPressTimeout); + nodeMouseDown.call(this,d) + }) + .on("touchend", function(d) { + clearTimeout(touchStartTime); + touchStartTime = null; + if (RED.touch.radialMenu.active()) { + d3.event.stopPropagation(); + return; + } + nodeMouseUp.call(this,d); + }) + .on("mouseover",function(d) { + if (mouse_mode === 0) { + var node = d3.select(this); + node.classed("node_hovered",true); + } + }) + .on("mouseout",function(d) { + var node = d3.select(this); + node.classed("node_hovered",false); + }); + + //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); + //node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none"); + + if (d._def.icon) { + + var icon_group = node.append("g") + .attr("class","node_icon_group") + .attr("x",0).attr("y",0); + + var icon_shade = icon_group.append("rect") + .attr("x",0).attr("y",0) + .attr("class","node_icon_shade") + .attr("width","30") + .attr("stroke","none") + .attr("fill","#000") + .attr("fill-opacity","0.05") + .attr("height",function(d){return Math.min(50,d.h-4);}); + + var icon = icon_group.append("image") + .attr("xlink:href","icons/"+d._def.icon) + .attr("class","node_icon") + .attr("x",0) + .attr("width","30") + .attr("height","30"); + + var icon_shade_border = icon_group.append("path") + .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)}) + .attr("class","node_icon_shade_border") + .attr("stroke-opacity","0.1") + .attr("stroke","#000") + .attr("stroke-width","2"); + + if ("right" == d._def.align) { + icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align); + icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)}) + //icon.attr('class','node_icon node_icon_'+d._def.align); + //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align); + //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align); + } + + //if (d._def.inputs > 0 && d._def.align == null) { + // icon_shade.attr("width",35); + // icon.attr("transform","translate(5,0)"); + // icon_shade_border.attr("transform","translate(5,0)"); + //} + //if (d._def.outputs > 0 && "right" == d._def.align) { + // icon_shade.attr("width",35); //icon.attr("x",5); + //} + + var img = new Image(); + img.src = "icons/"+d._def.icon; + img.onload = function() { + icon.attr("width",Math.min(img.width,30)); + icon.attr("height",Math.min(img.height,30)); + icon.attr("x",15-Math.min(img.width,30)/2); + //if ("right" == d._def.align) { + // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); + // icon_shade.attr("x",function(d){return d.w-30}); + // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); + //} + } + + //icon.style("pointer-events","none"); + icon_group.style("pointer-events","none"); + } + var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start'); + if (d._def.align) { + text.attr('class','node_label node_label_'+d._def.align); + text.attr('text-anchor','end'); + } + + var status = node.append("svg:g").attr("class","node_status_group").style("display","none"); + + var statusRect = status.append("rect").attr("class","node_status") + .attr("x",6).attr("y",1).attr("width",9).attr("height",9) + .attr("rx",2).attr("ry",2).attr("stroke-width","3"); + + var statusLabel = status.append("svg:text") + .attr("class","node_status_label") + .attr('x',20).attr('y',9) + .style({ + 'stroke-width': 0, + 'fill': '#888', + 'font-size':'9pt', + 'stroke':'#000', + 'text-anchor':'start' + }); + + var dgNumber = node.append("svg:g").attr("class","node_dgnumber_group").style("display","none"); + + /*var dgNumberRect = dgNumber.append("rect").attr("class","node_dgnumber") + .attr("x",6).attr("y",-49).attr("width",9).attr("height",9) + .attr("rx",2).attr("ry",2).attr("stroke-width","3"); + */ + + var dgNumberLabel = dgNumber.append("svg:text") + .attr("class","node_dgnumber_label") + .attr('x',1).attr('y',-43) + .style({ + 'stroke-width': 0, + /*'fill': '#2E4F83',*/ + 'fill': 'blue', + 'font-size':'12pt', + 'stroke':'#000', + 'text-anchor':'start' + }); + + var dgNumberTitle = dgNumber.append("title") + .attr("class","node_dgnumber_title"); + + //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5}); + + if (d._def.inputs > 0) { + text.attr("x",38); + node.append("rect").attr("class","port port_input").attr("rx",3).attr("ry",3).attr("x",-5).attr("width",10).attr("height",10) + .on("mousedown",function(d){portMouseDown(d,1,0);}) + .on("touchstart",function(d){portMouseDown(d,1,0);}) + .on("mouseup",function(d){portMouseUp(d,1,0);} ) + .on("touchend",function(d){portMouseUp(d,1,0);} ) + .on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));}) + .on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);}) + } + + //node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z"); + node.append("image").attr("class","node_error hidden").attr("xlink:href","icons/node-error.png").attr("x",0).attr("y",-6).attr("width",10).attr("height",9); + node.append("image").attr("class","node_changed hidden").attr("xlink:href","icons/node-changed.png").attr("x",12).attr("y",-6).attr("width",10).attr("height",10); + }); + + node.each(function(d,i) { + if (d.dirty) { + //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette + if (d.resize) { + var l = d._def.label; + l = (typeof l === "function" ? l.call(d) : l)||""; + d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) ); + d.h = Math.max(node_height,(d.outputs||0) * 15); + } + var thisNode = d3.select(this); + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); + thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); + thisNode.selectAll(".node") + .attr("width",function(d){return d.w}) + .attr("height",function(d){return d.h}) + .classed("node_selected",function(d) { return d.selected; }) + .classed("node_highlighted",function(d) { return d.highlighted; }) + ; + //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w}); + //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30}); + + thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"}); + thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38}); + //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); + //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;}); + //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); + + + var numOutputs = d.outputs; + var y = (d.h/2)-((numOutputs-1)/2)*13; + d.ports = d.ports || d3.range(numOutputs); + d._ports = thisNode.selectAll(".port_output").data(d.ports); + d._ports.enter().append("rect").attr("class","port port_output").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) + .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) + .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() ) + .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() ) + .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));}) + .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); + d._ports.exit().remove(); + if (d._ports) { + numOutputs = d.outputs || 1; + y = (d.h/2)-((numOutputs-1)/2)*13; + var x = d.w - 5; + d._ports.each(function(d,i) { + var port = d3.select(this); + port.attr("y",(y+13*i)-5).attr("x",x); + }); + } + thisNode.selectAll('text.node_label').text(function(d,i){ + if (d._def.label) { + if (typeof d._def.label == "function") { + return d._def.label.call(d); + } else { + return d._def.label; + } + } + return ""; + }) + .attr('y', function(d){return (d.h/2)-1;}) + .attr('class',function(d){ + return 'node_label'+ + (d._def.align?' node_label_'+d._def.align:'')+ + (d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ; + }); + thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;}); + + thisNode.selectAll(".node_changed") + .attr("x",function(d){return d.w-10}) + .classed("hidden",function(d) { return !d.changed; }); + + thisNode.selectAll(".node_error") + .attr("x",function(d){return d.w-10-(d.changed?13:0)}) + .classed("hidden",function(d) { return d.valid; }); + + thisNode.selectAll(".port_input").each(function(d,i) { + var port = d3.select(this); + port.attr("y",function(d){return (d.h/2)-5;}) + }); + + thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); + thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;}); + thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)}); + + + thisNode.selectAll('.node_right_button').attr("transform",function(d){ + var x = d.w-6; + if (d._def.button.toggle && !d[d._def.button.toggle]) { + x = x - 8; + } + return "translate("+x+",2)"; + }); + thisNode.selectAll('.node_right_button rect').attr("fill-opacity",function(d){ + if (d._def.button.toggle) { + return d[d._def.button.toggle]?1:0.2; + } + return 1; + }); + + //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) { + // return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color) + //}); + + thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + thisNode.selectAll('text.node_badge_label').text(function(d,i) { + if (d._def.badge) { + if (typeof d._def.badge == "function") { + return d._def.badge.call(d); + } else { + return d._def.badge; + } + } + return ""; + }); + if (!showStatus || !d.status) { + thisNode.selectAll('.node_status_group').style("display","none"); + } else { + thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")"); + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + thisNode.selectAll('.node_status').style("display","none"); + } else { + var style; + if (d.status.shape == null || d.status.shape == "dot") { + style = { + display: "inline", + fill: fill, + stroke: fill + }; + } else if (d.status.shape == "ring" ){ + style = { + display: "inline", + fill: '#fff', + stroke: fill + } + } + thisNode.selectAll('.node_status').style(style); + } + if (d.status.text) { + thisNode.selectAll('.node_status_label').text(d.status.text); + } else { + thisNode.selectAll('.node_status_label').text(""); + } + } + //console.dir("d value"); + //console.dir(d); + if (showNumbers && d.dgnumber != null && d.dgnumber != undefined && d.dgnumber.length >0) { + thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")"); + thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString()); + var dgnumberList = d.dgnumber; + var dgnum = ""; + if(dgnumberList != null && dgnumberList.length >=1){ + dgnum = dgnumberList[0]; + thisNode.select('.node_dgnumber_label').text(dgnum); + //console.log(dgnumberList); + thisNode.select('.node_dgnumber_title').text(dgnumberList); + } + /* + if(d.dgnumber.length > 1){ + thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h-15)+")"); + thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString()); + }else{ + thisNode.selectAll('.node_dgnumber_group').style("display","inline").attr("transform","translate(9,"+(d.h+9)+")"); + thisNode.selectAll('.node_dgnumber_label').text(d.dgnumber.toString()); + } + */ + } else { + //console.log("fhfjhfjh "); + thisNode.select('.node_dgnumber').style("display","none"); + thisNode.select('.node_dgnumber_label').text(""); + thisNode.select('.node_dgnumber_title').text(""); + } + + d.dirty = false; + } + }); + } + + var link = vis.selectAll(".link").data(RED.nodes.links.filter(function(d) { return d.source.z == activeWorkspace && d.target.z == activeWorkspace }),function(d) { return d.source.id+":"+d.sourcePort+":"+d.target.id;}); + + var linkEnter = link.enter().insert("g",".node").attr("class","link"); + + linkEnter.each(function(d,i) { + var l = d3.select(this); + l.append("svg:path").attr("class","link_background link_path") + .on("mousedown",function(d) { + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + }) + .on("touchstart",function(d) { + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + }); + l.append("svg:path").attr("class","link_outline link_path"); + l.append("svg:path").attr("class","link_line link_path"); + }); + + link.exit().remove(); + + var links = vis.selectAll(".link_path") + links.attr("d",function(d){ + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + + var dy = d.target.y-(d.source.y+y); + var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2); + var delta = Math.sqrt(dy*dy+dx*dx); + var scale = lineCurveScale; + var scaleY = 0; + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + } + + if (dx < 0) { + scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + if (Math.abs(dy) < 3*node_height) { + scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + } + } + + d.x1 = d.source.x+d.source.w/2; + d.y1 = d.source.y+y; + d.x2 = d.target.x-d.target.w/2; + d.y2 = d.target.y; + + return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+ + " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ + (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ + (d.target.x-d.target.w/2)+" "+d.target.y; + }) + + link.classed("link_selected", function(d) { return d === selected_link || d.selected; }); + link.classed("link_unknown",function(d) { return d.target.type == "unknown" || d.source.type == "unknown"}); + + if (d3.event) { + d3.event.preventDefault(); + } + } + + RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();}); + RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();}); + RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();}); + RED.keyboard.add(/* - */ 189,{ctrl:true},function(){zoomOut();d3.event.preventDefault();}); + RED.keyboard.add(/* 0 */ 48,{ctrl:true},function(){zoomZero();d3.event.preventDefault();}); + RED.keyboard.add(/* v */ 86,{ctrl:true},function(){importNodes(clipboard);d3.event.preventDefault();}); + RED.keyboard.add(/* e */ 69,{ctrl:true},function(){showExportNodesDialog();d3.event.preventDefault();}); + RED.keyboard.add(/* i */ 73,{ctrl:true},function(){showImportNodesDialog();d3.event.preventDefault();}); + RED.keyboard.add(/* B */ 66,{ctrl:true},function(){RED.view.showDgNumberDialog();d3.event.preventDefault();}); + RED.keyboard.add(/* [ */ 219,{ctrl:true},function(){RED.view.showSearchTextDialog();d3.event.preventDefault();}); + RED.keyboard.add(/* O */ 79,{ctrl:true},function(){RED.view.showRequestTemplateDialog();d3.event.preventDefault();}); + + + // TODO: 'dirty' should be a property of RED.nodes - with an event callback for ui hooks + function setDirty(d) { + dirty = d; + if (dirty) { + $("#btn-deploy").removeClass("disabled"); + } else { + $("#btn-deploy").addClass("disabled"); + } + } + + /** + * Imports a new collection of nodes from a JSON String. + * - all get new IDs assigned + * - all 'selected' + * - attached to mouse for placing - 'IMPORT_DRAGGING' + */ + function importNodes(newNodesStr,touchImport) { + try { + var result = RED.nodes.import(newNodesStr,true); + if (result) { + var new_nodes = result[0]; + var new_links = result[1]; + var new_workspaces = result[2]; + + var new_ms = new_nodes.filter(function(n) { return n.z == activeWorkspace }).map(function(n) { return {n:n};}); + var new_node_ids = new_nodes.map(function(n){ return n.id; }); + + // TODO: pick a more sensible root node + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + var dx = root_node.x; + var dy = root_node.y; + + if (mouse_position == null) { + mouse_position = [0,0]; + } + + var minX = 0; + var minY = 0; + var i; + var node; + + for (i=0;i<new_ms.length;i++) { + node = new_ms[i]; + node.n.selected = true; + node.n.changed = true; + node.n.x -= dx - mouse_position[0]; + node.n.y -= dy - mouse_position[1]; + node.dx = node.n.x - mouse_position[0]; + node.dy = node.n.y - mouse_position[1]; + minX = Math.min(node.n.x-node_width/2-5,minX); + minY = Math.min(node.n.y-node_height/2-5,minY); + } + for (i=0;i<new_ms.length;i++) { + node = new_ms[i]; + node.n.x -= minX; + node.n.y -= minY; + node.dx -= minX; + node.dy -= minY; + } + if (!touchImport) { + mouse_mode = RED.state.IMPORT_DRAGGING; + } + + RED.keyboard.add(/* ESCAPE */ 27,function(){ + RED.keyboard.remove(/* ESCAPE */ 27); + clearSelection(); + RED.history.pop(); + mouse_mode = 0; + }); + clearSelection(); + moving_set = new_ms; + } + + RED.history.push({t:'add',nodes:new_node_ids,links:new_links,workspaces:new_workspaces,dirty:RED.view.dirty()}); + + + redraw(); + } + } catch(error) { + console.log(error.stack); + RED.notify("<strong>Error</strong>: "+error,"error"); + } + } + + function showExportNodesDialog() { + mouse_mode = RED.state.EXPORT; + var nns = RED.nodes.createExportableNodeSet(moving_set); + $("#dialog-form").html($("script[data-template-name='export-clipboard-dialog']").html()); + $("#node-input-export").val(JSON.stringify(nns)); + $("#node-input-export").focus(function() { + var textarea = $(this); + textarea.select(); + textarea.mouseup(function() { + textarea.unbind("mouseup"); + return false; + }); + }); + $( "#dialog" ).dialog("option","title","Export nodes to clipboard").dialog( "open" ); + $("#node-input-export").focus(); + } + + function showExportNodesLibraryDialog() { + mouse_mode = RED.state.EXPORT; + var nns = RED.nodes.createExportableNodeSet(moving_set); + $("#dialog-form").html($("script[data-template-name='export-library-dialog']").html()); + $("#node-input-filename").attr('nodes',JSON.stringify(nns)); + $( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" ); + } + + function showImportNodesDialog() { + mouse_mode = RED.state.IMPORT; + $("#dialog-form").html($("script[data-template-name='import-dialog']").html()); + $("#node-input-import").val(""); + $( "#dialog" ).dialog("option","title","Import nodes").dialog( "open" ); + } + + function showRenameWorkspaceDialog(id) { + var ws = RED.nodes.workspace(id); + $( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws); + + if (workspace_tabs.count() == 1) { + $( "#node-dialog-rename-workspace").next().find(".leftButton") + .prop('disabled',true) + .addClass("ui-state-disabled"); + } else { + $( "#node-dialog-rename-workspace").next().find(".leftButton") + .prop('disabled',false) + .removeClass("ui-state-disabled"); + } + + $( "#node-input-workspace-name" ).val(ws.label); + $( "#node-dialog-rename-workspace" ).dialog("open"); + } + + $("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();}); + $( "#node-dialog-rename-workspace" ).dialog({ + modal: true, + autoOpen: false, + width: 500, + title: "Rename sheet", + buttons: [ + { + class: 'leftButton', + text: "Delete", + click: function() { + var workspace = $(this).dialog('option','workspace'); + $( this ).dialog( "close" ); + deleteWorkspace(workspace.id); + } + }, + { + text: "Ok", + click: function() { + var workspace = $(this).dialog('option','workspace'); + var label = $( "#node-input-workspace-name" ).val(); + if (workspace.label != label) { + workspace.label = label; + var link = $("#workspace-tabs a[href='#"+workspace.id+"']"); + link.attr("title",label); + link.text(label); + RED.view.dirty(true); + } + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function(e) { + RED.keyboard.disable(); + }, + close: function(e) { + RED.keyboard.enable(); + } + }); + $( "#node-dialog-delete-workspace" ).dialog({ + modal: true, + autoOpen: false, + width: 500, + title: "Confirm delete", + buttons: [ + { + text: "Ok", + click: function() { + var workspace = $(this).dialog('option','workspace'); + RED.view.removeWorkspace(workspace); + var historyEvent = RED.nodes.removeWorkspace(workspace.id); + historyEvent.t = 'delete'; + historyEvent.dirty = dirty; + historyEvent.workspaces = [workspace]; + RED.history.push(historyEvent); + RED.view.dirty(true); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function(e) { + RED.keyboard.disable(); + }, + close: function(e) { + RED.keyboard.enable(); + } + + }); + return { + state:function(state) { + if (state == null) { + return mouse_mode + } else { + mouse_mode = state; + } + }, + addWorkspace: function(ws) { + workspace_tabs.addTab(ws); + workspace_tabs.resize(); + }, + removeWorkspace: function(ws) { + workspace_tabs.removeTab(ws.id); + }, + getWorkspace: function() { + return activeWorkspace; + }, + showWorkspace: function(id) { + workspace_tabs.activateTab(id); + }, + redraw:redraw, + dirty: function(d) { + if (d == null) { + return dirty; + } else { + setDirty(d); + } + }, + importNodes: importNodes, + resize: function() { + workspace_tabs.resize(); + }, + status: function(s) { + validateEachNodeXml(); + showStatus = s; + RED.nodes.eachNode(function(n) { n.dirty = true;}); + //TODO: subscribe/unsubscribe here + redraw(); + }, + showYangUploadDialog:function showYangUploadDialog(){ + $(function() { + var htmlStr= "<div id='yang-upload-div' style='width:375;height:225'>" + + '<form id="uploadForm" name="uploadForm" enctype="multipart/form-data" action="/api/uploadyang" method="post" >' + + "<input id='yang-file-id' name='yangFile' type='file' accept='.yang,.zip'><p style='font-size:0.7em'><i>For Module depending on multiple yang files, zip them and upload the zip file</i</p><br><br><br><br><br><p id='yang-upload-status'></p>" + + //'<input id="upload-yang-button-id" style="font-size:1em;font-weight:bold" type="button" value="Upload Yang" name="upload-yang-button">' + + "</form></div>"; + + $("#yang-upload-dialog").dialog({ + modal:true, + autoOpen :false, + title: "Upload Yang", + width: 500, + height: 260, + minWidth : 300, + minHeight :260, + buttons :[ + { + text: "Upload Yang", + click: function() { + if( document.getElementById("yang-file-id").files.length == 0 ){ + $("#yang-upload-status").html("<span>No files selected.</span>"); + return ; + } + $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("disable"); + //$("#yang-upload-status").empty().text("File is uploading..."); + $("#yang-upload-status").html("<span>Processing...Please wait</span><img src='images/page-loading.gif'>"); + $.ajax({ + url: "/api/uploadyang", + type: "POST", + data: new FormData(document.forms['uploadForm']), + contentType: false, + cache: false, + processData:false, + success: function(data) { + $("#yang-upload-status").html(""); + $("#yang-upload-status").text(data.msg); + $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable"); + /* + sliValuesObj = {}; + rpcValues = {}; + reqInputValues = {}; + for(var i=0;i<data.sliValuesObj.length;i++){ + var moduleName = data.sliValuesObj[i].moduleName; + sliValuesObj[moduleName] = data.sliValuesObj[i][moduleName + '_PROPS']; + rpcValues[moduleName] = data.sliValuesObj[i][ moduleName +'_RPCS']; + for(var k=0;rpcValues[moduleName] != undefined && k<rpcValues[moduleName].length;k++){ + var rpcName = rpcValues[moduleName][k]; + reqInputValues[moduleName + "_" + rpcName] = data.sliValuesObj[i][rpcName +"-input"]; + } + } + */ + //close the yang upload dialogog box and open the load dialogbox + $('#yang-upload-dialog').dialog("close"); + $("#btn-available-yang-modules").trigger("click"); + }, + error:function (xhr, desc, err){ + $("#yang-upload-status").html(err); + $('#yang-upload-dialog').parent().find('.ui-dialog-buttonpane button:first').button("enable"); + } + }); + } + }, + { + text: "Close", + click: function() { + $("#yang-upload-dialog").dialog("close"); + } + } + ] + }).dialog("open").html(htmlStr); + }); + }, + showDgNumberDialog: function showDgNumberDialog(){ + $(function() { + var isLoopDetected = detectLoop(); + console.log("isLoopDetected:" + isLoopDetected); + if(isLoopDetected){ + return false; + } + updateDgNumbers(); + var htmlStr="<div id='find-dgnumber-div' style='width:375;height:225'><label>DG Number</label><input id='dgnumber-val-id' type='text' value=''><p id='find-dgnumber-status' style='color:red'></p></div>"; + $("#dgnumber-find-dialog").dialog({ + modal:true, + autoOpen :false, + title: "Find Node By DGNumber", + width: 300, + height: 215, + minWidth : 300, + minHeight :215, + buttons :[ + { + text: "Find", + click: function() { + var dgnumVal = $("#dgnumber-val-id").val(); + $("#find-dgnumber-status").text(""); + if(dgnumVal != undefined && dgnumVal != '' && dgnumVal != ''){ + dgnumVal = dgnumVal.trim(); + }else{ + dgnumVal =""; + } + + var dgNumberFound = false; + var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id}); + node.each(function(d,i) { + var thisNode = d3.select(this); + var dgn = d.dgnumber; + + if(dgn != undefined && typeof dgn == 'object'){ + var found = false; + for(var k=0;k<dgn.length;k++){ + if(dgn[k] == dgnumVal){ + found = true; + break; + } + } + if(found){ + //thisNode.select("rect").style({"stroke":"blue","stroke-width" :"3","stroke-dasharray":"5,1"}); + //$("#" + d.id).find("rect").attr("class","node-found-selected"); + //$("#" + d.id).find("rect").attr("class","node node_selected"); + //thisNode.select("rect").attr("class","node node_selected"); + thisNode.select("rect").attr("class","node node_found"); + document.getElementById( d.id ).scrollIntoView(); + $("#dgnumber-find-dialog").dialog("close"); + + //display the node edit dialogbox + RED.editor.edit(d); + dgNumberFound = true; + }else{ + //thisNode.select("rect").style({"stroke":"#999","stroke-width" :"2","stroke-dasharray":"none"}); + //$("#" + d.id ).find("rect").attr("class","node-found-clear"); + thisNode.select("rect").attr("class","node"); + //$("#" + d.id ).find("rect").attr("class","node"); + //$("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found"); + + } + } + }); + if(!dgNumberFound){ + $("#find-dgnumber-status").text("DGNumber :" + dgnumVal + " Not found"); + } + } + }, + { + text: "Close", + click: function() { + $("#dgnumber-find-dialog").dialog("close"); + } + } + ], + open:function(){ + //Bind the Enter key to Find button + $('#dgnumber-find-dialog').keypress(function(e) { + if (e.keyCode == $.ui.keyCode.ENTER) { + $('#dgnumber-find-dialog').parent().find('.ui-dialog-buttonpane button:first').click(); + return false; + } + }); + $(function(){ + //set focus on the input box + $("#dgnumber-val-id").focus(); + }); + } + }).dialog("open").html(htmlStr); + }); + }, + showSearchTextDialog: function showSearchTextDialog(){ + $(function() { + var isLoopDetected = detectLoop(); + console.log("isLoopDetected:" + isLoopDetected); + if(isLoopDetected){ + return false; + } + updateDgNumbers(); + //console.log("In the showSearchTextDialog."); + var htmlStr="<div id='search-text-div' style='width:675;height:525'><label>Search Text</label><input style='width:500px;' id='search-text-val-id' type='text' value=''><br><input id='ignore-case-id' type='checkbox' name='ignorecase' value='1' >Ignore Case<br><p id='search-text-status' style='color:red'></p></div>"; + //console.log("setting up search-text-dialog."); + $("#search-text-dialog").dialog({ + modal:true, + autoOpen :false, + title: "Search text in DG", + width: 600, + height: 515, + minWidth : 500, + minHeight :415, + buttons :[ + { + text: "Search", + click: function() { + + var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id}); + var searchText = $("#search-text-val-id").val(); + $("#search-text-status").text(""); + if(searchText != undefined && searchText != '' && searchText != ''){ + searchText = searchText.trim(); + }else{ + searchText =""; + node.each(function(d,i) { + var thisNode = d3.select(this); + thisNode.select("rect").attr("class","node"); + }); + return; + } + + var foundSearchText = false; + var foundInDgNumArr = []; + //console.log("In search function"); + node.each(function(d,i) { + var thisNode = d3.select(this); + var dgn = d.dgnumber; + var xml = d.xml; + var nName = d.name; + + var ignoreCase = $('#ignore-case-id').prop('checked') + var options = 'g'; + if(ignoreCase){ + options='gi'; + } + var searchPattern = new RegExp(searchText, options ); + if(xml == undefined || xml == null){ + xml = ""; + } + if(nName == undefined || nName == null){ + nName = ""; + } + //console.log(searchPattern); + var count1 = (xml.match(searchPattern) || []).length; + //console.log(count1); + var count2 = (nName.match(searchPattern) || []).length; + //console.log(count2); + + if(count1 >0 || count2 > 0){ + thisNode.select("rect").attr("class","node text_found"); + var dgn = d.dgnumber; + + var dgNumber = dgn; + if(dgn != undefined && typeof dgn == 'object'){ + console.log("DGNUMBERS:" + dgn); + dgNumber = dgn[0]; + } + if(dgn != undefined ){ + foundInDgNumArr.push(dgNumber); + }else{ + foundInDgNumArr.push(d.type); + } + foundSearchText=true; + }else{ + thisNode.select("rect").attr("class","node"); + } + }); + if(!foundSearchText){ + $("#search-text-status").text("Search Text :" + searchText + " Not found"); + }else{ + //console.log("closing dialog"); + //$("#search-text-dialog").dialog("close"); + console.log(foundInDgNumArr); + $("#search-text-status").text("Found in DG numbers :" + foundInDgNumArr); + } + } + }, + { + text: "Close", + click: function() { + //console.log("closing dialog"); + $("#search-text-dialog").dialog("close"); + } + } + ], + open:function(){ + //console.log("called open."); + //Bind the Enter key to Find button + $('#search-text-dialog').keypress(function(e) { + if (e.keyCode == $.ui.keyCode.ENTER) { + $('#search-text-dialog').parent().find('.ui-dialog-buttonpane button:first').click(); + return false; + } + }); + $(function(){ + //set focus on the input box + $("#search-text-id").focus(); + }); + //console.log("done open call."); + } + }).dialog('open').html(htmlStr); + }); + }, + showRequestTemplateDialog: function showRequestTemplateDialog(){ + $(function() { + var currNodes = RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }) + var moduleName = ""; + var rpcName = ""; + if(currNodes != null && currNodes.length > 1){ + currNodes.forEach(function(n){ + if(n.type == 'service-logic'){ + moduleName = getAttributeValue(n.xml,"module"); + }else if(n.type == 'method'){ + rpcName = getAttributeValue(n.xml,"rpc"); + } + }); + } + console.log("moduleName:" + moduleName); + console.log("rpcName:" + rpcName); + var inputValObj = reqInputValues[moduleName + "_" + rpcName]; + var inputValStr = "Not found. Please make sure that the Module is loaded and the rpc has input."; + if(inputValObj != undefined && inputValObj != null){ + inputValStr = "{\n\"input\" : " + JSON.stringify(inputValObj,null,4)+ "\n}"; + } + + //var htmlStr="<div id='request-template-div' style='width:875px;height:575px'><textarea style='width:875px;height:575px'>" + inputValStr + "</textarea></div>" + //var htmlStr="<div id='request-template-div' style='width:750px;height:550px;font-weight:bold;font-size:1em'><pre>" + inputValStr + "</pre></div>" + var htmlStr="<textarea readonly='1' id='request-template-textarea' style='width:750px;height:550px;font-weight:bold;font-size:1em'>" + inputValStr + "</textarea>" + $("#request-input-dialog").dialog({ + dialogClass :"no-close", + modal:true, + autoOpen :false, + title: "Request Template for Module:" + moduleName + " RPC:" + rpcName, + width: 800, + height: "auto", + buttons :[ + { + text: "Close", + click: function() { + $("#request-input-dialog").dialog("close"); + } + } + ], + open:function(){ + $('#request-input-dialog').css('overflow', 'hidden'); + } + }).dialog("open").html(htmlStr); + }); + }, + showNumbers: function(s) { + console.log("showNumbers:" + s); + showNumbers = s; + RED.nodes.eachNode(function(n) { n.dirty = true;}); + redraw(); + }, + showNodePalette: function(s) { + showNodePalette=s; + if(!s){ + $("#main-container").addClass("palette-bar-closed"); + //RED.menu.setSelected("btn-node-panel",true); + }else{ + $("#main-container").removeClass("palette-bar-closed"); + } + //console.log("showNodePalette:" + showNodePalette); + }, + + //TODO: should these move to an import/export module? + showImportNodesDialog: showImportNodesDialog, + showExportNodesDialog: showExportNodesDialog, + showExportNodesLibraryDialog: showExportNodesLibraryDialog + }; +})(); diff --git a/dgbuilder/public/red/ui/view.js.orig b/dgbuilder/public/red/ui/view.js.orig new file mode 100644 index 00000000..73981fd6 --- /dev/null +++ b/dgbuilder/public/red/ui/view.js.orig @@ -0,0 +1,1639 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + +RED.view = (function() { + var space_width = 5000, + space_height = 5000, + lineCurveScale = 0.75, + scaleFactor = 1, + node_width = 100, + node_height = 30; + + var touchLongPressTimeout = 1000, + startTouchDistance = 0, + startTouchCenter = [], + moveTouchCenter = [], + touchStartTime = 0; + + + var activeWorkspace = 0; + var workspaceScrollPositions = {}; + + var selected_link = null, + mousedown_link = null, + mousedown_node = null, + mousedown_port_type = null, + mousedown_port_index = 0, + mouseup_node = null, + mouse_offset = [0,0], + mouse_position = null, + mouse_mode = 0, + moving_set = [], + dirty = false, + lasso = null, + showStatus = false, + lastClickNode = null, + dblClickPrimed = null, + clickTime = 0, + clickElapsed = 0; + + var clipboard = ""; + + var status_colours = { + "red": "#c00", + "green": "#5a8", + "yellow": "#F9DF31", + "blue": "#53A3F3", + "grey": "#d3d3d3" + } + + var outer = d3.select("#chart") + .append("svg:svg") + .attr("width", space_width) + .attr("height", space_height) + .attr("pointer-events", "all") + .style("cursor","crosshair"); + + var vis = outer + .append('svg:g') + .on("dblclick.zoom", null) + .append('svg:g') + .on("mousemove", canvasMouseMove) + .on("mousedown", canvasMouseDown) + .on("mouseup", canvasMouseUp) + .on("touchend", function() { + clearTimeout(touchStartTime); + touchStartTime = null; + if (RED.touch.radialMenu.active()) { + return; + } + if (lasso) { + outer_background.attr("fill","#fff"); + } + canvasMouseUp.call(this); + }) + .on("touchcancel", canvasMouseUp) + .on("touchstart", function() { + var touch0; + if (d3.event.touches.length>1) { + clearTimeout(touchStartTime); + touchStartTime = null; + d3.event.preventDefault(); + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0['pageY']-touch1['pageY']; + var b = touch0['pageX']-touch1['pageX']; + + var offset = $("#chart").offset(); + var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + startTouchCenter = [ + (touch1['pageX']+(b/2)-offset.left+scrollPos[0])/scaleFactor, + (touch1['pageY']+(a/2)-offset.top+scrollPos[1])/scaleFactor + ]; + moveTouchCenter = [ + touch1['pageX']+(b/2), + touch1['pageY']+(a/2) + ] + startTouchDistance = Math.sqrt((a*a)+(b*b)); + } else { + var obj = d3.select(document.body); + touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + var point = d3.touches(this)[0]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + //lasso = vis.append('rect') + // .attr("ox",point[0]) + // .attr("oy",point[1]) + // .attr("rx",2) + // .attr("ry",2) + // .attr("x",point[0]) + // .attr("y",point[1]) + // .attr("width",0) + // .attr("height",0) + // .attr("class","lasso"); + //outer_background.attr("fill","#e3e3f3"); + },touchLongPressTimeout); + } + }) + .on("touchmove", function(){ + if (RED.touch.radialMenu.active()) { + d3.event.preventDefault(); + return; + } + var touch0; + if (d3.event.touches.length<2) { + if (touchStartTime) { + touch0 = d3.event.touches.item(0); + var dx = (touch0.pageX-startTouchCenter[0]); + var dy = (touch0.pageY-startTouchCenter[1]); + var d = Math.abs(dx*dx+dy*dy); + if (d > 64) { + clearTimeout(touchStartTime); + touchStartTime = null; + } + } else if (lasso) { + d3.event.preventDefault(); + } + canvasMouseMove.call(this); + } else { + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0['pageY']-touch1['pageY']; + var b = touch0['pageX']-touch1['pageX']; + var offset = $("#chart").offset(); + var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + var moveTouchDistance = Math.sqrt((a*a)+(b*b)); + var touchCenter = [ + touch1['pageX']+(b/2), + touch1['pageY']+(a/2) + ]; + + if (!isNaN(moveTouchDistance)) { + oldScaleFactor = scaleFactor; + scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); + + var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% + startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), + startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) + ]; + + startTouchDistance = moveTouchDistance; + moveTouchCenter = touchCenter; + + $("#chart").scrollLeft(scrollPos[0]+deltaTouchCenter[0]); + $("#chart").scrollTop(scrollPos[1]+deltaTouchCenter[1]); + redraw(); + } + } + }); + + var outer_background = vis.append('svg:rect') + .attr('width', space_width) + .attr('height', space_height) + .attr('fill','#fff'); + + //var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]); + //var grid = vis.append('g'); + // + //grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter() + // .append("line") + // .attr( + // { + // "class":"horizontal", + // "x1" : 0, + // "x2" : 2000, + // "y1" : function(d){ return gridScale(d);}, + // "y2" : function(d){ return gridScale(d);}, + // "fill" : "none", + // "shape-rendering" : "crispEdges", + // "stroke" : "#eee", + // "stroke-width" : "1px" + // }); + //grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter() + // .append("line") + // .attr( + // { + // "class":"vertical", + // "y1" : 0, + // "y2" : 2000, + // "x1" : function(d){ return gridScale(d);}, + // "x2" : function(d){ return gridScale(d);}, + // "fill" : "none", + // "shape-rendering" : "crispEdges", + // "stroke" : "#eee", + // "stroke-width" : "1px" + // }); + + + var drag_line = vis.append("svg:path").attr("class", "drag_line"); + + var workspace_tabs = RED.tabs.create({ + id: "workspace-tabs", + onchange: function(tab) { + if (tab.type == "subflow") { + $("#workspace-toolbar").show(); + } else { + $("#workspace-toolbar").hide(); + } + var chart = $("#chart"); + if (activeWorkspace !== 0) { + workspaceScrollPositions[activeWorkspace] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + } + var scrollStartLeft = chart.scrollLeft(); + var scrollStartTop = chart.scrollTop(); + + activeWorkspace = tab.id; + if (workspaceScrollPositions[activeWorkspace]) { + chart.scrollLeft(workspaceScrollPositions[activeWorkspace].left); + chart.scrollTop(workspaceScrollPositions[activeWorkspace].top); + } else { + chart.scrollLeft(0); + chart.scrollTop(0); + } + var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; + var scrollDeltaTop = chart.scrollTop() - scrollStartTop; + if (mouse_position != null) { + mouse_position[0] += scrollDeltaLeft; + mouse_position[1] += scrollDeltaTop; + } + + clearSelection(); + RED.nodes.eachNode(function(n) { + n.dirty = true; + }); + redraw(); + }, + ondblclick: function(tab) { + showRenameWorkspaceDialog(tab.id); + }, + onadd: function(tab) { + RED.menu.addItem("btn-workspace-menu",{ + id:"btn-workspace-menu-"+tab.id.replace(".","-"), + label:tab.label, + onselect:function() { + workspace_tabs.activateTab(tab.id); + } + }); + RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); + }, + onremove: function(tab) { + RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); + RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-")); + } + }); + + var workspaceIndex = 0; + + function addWorkspace() { + var tabId = RED.nodes.id(); + do { + workspaceIndex += 1; + } while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0); + + var ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex}; + RED.nodes.addWorkspace(ws); + workspace_tabs.addTab(ws); + workspace_tabs.activateTab(tabId); + RED.history.push({t:'add',workspaces:[ws],dirty:dirty}); + RED.view.dirty(true); + } + $(function() { + $('#btn-workspace-add-tab').on("click",addWorkspace); + $('#btn-workspace-add').on("click",addWorkspace); + $('#btn-workspace-edit').on("click",function() { + showRenameWorkspaceDialog(activeWorkspace); + }); + $('#btn-workspace-delete').on("click",function() { + deleteWorkspace(activeWorkspace); + }); + }); + + function deleteWorkspace(id) { + if (workspace_tabs.count() == 1) { + return; + } + var ws = RED.nodes.workspace(id); + $( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws); + $( "#node-dialog-delete-workspace-name" ).text(ws.label); + $( "#node-dialog-delete-workspace" ).dialog('open'); + } + + function canvasMouseDown() { + if (!mousedown_node && !mousedown_link) { + selected_link = null; + updateSelection(); + } + if (mouse_mode === 0) { + if (lasso) { + lasso.remove(); + lasso = null; + } + + if (!touchStartTime) { + var point = d3.mouse(this); + lasso = vis.append('rect') + .attr("ox",point[0]) + .attr("oy",point[1]) + .attr("rx",2) + .attr("ry",2) + .attr("x",point[0]) + .attr("y",point[1]) + .attr("width",0) + .attr("height",0) + .attr("class","lasso"); + d3.event.preventDefault(); + } + } + } + + function canvasMouseMove() { + mouse_position = d3.touches(this)[0]||d3.mouse(this); + + // Prevent touch scrolling... + //if (d3.touches(this)[0]) { + // d3.event.preventDefault(); + //} + + // TODO: auto scroll the container + //var point = d3.mouse(this); + //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } + //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); + + if (lasso) { + var ox = parseInt(lasso.attr("ox")); + var oy = parseInt(lasso.attr("oy")); + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var w; + var h; + if (mouse_position[0] < ox) { + x = mouse_position[0]; + w = ox-x; + } else { + w = mouse_position[0]-x; + } + if (mouse_position[1] < oy) { + y = mouse_position[1]; + h = oy-y; + } else { + h = mouse_position[1]-y; + } + lasso + .attr("x",x) + .attr("y",y) + .attr("width",w) + .attr("height",h) + ; + return; + } + + if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + return; + } + + var mousePos; + if (mouse_mode == RED.state.JOINING) { + // update drag line + drag_line.attr("class", "drag_line"); + mousePos = mouse_position; + var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1; + var sourcePort = mousedown_port_index; + var portY = -((numOutputs-1)/2)*13 +13*sourcePort; + + var sc = (mousedown_port_type === 0)?1:-1; + + var dy = mousePos[1]-(mousedown_node.y+portY); + var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2); + var delta = Math.sqrt(dy*dy+dx*dx); + var scale = lineCurveScale; + var scaleY = 0; + + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + } + if (dx*sc < 0) { + scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + if (Math.abs(dy) < 3*node_height) { + scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + } + } + + drag_line.attr("d", + "M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+ + " C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+ + (mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+ + mousePos[0]+" "+mousePos[1] + ); + d3.event.preventDefault(); + } else if (mouse_mode == RED.state.MOVING) { + mousePos = mouse_position; + var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); + if (d > 2) { + mouse_mode = RED.state.MOVING_ACTIVE; + clickElapsed = 0; + } + } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) { + mousePos = mouse_position; + var node; + var i; + var minX = 0; + var minY = 0; + for (var n = 0; n<moving_set.length; n++) { + node = moving_set[n]; + if (d3.event.shiftKey) { + node.n.ox = node.n.x; + node.n.oy = node.n.y; + } + node.n.x = mousePos[0]+node.dx; + node.n.y = mousePos[1]+node.dy; + node.n.dirty = true; + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + } + if (minX !== 0 || minY !== 0) { + for (i = 0; i<moving_set.length; i++) { + node = moving_set[i]; + node.n.x -= minX; + node.n.y -= minY; + } + } + if (d3.event.shiftKey && moving_set.length > 0) { + var gridOffset = [0,0]; + node = moving_set[0]; + gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2); + gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20)); + if (gridOffset[0] !== 0 || gridOffset[1] !== 0) { + for (i = 0; i<moving_set.length; i++) { + node = moving_set[i]; + node.n.x -= gridOffset[0]; + node.n.y -= gridOffset[1]; + if (node.n.x == node.n.ox && node.n.y == node.n.oy) { + node.dirty = false; + } + } + } + } + } + redraw(); + } + + function canvasMouseUp() { + if (mousedown_node && mouse_mode == RED.state.JOINING) { + drag_line.attr("class", "drag_line_hidden"); + } + if (lasso) { + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var x2 = x+parseInt(lasso.attr("width")); + var y2 = y+parseInt(lasso.attr("height")); + if (!d3.event.ctrlKey) { + clearSelection(); + } + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace && !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}); + } + } + }); + updateSelection(); + lasso.remove(); + lasso = null; + } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) { + clearSelection(); + updateSelection(); + } + if (mouse_mode == RED.state.MOVING_ACTIVE) { + if (moving_set.length > 0) { + var ns = []; + for (var j=0;j<moving_set.length;j++) { + ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy}); + } + RED.history.push({t:'move',nodes:ns,dirty:dirty}); + } + } + if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { + for (var i=0;i<moving_set.length;i++) { + delete moving_set[i].ox; + delete moving_set[i].oy; + } + } + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove(/* ESCAPE */ 27); + setDirty(true); + } + redraw(); + // clear mouse event vars + resetMouseVars(); + } + + $('#btn-zoom-out').click(function() {zoomOut();}); + $('#btn-zoom-zero').click(function() {zoomZero();}); + $('#btn-zoom-in').click(function() {zoomIn();}); + $("#chart").on('DOMMouseScroll mousewheel', function (evt) { + if ( evt.altKey ) { + evt.preventDefault(); + evt.stopPropagation(); + var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; + if (move <= 0) { zoomOut(); } + else { zoomIn(); } + } + }); + $("#chart").droppable({ + accept:".palette_node", + drop: function( event, ui ) { + d3.event = event; + var selected_tool = ui.draggable[0].type; + var mousePos = d3.touches(this)[0]||d3.mouse(this); + mousePos[1] += this.scrollTop; + mousePos[0] += this.scrollLeft; + mousePos[1] /= scaleFactor; + mousePos[0] /= scaleFactor; + + var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:activeWorkspace}; + + nn.type = selected_tool; + nn._def = RED.nodes.getType(nn.type); + nn.outputs = nn._def.outputs; + nn.changed = true; + + for (var d in nn._def.defaults) { + if (nn._def.defaults.hasOwnProperty(d)) { + nn[d] = nn._def.defaults[d].value; + } + } + + if (nn._def.onadd) { + nn._def.onadd.call(nn); + } + + nn.h = Math.max(node_height,(nn.outputs||0) * 15); + RED.history.push({t:'add',nodes:[nn.id],dirty:dirty}); + RED.nodes.add(nn); + RED.editor.validateNode(nn); + setDirty(true); + // auto select dropped node - so info shows (if visible) + clearSelection(); + nn.selected = true; + moving_set.push({n:nn}); + updateSelection(); + redraw(); + + if (nn._def.autoedit) { + RED.editor.edit(nn); + } + } + }); + + function zoomIn() { + if (scaleFactor < 2) { + scaleFactor += 0.1; + redraw(); + } + } + function zoomOut() { + if (scaleFactor > 0.3) { + scaleFactor -= 0.1; + redraw(); + } + } + function zoomZero() { + scaleFactor = 1; + redraw(); + } + + function selectAll() { + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace) { + if (!n.selected) { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } + } + }); + selected_link = null; + updateSelection(); + redraw(); + } + + function clearSelection() { + for (var i=0;i<moving_set.length;i++) { + var n = moving_set[i]; + n.n.dirty = true; + n.n.selected = false; + } + moving_set = []; + selected_link = null; + } + + function updateSelection() { + if (moving_set.length === 0) { + RED.menu.setDisabled("btn-export-menu",true); + RED.menu.setDisabled("btn-export-clipboard",true); + RED.menu.setDisabled("btn-export-library",true); + } else { + RED.menu.setDisabled("btn-export-menu",false); + RED.menu.setDisabled("btn-export-clipboard",false); + RED.menu.setDisabled("btn-export-library",false); + } + if (moving_set.length === 0 && selected_link == null) { + RED.keyboard.remove(/* backspace */ 8); + RED.keyboard.remove(/* delete */ 46); + RED.keyboard.remove(/* c */ 67); + RED.keyboard.remove(/* x */ 88); + } else { + RED.keyboard.add(/* backspace */ 8,function(){deleteSelection();d3.event.preventDefault();}); + RED.keyboard.add(/* delete */ 46,function(){deleteSelection();d3.event.preventDefault();}); + RED.keyboard.add(/* c */ 67,{ctrl:true},function(){copySelection();d3.event.preventDefault();}); + RED.keyboard.add(/* x */ 88,{ctrl:true},function(){copySelection();deleteSelection();d3.event.preventDefault();}); + } + if (moving_set.length === 0) { + RED.keyboard.remove(/* up */ 38); + RED.keyboard.remove(/* down */ 40); + RED.keyboard.remove(/* left */ 37); + RED.keyboard.remove(/* right*/ 39); + } else { + RED.keyboard.add(/* up */ 38, function() { if(d3.event.shiftKey){moveSelection( 0,-20)}else{moveSelection( 0,-1);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* down */ 40, function() { if(d3.event.shiftKey){moveSelection( 0, 20)}else{moveSelection( 0, 1);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove); + RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove); + } + if (moving_set.length == 1) { + RED.sidebar.info.refresh(moving_set[0].n); + } else { + RED.sidebar.info.clear(); + } + } + function endKeyboardMove() { + var ns = []; + for (var i=0;i<moving_set.length;i++) { + ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy}); + delete moving_set[i].ox; + delete moving_set[i].oy; + } + RED.history.push({t:'move',nodes:ns,dirty:dirty}); + } + function moveSelection(dx,dy) { + var minX = 0; + var minY = 0; + var node; + + for (var i=0;i<moving_set.length;i++) { + node = moving_set[i]; + if (node.ox == null && node.oy == null) { + node.ox = node.n.x; + node.oy = node.n.y; + } + node.n.x += dx; + node.n.y += dy; + node.n.dirty = true; + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + } + + if (minX !== 0 || minY !== 0) { + for (var n = 0; n<moving_set.length; n++) { + node = moving_set[n]; + node.n.x -= minX; + node.n.y -= minY; + } + } + + redraw(); + } + function deleteSelection() { + var removedNodes = []; + var removedLinks = []; + var startDirty = dirty; + if (moving_set.length > 0) { + for (var i=0;i<moving_set.length;i++) { + var node = moving_set[i].n; + node.selected = false; + if (node.x < 0) { + node.x = 25 + } + var rmlinks = RED.nodes.remove(node.id); + removedNodes.push(node); + removedLinks = removedLinks.concat(rmlinks); + } + moving_set = []; + setDirty(true); + } + if (selected_link) { + RED.nodes.removeLink(selected_link); + removedLinks.push(selected_link); + setDirty(true); + } + RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,dirty:startDirty}); + + selected_link = null; + updateSelection(); + redraw(); + } + + function copySelection() { + if (moving_set.length > 0) { + var nns = []; + for (var n=0;n<moving_set.length;n++) { + var node = moving_set[n].n; + nns.push(RED.nodes.convertNode(node)); + } + clipboard = JSON.stringify(nns); + RED.notify(moving_set.length+" node"+(moving_set.length>1?"s":"")+" copied"); + } + } + + + function calculateTextWidth(str) { + var sp = document.createElement("span"); + sp.className = "node_label"; + sp.style.position = "absolute"; + sp.style.top = "-1000px"; + sp.innerHTML = (str||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); + document.body.appendChild(sp); + var w = sp.offsetWidth; + document.body.removeChild(sp); + return 50+w; + } + + function resetMouseVars() { + mousedown_node = null; + mouseup_node = null; + mousedown_link = null; + mouse_mode = 0; + mousedown_port_type = 0; + } + + function portMouseDown(d,portType,portIndex) { + // disable zoom + //vis.call(d3.behavior.zoom().on("zoom"), null); + mousedown_node = d; + selected_link = null; + mouse_mode = RED.state.JOINING; + mousedown_port_type = portType; + mousedown_port_index = portIndex || 0; + document.body.style.cursor = "crosshair"; + d3.event.preventDefault(); + } + + function portMouseUp(d,portType,portIndex) { + document.body.style.cursor = ""; + if (mouse_mode == RED.state.JOINING && mousedown_node) { + if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) { + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace) { + var hw = n.w/2; + var hh = n.h/2; + if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] && + n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) { + mouseup_node = n; + portType = mouseup_node._def.inputs>0?1:0; + portIndex = 0; + } + } + }); + } else { + mouseup_node = d; + } + if (portType == mousedown_port_type || mouseup_node === mousedown_node) { + drag_line.attr("class", "drag_line_hidden"); + resetMouseVars(); + return; + } + var src,dst,src_port; + if (mousedown_port_type === 0) { + src = mousedown_node; + src_port = mousedown_port_index; + dst = mouseup_node; + } else if (mousedown_port_type == 1) { + src = mouseup_node; + dst = mousedown_node; + src_port = portIndex; + } + + var existingLink = false; + RED.nodes.eachLink(function(d) { + existingLink = existingLink || (d.source === src && d.target === dst && d.sourcePort == src_port); + }); + if (!existingLink) { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + RED.history.push({t:'add',links:[link],dirty:dirty}); + setDirty(true); + } + selected_link = null; + redraw(); + } + } + + function nodeMouseUp(d) { + if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < 750) { + RED.editor.edit(d); + clickElapsed = 0; + d3.event.stopPropagation(); + return; + } + portMouseUp(d, d._def.inputs > 0 ? 1 : 0, 0); + } + + function nodeMouseDown(d) { + //var touch0 = d3.event; + //var pos = [touch0.pageX,touch0.pageY]; + //RED.touch.radialMenu.show(d3.select(this),pos); + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove(/* ESCAPE */ 27); + updateSelection(); + setDirty(true); + redraw(); + resetMouseVars(); + d3.event.stopPropagation(); + return; + } + mousedown_node = d; + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = (lastClickNode == mousedown_node); + lastClickNode = mousedown_node; + + var i; + + if (d.selected && d3.event.ctrlKey) { + d.selected = false; + for (i=0;i<moving_set.length;i+=1) { + if (moving_set[i].n === d) { + moving_set.splice(i,1); + break; + } + } + } else { + if (d3.event.shiftKey) { + clearSelection(); + var cnodes = RED.nodes.getAllFlowNodes(mousedown_node); + for (var n=0;n<cnodes.length;n++) { + cnodes[n].selected = true; + cnodes[n].dirty = true; + moving_set.push({n:cnodes[n]}); + } + } else if (!d.selected) { + if (!d3.event.ctrlKey) { + clearSelection(); + } + mousedown_node.selected = true; + moving_set.push({n:mousedown_node}); + } + selected_link = null; + if (d3.event.button != 2) { + mouse_mode = RED.state.MOVING; + 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]; + } + } + } + d.dirty = true; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function nodeButtonClicked(d) { + if (d._def.button.toggle) { + d[d._def.button.toggle] = !d[d._def.button.toggle]; + d.dirty = true; + } + if (d._def.button.onclick) { + d._def.button.onclick.call(d); + } + if (d.dirty) { + redraw(); + } + d3.event.preventDefault(); + } + + function showTouchMenu(obj,pos) { + var mdn = mousedown_node; + var options = []; + options.push({name:"delete",disabled:(moving_set.length===0),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}}); + options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}}); + options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"select",onselect:function() {selectAll();}}); + options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); + + RED.touch.radialMenu.show(obj,pos,options); + resetMouseVars(); + } + function redraw() { + vis.attr("transform","scale("+scaleFactor+")"); + outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); + + if (mouse_mode != RED.state.JOINING) { + // Don't bother redrawing nodes if we're drawing links + + var node = vis.selectAll(".nodegroup").data(RED.nodes.nodes.filter(function(d) { return d.z == activeWorkspace }),function(d){return d.id}); + node.exit().remove(); + + var nodeEnter = node.enter().insert("svg:g").attr("class", "node nodegroup"); + nodeEnter.each(function(d,i) { + var node = d3.select(this); + node.attr("id",d.id); + var l = d._def.label; + l = (typeof l === "function" ? l.call(d) : l)||""; + d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) ); + d.h = Math.max(node_height,(d.outputs||0) * 15); + + if (d._def.badge) { + var badge = node.append("svg:g").attr("class","node_badge_group"); + var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); + badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr('text-anchor','end').text(d._def.badge()); + if (d._def.onbadgeclick) { + badgeRect.attr("cursor","pointer") + .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); + } + } + + if (d._def.button) { + var nodeButtonGroup = node.append('svg:g') + .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) + .attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); }); + nodeButtonGroup.append('rect') + .attr("rx",8) + .attr("ry",8) + .attr("width",32) + .attr("height",node_height-4) + .attr("fill","#eee");//function(d) { return d._def.color;}) + nodeButtonGroup.append('rect') + .attr("x",function(d) { return d._def.align == "right"? 10:5}) + .attr("y",4) + .attr("rx",5) + .attr("ry",5) + .attr("width",16) + .attr("height",node_height-12) + .attr("fill",function(d) { return d._def.color;}) + .attr("cursor","pointer") + .on("mousedown",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) + .on("mouseup",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}}) + .on("mouseover",function(d) {if (!lasso) { d3.select(this).attr("fill-opacity",0.4);}}) + .on("mouseout",function(d) {if (!lasso) { + var op = 1; + if (d._def.button.toggle) { + op = d[d._def.button.toggle]?1:0.2; + } + d3.select(this).attr("fill-opacity",op); + }}) + .on("click",nodeButtonClicked) + .on("touchstart",nodeButtonClicked) + } + + var mainRect = node.append("rect") + .attr("class", "node") + .classed("node_unknown",function(d) { return d.type == "unknown"; }) + .attr("rx", 6) + .attr("ry", 6) + .attr("fill",function(d) { return d._def.color;}) + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",function(d) { + var obj = d3.select(this); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + touchStartTime = setTimeout(function() { + showTouchMenu(obj,pos); + },touchLongPressTimeout); + nodeMouseDown.call(this,d) + }) + .on("touchend", function(d) { + clearTimeout(touchStartTime); + touchStartTime = null; + if (RED.touch.radialMenu.active()) { + d3.event.stopPropagation(); + return; + } + nodeMouseUp.call(this,d); + }) + .on("mouseover",function(d) { + if (mouse_mode === 0) { + var node = d3.select(this); + node.classed("node_hovered",true); + } + }) + .on("mouseout",function(d) { + var node = d3.select(this); + node.classed("node_hovered",false); + }); + + //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); + //node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none"); + + if (d._def.icon) { + + var icon_group = node.append("g") + .attr("class","node_icon_group") + .attr("x",0).attr("y",0); + + var icon_shade = icon_group.append("rect") + .attr("x",0).attr("y",0) + .attr("class","node_icon_shade") + .attr("width","30") + .attr("stroke","none") + .attr("fill","#000") + .attr("fill-opacity","0.05") + .attr("height",function(d){return Math.min(50,d.h-4);}); + + var icon = icon_group.append("image") + .attr("xlink:href","icons/"+d._def.icon) + .attr("class","node_icon") + .attr("x",0) + .attr("width","30") + .attr("height","30"); + + var icon_shade_border = icon_group.append("path") + .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)}) + .attr("class","node_icon_shade_border") + .attr("stroke-opacity","0.1") + .attr("stroke","#000") + .attr("stroke-width","2"); + + if ("right" == d._def.align) { + icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align); + icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)}) + //icon.attr('class','node_icon node_icon_'+d._def.align); + //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align); + //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align); + } + + //if (d._def.inputs > 0 && d._def.align == null) { + // icon_shade.attr("width",35); + // icon.attr("transform","translate(5,0)"); + // icon_shade_border.attr("transform","translate(5,0)"); + //} + //if (d._def.outputs > 0 && "right" == d._def.align) { + // icon_shade.attr("width",35); //icon.attr("x",5); + //} + + var img = new Image(); + img.src = "icons/"+d._def.icon; + img.onload = function() { + icon.attr("width",Math.min(img.width,30)); + icon.attr("height",Math.min(img.height,30)); + icon.attr("x",15-Math.min(img.width,30)/2); + //if ("right" == d._def.align) { + // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); + // icon_shade.attr("x",function(d){return d.w-30}); + // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); + //} + } + + //icon.style("pointer-events","none"); + icon_group.style("pointer-events","none"); + } + var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start'); + if (d._def.align) { + text.attr('class','node_label node_label_'+d._def.align); + text.attr('text-anchor','end'); + } + + var status = node.append("svg:g").attr("class","node_status_group").style("display","none"); + + var statusRect = status.append("rect").attr("class","node_status") + .attr("x",6).attr("y",1).attr("width",9).attr("height",9) + .attr("rx",2).attr("ry",2).attr("stroke-width","3"); + + var statusLabel = status.append("svg:text") + .attr("class","node_status_label") + .attr('x',20).attr('y',9) + .style({ + 'stroke-width': 0, + 'fill': '#888', + 'font-size':'9pt', + 'stroke':'#000', + 'text-anchor':'start' + }); + + //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5}); + + if (d._def.inputs > 0) { + text.attr("x",38); + node.append("rect").attr("class","port port_input").attr("rx",3).attr("ry",3).attr("x",-5).attr("width",10).attr("height",10) + .on("mousedown",function(d){portMouseDown(d,1,0);}) + .on("touchstart",function(d){portMouseDown(d,1,0);}) + .on("mouseup",function(d){portMouseUp(d,1,0);} ) + .on("touchend",function(d){portMouseUp(d,1,0);} ) + .on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));}) + .on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);}) + } + + //node.append("path").attr("class","node_error").attr("d","M 3,-3 l 10,0 l -5,-8 z"); + node.append("image").attr("class","node_error hidden").attr("xlink:href","icons/node-error.png").attr("x",0).attr("y",-6).attr("width",10).attr("height",9); + node.append("image").attr("class","node_changed hidden").attr("xlink:href","icons/node-changed.png").attr("x",12).attr("y",-6).attr("width",10).attr("height",10); + }); + + node.each(function(d,i) { + if (d.dirty) { + //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette + if (d.resize) { + var l = d._def.label; + l = (typeof l === "function" ? l.call(d) : l)||""; + d.w = Math.max(node_width,calculateTextWidth(l)+(d._def.inputs>0?7:0) ); + d.h = Math.max(node_height,(d.outputs||0) * 15); + } + var thisNode = d3.select(this); + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); + thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); + thisNode.selectAll(".node") + .attr("width",function(d){return d.w}) + .attr("height",function(d){return d.h}) + .classed("node_selected",function(d) { return d.selected; }) + .classed("node_highlighted",function(d) { return d.highlighted; }) + ; + //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w}); + //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30}); + + thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"}); + thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38}); + //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); + //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;}); + //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); + + + var numOutputs = d.outputs; + var y = (d.h/2)-((numOutputs-1)/2)*13; + d.ports = d.ports || d3.range(numOutputs); + d._ports = thisNode.selectAll(".port_output").data(d.ports); + d._ports.enter().append("rect").attr("class","port port_output").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) + .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) + .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() ) + .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() ) + .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));}) + .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); + d._ports.exit().remove(); + if (d._ports) { + numOutputs = d.outputs || 1; + y = (d.h/2)-((numOutputs-1)/2)*13; + var x = d.w - 5; + d._ports.each(function(d,i) { + var port = d3.select(this); + port.attr("y",(y+13*i)-5).attr("x",x); + }); + } + thisNode.selectAll('text.node_label').text(function(d,i){ + if (d._def.label) { + if (typeof d._def.label == "function") { + return d._def.label.call(d); + } else { + return d._def.label; + } + } + return ""; + }) + .attr('y', function(d){return (d.h/2)-1;}) + .attr('class',function(d){ + return 'node_label'+ + (d._def.align?' node_label_'+d._def.align:'')+ + (d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ; + }); + thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;}); + + thisNode.selectAll(".node_changed") + .attr("x",function(d){return d.w-10}) + .classed("hidden",function(d) { return !d.changed; }); + + thisNode.selectAll(".node_error") + .attr("x",function(d){return d.w-10-(d.changed?13:0)}) + .classed("hidden",function(d) { return d.valid; }); + + thisNode.selectAll(".port_input").each(function(d,i) { + var port = d3.select(this); + port.attr("y",function(d){return (d.h/2)-5;}) + }); + + thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); + thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;}); + thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)}); + + + thisNode.selectAll('.node_right_button').attr("transform",function(d){ + var x = d.w-6; + if (d._def.button.toggle && !d[d._def.button.toggle]) { + x = x - 8; + } + return "translate("+x+",2)"; + }); + thisNode.selectAll('.node_right_button rect').attr("fill-opacity",function(d){ + if (d._def.button.toggle) { + return d[d._def.button.toggle]?1:0.2; + } + return 1; + }); + + //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) { + // return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color) + //}); + + thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + thisNode.selectAll('text.node_badge_label').text(function(d,i) { + if (d._def.badge) { + if (typeof d._def.badge == "function") { + return d._def.badge.call(d); + } else { + return d._def.badge; + } + } + return ""; + }); + if (!showStatus || !d.status) { + thisNode.selectAll('.node_status_group').style("display","none"); + } else { + thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")"); + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + thisNode.selectAll('.node_status').style("display","none"); + } else { + var style; + if (d.status.shape == null || d.status.shape == "dot") { + style = { + display: "inline", + fill: fill, + stroke: fill + }; + } else if (d.status.shape == "ring" ){ + style = { + display: "inline", + fill: '#fff', + stroke: fill + } + } + thisNode.selectAll('.node_status').style(style); + } + if (d.status.text) { + thisNode.selectAll('.node_status_label').text(d.status.text); + } else { + thisNode.selectAll('.node_status_label').text(""); + } + } + + d.dirty = false; + } + }); + } + + var link = vis.selectAll(".link").data(RED.nodes.links.filter(function(d) { return d.source.z == activeWorkspace && d.target.z == activeWorkspace }),function(d) { return d.source.id+":"+d.sourcePort+":"+d.target.id;}); + + var linkEnter = link.enter().insert("g",".node").attr("class","link"); + + linkEnter.each(function(d,i) { + var l = d3.select(this); + l.append("svg:path").attr("class","link_background link_path") + .on("mousedown",function(d) { + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + }) + .on("touchstart",function(d) { + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + d3.event.stopPropagation(); + }); + l.append("svg:path").attr("class","link_outline link_path"); + l.append("svg:path").attr("class","link_line link_path"); + }); + + link.exit().remove(); + + var links = vis.selectAll(".link_path") + links.attr("d",function(d){ + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + + var dy = d.target.y-(d.source.y+y); + var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2); + var delta = Math.sqrt(dy*dy+dx*dx); + var scale = lineCurveScale; + var scaleY = 0; + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + } + + if (dx < 0) { + scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + if (Math.abs(dy) < 3*node_height) { + scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + } + } + + d.x1 = d.source.x+d.source.w/2; + d.y1 = d.source.y+y; + d.x2 = d.target.x-d.target.w/2; + d.y2 = d.target.y; + + return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+ + " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ + (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ + (d.target.x-d.target.w/2)+" "+d.target.y; + }) + + link.classed("link_selected", function(d) { return d === selected_link || d.selected; }); + link.classed("link_unknown",function(d) { return d.target.type == "unknown" || d.source.type == "unknown"}); + + if (d3.event) { + d3.event.preventDefault(); + } + } + + RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();}); + RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();}); + RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();}); + RED.keyboard.add(/* - */ 189,{ctrl:true},function(){zoomOut();d3.event.preventDefault();}); + RED.keyboard.add(/* 0 */ 48,{ctrl:true},function(){zoomZero();d3.event.preventDefault();}); + RED.keyboard.add(/* v */ 86,{ctrl:true},function(){importNodes(clipboard);d3.event.preventDefault();}); + RED.keyboard.add(/* e */ 69,{ctrl:true},function(){showExportNodesDialog();d3.event.preventDefault();}); + RED.keyboard.add(/* i */ 73,{ctrl:true},function(){showImportNodesDialog();d3.event.preventDefault();}); + + // TODO: 'dirty' should be a property of RED.nodes - with an event callback for ui hooks + function setDirty(d) { + dirty = d; + if (dirty) { + $("#btn-deploy").removeClass("disabled"); + } else { + $("#btn-deploy").addClass("disabled"); + } + } + + /** + * Imports a new collection of nodes from a JSON String. + * - all get new IDs assigned + * - all 'selected' + * - attached to mouse for placing - 'IMPORT_DRAGGING' + */ + function importNodes(newNodesStr,touchImport) { + try { + var result = RED.nodes.import(newNodesStr,true); + if (result) { + var new_nodes = result[0]; + var new_links = result[1]; + var new_workspaces = result[2]; + + var new_ms = new_nodes.filter(function(n) { return n.z == activeWorkspace }).map(function(n) { return {n:n};}); + var new_node_ids = new_nodes.map(function(n){ return n.id; }); + + // TODO: pick a more sensible root node + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + var dx = root_node.x; + var dy = root_node.y; + + if (mouse_position == null) { + mouse_position = [0,0]; + } + + var minX = 0; + var minY = 0; + var i; + var node; + + for (i=0;i<new_ms.length;i++) { + node = new_ms[i]; + node.n.selected = true; + node.n.changed = true; + node.n.x -= dx - mouse_position[0]; + node.n.y -= dy - mouse_position[1]; + node.dx = node.n.x - mouse_position[0]; + node.dy = node.n.y - mouse_position[1]; + minX = Math.min(node.n.x-node_width/2-5,minX); + minY = Math.min(node.n.y-node_height/2-5,minY); + } + for (i=0;i<new_ms.length;i++) { + node = new_ms[i]; + node.n.x -= minX; + node.n.y -= minY; + node.dx -= minX; + node.dy -= minY; + } + if (!touchImport) { + mouse_mode = RED.state.IMPORT_DRAGGING; + } + + RED.keyboard.add(/* ESCAPE */ 27,function(){ + RED.keyboard.remove(/* ESCAPE */ 27); + clearSelection(); + RED.history.pop(); + mouse_mode = 0; + }); + clearSelection(); + moving_set = new_ms; + } + + RED.history.push({t:'add',nodes:new_node_ids,links:new_links,workspaces:new_workspaces,dirty:RED.view.dirty()}); + + + redraw(); + } + } catch(error) { + console.log(error.stack); + RED.notify("<strong>Error</strong>: "+error,"error"); + } + } + + function showExportNodesDialog() { + mouse_mode = RED.state.EXPORT; + var nns = RED.nodes.createExportableNodeSet(moving_set); + $("#dialog-form").html($("script[data-template-name='export-clipboard-dialog']").html()); + $("#node-input-export").val(JSON.stringify(nns)); + $("#node-input-export").focus(function() { + var textarea = $(this); + textarea.select(); + textarea.mouseup(function() { + textarea.unbind("mouseup"); + return false; + }); + }); + $( "#dialog" ).dialog("option","title","Export nodes to clipboard").dialog( "open" ); + $("#node-input-export").focus(); + } + + function showExportNodesLibraryDialog() { + mouse_mode = RED.state.EXPORT; + var nns = RED.nodes.createExportableNodeSet(moving_set); + $("#dialog-form").html($("script[data-template-name='export-library-dialog']").html()); + $("#node-input-filename").attr('nodes',JSON.stringify(nns)); + $( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" ); + } + + function showImportNodesDialog() { + mouse_mode = RED.state.IMPORT; + $("#dialog-form").html($("script[data-template-name='import-dialog']").html()); + $("#node-input-import").val(""); + $( "#dialog" ).dialog("option","title","Import nodes").dialog( "open" ); + } + + function showRenameWorkspaceDialog(id) { + var ws = RED.nodes.workspace(id); + $( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws); + + if (workspace_tabs.count() == 1) { + $( "#node-dialog-rename-workspace").next().find(".leftButton") + .prop('disabled',true) + .addClass("ui-state-disabled"); + } else { + $( "#node-dialog-rename-workspace").next().find(".leftButton") + .prop('disabled',false) + .removeClass("ui-state-disabled"); + } + + $( "#node-input-workspace-name" ).val(ws.label); + $( "#node-dialog-rename-workspace" ).dialog("open"); + } + + $("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();}); + $( "#node-dialog-rename-workspace" ).dialog({ + modal: true, + autoOpen: false, + width: 500, + title: "Rename sheet", + buttons: [ + { + class: 'leftButton', + text: "Delete", + click: function() { + var workspace = $(this).dialog('option','workspace'); + $( this ).dialog( "close" ); + deleteWorkspace(workspace.id); + } + }, + { + text: "Ok", + click: function() { + var workspace = $(this).dialog('option','workspace'); + var label = $( "#node-input-workspace-name" ).val(); + if (workspace.label != label) { + workspace.label = label; + var link = $("#workspace-tabs a[href='#"+workspace.id+"']"); + link.attr("title",label); + link.text(label); + RED.view.dirty(true); + } + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function(e) { + RED.keyboard.disable(); + }, + close: function(e) { + RED.keyboard.enable(); + } + }); + $( "#node-dialog-delete-workspace" ).dialog({ + modal: true, + autoOpen: false, + width: 500, + title: "Confirm delete", + buttons: [ + { + text: "Ok", + click: function() { + var workspace = $(this).dialog('option','workspace'); + RED.view.removeWorkspace(workspace); + var historyEvent = RED.nodes.removeWorkspace(workspace.id); + historyEvent.t = 'delete'; + historyEvent.dirty = dirty; + historyEvent.workspaces = [workspace]; + RED.history.push(historyEvent); + RED.view.dirty(true); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function(e) { + RED.keyboard.disable(); + }, + close: function(e) { + RED.keyboard.enable(); + } + + }); + + return { + state:function(state) { + if (state == null) { + return mouse_mode + } else { + mouse_mode = state; + } + }, + addWorkspace: function(ws) { + workspace_tabs.addTab(ws); + workspace_tabs.resize(); + }, + removeWorkspace: function(ws) { + workspace_tabs.removeTab(ws.id); + }, + getWorkspace: function() { + return activeWorkspace; + }, + showWorkspace: function(id) { + workspace_tabs.activateTab(id); + }, + redraw:redraw, + dirty: function(d) { + if (d == null) { + return dirty; + } else { + setDirty(d); + } + }, + importNodes: importNodes, + resize: function() { + workspace_tabs.resize(); + }, + status: function(s) { + showStatus = s; + RED.nodes.eachNode(function(n) { n.dirty = true;}); + //TODO: subscribe/unsubscribe here + redraw(); + }, + + //TODO: should these move to an import/export module? + showImportNodesDialog: showImportNodesDialog, + showExportNodesDialog: showExportNodesDialog, + showExportNodesLibraryDialog: showExportNodesLibraryDialog + }; +})(); |