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 | |
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')
-rw-r--r-- | dgbuilder/public/red/comms.js | 93 | ||||
-rw-r--r-- | dgbuilder/public/red/history.js | 99 | ||||
-rw-r--r-- | dgbuilder/public/red/main.js | 1615 | ||||
-rw-r--r-- | dgbuilder/public/red/main.js.orig | 323 | ||||
-rw-r--r-- | dgbuilder/public/red/nodes.js | 553 | ||||
-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 | ||||
-rw-r--r-- | dgbuilder/public/red/validators.js | 19 |
20 files changed, 8573 insertions, 0 deletions
diff --git a/dgbuilder/public/red/comms.js b/dgbuilder/public/red/comms.js new file mode 100644 index 00000000..64912476 --- /dev/null +++ b/dgbuilder/public/red/comms.js @@ -0,0 +1,93 @@ +/** + * 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.comms = (function() { + + var errornotification = null; + var subscriptions = {}; + var ws; + function connectWS() { + var path = location.hostname+":"+location.port+document.location.pathname; + path = path+(path.slice(-1) == "/"?"":"/")+"comms"; + path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; + ws = new WebSocket(path); + ws.onopen = function() { + if (errornotification) { + errornotification.close(); + errornotification = null; + } + for (var t in subscriptions) { + if (subscriptions.hasOwnProperty(t)) { + ws.send(JSON.stringify({subscribe:t})); + } + } + } + ws.onmessage = function(event) { + var msg = JSON.parse(event.data); + if (msg.topic) { + for (var t in subscriptions) { + if (subscriptions.hasOwnProperty(t)) { + var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); + if (re.test(msg.topic)) { + var subscribers = subscriptions[t]; + if (subscribers) { + for (var i=0;i<subscribers.length;i++) { + subscribers[i](msg.topic,msg.data); + } + } + } + } + } + } + }; + ws.onclose = function() { + if (errornotification == null) { + errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true); + } + setTimeout(connectWS,1000); + } + } + + function subscribe(topic,callback) { + if (subscriptions[topic] == null) { + subscriptions[topic] = []; + } + subscriptions[topic].push(callback); + if (ws && ws.readyState == 1) { + ws.send(JSON.stringify({subscribe:topic})); + } + } + + function unsubscribe(topic,callback) { + if (subscriptions.topic) { + for (var i=0;i<subscriptions.topic.length;i++) { + if (subscriptions.topic[i] === callback) { + subscriptions.topic.splice(i,1); + break; + } + } + if (subscriptions.topic.length === 0) { + delete subscriptions.topic; + } + } + } + + return { + connect: connectWS, + subscribe: subscribe, + unsubscribe:unsubscribe + } +})(); diff --git a/dgbuilder/public/red/history.js b/dgbuilder/public/red/history.js new file mode 100644 index 00000000..3a6d5513 --- /dev/null +++ b/dgbuilder/public/red/history.js @@ -0,0 +1,99 @@ +/** + * 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.history = (function() { + var undo_history = []; + + return { + //TODO: this function is a placeholder until there is a 'save' event that can be listened to + markAllDirty: function() { + for (var i=0;i<undo_history.length;i++) { + undo_history[i].dirty = true; + } + }, + depth: function() { + return undo_history.length; + }, + push: function(ev) { + undo_history.push(ev); + }, + pop: function() { + var ev = undo_history.pop(); + var i; + if (ev) { + if (ev.t == 'add') { + if (ev.nodes) { + for (i=0;i<ev.nodes.length;i++) { + RED.nodes.remove(ev.nodes[i]); + } + } + if (ev.links) { + for (i=0;i<ev.links.length;i++) { + RED.nodes.removeLink(ev.links[i]); + } + } + if (ev.workspaces) { + for (i=0;i<ev.workspaces.length;i++) { + RED.nodes.removeWorkspace(ev.workspaces[i].id); + RED.view.removeWorkspace(ev.workspaces[i]); + } + } + } else if (ev.t == "delete") { + if (ev.workspaces) { + for (i=0;i<ev.workspaces.length;i++) { + RED.nodes.addWorkspace(ev.workspaces[i]); + RED.view.addWorkspace(ev.workspaces[i]); + } + } + if (ev.nodes) { + for (i=0;i<ev.nodes.length;i++) { + RED.nodes.add(ev.nodes[i]); + } + } + if (ev.links) { + for (i=0;i<ev.links.length;i++) { + RED.nodes.addLink(ev.links[i]); + } + } + } else if (ev.t == "move") { + for (i=0;i<ev.nodes.length;i++) { + var n = ev.nodes[i]; + n.n.x = n.ox; + n.n.y = n.oy; + n.n.dirty = true; + } + } else if (ev.t == "edit") { + for (i in ev.changes) { + if (ev.changes.hasOwnProperty(i)) { + ev.node[i] = ev.changes[i]; + } + } + RED.editor.updateNodeProperties(ev.node); + if (ev.links) { + for (i=0;i<ev.links.length;i++) { + RED.nodes.addLink(ev.links[i]); + } + } + RED.editor.validateNode(ev.node); + ev.node.dirty = true; + ev.node.changed = ev.changed; + } + RED.view.dirty(ev.dirty); + RED.view.redraw(); + } + } + } + +})(); diff --git a/dgbuilder/public/red/main.js b/dgbuilder/public/red/main.js new file mode 100644 index 00000000..77b5cdfe --- /dev/null +++ b/dgbuilder/public/red/main.js @@ -0,0 +1,1615 @@ +/** + * 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. + **/ +var RED = (function() { + + function hideDropTarget() { + $("#dropTarget").hide(); + RED.keyboard.remove(/* ESCAPE */ 27); + } + + $('#chart').on("dragenter",function(event) { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + $("#dropTarget").css({display:'table'}); + RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget); + } + }); + + $('#dropTarget').on("dragover",function(event) { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + event.preventDefault(); + } + }) + .on("dragleave",function(event) { + hideDropTarget(); + }) + .on("drop",function(event) { + var data = event.originalEvent.dataTransfer.getData("text/plain"); + hideDropTarget(); + RED.view.importNodes(data); + event.preventDefault(); + }); + + + function save(force) { + if (RED.view.dirty()) { + //$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy + + if (!force) { + var invalid = false; + var unknownNodes = []; + RED.nodes.eachNode(function(node) { + invalid = invalid || !node.valid; + if (node.type === "unknown") { + if (unknownNodes.indexOf(node.name) == -1) { + unknownNodes.push(node.name); + } + invalid = true; + } + }); + if (invalid) { + if (unknownNodes.length > 0) { + $( "#node-dialog-confirm-deploy-config" ).hide(); + $( "#node-dialog-confirm-deploy-unknown" ).show(); + var list = "<li>"+unknownNodes.join("</li><li>")+"</li>"; + $( "#node-dialog-confirm-deploy-unknown-list" ).html(list); + } else { + $( "#node-dialog-confirm-deploy-config" ).show(); + $( "#node-dialog-confirm-deploy-unknown" ).hide(); + } + $( "#node-dialog-confirm-deploy" ).dialog( "open" ); + return; + } + } + var nns = RED.nodes.createCompleteNodeSet(); + /****************************************/ + /*added new code to save the Tabs order */ + /****************************************/ + //console.log("nns before changes."); + //console.dir(nns); + var allTabsObj={}; + var allTabsList=[]; + var nnsTabIdsArr = []; + var guiTabIdsArr = []; + nns.forEach(function(n) { + if(n.type == 'tab'){ + allTabsObj[n.id] = n; + allTabsList.push(n); + nnsTabIdsArr.push(n.id); + } + }); + var idx =0; + $("#workspace-tabs li a").each(function(){ + var href = $(this).prop("href"); + var indexOfHash = href.indexOf("#"); + var idVal = href.slice(indexOfHash+1); + guiTabIdsArr.push(idVal); + nns.splice(idx,1,allTabsObj[idVal]);; + idx++; + }); + //console.log(nnsTabIdsArr.join(",")); + //console.log(guiTabIdsArr.join(",")); + //console.log("nns after changes."); + //console.dir(nns); + /****************************/ + $("#btn-icn-deploy").removeClass('fa-download'); + $("#btn-icn-deploy").addClass('spinner'); + RED.view.dirty(false); + + $.ajax({ + url:"flows", + type: "POST", + data: JSON.stringify(nns), + contentType: "application/json; charset=utf-8" + }).done(function(data,textStatus,xhr) { + RED.notify("Successfully saved","success"); + RED.nodes.eachNode(function(node) { + if (node.changed) { + node.dirty = true; + node.changed = false; + } + if(node.credentials) { + delete node.credentials; + } + }); + RED.nodes.eachConfig(function (confNode) { + if (confNode.credentials) { + delete confNode.credentials; + } + }); + // Once deployed, cannot undo back to a clean state + RED.history.markAllDirty(); + RED.view.redraw(); + }).fail(function(xhr,textStatus,err) { + RED.view.dirty(true); + if (xhr.responseText) { + RED.notify("<strong>Error</strong>: "+xhr.responseText,"error"); + } else { + RED.notify("<strong>Error</strong>: no response from server","error"); + } + }).always(function() { + $("#btn-icn-deploy").removeClass('spinner'); + $("#btn-icn-deploy").addClass('fa-download'); + }); + } + } + + $('#btn-deploy').click(function() { save(); }); + + $( "#node-dialog-confirm-deploy" ).dialog({ + title: "Confirm deploy", + modal: true, + autoOpen: false, + width: 530, + height: 230, + buttons: [ + { + text: "Confirm deploy", + click: function() { + save(true); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }); + + function loadSettings() { + $.get('settings', function(data) { + RED.settings = data; + console.log("Node-RED: "+data.version); + loadNodeList(); + }); + } + + function loadNodeList() { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'nodes', + success: function(data) { + RED.nodes.setNodeList(data); + loadNodes(); + } + }); + } + + function loadNodes() { + $.ajax({ + headers: { + "Accept":"text/html" + }, + cache: false, + url: 'nodes', + success: function(data) { + $("body").append(data); + $(".palette-spinner").hide(); + $(".palette-scroll").show(); + $("#palette-search").show(); + loadFlows(); + } + }); + } + + function loadFlows() { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'flows', + success: function(nodes) { + RED.nodes.import(nodes); + RED.view.dirty(false); + RED.view.redraw(); + RED.comms.subscribe("status/#",function(topic,msg) { + var parts = topic.split("/"); + var node = RED.nodes.node(parts[1]); + if (node) { + node.status = msg; + if (statusEnabled) { + node.dirty = true; + RED.view.redraw(); + } + } + }); + RED.comms.subscribe("node/#",function(topic,msg) { + var i,m; + var typeList; + var info; + + if (topic == "node/added") { + var addedTypes = []; + for (i=0;i<msg.length;i++) { + m = msg[i]; + var id = m.id; + RED.nodes.addNodeSet(m); + if (m.loaded) { + addedTypes = addedTypes.concat(m.types); + $.get('nodes/'+id, function(data) { + $("body").append(data); + }); + } + } + if (addedTypes.length) { + typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); + } + } else if (topic == "node/removed") { + for (i=0;i<msg.length;i++) { + m = msg[i]; + info = RED.nodes.removeNodeSet(m.id); + if (info.added) { + typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success"); + } + } + } else if (topic == "node/enabled") { + if (msg.types) { + info = RED.nodes.getNodeSet(msg.id); + if (info.added) { + RED.nodes.enableNodeSet(msg.id); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success"); + } else { + $.get('nodes/'+msg.id, function(data) { + $("body").append(data); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); + }); + } + } + } else if (topic == "node/disabled") { + if (msg.types) { + RED.nodes.disableNodeSet(msg.id); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success"); + } + } + }); + } + }); + } + + var statusEnabled = false; + function toggleStatus(state) { + statusEnabled = state; + RED.view.status(statusEnabled); + } + + function performLoopDetection(state) { + loopDetectionEnabled = state; + console.log("loopDetectionEnabled:" + loopDetectionEnabled); + } + + var dgNumberEnabled = false; + function toggleDgNumberDisplay(state) { + dgNumberEnabled = state; + RED.view.showNumbers(dgNumberEnabled); + } + + var nodePaletteDisplay = false; + function toggleNodePaletteDisplay(state) { + nodePaletteDisplay = state; + RED.view.showNodePalette(nodePaletteDisplay); + } + function displayAllDGs(state) { + //defined showSLa() in dgstart.html + showSLA(); + } + + + function showHelp() { + + var dialog = $('#node-help'); + + //$("#node-help").draggable({ + // handle: ".modal-header" + //}); + + dialog.on('show',function() { + RED.keyboard.disable(); + }); + dialog.on('hidden',function() { + RED.keyboard.enable(); + }); + + dialog.modal(); + } + + +//Custom Functions Added here + function showCodeCloudFlows(){ + codeCloudFlowFiles=[]; + var divStyle="<style>#codecloud-data-container a { color: #067ab4; font-size: 0.75em;} #codecloud-data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }</style>"; + $.get( "/getCodeCloudFlows") + .done(function( data ) { + + var header="<div class='header'>List of DG Flows in Code Cloud</div><div><input id='flowFilterBoxId' type='text' onkeyup='filterFlows(this.value)'></div>"; + var html= divStyle + header + "<div id='codecloud-data-container'>"; + html+="<ul>"; + if(data != null){ + var files=data.files; + codeCloudFlowFiles=files; + //console.dir(files); + files.sort(function (a,b){ + if(a > b){ + return 1; + }else if(a < b){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;files != null && i<files.length;i++){ + html+="<li><a href=\"#\" onclick=\"getCommits('" + files[i] + "')\">" + files[i] + "</a></li>"; + } + } + html+="</ul>"; + html+="</div>"; + $( "#codecloud-browser-dialog" ).dialog({ + title: "Code Cloud DG Flow Browser", + modal: true, + autoOpen: true, + width: 830, + height: 630, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + $("#codecloud-browser-dialog").show(); + }) + .fail(function(err) { + RED.notify("Failed to get users."); + }) + .always(function() { + }); + } + + /* + function listYangFiles(){ + yangFilesList=[]; + var divStyle="<style>#list-yang-data-container a { color: #067ab4; font-size: 0.75em;} #list-yang-data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }</style>"; + $.get( "/getYangFiles") + .done(function( data ) { + + var header="<div class='header'>List of Yang Files </div><div><input id='flowFilterBoxId' type='text' onkeyup='filterYangFiles(this.value)'></div>"; + var html= divStyle + header + "<div id='list-yang-data-container'>"; + html+="<ul>"; + if(data != null){ + var files=data.files; + yangFilesList=files; + //console.dir(files); + files.sort(function (a,b){ + if(a > b){ + return 1; + }else if(a < b){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;files != null && i<files.length;i++){ + html+="<li><a href=\"#\" onclick=\"getYangFile('" + files[i] + "')\">" + files[i] + "</a></li>"; + } + } + html+="</ul>"; + html+="</div>"; + $( "#list-yang-browser-dialog" ).dialog({ + title: "List Yang Files", + modal: true, + autoOpen: true, + width: 830, + height: 630, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + $("#list-yang-browser-dialog").show(); + }) + .fail(function(err) { + RED.notify("Failed to get yang files."); + }) + .always(function() { + }); + } + */ + + function listYangFiles(){ + yangFilesList=[]; + + var divStyle="<style>#yang-files-data-container a { color: #067ab4; font-size: 0.75em;} #yang-files-data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } table#yang-file-list-table { width:100%; } table#yang-file-list-table th,table#yang-file-list-table td { border: 1px solid black; border-collapse: collapse; } table#yang-file-list-table th,table#yang-file-list-table td { padding: 5px; text-align: left; } table#yang-file-list-table tr:nth-child(even) { background-color: #eee; } table#yang-file-list-table tr:nth-child(odd) { background-color:#fff; } table#yang-file-list-table th { background-color: #65a9d7; color: white; } table#yang-file-list-table a { color: #337ab7; } table#yang-file-list-table a:link { color: #65a9d7; } table#yang-file-list-table a:visited { color: #636; } table#yang-file-list-table a:hover { color: #3366CC; cursor: pointer } table#yang-file-list-table a:active { color: #65a9d7 }</style>"; + $.get( "/getYangFiles") + .done(function( data ) { + + var header="<div class='header'>List of Yang Files </div><div><input id='flowFilterBoxId' type='text' onkeyup='filterYangFiles(this.value)'></div>"; + var html= divStyle + header + "<div id='yang-files-data-container'>"; + html+="<table id='yang-file-list-table' border=1>"; + html+="<tr>"; + html+="<th>File</th>"; + html+="<th>Delete</th>"; + html+="</tr>"; + if(data != null){ + var files=data.files; + yangFilesList=files; + //console.dir(files); + files.sort(function (a,b){ + if(a > b){ + return 1; + }else if(a < b){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;files != null && i<files.length;i++){ + html+="<tr><td><a href=\"#\" onclick=\"getYangFile('" + files[i] + "')\">" + files[i] + "</a></td><td>" + "<input type='button' onclick='deleteYangFile(\"" + files[i] + "\")' value='Delete'></td></td></td></tr>"; + } + } + html+="</table>"; + html+="</div>"; + $( "#list-yang-browser-dialog" ).dialog({ + title: "List Yang Files", + modal: true, + autoOpen: true, + width: 830, + height: 630, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + $("#list-yang-browser-dialog").show(); + }) + .fail(function(err) { + RED.notify("Failed to get yang files."); + }) + .always(function() { + }); + } + + + function showGitPullDialog(){ + $.get( "/getCurrentGitBranch") + .done(function( data ) { + if(data != null){ + if(data.output == "GIT_LOCAL_REPOSITORY_NOT_SET" ){ + RED.notify("Git Local Repository path is not set. Please set it by choosing Configuration from the menu."); + return; + } + + var html= "<div id='gitcheckout-container'>"; + html+="<table>"; + html+="<tr>"; + html+="<td>Branch</td>"; + html+="<td>" + data.output + "</td>"; + html+="</tr>"; + html+="<tr>"; + html+="<td><input id='gitPullBtnId' type='button' value='Pull' onclick='performGitPull()'></td>"; + html+="<td> </td>" + html+="</tr>"; + html+="<tr>"; + //html+="<td colspan=3><textarea readonly='1' rows='5' cols='200' id='responseId'></textarea></td>"; + html+="</tr>"; + html+="</table>"; + html+="<br><div id='responseId'></div>"; + html+="</div>"; + $( "#gitcommands-dialog" ).dialog({ + title: "Git Pull", + modal: true, + autoOpen: true, + width: 630, + height: 500, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + $("#responseId").css({width:'550',height:'275px', border: '2px solid lightgrey',overflow:'scroll', padding: '20px' }); + $("#responseId").hide(); + $("#gitcommands-dialog").show(); + } + }) + .fail(function(err) { + RED.notify("Failed to get gitBranch."); + }) + .always(function() { + }); + } + + function showGitStatusDialog(){ + $.get( "/getCurrentGitBranch") + .done(function( data ) { + if(data != null){ + if(data.output == "GIT_LOCAL_REPOSITORY_NOT_SET" ){ + RED.notify("Git Local Repository path is not set. Please set it by choosing Configuration from the menu."); + return; + } + + var html= "<div id='gitcheckout-container'>"; + html+="<table>"; + html+="<tr>"; + html+="<td>Branch</td>"; + html+="<td>" + data.output + "</td>"; + html+="</tr>"; + html+="<tr>"; + html+="<td><input id='gitStatusBtnId' type='button' value='Status' onclick='performGitStatus()'></td>"; + html+="<td> </td>" + html+="</tr>"; + html+="<tr>"; + //html+="<td colspan=3><textarea readonly='1' rows='5' cols='200' id='responseId'></textarea></td>"; + html+="</tr>"; + html+="</table>"; + html+="<br><div id='responseId'></div>"; + html+="</div>"; + $( "#gitcommands-dialog" ).dialog({ + title: "Git Status", + modal: true, + autoOpen: true, + width: 630, + height: 500, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + //$("#responseId").css({width:'600px',height:'100px','border-radius' : '25px', border: '2px solid lightgrey', padding: '20px' }); + $("#responseId").css({width:'550px',height:'100px', border: '2px solid lightgrey',overflow:'scroll', padding: '20px' }); + $("#responseId").hide(); + $("#gitcommands-dialog").show(); + } + }) + .fail(function(err) { + RED.notify("Failed to get gitBranch."); + }) + .always(function() { + }); + } + + function showGitCheckoutDialog(){ + $.get( "/getCurrentGitBranch") + .done(function( data ) { + if(data != null){ + if(data.output == "GIT_LOCAL_REPOSITORY_NOT_SET" ){ + RED.notify("Git Local Repository path is not set. Please set it by choosing Configuration from the menu."); + return; + } + + var html= "<div id='gitcheckout-container'>"; + html+="<table>"; + html+="<tr>"; + html+="<td>Branch</td>"; + html+="<td><input id='branchId' type='text' value='" + data.output + "'></td>"; + html+="</tr>"; + html+="<tr>"; + html+="<td><input id='checkoutBtnId' type='button' value='Checkout' onclick='performGitCheckout()'></td>"; + html+="<td> </td>" + html+="</tr>"; + html+="<tr>"; + //html+="<td colspan=3><textarea readonly='1' rows='5' cols='200' id='responseId'></textarea></td>"; + html+="</tr>"; + html+="</table>"; + html+="<br><div id='responseId'></div>"; + html+="</div>"; + $( "#gitcommands-dialog" ).dialog({ + title: "Git Checkout", + modal: true, + autoOpen: true, + width: 430, + height: 350, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + close: function(ev,ui){ + $(this).dialog("destroy"); + } + }).html(html); + $("#responseId").css({width:'300',height:'100px', border: '2px solid lightgrey',overflow:'scroll', padding: '20px' }); + $("#responseId").hide(); + $("#gitcommands-dialog").show(); + } + }) + .fail(function(err) { + RED.notify("Failed to get gitBranch."); + }) + .always(function() { + }); + } + + function showGitLocalFlows(){ + giLocalFlowFiles=[]; + var divStyle="<style>#gitlocal-data-container a { color: #067ab4; font-size: 0.75em;} #gitlocal-data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }</style>"; + $.get( "/getGitLocalFlows") + .done(function( data ) { + if(data != null && data.files != null && data.files.length == 1){ + if(data.files[0] == "GIT_LOCAL_REPOSITORY_NOT_SET" ){ + RED.notify("Git Local Repository path is not set. Please set it by choosing Configuration from the menu."); + return; + } + } + //console.log("got response from server."); + + var header="<div class='header'>List of DG Flows from Git Local Repository </div><div><input id='flowFilterBoxId' type='text' onkeyup='filterGitLocalFlows(this.value)'></div>"; + var html= divStyle + header + "<div id='gitlocal-data-container'>"; + html+="<ul>"; + if(data != null){ + var files=data.files; + gitLocalFlowFiles=files; + //console.dir(files); + files.sort(function (a,b){ + if(a > b){ + return 1; + }else if(a < b){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;files != null && i<files.length;i++){ + html+="<li><a href=\"#\" onclick=\"importGitLocalFlow('" + files[i] + "')\">" + files[i] + "</a></li>"; + } + } + html+="</ul>"; + html+="</div>"; + $( "#gitlocal-browser-dialog" ).dialog({ + title: "Git Local Repository DG Flow Browser", + modal: true, + autoOpen: true, + width: 830, + height: 630, + buttons: [ + { + text: "Close", + click: function() { + $(this).dialog("close"); + } + } + ] + }).html(html); + $("#gitlocal-browser-dialog").show(); + /* + if ($("#gitlocal-browser-dialog").dialog( "isOpen" )===true) { + console.log("gitlocal dialog box is open"); + //true + } else { + console.log("gitlocal dialog box is not open"); + // $( "#gitlocal-browser-dialog" ).dialog("destroy").remove(); + console.log($("#gitlocal-browser-dialog").dialog( "widget" )); + $("#gitlocal-browser-dialog").dialog( "open" ); + if ($("#gitlocal-browser-dialog").dialog( "isOpen" )===true) { + console.log("gitlocal dialog box is now open"); + } + $("#gitlocal-browser-dialog").show(); + //false + } + */ + }) + .fail(function(err) { + RED.notify("Failed to get flows."); + }) + .always(function() { + console.log("Done displaying"); + }); + } + + function showFlowShareUsers(){ + var divStyle="<style>#data-container a { color: #067ab4; font-size: 0.75em;} #data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }</style>"; + $.get("/flowShareUsers") + .done(function (data){ + + var header="<div class='header'>List of Downloaded DG Flows</div>"; + var html= divStyle + header + "<div id='data-container'>"; + html+="<ul>"; + if(data != null){ + var users=data.flowShareUsers; + users.sort(function (a,b){ + if(a.name > b.name){ + return 1; + }else if(a.name < b.name){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;users != null && i<users.length;i++){ + html+="<li><a href=\"#\" onclick=\"showFlowFiles('" + users[i].rootDir + "')\">" + users[i].name + "</a></li>"; + } + } + html+="</ul>"; + html+="</div>"; + $( "#dgflow-browser-dialog" ).dialog({ + title: "Downloaded DG Flows Browser", + modal: true, + autoOpen: true, + width: 530, + height: 530, + buttons: [ + { + text: "Close", + click: function() { + $( this ).dialog( "close" ); + //$(this).dialog('destroy').remove(); + } + } + ] + }).html(html); + $("#dgflow-browser-dialog").show(); + /* + if ($("#dgflow-browser-dialog").dialog( "isOpen" )===true) { + console.log("dgflow dialog box is open"); + //true + } else { + console.log("dgflow dialog box is not open"); + $("#dgflow-browser-dialog").dialog( "open" ); + $("#dgflow-browser-dialog").show(); + //false + } + */ + }) + .fail(function(err) { + RED.notify("Failed to get users."); + }) + .always(function() { + }); + } + +/* function showFlowShareUsers(){ + var divStyle="<style>#data-container a { color: #067ab4; font-size: 0.75em;} #data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }</style>"; + $.get( "/flowShareUsers") + .done(function( data ) { + + var header="<div class='header'>List of Downloaded DG Flows</div>"; + var html= divStyle + header + "<div id='data-container'>"; + html+="<ul>"; + if(data != null){ + var users=data.flowShareUsers; + users.sort(function (a,b){ + if(a.name > b.name){ + return 1; + }else if(a.name < b.name){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;users != null && i<users.length;i++){ + html+="<li><a href=\"#\" onclick=\"showFlowFiles('" + users[i].rootDir + "')\">" + users[i].name + "</a></li>"; + } + } + html+="</ul>"; + html+="</div>"; + $( "#dgflow-browser-dialog" ).dialog({ + title: "Downloaded DG Flows Browser", + modal: true, + autoOpen: true, + width: 530, + height: 530, + buttons: [ + { + text: "Close", + click: function() { + //$( this ).dialog( "close" ); + $(this).dialog('destroy').remove(); + } + } + ] + }).html(html); + //$("#dgflow-browser-dialog").show(); + $( "#dgflow-browser-dialog" ).dialog( "open" ); + }) + .fail(function(err) { + RED.notify("Failed to get users."); + }) + .always(function() { + }); + } + */ + + +function detectLoopInFlow(){ + var errList = []; + var activeWorkspace=RED.view.getWorkspace(); + var nSet=[]; + + RED.nodes.eachNode(function(n) { + if (n.z == activeWorkspace) { + nSet.push({n:n}); + } + }); + + var nodeSet = RED.nodes.createExportableNodeSet(nSet); + + var isLoopDetected = false; + var dgStartNode = getDgStartNode(nodeSet); + if(dgStartNode == null || dgStartNode == undefined) { + console.log("dgstart node not linked."); + return null; + } + + var wires = dgStartNode.wires; + var nodesInPath = {}; + var dgStartNodeId = dgStartNode.id; + if(wires != null && wires != undefined && wires[0] != undefined){ + for(var k=0;k<wires[0].length;k++){ + var val = wires[0][k]; + nodesInPath[dgStartNodeId + "->" + val] = ""; + } + }else{ + nodesInPath[dgStartNodeId + "->" + ""] = ""; + } + + var loopDetectedObj = {}; + /* the nodes will not be in order so will need to loop thru again */ + for(var m=0;nodeSet != null && m<nodeSet.length;m++){ + for(var i=0;nodeSet != null && i<nodeSet.length;i++){ + var link=nodeSet[i].id; + //console.log("NAME:" + nodeSet[i].name + ":" + link); + if(link == dgStartNodeId) continue; + var wires = nodeSet[i].wires; + //console.log("link:" + link); + var delKeys = []; + if(wires != null && wires != undefined && wires[0] != undefined){ + for(var k=0;k<wires[0].length;k++){ + var val = (wires[0])[k]; + var keys = Object.keys(nodesInPath); + //console.log("keys:" + keys); + for (var j=0;j<keys.length;j++){ + //console.log("key:" + keys[j]); + //console.log("val:" + val); + var index = keys[j].indexOf("->" + link); + var lastIndex = keys[j].lastIndexOf("->"); + if(index != -1 && index == lastIndex){ + //delete nodesInPath[key]; + var previousNodeId = keys[j].substr(lastIndex +2); + var indexOfArrow = -1; + if(previousNodeId != ""){ + indexOfArrow = previousNodeId.indexOf("->"); + } + if(previousNodeId != null && indexOfArrow != -1){ + previousNodeId = previousNodeId.substr(0,indexOfArrow); + } + nodesInPath[keys[j] + "->" + val] = ""; + //console.log("keys[j]:" + keys[j]); + delKeys.push(keys[j]); + var prevNodeIdIndex = keys[j].indexOf("->" + previousNodeId); + var priorOccurence = keys[j].indexOf(val + "->"); + if(priorOccurence != -1 && priorOccurence<prevNodeIdIndex){ + //console.log("previousNodeId:" + previousNodeId); + //console.log("val:" + val); + var n1 = getNode(nodeSet,previousNodeId); + var n2 = getNode(nodeSet,val); + //console.log("loop detected for node " + n1.name + " and " + n2.name); + loopDetectedObj[n1.name + "->" + n2.name] ="looped"; + console.dir(loopDetectedObj); + errList.push("Loop detected between " + n1.name + " and " + n2.name); + isLoopDetected = true; + } + } + } + } + } + for(var l=0;delKeys != null && l<delKeys.length;l++){ + delete nodesInPath[delKeys[l]]; + } + } + + + } + /* + if(loopDetectedObj != null ){ + var msg = ""; + for(var key in loopDetectedObj){ + if(loopDetectedObj.hasOwnProperty(key)) { + console.log("Loop detected " + key); + msg += "<strong>Loop detected for:" + key + "</strong><br>"; + } + } + if(msg != ""){ + isLoopDetected = true; + //RED.notify(msg); + } + } + */ + //images/page-loading.gif + return errList; +} + +function showLoopDetectionBox(){ + $(function() { + var htmlStr="<div id='loop-box-div' style='width:375;height:225'><p>Loop detection in Progress ...</p><img src='images/page-loading.gif'></div>" + $("#loop-detection-dialog").dialog({ + modal:true, + autoOpen :true, + title: "DG Flow Loop Detection", + width: 400, + height: 250, + minWidth : 400, + minHeight :200, + }).html(htmlStr); + if($("#loop-detection-dialog").dialog("isOpen") == true){ + var errList = detectLoopInFlow(); + var errList=[]; + if(errList == null){ + $("#loop-detection-dialog").dialog("close"); + } + var msgHtml = ""; + for(var i=0;errList != null && i<errList.length;i++){ + msgHtml += "<p>" + errList[i] + "</p>"; + } + if(msgHtml == ""){ + $("loop-box-div").html("<p>SUCCESS. No Loop detected.</p>"); + }else{ + $("loop-box-div").html(msgHtml); + } + } + }); + +} + +function showSelectedTabs(){ + var tabSheets = []; + var beforeTabsOrder=[]; + $(".red-ui-tabs li a").each(function(i){ + var id=$(this).attr("href").replace('#',''); + var title=$(this).attr("title"); + var isVisible = $(this).parent().is(":visible"); + if(title != 'info'){ + tabSheets.push({"id" : id ,"title":title,"module":"NOT_SET","version" : "NOT_SET","rpc":"NOT_SET","isVisible":isVisible}); + beforeTabsOrder.push(id); + } + }); + + RED.nodes.eachNode(function(n) { + if(n.type == 'service-logic'){ + var id = n.z; + var module = n.module; + tabSheets.forEach(function(tab){ + if(tab.id == id){ + tab.module=module; + tab.version=n.version; + } + }); + }else if(n.type == 'method'){ + var id = n.z; + tabSheets.forEach(function(tab){ + if(tab.id == id){ + var rpc=getAttributeValue(n.xml,"rpc"); + tab.rpc=rpc; + } + }); + } + }); + //console.dir(tabSheets); + var htmlStr = getHtmlStr(tabSheets); + $("#filter-tabs-dialog").dialog({ + modal:true, + title: "DG Builder Tabs", + width: 1200, + height: 750, + minWidth : 600, + minHeight :450, + }).html(htmlStr); +/* This code allows for the drag-drop of the rows in the table */ + var fixHelperModified = function(e, tr) { + var $originals = tr.children(); + var $helper = tr.clone(); + $helper.children().each(function(index) { + $(this).width($originals.eq(index).width()) + }); + return $helper; + }, + updateIndex = function(e, ui) { + var afterTabsOrder=[]; + $('td.index', ui.item.parent()).each(function (i) { + $(this).html(i + 1); + }); + //RE-ARRANGE the tabs + var ul = $("#workspace-tabs"); + $("#ftab02 tr td:nth-child(1)").each(function(i){ + var idStr = $(this).prop("id").replace("tab-td_",""); + afterTabsOrder.push(idStr); + link = ul.find("a[href='#"+ idStr+"']"); + li = link.parent(); + //li.remove(); + firstTab = $("#workspace-tabs li:first-child"); + lastTab = $("#workspace-tabs li:last-child"); + li.insertAfter(lastTab); + //console.log( idStr); + }); + var beforeTabsStr = beforeTabsOrder.join(","); + var afterTabsStr = afterTabsOrder.join(","); + //console.log("beforeTabsStr:" +beforeTabsStr); + //console.log("afterTabsStr:" +afterTabsStr); + if(beforeTabsStr !== afterTabsStr){ + //activate only when order has changed + //activate the deploy button + RED.view.dirty(true); + $("#btn-deploy").removeClass("disabled"); + } + }; + + $("#ftab02 tbody").sortable({ + helper: fixHelperModified, + stop: updateIndex + }).disableSelection(); + +} + +function getHtmlStr(rows){ + var styleStr = "<style> " + + "table#ftab02 { width:100%; } \n" + + "table#ftab02 th,table#ftab02 td { border: 1px solid black; border-collapse: collapse; } \n" + + /*"table, th, td { border: 1px solid #65a9d7; border-collapse: collapse; } \n" +*/ + "table#ftab02 th,table#ftab02 td { padding: 5px; text-align: left; } \n" + + "table#ftab02 tr:nth-child(even) { background-color: #eee; }\n" + + "table#ftab02 tr:nth-child(odd) { background-color:#fff; }\n" + + "table#ftab02 th { background-color: #65a9d7; color: white; }\n" + + "table#ftab02 a { color: #337ab7; }\n" + + "table#ftab02 a:link { color: #65a9d7; }\n" + + "table#ftab02 a:visited { color: #636; }\n" + + "table#ftab02 a:hover { color: #3366CC; cursor: pointer }\n" + + "table#ftab02 a:active { color: #65a9d7 }\n" + + "</style>"; + if(rows != null && rows != undefined){ + //var alertDialog = '<div id="confdialog"></div>'; + //htmlStr= alertDialog + "<div style='width:1050;height:650'>" + styleStr; + var alertDialog = '<div id="tabAlertDialog"></div>'; + htmlStr= alertDialog + "<div id='tabs-div' style='width:1050;height:650'>" + styleStr; + htmlStr += "<table id='ftab02' >"; + htmlStr += "<tr>"; + htmlStr += "<th class='index'>No.</th>" ; + htmlStr += "<th>Tab Title</th>" ; + htmlStr += "<th>Module</th>" ; + htmlStr += "<th>RPC</th>" ; + htmlStr += "<th>Version</th>" ; + htmlStr += "<th>Rename</th>" ; + htmlStr += "<th>Delete</th>" ; + htmlStr += "</tr>"; + htmlStr += "<tbody>"; + if(rows != null && rows.length == 0){ + htmlStr += "<tr>"; + htmlStr += "<td><b>No rows found</b></td>"; + htmlStr += "</tr></table></div>"; + return htmlStr; + } + for(var i=0;i<rows.length;i++){ + var row = rows[i]; + var title = row.title; + var _module = row.module; + var version = row.version; + var rpc = row.rpc; + var idVal = row.id; + var isVisible = row.isVisible; + htmlStr += "<tr id='tab-tr_" + idVal + "'>"; + //htmlStr += "<td id=" + "'tab-td_" + idVal + "' ><a href='javascript:activateClickedTab(\"" + idVal + "\")'>" + (i+1) + "</a></td>"; + htmlStr += "<td class='index' id=" + "'tab-td_" + idVal + "' >" + (i+1) + "</td>"; + htmlStr += "<td><a href='javascript:activateClickedTab(\"" + idVal + "\")'>" + title + "</a></td>"; + htmlStr += "<td>" + _module + "</td>"; + htmlStr += "<td>" + rpc + "</td>"; + htmlStr += "<td>" + version + "</td>"; + //htmlStr += "<td><a href='javascript:deleteOrRenameTab(\"" + idVal + "\")'>Delete/Rename</a></td>"; + htmlStr += "<td><input type='button' onclick='renameSelectedTab(\"" + idVal + "\",\"" + title + "\",\"" + _module + "\",\"" + rpc + "\",\"" + version + "\")' value='Rename'></td>"; + if(rows.length == 1){ + htmlStr += "<td><input type='button' disabled='1' onclick='deleteSelectedTab(\"" + idVal + "\",\"" + title + "\",\"" + _module + "\",\"" + rpc + "\",\"" + version + "\")' value='Delete'></td>"; + }else{ + htmlStr += "<td><input type='button' onclick='deleteSelectedTab(\"" + idVal + "\",\"" + title + "\",\"" + _module + "\",\"" + rpc + "\",\"" + version + "\")' value='Delete'></td>"; + } + /* + if(isVisible){ + htmlStr += "<td><input type='checkbox' onclick=\"showOrHideTab(this,'" + idVal + "')\" checked='true'></td>"; + }else{ + htmlStr += "<td><input type='checkbox' onclick=\"showOrHideTab(this,'" + idVal + "')\"></td>"; + } + */ + htmlStr += "</tr>"; + } + htmlStr += "</tbody>"; + htmlStr += "</table>"; + htmlStr += "</div>"; + } + return htmlStr; +} +/* +Added this logic because , when the configuration item is choosen in the menu the other dialog boxes were not poping up +*/ +(function(){ + //var msecs1= Date.now(); + $( "#gitlocal-browser-dialog" ).dialog(); + $( "#gitlocal-browser-dialog" ).dialog("close"); + $( "#dgflow-browser-dialog" ).dialog(); + $( "#dgflow-browser-dialog" ).dialog("close"); + $( "#update-password-dialog" ).dialog(); + $( "#update-password-dialog" ).dialog("close"); + $( "#codecloud-browser-dialog" ).dialog(); + $( "#codecloud-browser-dialog" ).dialog("close"); + $( "#update-configuration-dialog" ).dialog(); + $( "#update-configuration-dialog" ).dialog("close"); + $( "#gitcommands-dialog" ).dialog(); + $( "#gitcommands-dialog" ).dialog("close"); + $("#filter-tabs-dialog").dialog(); + $("#filter-tabs-dialog").dialog("close"); + $("#loop-detection-dialog").dialog(); + $("#loop-detection-dialog").dialog("close"); + $("#dgstart-generate-xml-dialog").dialog(); + $("#dgstart-generate-xml-dialog").dialog("close"); + $("#xmldialog").dialog(); + $("#xmldialog").dialog("close"); + $("#upload-xml-status-dialog").dialog(); + $("#upload-xml-status-dialog").dialog("close"); + $("#flow-design-err-dialog").dialog(); + $("#flow-design-err-dialog").dialog("close"); + $("#sli-values-dialog").dialog(); + $("#sli-values-dialog").dialog("close"); + $("#comments-dialog").dialog(); + $("#comments-dialog").dialog("close"); + $("#show-errors-dialog").dialog(); + $("#show-errors-dialog").dialog("close"); + $("#dgnumber-find-dialog").dialog(); + $("#dgnumber-find-dialog").dialog("close"); + $("#search-text-dialog").dialog(); + $("#search-text-dialog").dialog("close"); + $("#yang-upload-dialog").dialog(); + $("#yang-upload-dialog").dialog("close"); + $("#yang-modules-browser-dialog").dialog(); + $("#yang-modules-browser-dialog").dialog("close"); + $("#list-yang-browser-dialog").dialog(); + $("#list-yang-browser-dialog").dialog("close"); + $("#request-input-dialog").dialog(); + $("#request-input-dialog").dialog("close"); + //var msecs2= Date.now(); + //console.log("Time taken for dialog boxes:" + (msecs2 - msecs1)); +})(); + + function updateConfiguration(){ + //console.log("in updateConfiguration"); + $.get("/getCurrentSettings",function (data){ + var dbHost = data.dbHost; + var dbPort = data.dbPort; + var dbName = data.dbName; + var dbUser = data.dbUser; + var dbPassword = data.dbPassword; + var gitLocalRepository = data.gitLocalRepository; + var performGitPull = data.performGitPull; + + if(dbHost == undefined) dbHost=""; + if(dbPort == undefined) dbPort=""; + if(dbName == undefined) dbName=""; + if(dbUser == undefined) dbUser=""; + if(dbPassword == undefined) dbPassword=""; + if(gitLocalRepository == undefined) gitLocalRepository=""; + if(performGitPull == undefined || performGitPull == null) performGitPull="N"; + + var divStyle="border: 1px solid #a1a1a1; padding: 10px 40px; background: #dddddd; width: 500px; border-radius: 25px;"; + //var divStyle="border: 2px solid #a1a1a1; padding: 10px 40px; background: #99CCFF; width: 400px; border-radius: 25px;"; + + + var html = "<div>"; + html += "<script>function changeType(obj,targetId){if( obj.checked== true){$('#' + targetId).prop('type','password');}else{$('#'+ targetId).prop('type','text');}} function changeTitle(){ document.getElementById(\"gitLocalRepository\").title=document.getElementById(\"gitLocalRepository\").value;}</script>"; + html += "<div style='" + divStyle + "' >"; + html += "<table border='0' cellpadding='5' >"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>DB Host IP</b></td>"; + html += "<td><input style='align:center;font-size:11px;font-weight:bold' id='dbhost' name='dbhost' type='text' value='" + dbHost + "'></td>"; + html += "</tr>"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>DB Port</b></td>"; + html += "<td><input style='align:center;font-size:11px;font-weight:bold' id='dbport' name='dbport' type='text' value='" + dbPort + "'></td>"; + html += "</tr>"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>DB Name</b></td>"; + html += "<td><input style='align:center;font-size:11px;font-weight:bold' id='dbname' name='dbname' type='text' value='" + dbName + "'></td>"; + html += "</tr>"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>DB UserName</b></td>"; + html += "<td><input style='align:center;font-size:11px;font-weight:bold' id='dbuser' name='dbuser' type='password' value='" + dbUser + "'></td>"; + html += "<td><input style='background:background:white;width:20px;height:20px' type='checkbox' checked value='1' onclick=\"changeType(this,'dbuser')\">Hide</td>"; + html += "</tr>"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>DB Password</b></td>"; + html += "<td><input style='align:center;font-size:11px;font-weight:bold' id='dbpassword' name='dbpassword' type='password' value='" + dbPassword + "'></td>"; + html += "<td><input style='background:background:white;width:20px;height:20px' type='checkbox' checked value='1' onclick=\"changeType(this,'dbpassword')\">Hide</td>"; + html += "</tr>"; + html += "</table>"; + html += "</div>"; + html += "<div style='fill:both;clear:both'></div><br>"; + + html += "<div style='" + divStyle + "' >"; + html += "<table border='0' cellpadding='5' >"; + html += "<tr>"; + html += "<td style='font-size:12px;align:center'><b>Git Local Repository Path</b></td>"; + html += "<td><textarea style='align:center;font-size:14px;' cols='50' rows='4' id='gitLocalRepository' name='gitLocalRepository' onkeyup='changeTitle()' title='" + gitLocalRepository + "'>" + gitLocalRepository + "</textarea></td>"; + html += "</tr>"; + html += "</table>"; + html += "<table border='0' cellpadding='5' >"; + html += "<tr>"; + if(performGitPull == "N"){ + html += "<td style='align:center;'><input style='color:blue;width:20px;height:20px;' id='performGitPull' type='checkbox' value='Y'>Perform Git Pull in Local Git Repository prior to import</td>"; + }else{ + html += "<td style='align:center;'><input style='color:blue;width:20px;height:20px;' id='performGitPull' type='checkbox' value='Y' checked>Perform Git Pull in Local Git Repository prior to import</td>"; + } + html += "</tr>"; + html += "</table>"; + html += "</div>"; + html += "</div>"; + //console.log("html:" + html); + $( "#update-configuration-dialog" ).dialog({ + title: "Configuration", + modal: true, + autoOpen: true, + width: 630, + height: 630, + buttons: [ + { + text: "Save", + click: function() { + var newDBHost = $("#dbhost").val().trim(); + var newDBPort = $("#dbport").val().trim(); + var newDBName = $("#dbname").val().trim(); + var newDBUser = $("#dbuser").val().trim(); + var newDBPassword = $("#dbpassword").val().trim(); + var newGitLocalRepository = $("#gitLocalRepository").val().trim(); + var isPerformGitPullChecked = $('#performGitPull').is(':checked'); + var newPerformGitPull = "N"; + if(isPerformGitPullChecked){ + newPerformGitPull = "Y"; + } + if(newDBHost == ""){ + RED.notify("Error: DB Host is required."); + $("#dbhost").focus(); + return; + }else if(newDBPort == ""){ + RED.notify("Error: DB Port is required."); + $("#dbport").focus(); + return; + }else if(newDBName == ""){ + RED.notify("Error: DB Name is required."); + $("#dbname").focus(); + return; + }else if(newDBUser == ""){ + RED.notify("Error: DB User is required."); + $("#dbuser").focus(); + return; + }else if(newDBPassword == ""){ + RED.notify("Error: DB Password is required."); + $("#dbpassword").focus(); + return; + }else{ + console.log("newGitLocalRepository:" + newGitLocalRepository); + var reqData= {"dbHost":newDBHost, + "dbPort" : newDBPort, + "dbName" : newDBName, + "dbUser" : newDBUser, + "dbPassword" : newDBPassword, + "gitLocalRepository" : newGitLocalRepository, + "performGitPull" : newPerformGitPull + }; + $.post( "/updateConfiguration",reqData ) + .done(function( data ) { + RED.notify("Configuration updated successfully"); + //loadSettings(); + //RED.comms.connect(); + //$( "#update-configuration-dialog" ).dialog('close'); + $("#update-configuration-dialog").dialog("close"); + //location.reload(); + + }) + .fail(function(err) { + console.log( "error" + err ); + RED.notify("Failed to update the Configuration."); + }) + .always(function() { + }); + } + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }).html(html); + //$("#update-configuration-dialog").show(); + $("#gitLocalRepository").css({"width" : 300}); + + }); + } + + function updatePassword(){ + var html="<div>"; + html += "<div><span><b>New Password</b></span><br>"; + html += "<input id='passwd1' name='passwd1' type='password' value=''>"; + html += "</div>"; + html += "<div><span><b>Confirm Password</b></span><br>"; + html += "<input id='passwd2' name='passwd2' type='password' value=''>"; + html += "</div>"; + $( "#update-password-dialog" ).dialog({ + title: "Update Password", + modal: true, + autoOpen: true, + width: 530, + height: 230, + buttons: [ + { + text: "Update Password", + click: function() { + var passwd1 = $("#passwd1").val().trim(); + var passwd2 = $("#passwd2").val().trim(); + if((passwd1 != passwd2) || (passwd1 == "" || passwd2 == "")){ + RED.notify("Error:Passwords entered must be same and must be populated."); + return; + }else{ + var reqData= {"password":passwd1}; + $.post( "/updatePassword",reqData ) + .done(function( data ) { + RED.notify("Password updated successfully"); + //loadSettings(); + $( "#update-password-dialog" ).dialog('close'); + }) + .fail(function(err) { + console.log( "error" + err ); + RED.notify("Failed to update the password."); + }) + .always(function() { + }); + } + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }).html(html); + $("#update-password-dialog").show(); + + } + + function showAvailableYangModules(){ + availableYangModules=[]; + var divStyle="<style>#yang-modules-data-container a { color: #067ab4; font-size: 0.75em;} #yang-modules-data-container a:hover { text-decoration: underline; padding: -15px -15px -15px 15px; } .header { height: 40px; border-bottom: 1px solid #EEE; background-color: #ffffff; height: 40px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; } .footer { height: 40px; background-color: whiteSmoke; border-top: 1px solid #DDD; -webkit-border-bottom-left-radius: 5px; -webkit-border-bottom-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } table#yang-list-table { width:100%; } table#yang-list-table th,table#yang-list-table td { border: 1px solid black; border-collapse: collapse; } table#yang-list-table th,table#yang-list-table td { padding: 5px; text-align: left; } table#yang-list-table tr:nth-child(even) { background-color: #eee; } table#yang-list-table tr:nth-child(odd) { background-color:#fff; } table#yang-list-table th { background-color: #65a9d7; color: white; } table#yang-list-table a { color: #337ab7; } table#yang-list-table a:link { color: #65a9d7; } table#yang-list-table a:visited { color: #636; } table#yang-list-table a:hover { color: #3366CC; cursor: pointer } table#yang-list-table a:active { color: #65a9d7 }</style>"; + $.get( "/listAvailableModules") + .done(function( data ) { + var header="<div class='header'>List of Available Yang Modules</div>"; + header += "<div><p><i>Check the modules that you want to load and click on the Load button.</i></p></div>"; + //header += "<div><input id='yangModuleFilterBoxId' type='text' onkeyup='filterYangModules(this.value)'></div>"; + var html= divStyle + header + "<div id='yang-modules-data-container'>"; + html+="<table id='yang-list-table' border=1>"; + html+="<tr>"; + html+="<th>#</th>"; + html+="<th>Load</th>"; + html+="<th>Module</th>"; + html+="</tr>"; + if(data != null){ + var files=data.files; + availableYangModules=files; + //console.dir(files); + files.sort(function (a,b){ + if(a > b){ + return 1; + }else if(a < b){ + return -1; + }else{ + return 0; + } + }); + var count=1; + for(var i=0;files != null && i<files.length;i++){ + var val = files[i].replace(/:.*/,""); + if(files[i].indexOf(":checked") != -1){ + html+="<tr><td>" + count + "</td><td><input type='checkbox' checked value='" + val + "'></td><td>" + val + "</td></tr>"; + }else{ + html+="<tr><td>" + count + "</td><td><input type='checkbox' value='" + val + "'></td><td>" + val + "</td></tr>"; + } + count++; + } + } + html+="</table>"; + html+="</div>"; + $( "#yang-modules-browser-dialog" ).dialog({ + title: "Available Yang Modules", + modal: true, + autoOpen: true, + width: 830, + height: 630, + buttons: [ + { + text: "Load", + click: function() { + var allVals = []; + function getValuesForSelected() { + $('#yang-modules-data-container :checked').each(function() { + allVals.push($(this).val()); + }); + return allVals; + } + var selectedModules = getValuesForSelected().toString(); + console.log(selectedModules); + $.ajax({ + type: 'GET', + /*contentType: "application/x-www-form-urlencoded",*/ + url: '/loadSelectedModules?selectedModules=' + selectedModules, + success: function(data) { + RED.notify("Modules Loaded successfully"); + //emptying existing g;obal variables + sliValuesObj = {}; + rpcValues = {}; + reqInputValues = {}; + + if(data != undefined && data != null){ + 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"]; + //console.dir(reqInputValues); + } + } + } + $( "#yang-modules-browser-dialog" ).dialog('close'); + console.log('success'); + //console.log(JSON.stringify(data)); + }, + error: function(error) { + RED.notify("Failed to load modules."); + console.log("some error in fetching the notifications"); + } + }); + } + }, + { + text: "Close", + click: function() { + $(this).dialog("close"); + } + } + ] + }).html(html); + $("#yang-modules-browser-dialog").show(); + }) + .fail(function(err) { + RED.notify("Failed to get yang modules."); + }) + .always(function() { + console.log("Done displaying"); + }); + } + + + $(function() { + RED.menu.init({id:"btn-sidemenu", + options: [ + {id:"btn-sidebar",icon:"fa fa-columns",label:"Sidebar (Ctrl+Space)",toggle:true,onselect:RED.sidebar.toggleSidebar}, + null, + {id:"btn-configure-upload",icon:"fa fa-book",label:"Configuration",toggle:false,onselect:updateConfiguration}, + null, + {id:"btn-manage-tabs",icon:"fa fa-info",label:"Manage Tabs",toggle:false,onselect:showSelectedTabs}, + null, + {id:"btn-find-dgnumber",icon:"fa fa-info",label:"Search Text (Ctrl+[)",toggle:false,onselect:RED.view.showSearchTextDialog}, + null, + {id:"btn-find-dgnumber",icon:"fa fa-info",label:"Find Node (Ctrl+B)",toggle:false,onselect:RED.view.showDgNumberDialog}, + null, + /*{id:"btn-loop-detection",icon:"fa fa-info",label:"Loop Detection",toggle:true,onselect:performLoopDetection}, + null ,*/ + {id:"btn-node-status",icon:"fa fa-info",label:"Node Status",toggle:true,onselect:toggleStatus}, + null, + {id:"btn-node-dgnumber",icon:"fa fa-info",label:"Show Node Numbers",toggle:true,onselect:toggleDgNumberDisplay}, + null, + {id:"btn-node-panel",icon:"fa fa-columns",label:"Node Palette (Ctrl+M)",toggle:true,onselect:toggleNodePaletteDisplay}, + null, + {id:"btn-node-viewdgs",icon:"fa fa-info",label:"View All DG List",toggle:false,onselect:displayAllDGs}, + null, + /* + {id:"btn-node-gitmenu",icon:"fa fa-info",label:"Git Commands",options: [ + {id:"btn-node-gitcheckout",icon:"fa fa-info",label:"Git Checkout",onselect:showGitCheckoutDialog}, + {id:"btn-node-gitpull",icon:"fa fa-info",label:"Git Pull",onselect:showGitPullDialog}, + {id:"btn-node-gitstatus",icon:"fa fa-info",label:"Git Status",onselect:showGitStatusDialog} + ]}, + null, + */ + {id:"btn-import-menu",icon:"fa fa-sign-in",label:"Import...",options:[ + /*{id:"btn-import-codecloud",icon:"fa fa-clipboard",label:"Code Cloud",onselect:showCodeCloudFlows}, + */ + {id:"btn-import-codecloud",icon:"fa fa-clipboard",label:"Git Local Repository",onselect:showGitLocalFlows}, + {id:"btn-import-userflows",icon:"fa fa-clipboard",label:"Downloaded DG Flows...",onselect:showFlowShareUsers}, + {id:"btn-import-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",onselect:RED.view.showImportNodesDialog}, + {id:"btn-import-library",icon:"fa fa-book",label:"Library",options:[]} + ]}, + {id:"btn-export-menu",icon:"fa fa-sign-out",label:"Export...",disabled:true,options:[ + {id:"btn-export-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",disabled:true,onselect:RED.view.showExportNodesDialog}, + {id:"btn-export-library",icon:"fa fa-book",label:"Library...",disabled:true,onselect:RED.view.showExportNodesLibraryDialog} + ]}, + null, + {id:"btn-change-password",icon:"fa fa-columns",label:"Change Password",toggle:false,onselect:updatePassword}, + null, + /*{id:"btn-config-nodes",icon:"fa fa-th-list",label:"Configuration nodes...",onselect:RED.sidebar.config.show}, + null,*/ + {id:"btn-workspace-menu",icon:"fa fa-th-large",label:"Workspaces",options:[ + {id:"btn-workspace-add",icon:"fa fa-plus",label:"Add"}, + {id:"btn-workspace-edit",icon:"fa fa-pencil",label:"Rename"}, + {id:"btn-workspace-delete",icon:"fa fa-minus",label:"Delete"}, + null + ]}, + null, + {id:"btn-keyboard-shortcuts",icon:"fa fa-keyboard-o",label:"Keyboard Shortcuts",onselect:showHelp} + /*{id:"btn-help",icon:"fa fa-question",label:"Help...", href:"http://nodered.org/docs"}*/ + ] + }); + + //Making default loop detection on and display check mark in the menu + //$("#btn-loop-detection").addClass("active"); + + RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); + loadSettings(); + RED.comms.connect(); + }); + + return { + }; +})(); diff --git a/dgbuilder/public/red/main.js.orig b/dgbuilder/public/red/main.js.orig new file mode 100644 index 00000000..a47e9958 --- /dev/null +++ b/dgbuilder/public/red/main.js.orig @@ -0,0 +1,323 @@ +/** + * 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. + **/ +var RED = (function() { + + function hideDropTarget() { + $("#dropTarget").hide(); + RED.keyboard.remove(/* ESCAPE */ 27); + } + + $('#chart').on("dragenter",function(event) { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + $("#dropTarget").css({display:'table'}); + RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget); + } + }); + + $('#dropTarget').on("dragover",function(event) { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + event.preventDefault(); + } + }) + .on("dragleave",function(event) { + hideDropTarget(); + }) + .on("drop",function(event) { + var data = event.originalEvent.dataTransfer.getData("text/plain"); + hideDropTarget(); + RED.view.importNodes(data); + event.preventDefault(); + }); + + function save(force) { + if (RED.view.dirty()) { + //$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy + + if (!force) { + var invalid = false; + var unknownNodes = []; + RED.nodes.eachNode(function(node) { + invalid = invalid || !node.valid; + if (node.type === "unknown") { + if (unknownNodes.indexOf(node.name) == -1) { + unknownNodes.push(node.name); + } + invalid = true; + } + }); + if (invalid) { + if (unknownNodes.length > 0) { + $( "#node-dialog-confirm-deploy-config" ).hide(); + $( "#node-dialog-confirm-deploy-unknown" ).show(); + var list = "<li>"+unknownNodes.join("</li><li>")+"</li>"; + $( "#node-dialog-confirm-deploy-unknown-list" ).html(list); + } else { + $( "#node-dialog-confirm-deploy-config" ).show(); + $( "#node-dialog-confirm-deploy-unknown" ).hide(); + } + $( "#node-dialog-confirm-deploy" ).dialog( "open" ); + return; + } + } + var nns = RED.nodes.createCompleteNodeSet(); + + $("#btn-icn-deploy").removeClass('fa-download'); + $("#btn-icn-deploy").addClass('spinner'); + RED.view.dirty(false); + + $.ajax({ + url:"flows", + type: "POST", + data: JSON.stringify(nns), + contentType: "application/json; charset=utf-8" + }).done(function(data,textStatus,xhr) { + RED.notify("Successfully deployed","success"); + RED.nodes.eachNode(function(node) { + if (node.changed) { + node.dirty = true; + node.changed = false; + } + if(node.credentials) { + delete node.credentials; + } + }); + RED.nodes.eachConfig(function (confNode) { + if (confNode.credentials) { + delete confNode.credentials; + } + }); + // Once deployed, cannot undo back to a clean state + RED.history.markAllDirty(); + RED.view.redraw(); + }).fail(function(xhr,textStatus,err) { + RED.view.dirty(true); + if (xhr.responseText) { + RED.notify("<strong>Error</strong>: "+xhr.responseText,"error"); + } else { + RED.notify("<strong>Error</strong>: no response from server","error"); + } + }).always(function() { + $("#btn-icn-deploy").removeClass('spinner'); + $("#btn-icn-deploy").addClass('fa-download'); + }); + } + } + + $('#btn-deploy').click(function() { save(); }); + + $( "#node-dialog-confirm-deploy" ).dialog({ + title: "Confirm deploy", + modal: true, + autoOpen: false, + width: 530, + height: 230, + buttons: [ + { + text: "Confirm deploy", + click: function() { + save(true); + $( this ).dialog( "close" ); + } + }, + { + text: "Cancel", + click: function() { + $( this ).dialog( "close" ); + } + } + ] + }); + + function loadSettings() { + $.get('settings', function(data) { + RED.settings = data; + console.log("Node-RED: "+data.version); + loadNodeList(); + }); + } + function loadNodeList() { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'nodes', + success: function(data) { + RED.nodes.setNodeList(data); + loadNodes(); + } + }); + } + + function loadNodes() { + $.ajax({ + headers: { + "Accept":"text/html" + }, + cache: false, + url: 'nodes', + success: function(data) { + $("body").append(data); + $(".palette-spinner").hide(); + $(".palette-scroll").show(); + $("#palette-search").show(); + loadFlows(); + } + }); + } + + function loadFlows() { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'flows', + success: function(nodes) { + RED.nodes.import(nodes); + RED.view.dirty(false); + RED.view.redraw(); + RED.comms.subscribe("status/#",function(topic,msg) { + var parts = topic.split("/"); + var node = RED.nodes.node(parts[1]); + if (node) { + node.status = msg; + if (statusEnabled) { + node.dirty = true; + RED.view.redraw(); + } + } + }); + RED.comms.subscribe("node/#",function(topic,msg) { + var i,m; + var typeList; + var info; + + if (topic == "node/added") { + var addedTypes = []; + for (i=0;i<msg.length;i++) { + m = msg[i]; + var id = m.id; + RED.nodes.addNodeSet(m); + if (m.loaded) { + addedTypes = addedTypes.concat(m.types); + $.get('nodes/'+id, function(data) { + $("body").append(data); + }); + } + } + if (addedTypes.length) { + typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); + } + } else if (topic == "node/removed") { + for (i=0;i<msg.length;i++) { + m = msg[i]; + info = RED.nodes.removeNodeSet(m.id); + if (info.added) { + typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success"); + } + } + } else if (topic == "node/enabled") { + if (msg.types) { + info = RED.nodes.getNodeSet(msg.id); + if (info.added) { + RED.nodes.enableNodeSet(msg.id); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success"); + } else { + $.get('nodes/'+msg.id, function(data) { + $("body").append(data); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); + }); + } + } + } else if (topic == "node/disabled") { + if (msg.types) { + RED.nodes.disableNodeSet(msg.id); + typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; + RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success"); + } + } + }); + } + }); + } + + var statusEnabled = false; + function toggleStatus(state) { + statusEnabled = state; + RED.view.status(statusEnabled); + } + + function showHelp() { + + var dialog = $('#node-help'); + + //$("#node-help").draggable({ + // handle: ".modal-header" + //}); + + dialog.on('show',function() { + RED.keyboard.disable(); + }); + dialog.on('hidden',function() { + RED.keyboard.enable(); + }); + + dialog.modal(); + } + + $(function() { + RED.menu.init({id:"btn-sidemenu", + options: [ + {id:"btn-sidebar",icon:"fa fa-columns",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar}, + null, + {id:"btn-node-status",icon:"fa fa-info",label:"Node Status",toggle:true,onselect:toggleStatus}, + null, + {id:"btn-import-menu",icon:"fa fa-sign-in",label:"Import...",options:[ + {id:"btn-import-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",onselect:RED.view.showImportNodesDialog}, + {id:"btn-import-library",icon:"fa fa-book",label:"Library",options:[]} + ]}, + {id:"btn-export-menu",icon:"fa fa-sign-out",label:"Export...",disabled:true,options:[ + {id:"btn-export-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",disabled:true,onselect:RED.view.showExportNodesDialog}, + {id:"btn-export-library",icon:"fa fa-book",label:"Library...",disabled:true,onselect:RED.view.showExportNodesLibraryDialog} + ]}, + null, + {id:"btn-config-nodes",icon:"fa fa-th-list",label:"Configuration nodes...",onselect:RED.sidebar.config.show}, + null, + {id:"btn-workspace-menu",icon:"fa fa-th-large",label:"Workspaces",options:[ + {id:"btn-workspace-add",icon:"fa fa-plus",label:"Add"}, + {id:"btn-workspace-edit",icon:"fa fa-pencil",label:"Rename"}, + {id:"btn-workspace-delete",icon:"fa fa-minus",label:"Delete"}, + null + ]}, + null, + {id:"btn-keyboard-shortcuts",icon:"fa fa-keyboard-o",label:"Keyboard Shortcuts",onselect:showHelp}, + {id:"btn-help",icon:"fa fa-question",label:"Help...", href:"http://nodered.org/docs"} + ] + }); + + RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); + loadSettings(); + RED.comms.connect(); + }); + + return { + }; +})(); diff --git a/dgbuilder/public/red/nodes.js b/dgbuilder/public/red/nodes.js new file mode 100644 index 00000000..dc0827a6 --- /dev/null +++ b/dgbuilder/public/red/nodes.js @@ -0,0 +1,553 @@ +/** + * 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.nodes = (function() { + + var node_defs = {}; + var nodes = []; + var configNodes = {}; + var links = []; + var defaultWorkspace; + var workspaces = {}; + + var registry = (function() { + var nodeList = []; + var nodeSets = {}; + var typeToId = {}; + var nodeDefinitions = {}; + + var exports = { + getNodeList: function() { + return nodeList; + }, + setNodeList: function(list) { + nodeList = []; + for(var i=0;i<list.length;i++) { + var ns = list[i]; + exports.addNodeSet(ns); + } + }, + addNodeSet: function(ns) { + ns.added = false; + nodeSets[ns.id] = ns; + for (var j=0;j<ns.types.length;j++) { + typeToId[ns.types[j]] = ns.id; + } + nodeList.push(ns); + }, + removeNodeSet: function(id) { + var ns = nodeSets[id]; + for (var j=0;j<ns.types.length;j++) { + if (ns.added) { + // TODO: too tightly coupled into palette UI + RED.palette.remove(ns.types[j]); + var def = nodeDefinitions[ns.types[j]]; + if (def.onpaletteremove && typeof def.onpaletteremove === "function") { + def.onpaletteremove.call(def); + } + } + delete typeToId[ns.types[j]]; + } + delete nodeSets[id]; + for (var i=0;i<nodeList.length;i++) { + if (nodeList[i].id == id) { + nodeList.splice(i,1); + break; + } + } + return ns; + }, + getNodeSet: function(id) { + return nodeSets[id]; + }, + enableNodeSet: function(id) { + var ns = nodeSets[id]; + ns.enabled = true; + for (var j=0;j<ns.types.length;j++) { + // TODO: too tightly coupled into palette UI + RED.palette.show(ns.types[j]); + var def = nodeDefinitions[ns.types[j]]; + if (def.onpaletteadd && typeof def.onpaletteadd === "function") { + def.onpaletteadd.call(def); + } + } + }, + disableNodeSet: function(id) { + var ns = nodeSets[id]; + ns.enabled = false; + for (var j=0;j<ns.types.length;j++) { + // TODO: too tightly coupled into palette UI + RED.palette.hide(ns.types[j]); + var def = nodeDefinitions[ns.types[j]]; + if (def.onpaletteremove && typeof def.onpaletteremove === "function") { + def.onpaletteremove.call(def); + } + } + }, + registerNodeType: function(nt,def) { + nodeDefinitions[nt] = def; + nodeSets[typeToId[nt]].added = true; + // TODO: too tightly coupled into palette UI + RED.palette.add(nt,def); + if (def.onpaletteadd && typeof def.onpaletteadd === "function") { + def.onpaletteadd.call(def); + } + }, + getNodeType: function(nt) { + return nodeDefinitions[nt]; + } + }; + return exports; + })(); + + function getID() { + return (1+Math.random()*4294967295).toString(16); + } + + function addNode(n) { + if (n._def.category == "config") { + configNodes[n.id] = n; + RED.sidebar.config.refresh(); + } else { + n.dirty = true; + nodes.push(n); + var updatedConfigNode = false; + for (var d in n._def.defaults) { + if (n._def.defaults.hasOwnProperty(d)) { + var property = n._def.defaults[d]; + if (property.type) { + var type = registry.getNodeType(property.type); + if (type && type.category == "config") { + var configNode = configNodes[n[d]]; + if (configNode) { + updatedConfigNode = true; + configNode.users.push(n); + } + } + } + } + } + if (updatedConfigNode) { + RED.sidebar.config.refresh(); + } + } + } + function addLink(l) { + links.push(l); + } + function addConfig(c) { + configNodes[c.id] = c; + } + + function getNode(id) { + if (id in configNodes) { + return configNodes[id]; + } else { + for (var n in nodes) { + if (nodes[n].id == id) { + return nodes[n]; + } + } + } + return null; + } + + function removeNode(id) { + var removedLinks = []; + if (id in configNodes) { + delete configNodes[id]; + RED.sidebar.config.refresh(); + } else { + var node = getNode(id); + if (node) { + nodes.splice(nodes.indexOf(node),1); + removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); }); + removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); }); + } + var updatedConfigNode = false; + for (var d in node._def.defaults) { + if (node._def.defaults.hasOwnProperty(d)) { + var property = node._def.defaults[d]; + if (property.type) { + var type = registry.getNodeType(property.type); + if (type && type.category == "config") { + var configNode = configNodes[node[d]]; + if (configNode) { + updatedConfigNode = true; + var users = configNode.users; + users.splice(users.indexOf(node),1); + } + } + } + } + } + if (updatedConfigNode) { + RED.sidebar.config.refresh(); + } + } + return removedLinks; + } + + function removeLink(l) { + var index = links.indexOf(l); + if (index != -1) { + links.splice(index,1); + } + } + + function refreshValidation() { + for (var n=0;n<nodes.length;n++) { + RED.editor.validateNode(nodes[n]); + } + } + + function addWorkspace(ws) { + workspaces[ws.id] = ws; + } + function getWorkspace(id) { + return workspaces[id]; + } + function removeWorkspace(id) { + delete workspaces[id]; + var removedNodes = []; + var removedLinks = []; + var n; + for (n=0;n<nodes.length;n++) { + var node = nodes[n]; + if (node.z == id) { + removedNodes.push(node); + } + } + for (n=0;n<removedNodes.length;n++) { + var rmlinks = removeNode(removedNodes[n].id); + removedLinks = removedLinks.concat(rmlinks); + } + return {nodes:removedNodes,links:removedLinks}; + } + + function getAllFlowNodes(node) { + var visited = {}; + visited[node.id] = true; + var nns = [node]; + var stack = [node]; + while(stack.length !== 0) { + var n = stack.shift(); + var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);}); + for (var i=0;i<childLinks.length;i++) { + var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source; + if (!visited[child.id]) { + visited[child.id] = true; + nns.push(child); + stack.push(child); + } + } + } + return nns; + } + + /** + * Converts a node to an exportable JSON Object + **/ + function convertNode(n, exportCreds) { + exportCreds = exportCreds || false; + var node = {}; + node.id = n.id; + node.type = n.type; + for (var d in n._def.defaults) { + if (n._def.defaults.hasOwnProperty(d)) { + node[d] = n[d]; + } + } + if(exportCreds && n.credentials) { + node.credentials = {}; + for (var cred in n._def.credentials) { + if (n._def.credentials.hasOwnProperty(cred)) { + if (n.credentials[cred] != null) { + node.credentials[cred] = n.credentials[cred]; + } + } + } + } + if (n._def.category != "config") { + node.x = n.x; + node.y = n.y; + node.z = n.z; + node.wires = []; + for(var i=0;i<n.outputs;i++) { + node.wires.push([]); + } + var wires = links.filter(function(d){return d.source === n;}); + for (var j=0;j<wires.length;j++) { + var w = wires[j]; + node.wires[w.sourcePort].push(w.target.id); + } + } + return node; + } + + /** + * Converts the current node selection to an exportable JSON Object + **/ + function createExportableNodeSet(set) { + var nns = []; + var exportedConfigNodes = {}; + for (var n=0;n<set.length;n++) { + var node = set[n].n; + var convertedNode = RED.nodes.convertNode(node); + for (var d in node._def.defaults) { + if (node._def.defaults[d].type && node[d] in configNodes) { + var confNode = configNodes[node[d]]; + var exportable = registry.getNodeType(node._def.defaults[d].type).exportable; + if ((exportable == null || exportable)) { + if (!(node[d] in exportedConfigNodes)) { + exportedConfigNodes[node[d]] = true; + nns.unshift(RED.nodes.convertNode(confNode)); + } + } else { + convertedNode[d] = ""; + } + } + } + + nns.push(convertedNode); + } + return nns; + } + + //TODO: rename this (createCompleteNodeSet) + function createCompleteNodeSet() { + var nns = []; + var i; + for (i in workspaces) { + if (workspaces.hasOwnProperty(i)) { + nns.push(workspaces[i]); + } + } + for (i in configNodes) { + if (configNodes.hasOwnProperty(i)) { + nns.push(convertNode(configNodes[i], true)); + } + } + for (i=0;i<nodes.length;i++) { + var node = nodes[i]; + nns.push(convertNode(node, true)); + } + return nns; + } + + function importNodes(newNodesObj,createNewIds) { + try { + var i; + var n; + var newNodes; + if (typeof newNodesObj === "string") { + if (newNodesObj === "") { + return; + } + newNodes = JSON.parse(newNodesObj); + } else { + newNodes = newNodesObj; + } + + if (!$.isArray(newNodes)) { + newNodes = [newNodes]; + } + var unknownTypes = []; + for (i=0;i<newNodes.length;i++) { + n = newNodes[i]; + // TODO: remove workspace in next release+1 + if (n.type != "workspace" && n.type != "tab" && !registry.getNodeType(n.type)) { + // TODO: get this UI thing out of here! (see below as well) + n.name = n.type; + n.type = "unknown"; + if (unknownTypes.indexOf(n.name)==-1) { + unknownTypes.push(n.name); + } + if (n.x == null && n.y == null) { + // config node - remove it + newNodes.splice(i,1); + i--; + } + } + } + if (unknownTypes.length > 0) { + var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>"; + var type = "type"+(unknownTypes.length > 1?"s":""); + RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000); + //"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error"); + } + + var new_workspaces = []; + var workspace_map = {}; + + for (i=0;i<newNodes.length;i++) { + n = newNodes[i]; + // TODO: remove workspace in next release+1 + if (n.type === "workspace" || n.type === "tab") { + if (n.type === "workspace") { + n.type = "tab"; + } + if (defaultWorkspace == null) { + defaultWorkspace = n; + } + if (createNewIds) { + var nid = getID(); + workspace_map[n.id] = nid; + n.id = nid; + } + addWorkspace(n); + RED.view.addWorkspace(n); + new_workspaces.push(n); + } + } + if (defaultWorkspace == null) { + defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" }; + addWorkspace(defaultWorkspace); + RED.view.addWorkspace(defaultWorkspace); + new_workspaces.push(defaultWorkspace); + } + + var node_map = {}; + var new_nodes = []; + var new_links = []; + + for (i=0;i<newNodes.length;i++) { + n = newNodes[i]; + // TODO: remove workspace in next release+1 + if (n.type !== "workspace" && n.type !== "tab") { + var def = registry.getNodeType(n.type); + if (def && def.category == "config") { + if (!RED.nodes.node(n.id)) { + var configNode = {id:n.id,type:n.type,users:[]}; + for (var d in def.defaults) { + if (def.defaults.hasOwnProperty(d)) { + configNode[d] = n[d]; + } + } + configNode.label = def.label; + configNode._def = def; + RED.nodes.add(configNode); + } + } else { + var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false}; + if (createNewIds) { + node.z = workspace_map[node.z]; + if (!workspaces[node.z]) { + node.z = RED.view.getWorkspace(); + } + node.id = getID(); + } else { + node.id = n.id; + if (node.z == null || !workspaces[node.z]) { + node.z = RED.view.getWorkspace(); + } + } + node.type = n.type; + node._def = def; + if (!node._def) { + node._def = { + color:"#fee", + defaults: {}, + label: "unknown: "+n.type, + labelStyle: "node_label_italic", + outputs: n.outputs||n.wires.length + } + } + node.outputs = n.outputs||node._def.outputs; + + for (var d2 in node._def.defaults) { + if (node._def.defaults.hasOwnProperty(d2)) { + node[d2] = n[d2]; + } + } + + addNode(node); + RED.editor.validateNode(node); + node_map[n.id] = node; + new_nodes.push(node); + } + } + } + for (i=0;i<new_nodes.length;i++) { + n = new_nodes[i]; + for (var w1=0;w1<n.wires.length;w1++) { + var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]]; + for (var w2=0;w2<wires.length;w2++) { + if (wires[w2] in node_map) { + var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]}; + addLink(link); + new_links.push(link); + } + } + } + delete n.wires; + } + return [new_nodes,new_links,new_workspaces]; + } catch(error) { + //TODO: get this UI thing out of here! (see above as well) + RED.notify("<strong>Error</strong>: "+error,"error"); + return null; + } + + } + + return { + registry:registry, + setNodeList: registry.setNodeList, + + getNodeSet: registry.getNodeSet, + addNodeSet: registry.addNodeSet, + removeNodeSet: registry.removeNodeSet, + enableNodeSet: registry.enableNodeSet, + disableNodeSet: registry.disableNodeSet, + + registerType: registry.registerNodeType, + getType: registry.getNodeType, + convertNode: convertNode, + add: addNode, + addLink: addLink, + remove: removeNode, + removeLink: removeLink, + addWorkspace: addWorkspace, + removeWorkspace: removeWorkspace, + workspace: getWorkspace, + eachNode: function(cb) { + for (var n=0;n<nodes.length;n++) { + cb(nodes[n]); + } + }, + eachLink: function(cb) { + for (var l=0;l<links.length;l++) { + cb(links[l]); + } + }, + eachConfig: function(cb) { + for (var id in configNodes) { + if (configNodes.hasOwnProperty(id)) { + cb(configNodes[id]); + } + } + }, + node: getNode, + import: importNodes, + refreshValidation: refreshValidation, + getAllFlowNodes: getAllFlowNodes, + createExportableNodeSet: createExportableNodeSet, + createCompleteNodeSet: createCompleteNodeSet, + id: getID, + nodes: nodes, // TODO: exposed for d3 vis + links: links // TODO: exposed for d3 vis + }; +})(); 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 + }; +})(); diff --git a/dgbuilder/public/red/validators.js b/dgbuilder/public/red/validators.js new file mode 100644 index 00000000..5e740973 --- /dev/null +++ b/dgbuilder/public/red/validators.js @@ -0,0 +1,19 @@ +/** + * 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.validators = { + number: function(){return function(v) { return v!=='' && !isNaN(v);}}, + regex: function(re){return function(v) { return re.test(v);}} +}; |