Add build step to Grunt file
BIN
editor/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
editor/icons/alert.png
Normal file
After Width: | Height: | Size: 308 B |
BIN
editor/icons/arduino.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
editor/icons/arrow-in.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
editor/icons/bluetooth.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
editor/icons/bridge-dash.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
editor/icons/bridge.png
Normal file
After Width: | Height: | Size: 575 B |
BIN
editor/icons/comment.png
Normal file
After Width: | Height: | Size: 601 B |
BIN
editor/icons/db.png
Normal file
After Width: | Height: | Size: 459 B |
BIN
editor/icons/debug.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
editor/icons/envelope.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
editor/icons/feed.png
Normal file
After Width: | Height: | Size: 378 B |
BIN
editor/icons/file.png
Normal file
After Width: | Height: | Size: 255 B |
BIN
editor/icons/function.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
editor/icons/hash.png
Normal file
After Width: | Height: | Size: 502 B |
BIN
editor/icons/inject.png
Normal file
After Width: | Height: | Size: 449 B |
BIN
editor/icons/leveldb.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
editor/icons/light.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
editor/icons/mongodb.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
editor/icons/mouse.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
editor/icons/node-changed.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/icons/node-error.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/icons/range.png
Normal file
After Width: | Height: | Size: 360 B |
BIN
editor/icons/redis.png
Normal file
After Width: | Height: | Size: 736 B |
BIN
editor/icons/rpi.png
Normal file
After Width: | Height: | Size: 482 B |
BIN
editor/icons/serial.png
Normal file
After Width: | Height: | Size: 273 B |
BIN
editor/icons/subflow.png
Normal file
After Width: | Height: | Size: 439 B |
BIN
editor/icons/swap.png
Normal file
After Width: | Height: | Size: 592 B |
BIN
editor/icons/switch.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
editor/icons/template.png
Normal file
After Width: | Height: | Size: 488 B |
BIN
editor/icons/timer.png
Normal file
After Width: | Height: | Size: 628 B |
BIN
editor/icons/trigger.png
Normal file
After Width: | Height: | Size: 258 B |
BIN
editor/icons/twitter.png
Normal file
After Width: | Height: | Size: 404 B |
BIN
editor/icons/watch.png
Normal file
After Width: | Height: | Size: 591 B |
BIN
editor/icons/white-globe.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
editor/images/deploy-flows-o.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
editor/images/deploy-flows.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/images/deploy-full-o.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
editor/images/deploy-full.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
editor/images/deploy-nodes-o.png
Normal file
After Width: | Height: | Size: 290 B |
BIN
editor/images/deploy-nodes.png
Normal file
After Width: | Height: | Size: 392 B |
BIN
editor/images/grip.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
editor/images/node-red-256.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
editor/images/node-red.png
Normal file
After Width: | Height: | Size: 1019 B |
BIN
editor/images/pw_maze_white.png
Normal file
After Width: | Height: | Size: 600 B |
41
editor/images/spin.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<!--
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Brent Jackson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
https://github.com/jxnblk/loading
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="#999">
|
||||
<path transform="translate(2)" d="M0 12 V20 H4 V12z">
|
||||
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
|
||||
</path>
|
||||
<path transform="translate(8)" d="M0 12 V20 H4 V12z">
|
||||
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.2" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
|
||||
</path>
|
||||
<path transform="translate(14)" d="M0 12 V20 H4 V12z">
|
||||
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.4" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
|
||||
</path>
|
||||
<path transform="translate(20)" d="M0 12 V20 H4 V12z">
|
||||
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.6" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
|
||||
</path>
|
||||
<path transform="translate(26)" d="M0 12 V20 H4 V12z">
|
||||
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.8" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
|
||||
</path>
|
||||
</svg>
|
184
editor/index.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<!--
|
||||
Copyright 2013, 2015 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.
|
||||
-->
|
||||
<head>
|
||||
<title>Node-RED</title>
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="vendor/orion/built-editor.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="vendor/font-awesome/css/font-awesome.min.css"/>
|
||||
<link rel="stylesheet" href="red/style.min.css">
|
||||
</head>
|
||||
<body spellcheck="false">
|
||||
<div id="header">
|
||||
<span class="logo"><img src="red/images/node-red.png"> <span>Node-RED</span></span>
|
||||
<ul class="header-toolbar hide">
|
||||
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||
<ul>
|
||||
</div>
|
||||
<div id="main-container" class="sidebar-closed hide">
|
||||
<div id="palette">
|
||||
<img src="red/images/spin.svg" class="palette-spinner hide"/>
|
||||
<div id="palette-container" class="palette-scroll">
|
||||
</div>
|
||||
<div id="palette-search">
|
||||
<i class="fa fa-search"></i><input id="palette-search-input" type="text" placeholder="filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
|
||||
</div>
|
||||
</div><!-- /palette -->
|
||||
|
||||
<div id="workspace">
|
||||
<ul id="workspace-tabs"></ul>
|
||||
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
|
||||
<div id="chart"></div>
|
||||
<div id="workspace-toolbar">
|
||||
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
|
||||
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
|
||||
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
|
||||
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chart-zoom-controls">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-mini" id="btn-zoom-out" href="#"><i class="fa fa-search-minus"></i></a>
|
||||
<a class="btn btn-mini" id="btn-zoom-zero" href="#"><i class="fa fa-dot-circle-o"></i></a>
|
||||
<a class="btn btn-mini" id="btn-zoom-in" href="#"><i class="fa fa-search-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sidebar">
|
||||
<ul id="sidebar-tabs"></ul>
|
||||
<div id="sidebar-content"></div>
|
||||
</div>
|
||||
|
||||
<div id="sidebar-separator"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="notifications"></div>
|
||||
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
|
||||
<div id="shade"></div>
|
||||
|
||||
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
|
||||
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
|
||||
<div id="subflow-dialog" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<label>Name</label><input type="text" id="subflow-input-name">
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-tips" id="subflow-dialog-user-count"></div>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-confirm-deploy" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div id="node-dialog-confirm-deploy-config" style="text-align: center; padding-top: 30px;">
|
||||
Some of the nodes are not properly configured. Are you sure you want to deploy?
|
||||
</div>
|
||||
<div id="node-dialog-confirm-deploy-unknown" style="text-align: center; padding-top: 10px;">
|
||||
The workspace contains some unknown node types:
|
||||
<ul style="width: 300px; margin: auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
||||
Are you sure you want to deploy?
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-save-confirm" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;">
|
||||
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-save" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label>
|
||||
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-lookup" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
|
||||
<li class="active"><a href="#">Library</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;">
|
||||
<div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div>
|
||||
</div>
|
||||
<div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;">
|
||||
<div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="node-dialog-rename-workspace" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> Name:</label>
|
||||
<input type="text" id="node-input-workspace-name">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="node-dialog-delete-workspace" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;">
|
||||
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/x-red" data-template-name="export-library-dialog">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="subflow">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script src="vendor/jquery/js/jquery-1.11.1.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="vendor/jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
|
||||
<script src="vendor/jquery/js/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="vendor/marked/marked.min.js"></script>
|
||||
<script src="vendor/orion/built-editor.min.js"></script>
|
||||
<script src="vendor/ace/ace.js"></script>
|
||||
<script src="vendor/ace/ext-language_tools.js"></script>
|
||||
<script src="vendor/d3/d3.v3.min.js"></script>
|
||||
<script src="red/red.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
116
editor/js/comms.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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 clearErrorTimer = null;
|
||||
|
||||
var subscriptions = {};
|
||||
var ws;
|
||||
var pendingAuth = false;
|
||||
|
||||
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;
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
pendingAuth = (auth_tokens!=null);
|
||||
|
||||
function completeConnection() {
|
||||
for (var t in subscriptions) {
|
||||
if (subscriptions.hasOwnProperty(t)) {
|
||||
ws.send(JSON.stringify({subscribe:t}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws = new WebSocket(path);
|
||||
ws.onopen = function() {
|
||||
if (errornotification) {
|
||||
clearErrorTimer = setTimeout(function() {
|
||||
errornotification.close();
|
||||
errornotification = null;
|
||||
},1000);
|
||||
}
|
||||
if (pendingAuth) {
|
||||
ws.send(JSON.stringify({auth:auth_tokens.access_token}));
|
||||
} else {
|
||||
completeConnection();
|
||||
}
|
||||
}
|
||||
ws.onmessage = function(event) {
|
||||
var msg = JSON.parse(event.data);
|
||||
if (pendingAuth && msg.auth == "ok") {
|
||||
pendingAuth = false;
|
||||
completeConnection();
|
||||
} else 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);
|
||||
} else if (clearErrorTimer) {
|
||||
clearTimeout(clearErrorTimer);
|
||||
clearErrorTimer = null;
|
||||
}
|
||||
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
|
||||
}
|
||||
})();
|
210
editor/js/history.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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;
|
||||
var node;
|
||||
var modifiedTabs = {};
|
||||
if (ev) {
|
||||
if (ev.t == 'add') {
|
||||
if (ev.nodes) {
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
node = RED.nodes.node(ev.nodes[i]);
|
||||
if (node.z) {
|
||||
modifiedTabs[node.z] = true;
|
||||
}
|
||||
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.workspaces.remove(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflows) {
|
||||
for (i=0;i<ev.subflows.length;i++) {
|
||||
RED.nodes.removeSubflow(ev.subflows[i]);
|
||||
RED.workspaces.remove(ev.subflows[i]);
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "delete") {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
RED.nodes.addWorkspace(ev.workspaces[i]);
|
||||
RED.workspaces.add(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
RED.nodes.addSubflow(ev.subflow);
|
||||
}
|
||||
var subflow;
|
||||
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
|
||||
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
|
||||
subflow.in.push(ev.subflowInputs[0]);
|
||||
subflow.in[0].dirty = true;
|
||||
}
|
||||
if (ev.subflowOutputs && ev.subflowOutputs.length > 0) {
|
||||
subflow = RED.nodes.subflow(ev.subflowOutputs[0].z);
|
||||
ev.subflowOutputs.sort(function(a,b) { return a.i-b.i});
|
||||
for (i=0;i<ev.subflowOutputs.length;i++) {
|
||||
var output = ev.subflowOutputs[i];
|
||||
subflow.out.splice(output.i,0,output);
|
||||
for (var j=output.i+1;j<subflow.out.length;j++) {
|
||||
subflow.out[j].i++;
|
||||
subflow.out[j].dirty = true;
|
||||
}
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.source.type == "subflow:"+subflow.id) {
|
||||
if (l.sourcePort >= output.i) {
|
||||
l.sourcePort++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (subflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
n.outputs = subflow.out.length;
|
||||
while (n.outputs > n.ports.length) {
|
||||
n.ports.push(n.ports.length);
|
||||
}
|
||||
n.resize = true;
|
||||
n.dirty = true;
|
||||
});
|
||||
}
|
||||
if (ev.nodes) {
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
RED.nodes.add(ev.nodes[i]);
|
||||
modifiedTabs[ev.nodes[i].z] = true;
|
||||
}
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
if (ev.subflow.hasOwnProperty('inputCount')) {
|
||||
if (ev.node.in.length > ev.subflow.inputCount) {
|
||||
ev.node.in.splice(ev.subflow.inputCount);
|
||||
} else if (ev.subflow.inputs.length > 0) {
|
||||
ev.node.in = ev.node.in.concat(ev.subflow.inputs);
|
||||
}
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('outputCount')) {
|
||||
if (ev.node.out.length > ev.subflow.outputCount) {
|
||||
ev.node.out.splice(ev.subflow.outputCount);
|
||||
} else if (ev.subflow.outputs.length > 0) {
|
||||
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
|
||||
}
|
||||
}
|
||||
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
|
||||
n.changed = ev.changed;
|
||||
n.inputs = ev.node.in.length;
|
||||
n.outputs = ev.node.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
});
|
||||
|
||||
RED.palette.refresh();
|
||||
} else {
|
||||
RED.editor.updateNodeProperties(ev.node);
|
||||
RED.editor.validateNode(ev.node);
|
||||
}
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.addLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
ev.node.dirty = true;
|
||||
ev.node.changed = ev.changed;
|
||||
} else if (ev.t == "createSubflow") {
|
||||
if (ev.nodes) {
|
||||
RED.nodes.filterNodes({z:ev.subflow.id}).forEach(function(n) {
|
||||
n.z = ev.activeWorkspace;
|
||||
n.dirty = true;
|
||||
});
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.removeSubflow(ev.subflow);
|
||||
RED.workspaces.remove(ev.subflow);
|
||||
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.keys(modifiedTabs).forEach(function(id) {
|
||||
var subflow = RED.nodes.subflow(id);
|
||||
if (subflow) {
|
||||
RED.editor.validateNode(subflow);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
RED.nodes.dirty(ev.dirty);
|
||||
RED.view.redraw(true);
|
||||
RED.palette.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
206
editor/js/main.js
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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 loadSettings() {
|
||||
RED.settings.init(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.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
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);
|
||||
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 loadEditor() {
|
||||
RED.menu.init({id:"btn-sidemenu",
|
||||
options: [
|
||||
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
||||
{id:"btn-node-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"btn-import-menu",label:"Import",options:[
|
||||
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
|
||||
{id:"btn-import-library",label:"Library",options:[]}
|
||||
]},
|
||||
{id:"btn-export-menu",label:"Export",disabled:true,options:[
|
||||
{id:"btn-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
|
||||
{id:"btn-export-library",label:"Library",disabled:true,onselect:RED.library.export}
|
||||
]},
|
||||
null,
|
||||
{id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
|
||||
null,
|
||||
{id:"btn-subflow-menu",label:"Subflows", options: [
|
||||
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.subflow.createSubflow},
|
||||
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"btn-workspace-menu",label:"Workspaces",options:[
|
||||
{id:"btn-workspace-add",label:"Add",onselect:RED.workspaces.add},
|
||||
{id:"btn-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
|
||||
{id:"btn-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
|
||||
null
|
||||
]},
|
||||
null,
|
||||
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
|
||||
{id:"btn-help",label:"Node-RED Website", href:"http://nodered.org/docs"}
|
||||
]
|
||||
});
|
||||
|
||||
RED.user.init();
|
||||
|
||||
RED.library.init();
|
||||
RED.palette.init();
|
||||
RED.sidebar.init();
|
||||
RED.subflow.init();
|
||||
RED.workspaces.init();
|
||||
RED.clipboard.init();
|
||||
RED.view.init();
|
||||
RED.deploy.init();
|
||||
|
||||
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
|
||||
RED.comms.connect();
|
||||
|
||||
$("#main-container").show();
|
||||
$(".header-toolbar").show();
|
||||
|
||||
loadNodeList();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||
document.title = "Node-RED : "+window.location.hostname;
|
||||
}
|
||||
|
||||
ace.require("ace/ext/language_tools");
|
||||
|
||||
RED.settings.init(loadEditor);
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
};
|
||||
})();
|
926
editor/js/nodes.js
Normal file
@@ -0,0 +1,926 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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 subflows = {};
|
||||
|
||||
var dirty = false;
|
||||
|
||||
function setDirty(d) {
|
||||
dirty = d;
|
||||
eventHandler.emit("change",{dirty:dirty});
|
||||
}
|
||||
|
||||
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;
|
||||
if (def.category != "subflows") {
|
||||
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);
|
||||
}
|
||||
},
|
||||
removeNodeType: function(nt) {
|
||||
if (nt.substring(0,8) != "subflow:") {
|
||||
throw new Error("this api is subflow only. called with:",nt);
|
||||
}
|
||||
delete nodeDefinitions[nt];
|
||||
RED.palette.remove(nt);
|
||||
},
|
||||
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;
|
||||
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();
|
||||
}
|
||||
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
||||
var nextId = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
nextId = Math.max(nextId,node.i||0);
|
||||
});
|
||||
n.i = nextId+1;
|
||||
}
|
||||
nodes.push(n);
|
||||
}
|
||||
if (n._def.onadd) {
|
||||
n._def.onadd.call(n);
|
||||
}
|
||||
}
|
||||
function addLink(l) {
|
||||
links.push(l);
|
||||
}
|
||||
|
||||
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 = [];
|
||||
var node;
|
||||
if (id in configNodes) {
|
||||
node = configNodes[id];
|
||||
delete configNodes[id];
|
||||
RED.sidebar.config.refresh();
|
||||
} else {
|
||||
node = getNode(id);
|
||||
if (node) {
|
||||
nodes.splice(nodes.indexOf(node),1);
|
||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||
removedLinks.forEach(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node._def.onremove) {
|
||||
node._def.onremove.call(n);
|
||||
}
|
||||
return removedLinks;
|
||||
}
|
||||
|
||||
function removeLink(l) {
|
||||
var index = links.indexOf(l);
|
||||
if (index != -1) {
|
||||
links.splice(index,1);
|
||||
}
|
||||
}
|
||||
|
||||
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 addSubflow(sf, createNewIds) {
|
||||
if (createNewIds) {
|
||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
||||
return subflows[sfid].name;
|
||||
});
|
||||
|
||||
subflowNames.sort();
|
||||
var copyNumber = 1;
|
||||
var subflowName = sf.name;
|
||||
subflowNames.forEach(function(name) {
|
||||
if (subflowName == name) {
|
||||
copyNumber++;
|
||||
subflowName = sf.name+" ("+copyNumber+")";
|
||||
}
|
||||
});
|
||||
sf.name = subflowName;
|
||||
}
|
||||
|
||||
subflows[sf.id] = sf;
|
||||
RED.nodes.registerType("subflow:"+sf.id, {
|
||||
defaults:{name:{value:""}},
|
||||
icon:"subflow.png",
|
||||
category: "subflows",
|
||||
inputs: sf.in.length,
|
||||
outputs: sf.out.length,
|
||||
color: "#da9",
|
||||
label: function() { return this.name||RED.nodes.subflow(sf.id).name },
|
||||
labelStyle: function() { return this.name?"node_label_italic":""; },
|
||||
paletteLabel: function() { return RED.nodes.subflow(sf.id).name }
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
function getSubflow(id) {
|
||||
return subflows[id];
|
||||
}
|
||||
function removeSubflow(sf) {
|
||||
delete subflows[sf.id];
|
||||
registry.removeNodeType("subflow:"+sf.id);
|
||||
}
|
||||
|
||||
function subflowContains(sfid,nodeid) {
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var node = nodes[i];
|
||||
if (node.z === sfid) {
|
||||
var m = /^subflow:(.+)$/.exec(node.type);
|
||||
if (m) {
|
||||
if (m[1] === nodeid) {
|
||||
return true;
|
||||
} else {
|
||||
var result = subflowContains(m[1],nodeid);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
var id = child.id;
|
||||
if (!id) {
|
||||
id = child.direction+":"+child.i;
|
||||
}
|
||||
if (!visited[id]) {
|
||||
visited[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;
|
||||
if (node.type == "unknown") {
|
||||
for (var p in n._orig) {
|
||||
if (n._orig.hasOwnProperty(p)) {
|
||||
node[p] = n._orig[p];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var d in n._def.defaults) {
|
||||
if (n._def.defaults.hasOwnProperty(d)) {
|
||||
node[d] = n[d];
|
||||
}
|
||||
}
|
||||
if(exportCreds && n.credentials) {
|
||||
var credentialSet = {};
|
||||
node.credentials = {};
|
||||
for (var cred in n._def.credentials) {
|
||||
if (n._def.credentials.hasOwnProperty(cred)) {
|
||||
if (n._def.credentials[cred].type == 'password') {
|
||||
if (n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
|
||||
(n.credentials["has_"+cred] && n.credentials[cred])) {
|
||||
credentialSet[cred] = n.credentials[cred];
|
||||
}
|
||||
} else if (n.credentials[cred] != null && n.credentials[cred] != n.credentials._[cred]) {
|
||||
credentialSet[cred] = n.credentials[cred];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(credentialSet).length > 0) {
|
||||
node.credentials = credentialSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
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];
|
||||
if (w.target.type != "subflow") {
|
||||
node.wires[w.sourcePort].push(w.target.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function convertSubflow(n) {
|
||||
var node = {};
|
||||
node.id = n.id;
|
||||
node.type = n.type;
|
||||
node.name = n.name;
|
||||
node.in = [];
|
||||
node.out = [];
|
||||
|
||||
n.in.forEach(function(p) {
|
||||
var nIn = {x:p.x,y:p.y,wires:[]};
|
||||
var wires = links.filter(function(d) { return d.source === p });
|
||||
for (var i=0;i<wires.length;i++) {
|
||||
var w = wires[i];
|
||||
if (w.target.type != "subflow") {
|
||||
nIn.wires.push({id:w.target.id})
|
||||
}
|
||||
}
|
||||
node.in.push(nIn);
|
||||
});
|
||||
n.out.forEach(function(p,c) {
|
||||
var nOut = {x:p.x,y:p.y,wires:[]};
|
||||
var wires = links.filter(function(d) { return d.target === p });
|
||||
for (i=0;i<wires.length;i++) {
|
||||
if (wires[i].source.type != "subflow") {
|
||||
nOut.wires.push({id:wires[i].source.id,port:wires[i].sourcePort})
|
||||
} else {
|
||||
nOut.wires.push({id:n.id,port:0})
|
||||
}
|
||||
}
|
||||
node.out.push(nOut);
|
||||
});
|
||||
|
||||
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
* Converts the current node selection to an exportable JSON Object
|
||||
**/
|
||||
function createExportableNodeSet(set) {
|
||||
var nns = [];
|
||||
var exportedConfigNodes = {};
|
||||
var exportedSubflows = {};
|
||||
for (var n=0;n<set.length;n++) {
|
||||
var node = set[n];
|
||||
if (node.type.substring(0,8) == "subflow:") {
|
||||
var subflowId = node.type.substring(8);
|
||||
if (!exportedSubflows[subflowId]) {
|
||||
exportedSubflows[subflowId] = true;
|
||||
var subflow = getSubflow(subflowId);
|
||||
var subflowSet = [subflow];
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.z == subflowId) {
|
||||
subflowSet.push(n);
|
||||
}
|
||||
});
|
||||
var exportableSubflow = createExportableNodeSet(subflowSet);
|
||||
nns = exportableSubflow.concat(nns);
|
||||
}
|
||||
}
|
||||
if (node.type != "subflow") {
|
||||
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);
|
||||
} else {
|
||||
var convertedSubflow = convertSubflow(node);
|
||||
nns.push(convertedSubflow);
|
||||
}
|
||||
}
|
||||
return nns;
|
||||
}
|
||||
|
||||
//TODO: rename this (createCompleteNodeSet)
|
||||
function createCompleteNodeSet() {
|
||||
var nns = [];
|
||||
var i;
|
||||
for (i in workspaces) {
|
||||
if (workspaces.hasOwnProperty(i)) {
|
||||
if (workspaces[i].type == "tab") {
|
||||
nns.push(workspaces[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i in subflows) {
|
||||
if (subflows.hasOwnProperty(i)) {
|
||||
nns.push(convertSubflow(subflows[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) {
|
||||
var i;
|
||||
var n;
|
||||
var newNodes;
|
||||
if (typeof newNodesObj === "string") {
|
||||
if (newNodesObj === "") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
newNodes = JSON.parse(newNodesObj);
|
||||
} catch(err) {
|
||||
var e = new Error("Invalid flow: "+err.message);
|
||||
e.code = "NODE_RED";
|
||||
throw e;
|
||||
}
|
||||
} 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" &&
|
||||
n.type != "subflow" &&
|
||||
!registry.getNodeType(n.type) &&
|
||||
n.type.substring(0,8) != "subflow:" &&
|
||||
unknownTypes.indexOf(n.type)==-1) {
|
||||
|
||||
unknownTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
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 activeWorkspace = RED.workspaces.active();
|
||||
var activeSubflow = getSubflow(activeWorkspace);
|
||||
if (activeSubflow) {
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
|
||||
if (m) {
|
||||
var subflowId = m[1];
|
||||
var err;
|
||||
if (subflowId === activeSubflow.id) {
|
||||
err = new Error("Cannot add subflow to itself");
|
||||
}
|
||||
if (subflowContains(m[1],activeSubflow.id)) {
|
||||
err = new Error("Cannot add subflow - circular reference detected");
|
||||
}
|
||||
if (err) {
|
||||
// TODO: standardise error codes
|
||||
err.code = "NODE_RED";
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var new_workspaces = [];
|
||||
var workspace_map = {};
|
||||
var new_subflows = [];
|
||||
var subflow_map = {};
|
||||
var nid;
|
||||
var def;
|
||||
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) {
|
||||
nid = getID();
|
||||
workspace_map[n.id] = nid;
|
||||
n.id = nid;
|
||||
}
|
||||
addWorkspace(n);
|
||||
RED.workspaces.add(n);
|
||||
new_workspaces.push(n);
|
||||
} else if (n.type === "subflow") {
|
||||
subflow_map[n.id] = n;
|
||||
if (createNewIds) {
|
||||
nid = getID();
|
||||
n.id = nid;
|
||||
}
|
||||
// TODO: handle createNewIds - map old to new subflow ids
|
||||
n.in.forEach(function(input,i) {
|
||||
input.type = "subflow";
|
||||
input.direction = "in";
|
||||
input.z = n.id;
|
||||
input.i = i;
|
||||
input.id = getID();
|
||||
});
|
||||
n.out.forEach(function(output,i) {
|
||||
output.type = "subflow";
|
||||
output.direction = "out";
|
||||
output.z = n.id;
|
||||
output.i = i;
|
||||
output.id = getID();
|
||||
});
|
||||
new_subflows.push(n);
|
||||
addSubflow(n,createNewIds);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defaultWorkspace == null) {
|
||||
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
|
||||
addWorkspace(defaultWorkspace);
|
||||
RED.workspaces.add(defaultWorkspace);
|
||||
new_workspaces.push(defaultWorkspace);
|
||||
activeWorkspace = RED.workspaces.active();
|
||||
}
|
||||
|
||||
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" && n.type !== "subflow") {
|
||||
def = registry.getNodeType(n.type);
|
||||
if (!def || def.category != "config") {
|
||||
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
|
||||
if (createNewIds) {
|
||||
if (subflow_map[node.z]) {
|
||||
node.z = subflow_map[node.z].id;
|
||||
} else {
|
||||
node.z = workspace_map[node.z];
|
||||
if (!workspaces[node.z]) {
|
||||
node.z = activeWorkspace;
|
||||
}
|
||||
}
|
||||
node.id = getID();
|
||||
} else {
|
||||
node.id = n.id;
|
||||
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
|
||||
node.z = activeWorkspace;
|
||||
}
|
||||
}
|
||||
node.type = n.type;
|
||||
node._def = def;
|
||||
if (n.type.substring(0,7) === "subflow") {
|
||||
var parentId = n.type.split(":")[1];
|
||||
var subflow = subflow_map[parentId]||getSubflow(parentId);
|
||||
if (createNewIds) {
|
||||
parentId = subflow.id;
|
||||
node.type = "subflow:"+parentId;
|
||||
node._def = registry.getNodeType(node.type);
|
||||
delete node.i;
|
||||
}
|
||||
node.name = n.name;
|
||||
node.outputs = subflow.out.length;
|
||||
node.inputs = subflow.in.length;
|
||||
} else {
|
||||
if (!node._def) {
|
||||
if (node.x && node.y) {
|
||||
node._def = {
|
||||
color:"#fee",
|
||||
defaults: {},
|
||||
label: "unknown: "+n.type,
|
||||
labelStyle: "node_label_italic",
|
||||
outputs: n.outputs||n.wires.length
|
||||
}
|
||||
} else {
|
||||
node._def = {
|
||||
category:"config"
|
||||
};
|
||||
node.users = [];
|
||||
}
|
||||
var orig = {};
|
||||
for (var p in n) {
|
||||
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||
orig[p] = n[p];
|
||||
}
|
||||
}
|
||||
node._orig = orig;
|
||||
node.name = n.type;
|
||||
node.type = "unknown";
|
||||
}
|
||||
if (node._def.category != "config") {
|
||||
node.inputs = n.inputs||node._def.inputs;
|
||||
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;
|
||||
if (node._def.category != "config") {
|
||||
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;
|
||||
}
|
||||
for (i=0;i<new_subflows.length;i++) {
|
||||
n = new_subflows[i];
|
||||
n.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete input.wires;
|
||||
});
|
||||
n.out.forEach(function(output) {
|
||||
output.wires.forEach(function(wire) {
|
||||
var link;
|
||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||
} else {
|
||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
||||
}
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete output.wires;
|
||||
});
|
||||
}
|
||||
|
||||
return [new_nodes,new_links,new_workspaces,new_subflows];
|
||||
}
|
||||
|
||||
// TODO: supports filter.z|type
|
||||
function filterNodes(filter) {
|
||||
var result = [];
|
||||
|
||||
for (var n=0;n<nodes.length;n++) {
|
||||
var node = nodes[n];
|
||||
if (filter.z && node.z !== filter.z) {
|
||||
continue;
|
||||
}
|
||||
if (filter.type && node.type !== filter.type) {
|
||||
continue;
|
||||
}
|
||||
result.push(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function filterLinks(filter) {
|
||||
var result = [];
|
||||
|
||||
for (var n=0;n<links.length;n++) {
|
||||
var link = links[n];
|
||||
if (filter.source) {
|
||||
if (filter.source.id && link.source.id !== filter.source.id) {
|
||||
continue;
|
||||
}
|
||||
if (filter.source.z && link.source.z !== filter.source.z) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter.target) {
|
||||
if (filter.target.id && link.target.id !== filter.target.id) {
|
||||
continue;
|
||||
}
|
||||
if (filter.target.z && link.target.z !== filter.target.z) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter.sourcePort && link.sourcePort !== filter.sourcePort) {
|
||||
continue;
|
||||
}
|
||||
result.push(link);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
on: eventHandler.on,
|
||||
|
||||
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,
|
||||
remove: removeNode,
|
||||
|
||||
addLink: addLink,
|
||||
removeLink: removeLink,
|
||||
|
||||
addWorkspace: addWorkspace,
|
||||
removeWorkspace: removeWorkspace,
|
||||
workspace: getWorkspace,
|
||||
|
||||
addSubflow: addSubflow,
|
||||
removeSubflow: removeSubflow,
|
||||
subflow: getSubflow,
|
||||
subflowContains: subflowContains,
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
eachSubflow: function(cb) {
|
||||
for (var id in subflows) {
|
||||
if (subflows.hasOwnProperty(id)) {
|
||||
cb(subflows[id]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
node: getNode,
|
||||
|
||||
filterNodes: filterNodes,
|
||||
filterLinks: filterLinks,
|
||||
|
||||
import: importNodes,
|
||||
|
||||
getAllFlowNodes: getAllFlowNodes,
|
||||
createExportableNodeSet: createExportableNodeSet,
|
||||
createCompleteNodeSet: createCompleteNodeSet,
|
||||
id: getID,
|
||||
dirty: function(d) {
|
||||
if (d == null) {
|
||||
return dirty;
|
||||
} else {
|
||||
setDirty(d);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
131
editor/js/settings.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright 2014 IBM, Antoine Aflalo
|
||||
*
|
||||
* 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.settings = (function () {
|
||||
|
||||
var loadedSettings = {};
|
||||
|
||||
var hasLocalStorage = function () {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var set = function (key, value) {
|
||||
if (!hasLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
/**
|
||||
* If the key is not set in the localStorage it returns <i>undefined</i>
|
||||
* Else return the JSON parsed value
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
var get = function (key) {
|
||||
if (!hasLocalStorage()) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(localStorage.getItem(key));
|
||||
};
|
||||
|
||||
var remove = function (key) {
|
||||
if (!hasLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
var setProperties = function(data) {
|
||||
for (var prop in loadedSettings) {
|
||||
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
|
||||
delete RED.settings[prop];
|
||||
}
|
||||
}
|
||||
for (prop in data) {
|
||||
if (data.hasOwnProperty(prop)) {
|
||||
RED.settings[prop] = data[prop];
|
||||
}
|
||||
}
|
||||
loadedSettings = data;
|
||||
};
|
||||
|
||||
var init = function (done) {
|
||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||
if (accessTokenMatch) {
|
||||
var accessToken = accessTokenMatch[1];
|
||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||
window.location.search = "";
|
||||
}
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings) {
|
||||
// Only attach auth header for requests to relative paths
|
||||
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
if (auth_tokens) {
|
||||
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
load(done);
|
||||
}
|
||||
|
||||
var load = function(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
url: 'settings',
|
||||
success: function (data) {
|
||||
setProperties(data);
|
||||
if (RED.settings.user && RED.settings.user.anonymous) {
|
||||
RED.settings.remove("auth-tokens");
|
||||
}
|
||||
console.log("Node-RED: " + data.version);
|
||||
done();
|
||||
},
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
if (jqXHR.status === 401) {
|
||||
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
|
||||
window.location.search = "";
|
||||
}
|
||||
RED.user.login(function() { load(done); });
|
||||
} else {
|
||||
console.log("Unexpected error:",jqXHR.status,textStatus);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
load: load,
|
||||
set: set,
|
||||
get: get,
|
||||
remove: remove
|
||||
}
|
||||
})
|
||||
();
|
178
editor/js/ui/clipboard.js
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright 2015 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.clipboard = (function() {
|
||||
|
||||
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "clipboard-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (/Import/.test(dialog.dialog("option","title"))) {
|
||||
RED.view.importNodes($("#clipboard-import").val());
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-close",
|
||||
text: "Close",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
var dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
var exportNodesDialog = '<div class="form-row">'+
|
||||
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+
|
||||
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
||||
'</div>'+
|
||||
'<div class="form-tips">'+
|
||||
'Select the text above and copy to the clipboard with Ctrl-C.'+
|
||||
'</div>';
|
||||
|
||||
var importNodesDialog = '<div class="form-row">'+
|
||||
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="Paste nodes here"></textarea>'+
|
||||
'</div>';
|
||||
|
||||
|
||||
function importNodes() {
|
||||
dialogContainer.empty();
|
||||
dialogContainer.append($(importNodesDialog));
|
||||
$("#clipboard-dialog-ok").show();
|
||||
$("#clipboard-dialog-cancel").show();
|
||||
$("#clipboard-dialog-close").hide();
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
$("#clipboard-import").keyup(function() {
|
||||
var v = $(this).val();
|
||||
try {
|
||||
JSON.parse(v);
|
||||
$(this).removeClass("input-error");
|
||||
$("#clipboard-dialog-ok").button("enable");
|
||||
} catch(err) {
|
||||
if (v !== "") {
|
||||
$(this).addClass("input-error");
|
||||
}
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
}
|
||||
});
|
||||
dialog.dialog("option","title","Import nodes").dialog("open");
|
||||
}
|
||||
|
||||
function exportNodes() {
|
||||
dialogContainer.empty();
|
||||
dialogContainer.append($(exportNodesDialog));
|
||||
$("#clipboard-dialog-ok").hide();
|
||||
$("#clipboard-dialog-cancel").hide();
|
||||
$("#clipboard-dialog-close").show();
|
||||
var selection = RED.view.selection();
|
||||
if (selection.nodes) {
|
||||
var nns = RED.nodes.createExportableNodeSet(selection.nodes);
|
||||
$("#clipboard-export")
|
||||
.val(JSON.stringify(nns))
|
||||
.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" );
|
||||
}
|
||||
}
|
||||
|
||||
function hideDropTarget() {
|
||||
$("#dropTarget").hide();
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
|
||||
|
||||
|
||||
|
||||
$('#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();
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
import: importNodes,
|
||||
export: exportNodes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})();
|
165
editor/js/ui/deploy.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright 2015 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.deploy = (function() {
|
||||
|
||||
var deploymentTypes = {
|
||||
"full":{img:"red/images/deploy-full-o.png"},
|
||||
"nodes":{img:"red/images/deploy-nodes-o.png"},
|
||||
"flows":{img:"red/images/deploy-flows-o.png"}
|
||||
}
|
||||
|
||||
var deploymentType = "full";
|
||||
|
||||
function changeDeploymentType(type) {
|
||||
deploymentType = type;
|
||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
var deployButton = $('<li><span class="deploy-button-group button-group">'+
|
||||
'<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+
|
||||
'<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>'+
|
||||
'</span></li>').prependTo(".header-toolbar");
|
||||
|
||||
$('#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" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
RED.menu.init({id:"btn-deploy-options",
|
||||
options: [
|
||||
{id:"btn-deploy-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||
{id:"btn-deploy-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||
{id:"btn-deploy-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||
]
|
||||
});
|
||||
|
||||
RED.nodes.on('change',function(state) {
|
||||
if (state.dirty) {
|
||||
window.onbeforeunload = function() {
|
||||
return "You have undeployed changes.\n\nLeaving this page will lose these changes.";
|
||||
}
|
||||
$("#btn-deploy").removeClass("disabled");
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
$("#btn-deploy").addClass("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function save(force) {
|
||||
if (RED.nodes.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.nodes.dirty(false);
|
||||
|
||||
$.ajax({
|
||||
url:"flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(nns),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).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.nodes.dirty(true);
|
||||
if (xhr.responseText) {
|
||||
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
})();
|
898
editor/js/ui/editor.js
Normal file
@@ -0,0 +1,898 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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;
|
||||
|
||||
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;
|
||||
var oldChanged = node.changed;
|
||||
node.valid = true;
|
||||
var subflow;
|
||||
var isValid;
|
||||
var hasChanged;
|
||||
|
||||
if (node.type.indexOf("subflow:")===0) {
|
||||
subflow = RED.nodes.subflow(node.type.substring(8));
|
||||
isValid = subflow.valid;
|
||||
hasChanged = subflow.changed;
|
||||
if (isValid === undefined) {
|
||||
isValid = validateNode(subflow);
|
||||
hasChanged = subflow.changed;
|
||||
}
|
||||
node.valid = isValid;
|
||||
node.changed = hasChanged;
|
||||
} else if (node._def) {
|
||||
node.valid = validateNodeProperties(node, node._def.defaults, node);
|
||||
if (node._def._creds) {
|
||||
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
|
||||
}
|
||||
} else if (node.type == "subflow") {
|
||||
var subflowNodes = RED.nodes.filterNodes({z:node.id});
|
||||
for (var i=0;i<subflowNodes.length;i++) {
|
||||
isValid = subflowNodes[i].valid;
|
||||
hasChanged = subflowNodes[i].changed;
|
||||
if (isValid === undefined) {
|
||||
isValid = validateNode(subflowNodes[i]);
|
||||
hasChanged = subflowNodes[i].changed;
|
||||
}
|
||||
node.valid = node.valid && isValid;
|
||||
node.changed = node.changed || hasChanged;
|
||||
}
|
||||
var subflowInstances = RED.nodes.filterNodes({type:"subflow:"+node.id});
|
||||
var modifiedTabs = {};
|
||||
for (i=0;i<subflowInstances.length;i++) {
|
||||
subflowInstances[i].valid = node.valid;
|
||||
subflowInstances[i].changed = node.changed;
|
||||
subflowInstances[i].dirty = true;
|
||||
modifiedTabs[subflowInstances[i].z] = true;
|
||||
}
|
||||
Object.keys(modifiedTabs).forEach(function(id) {
|
||||
var subflow = RED.nodes.subflow(id);
|
||||
if (subflow) {
|
||||
validateNode(subflow);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (oldValue !== node.valid || oldChanged !== node.changed) {
|
||||
node.dirty = true;
|
||||
subflow = RED.nodes.subflow(node.z);
|
||||
if (subflow) {
|
||||
validateNode(subflow);
|
||||
}
|
||||
}
|
||||
return node.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = definition[property].hasOwnProperty("required") && !definition[property].required;
|
||||
} 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.ports) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
} else if (node.outputs > node.ports.length) {
|
||||
while (node.outputs > node.ports.length) {
|
||||
node.ports.push(node.ports.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.inputs === 0) {
|
||||
removedLinks.concat(RED.nodes.filterLinks({target:node}));
|
||||
}
|
||||
for (var l=0;l<removedLinks.length;l++) {
|
||||
RED.nodes.removeLink(removedLinks[l]);
|
||||
}
|
||||
return removedLinks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$( "#dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "node-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (editing_node) {
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.nodes.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.nodes.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 (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify("Saved nodes","success");
|
||||
});
|
||||
}
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "node-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
if (editing_node && editing_node._def) {
|
||||
if (editing_node._def.oneditcancel) {
|
||||
editing_node._def.oneditcancel.call(editing_node);
|
||||
}
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
resize: function(e,ui) {
|
||||
if (editing_node) {
|
||||
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
|
||||
}
|
||||
},
|
||||
open: function(e) {
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
} else {
|
||||
$(this).dialog('option','width',$(this).outerWidth());
|
||||
}
|
||||
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','auto');
|
||||
if (editing_node) {
|
||||
RED.sidebar.info.refresh(editing_node);
|
||||
}
|
||||
RED.sidebar.config.refresh();
|
||||
|
||||
var buttons = $( this ).dialog("option","buttons");
|
||||
if (buttons.length == 3) {
|
||||
$( this ).dialog("option","buttons",buttons.splice(1));
|
||||
}
|
||||
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);
|
||||
var type = node.type;
|
||||
if (node.type.substring(0,8) == "subflow:") {
|
||||
type = "subflow";
|
||||
var id = editing_node.type.substring(8);
|
||||
var buttons = $( "#dialog" ).dialog("option","buttons");
|
||||
buttons.unshift({
|
||||
class: 'leftButton',
|
||||
text: "Edit flow",
|
||||
click: function() {
|
||||
RED.workspaces.show(id);
|
||||
$("#node-dialog-ok").click();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).dialog("option","buttons",buttons);
|
||||
}
|
||||
$("#dialog-form").html($("script[data-template-name='"+type+"']").html());
|
||||
$('<input type="text" style="display: none;" />').appendTo("#dialog-form");
|
||||
prepareEditDialog(node,node._def,"node-input");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$( "#dialog" ).dialog("option","title","Edit "+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));
|
||||
}
|
||||
if (configTypeDef.oneditdelete) {
|
||||
configTypeDef.oneditdelete.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.nodes.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,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
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);
|
||||
for (var i=0;i<configNode.users.length;i++) {
|
||||
var user = configNode.users[i];
|
||||
validateNode(user);
|
||||
}
|
||||
|
||||
RED.nodes.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) {
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
}
|
||||
if (RED.view.state() != RED.state.EDITING) {
|
||||
RED.keyboard.disable();
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
$(this).dialog('option','width','auto');
|
||||
$(this).dialog('option','height','auto');
|
||||
$("#dialog-config-form").html("");
|
||||
if (RED.view.state() != RED.state.EDITING) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
RED.sidebar.config.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
$( "#subflow-dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "subflow-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (editing_node) {
|
||||
var i;
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
|
||||
var newName = $("#subflow-input-name").val();
|
||||
|
||||
if (newName != editing_node.name) {
|
||||
changes['name'] = editing_node.name;
|
||||
editing_node.name = newName;
|
||||
changed = true;
|
||||
$("#btn-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
|
||||
}
|
||||
|
||||
RED.palette.refresh();
|
||||
|
||||
if (changed) {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+editing_node.id) {
|
||||
n.changed = true;
|
||||
updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.nodes.dirty(true);
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:editing_node,
|
||||
changes:changes,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged
|
||||
};
|
||||
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "subflow-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
editing_node = null;
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
|
||||
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
||||
RED.view.state(RED.state.DEFAULT);
|
||||
}
|
||||
RED.sidebar.info.refresh(editing_node);
|
||||
editing_node = null;
|
||||
}
|
||||
});
|
||||
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
|
||||
|
||||
|
||||
function showEditSubflowDialog(subflow) {
|
||||
editing_node = subflow;
|
||||
RED.view.state(RED.state.EDITING);
|
||||
$("#subflow-input-name").val(subflow.name);
|
||||
var userCount = 0;
|
||||
var subflowType = "subflow:"+editing_node.id;
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type === subflowType) {
|
||||
userCount++;
|
||||
}
|
||||
});
|
||||
|
||||
$("#subflow-dialog-user-count").html("There "+(userCount==1?"is":"are")+" "+userCount+" instance"+(userCount==1?" ":"s")+" of this subflow").show();
|
||||
$("#subflow-dialog").dialog("option","title","Edit flow "+subflow.name).dialog( "open" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
edit: showEditDialog,
|
||||
editConfig: showEditConfigNodeDialog,
|
||||
editSubflow: showEditSubflowDialog,
|
||||
validateNode: validateNode,
|
||||
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
|
||||
|
||||
createEditor: function(options) {
|
||||
var editor = ace.edit(options.id);
|
||||
editor.setTheme("ace/theme/tomorrow");
|
||||
if (options.mode) {
|
||||
editor.getSession().setMode(options.mode);
|
||||
}
|
||||
if (options.foldStyle) {
|
||||
editor.getSession().setFoldStyle(options.foldStyle);
|
||||
} else {
|
||||
editor.getSession().setFoldStyle('markbeginend');
|
||||
}
|
||||
if (options.options) {
|
||||
editor.setOptions(options.options);
|
||||
} else {
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion:true,
|
||||
enableSnippets:true
|
||||
});
|
||||
}
|
||||
editor.$blockScrolling = Infinity;
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
})();
|
114
editor/js/ui/keyboard.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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 || d3.event.metaKey) &&
|
||||
(!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 || d3.event.metaKey) &&
|
||||
(!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];
|
||||
}
|
||||
|
||||
|
||||
var dialog = $('<div id="keyboard-help-dialog" class="hide">'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
|
||||
'<tr><td> </td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: "800",
|
||||
title:"Keyboard shortcuts",
|
||||
resizable: false,
|
||||
open: function() {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function() {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
function showKeyboardHelp() {
|
||||
dialog.dialog("open");
|
||||
}
|
||||
|
||||
return {
|
||||
add: addHandler,
|
||||
remove: removeHandler,
|
||||
disable: function(){ active = false;},
|
||||
enable: function(){ active = true; },
|
||||
|
||||
showHelp: showKeyboardHelp
|
||||
}
|
||||
|
||||
})();
|
414
editor/js/ui/library.js
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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);
|
||||
});
|
||||
}
|
||||
|
||||
function createUI(options) {
|
||||
var libraryData = {};
|
||||
var selectedLibraryItem = null;
|
||||
var libraryEditor = null;
|
||||
|
||||
// Orion editor has set/getText
|
||||
// ACE editor has set/getValue
|
||||
// normalise to set/getValue
|
||||
if (options.editor.setText) {
|
||||
// Orion doesn't like having pos passed in, so proxy the call to drop it
|
||||
options.editor.setValue = function(text,pos) {
|
||||
options.editor.setText.call(options.editor,text);
|
||||
}
|
||||
}
|
||||
if (options.editor.getText) {
|
||||
options.editor.getValue = options.editor.getText;
|
||||
}
|
||||
|
||||
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.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
}
|
||||
|
||||
$('#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.setValue('',-1);
|
||||
|
||||
$.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();
|
||||
});
|
||||
|
||||
libraryEditor = ace.edit('node-select-library-text');
|
||||
libraryEditor.setTheme("ace/theme/tomorrow");
|
||||
if (options.mode) {
|
||||
libraryEditor.getSession().setMode(options.mode);
|
||||
}
|
||||
libraryEditor.setOptions({
|
||||
readOnly: true,
|
||||
highlightActiveLine: false,
|
||||
highlightGutterLine: false
|
||||
});
|
||||
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
|
||||
libraryEditor.$blockScrolling = Infinity;
|
||||
|
||||
$( "#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.setValue(libraryEditor.getValue(),-1);
|
||||
}
|
||||
$( 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 = [];
|
||||
var data = {};
|
||||
for (var i=0;i<options.fields.length;i++) {
|
||||
var field = options.fields[i];
|
||||
if (field == "name") {
|
||||
data.name = name;
|
||||
} else {
|
||||
data[field] = $("#node-input-"+field).val();
|
||||
}
|
||||
}
|
||||
|
||||
data.text = options.editor.getValue();
|
||||
$.ajax({
|
||||
url:"library/"+options.url+'/'+fullpath,
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify("Saved "+options.type,"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.notify("Saved failed: "+xhr.responseJSON.message,"error");
|
||||
});
|
||||
}
|
||||
$( "#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" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function exportFlow() {
|
||||
//TODO: don't rely on the main dialog
|
||||
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
|
||||
$("#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" );
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
loadFlowLibrary();
|
||||
},
|
||||
create: createUI,
|
||||
loadFlowLibrary: loadFlowLibrary,
|
||||
|
||||
export: exportFlow
|
||||
}
|
||||
})();
|
||||
|
||||
|
224
editor/js/ui/menu.js
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function setState() {
|
||||
var savedStateActive = isSavedStateActive(opt.id);
|
||||
if (savedStateActive) {
|
||||
link.addClass("active");
|
||||
opt.onselect.call(opt, true);
|
||||
} else if (savedStateActive === false) {
|
||||
link.removeClass("active");
|
||||
opt.onselect.call(opt, false);
|
||||
} else if (opt.hasOwnProperty("selected")) {
|
||||
if (opt.selected) {
|
||||
link.addClass("active");
|
||||
} else {
|
||||
link.removeClass("active");
|
||||
}
|
||||
opt.onselect.call(opt, opt.selected);
|
||||
}
|
||||
}
|
||||
|
||||
if (opt === null) {
|
||||
item = $('<li class="divider"></li>');
|
||||
} else {
|
||||
item = $('<li></li>');
|
||||
|
||||
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
|
||||
if (opt.toggle) {
|
||||
linkContent += '<i class="fa fa-square pull-left"></i>';
|
||||
linkContent += '<i class="fa fa-check-square pull-left"></i>';
|
||||
|
||||
}
|
||||
if (opt.icon !== undefined) {
|
||||
if (/\.png/.test(opt.icon)) {
|
||||
linkContent += '<img src="'+opt.icon+'"/> ';
|
||||
} else {
|
||||
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.sublabel) {
|
||||
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
|
||||
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
|
||||
} else {
|
||||
linkContent += '<span class="menu-label">'+opt.label+'</span>'
|
||||
}
|
||||
|
||||
linkContent += '</a>';
|
||||
|
||||
var link = $(linkContent).appendTo(item);
|
||||
|
||||
menuItems[opt.id] = opt;
|
||||
|
||||
if (opt.onselect) {
|
||||
link.click(function() {
|
||||
if ($(this).parent().hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
if (opt.toggle) {
|
||||
var selected = isSelected(opt.id);
|
||||
if (typeof opt.toggle === "string") {
|
||||
if (!selected) {
|
||||
for (var m in menuItems) {
|
||||
if (menuItems.hasOwnProperty(m)) {
|
||||
var mi = menuItems[m];
|
||||
if (mi.id != opt.id && opt.toggle == mi.toggle) {
|
||||
setSelected(mi.id,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelected(opt.id,true);
|
||||
}
|
||||
} else {
|
||||
setSelected(opt.id, !selected);
|
||||
}
|
||||
} else {
|
||||
opt.onselect.call(opt);
|
||||
}
|
||||
});
|
||||
setState();
|
||||
} else if (opt.href) {
|
||||
link.attr("target","_blank").attr("href",opt.href);
|
||||
} else if (!opt.options) {
|
||||
item.addClass("disabled");
|
||||
link.click(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
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");
|
||||
}
|
||||
if (opt.tip) {
|
||||
item.popover({
|
||||
placement:"left",
|
||||
trigger: "hover",
|
||||
delay: { show: 350, hide: 20 },
|
||||
html: true,
|
||||
container:'body',
|
||||
content: opt.tip
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
function createMenu(options) {
|
||||
|
||||
var button = $("#"+options.id);
|
||||
|
||||
//button.click(function(event) {
|
||||
// $("#"+options.id+"-submenu").show();
|
||||
// event.preventDefault();
|
||||
//});
|
||||
|
||||
|
||||
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
|
||||
|
||||
for (var i=0;i<options.options.length;i++) {
|
||||
var opt = options.options[i];
|
||||
createMenuItem(opt).appendTo(topMenu);
|
||||
}
|
||||
}
|
||||
|
||||
function isSavedStateActive(id) {
|
||||
return RED.settings.get("menu-" + id);
|
||||
}
|
||||
|
||||
function isSelected(id) {
|
||||
return $("#" + id).hasClass("active");
|
||||
}
|
||||
|
||||
function setSavedState(id, state) {
|
||||
RED.settings.set("menu-" + id, state);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
setSavedState(id, 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();
|
||||
}
|
||||
|
||||
function setAction(id,action) {
|
||||
menuItems[id].onselect = action;
|
||||
$("#"+id).click(function() {
|
||||
if ($(this).parent().hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
if (menuItems[id].toggle) {
|
||||
setSelected(id,!isSelected(id));
|
||||
} else {
|
||||
menuItems[id].onselect.call(menuItems[id]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: createMenu,
|
||||
setSelected: setSelected,
|
||||
isSelected: isSelected,
|
||||
setDisabled: setDisabled,
|
||||
addItem: addItem,
|
||||
removeItem: removeItem,
|
||||
setAction: setAction
|
||||
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
|
||||
}
|
||||
})();
|
59
editor/js/ui/notifications.js
Normal file
@@ -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;
|
||||
}
|
||||
})();
|
||||
|
309
editor/js/ui/palette.js
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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 = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
|
||||
|
||||
function createCategoryContainer(category){
|
||||
var escapedCategory = category.replace(" ","_");
|
||||
var catDiv = $("#palette-container").append('<div id="palette-container-'+category+'" class="palette-category hide">'+
|
||||
'<div id="palette-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>');
|
||||
|
||||
$("#palette-header-"+category).on('click', function(e) {
|
||||
$(this).next().slideToggle();
|
||||
$(this).children("i").toggleClass("expanded");
|
||||
});
|
||||
}
|
||||
|
||||
function setLabel(type, el,label) {
|
||||
var nodeWidth = 80;
|
||||
var nodeHeight = 25;
|
||||
var lineHeight = 20;
|
||||
var portHeight = 10;
|
||||
|
||||
var words = label.split(" ");
|
||||
|
||||
var displayLines = [];
|
||||
|
||||
var currentLine = words[0];
|
||||
var currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
|
||||
|
||||
for (var i=1;i<words.length;i++) {
|
||||
var newWidth = RED.view.calculateTextWidth(currentLine+" "+words[i], "palette_label", 0);
|
||||
if (newWidth < nodeWidth) {
|
||||
currentLine += " "+words[i];
|
||||
currentLineWidth = newWidth;
|
||||
} else {
|
||||
displayLines.push(currentLine);
|
||||
currentLine = words[i];
|
||||
currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
|
||||
}
|
||||
}
|
||||
displayLines.push(currentLine);
|
||||
|
||||
var lines = displayLines.join("<br/>");
|
||||
var multiLineNodeHeight = 8+(lineHeight*displayLines.length);
|
||||
el.css({height:multiLineNodeHeight+"px"});
|
||||
|
||||
var labelElement = el.find(".palette_label");
|
||||
labelElement.html(lines);
|
||||
|
||||
el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
|
||||
|
||||
var popOverContent;
|
||||
try {
|
||||
var l = "<p><b>"+label+"</b></p>";
|
||||
if (label != type) {
|
||||
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
|
||||
}
|
||||
|
||||
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>no information available</p>").trim())
|
||||
.filter(function(n) {
|
||||
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
|
||||
}).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 '"+type+"'.");
|
||||
console.log(err.toString());
|
||||
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>";
|
||||
}
|
||||
|
||||
|
||||
el.data('popover').options.content = popOverContent;
|
||||
}
|
||||
|
||||
function escapeNodeType(nt) {
|
||||
return nt.replace(" ","_").replace(".","_").replace(":","_");
|
||||
}
|
||||
|
||||
function addNodeType(nt,def) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
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;
|
||||
|
||||
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)||"";
|
||||
}
|
||||
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
|
||||
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+def.icon+")"}).appendTo(iconContainer);
|
||||
}
|
||||
|
||||
d.style.backgroundColor = def.color;
|
||||
|
||||
if (def.outputs > 0) {
|
||||
var portOut = document.createElement("div");
|
||||
portOut.className = "palette_port palette_port_output";
|
||||
d.appendChild(portOut);
|
||||
}
|
||||
|
||||
if (def.inputs > 0) {
|
||||
var portIn = document.createElement("div");
|
||||
portIn.className = "palette_port palette_port_input";
|
||||
d.appendChild(portIn);
|
||||
}
|
||||
|
||||
if ($("#palette-base-category-"+rootCategory).length === 0) {
|
||||
createCategoryContainer(rootCategory);
|
||||
}
|
||||
$("#palette-container-"+rootCategory).show();
|
||||
|
||||
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(); };
|
||||
|
||||
$(d).popover({
|
||||
title:d.type,
|
||||
placement:"right",
|
||||
trigger: "hover",
|
||||
delay: { show: 750, hide: 50 },
|
||||
html: true,
|
||||
container:'body'
|
||||
});
|
||||
$(d).click(function() {
|
||||
RED.view.focus();
|
||||
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,
|
||||
start: function() {RED.view.focus();}
|
||||
});
|
||||
|
||||
if (def.category == "subflows") {
|
||||
$(d).dblclick(function(e) {
|
||||
RED.workspaces.show(nt.substring(8));
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
setLabel(nt,$(d),label);
|
||||
|
||||
var categoryNode = $("#palette-container-"+category);
|
||||
if (categoryNode.find(".palette_node").length === 1) {
|
||||
if (!categoryNode.find("i").hasClass("expanded")) {
|
||||
categoryNode.find(".palette-content").slideToggle();
|
||||
categoryNode.find("i").toggleClass("expanded");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function removeNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
var paletteNode = $("#palette_node_"+nodeTypeId);
|
||||
var categoryNode = paletteNode.closest(".palette-category");
|
||||
paletteNode.remove();
|
||||
if (categoryNode.find(".palette_node").length === 0) {
|
||||
if (categoryNode.find("i").hasClass("expanded")) {
|
||||
categoryNode.find(".palette-content").slideToggle();
|
||||
categoryNode.find("i").toggleClass("expanded");
|
||||
}
|
||||
}
|
||||
}
|
||||
function hideNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
$("#palette_node_"+nodeTypeId).hide();
|
||||
}
|
||||
|
||||
function showNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
$("#palette_node_"+nodeTypeId).show();
|
||||
}
|
||||
|
||||
function refreshNodeTypes() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var paletteNode = $("#palette_node_subflow_"+sf.id.replace(".","_"));
|
||||
var portInput = paletteNode.find(".palette_port_input");
|
||||
var portOutput = paletteNode.find(".palette_port_output");
|
||||
|
||||
if (portInput.length === 0 && sf.in.length > 0) {
|
||||
var portIn = document.createElement("div");
|
||||
portIn.className = "palette_port palette_port_input";
|
||||
paletteNode.append(portIn);
|
||||
} else if (portInput.length !== 0 && sf.in.length === 0) {
|
||||
portInput.remove();
|
||||
}
|
||||
|
||||
if (portOutput.length === 0 && sf.out.length > 0) {
|
||||
var portOut = document.createElement("div");
|
||||
portOut.className = "palette_port palette_port_output";
|
||||
paletteNode.append(portOut);
|
||||
} else if (portOutput.length !== 0 && sf.out.length === 0) {
|
||||
portOutput.remove();
|
||||
}
|
||||
setLabel(sf.type+":"+sf.id,paletteNode,sf.name);
|
||||
});
|
||||
}
|
||||
|
||||
function filterChange() {
|
||||
var val = $("#palette-search-input").val();
|
||||
if (val === "") {
|
||||
$("#palette-search-clear").hide();
|
||||
} else {
|
||||
$("#palette-search-clear").show();
|
||||
}
|
||||
|
||||
var re = new RegExp(val,'i');
|
||||
$(".palette_node").each(function(i,el) {
|
||||
var currentLabel = $(el).find(".palette_label").text();
|
||||
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(".palette-spinner").show();
|
||||
if (RED.settings.paletteCategories) {
|
||||
RED.settings.paletteCategories.forEach(createCategoryContainer);
|
||||
} else {
|
||||
core.forEach(createCategoryContainer);
|
||||
}
|
||||
|
||||
$("#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 {
|
||||
init: init,
|
||||
add:addNodeType,
|
||||
remove:removeNodeType,
|
||||
hide:hideNodeType,
|
||||
show:showNodeType,
|
||||
refresh:refreshNodeTypes
|
||||
};
|
||||
})();
|
175
editor/js/ui/sidebar.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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);
|
||||
eventHandler.emit("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();
|
||||
eventHandler.emit("resize");
|
||||
},
|
||||
stop:function(event,ui) {
|
||||
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");
|
||||
eventHandler.emit("resize");
|
||||
}
|
||||
});
|
||||
|
||||
function toggleSidebar(state) {
|
||||
if (!state) {
|
||||
$("#main-container").addClass("sidebar-closed");
|
||||
} else {
|
||||
$("#main-container").removeClass("sidebar-closed");
|
||||
sidebar_tabs.resize();
|
||||
}
|
||||
eventHandler.emit("resize");
|
||||
}
|
||||
|
||||
function showSidebar(id) {
|
||||
if (id) {
|
||||
sidebar_tabs.activateTab("tab-"+id);
|
||||
}
|
||||
}
|
||||
|
||||
function containsTab(id) {
|
||||
return sidebar_tabs.contains("tab-"+id);
|
||||
}
|
||||
|
||||
function init () {
|
||||
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();});
|
||||
showSidebar();
|
||||
RED.sidebar.info.show();
|
||||
}
|
||||
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
init: init,
|
||||
addTab: addTab,
|
||||
removeTab: removeTab,
|
||||
show: showSidebar,
|
||||
containsTab: containsTab,
|
||||
toggleSidebar: toggleSidebar,
|
||||
on: eventHandler.on
|
||||
}
|
||||
|
||||
})();
|
26
editor/js/ui/state.js
Normal file
@@ -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
|
||||
}
|
402
editor/js/ui/subflow.js
Normal file
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Copyright 2015 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.subflow = (function() {
|
||||
|
||||
|
||||
function getSubflow() {
|
||||
return RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
|
||||
function findAvailableSubflowIOPosition(subflow) {
|
||||
var pos = {x:70,y:70};
|
||||
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
|
||||
var port;
|
||||
if (i < subflow.out.length) {
|
||||
port = subflow.out[i];
|
||||
} else {
|
||||
port = subflow.in[i-subflow.out.length];
|
||||
}
|
||||
if (port.x == pos.x && port.y == pos.y) {
|
||||
pos.x += 55;
|
||||
i=0;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
function addSubflowInput() {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
var newInput = {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
z:subflow.id,
|
||||
i:subflow.in.length,
|
||||
x:position.x,
|
||||
y:position.y,
|
||||
id:RED.nodes.id()
|
||||
};
|
||||
var oldInCount = subflow.in.length;
|
||||
subflow.in.push(newInput);
|
||||
subflow.dirty = true;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
inputCount: oldInCount
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",true);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
function addSubflowOutput(id) {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
|
||||
var newOutput = {
|
||||
type:"subflow",
|
||||
direction:"out",
|
||||
z:subflow.id,
|
||||
i:subflow.out.length,
|
||||
x:position.x,
|
||||
y:position.y,
|
||||
id:RED.nodes.id()
|
||||
};
|
||||
var oldOutCount = subflow.out.length;
|
||||
subflow.out.push(newOutput);
|
||||
subflow.dirty = true;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
n.outputs = subflow.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
outputCount: oldOutCount
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$("#workspace-subflow-edit").click(function(event) {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
|
||||
event.preventDefault();
|
||||
});
|
||||
$("#workspace-subflow-add-input").click(function(event) {
|
||||
event.preventDefault();
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
addSubflowInput();
|
||||
});
|
||||
$("#workspace-subflow-add-output").click(function(event) {
|
||||
event.preventDefault();
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
addSubflowOutput();
|
||||
});
|
||||
|
||||
$("#workspace-subflow-delete").click(function(event) {
|
||||
event.preventDefault();
|
||||
var removedNodes = [];
|
||||
var removedLinks = [];
|
||||
var startDirty = RED.nodes.dirty();
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+getSubflow().id) {
|
||||
removedNodes.push(n);
|
||||
}
|
||||
if (n.z == getSubflow().id) {
|
||||
removedNodes.push(n);
|
||||
}
|
||||
});
|
||||
|
||||
for (var i=0;i<removedNodes.length;i++) {
|
||||
var rmlinks = RED.nodes.remove(removedNodes[i].id);
|
||||
removedLinks = removedLinks.concat(rmlinks);
|
||||
}
|
||||
|
||||
var activeSubflow = getSubflow();
|
||||
|
||||
RED.nodes.removeSubflow(activeSubflow);
|
||||
|
||||
RED.history.push({
|
||||
t:'delete',
|
||||
nodes:removedNodes,
|
||||
links:removedLinks,
|
||||
subflow: activeSubflow,
|
||||
dirty:startDirty
|
||||
});
|
||||
|
||||
RED.workspaces.remove(activeSubflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("btn-convert-subflow",true);
|
||||
} else {
|
||||
RED.menu.setDisabled("btn-convert-subflow",false);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function createSubflow() {
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
||||
if (m) {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
id:subflowId,
|
||||
name:name,
|
||||
in: [],
|
||||
out: []
|
||||
};
|
||||
RED.nodes.addSubflow(subflow);
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
subflow: subflow,
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
RED.workspaces.show(subflowId);
|
||||
}
|
||||
|
||||
function convertToSubflow() {
|
||||
var selection = RED.view.selection();
|
||||
if (!selection.nodes) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
|
||||
return;
|
||||
}
|
||||
var i;
|
||||
var nodes = {};
|
||||
var new_links = [];
|
||||
var removedLinks = [];
|
||||
|
||||
var candidateInputs = [];
|
||||
var candidateOutputs = [];
|
||||
|
||||
var boundingBox = [selection.nodes[0].x,
|
||||
selection.nodes[0].y,
|
||||
selection.nodes[0].x,
|
||||
selection.nodes[0].y];
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
var n = selection.nodes[i];
|
||||
nodes[n.id] = {n:n,outputs:{}};
|
||||
boundingBox = [
|
||||
Math.min(boundingBox[0],n.x),
|
||||
Math.min(boundingBox[1],n.y),
|
||||
Math.max(boundingBox[2],n.x),
|
||||
Math.max(boundingBox[3],n.y)
|
||||
]
|
||||
}
|
||||
|
||||
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
|
||||
|
||||
RED.nodes.eachLink(function(link) {
|
||||
if (nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// A link wholely within the selection
|
||||
}
|
||||
|
||||
if (nodes[link.source.id] && !nodes[link.target.id]) {
|
||||
// An outbound link from the selection
|
||||
candidateOutputs.push(link);
|
||||
removedLinks.push(link);
|
||||
}
|
||||
if (!nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// An inbound link
|
||||
candidateInputs.push(link);
|
||||
removedLinks.push(link);
|
||||
}
|
||||
});
|
||||
|
||||
var outputs = {};
|
||||
candidateOutputs = candidateOutputs.filter(function(v) {
|
||||
if (outputs[v.source.id+":"+v.sourcePort]) {
|
||||
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
|
||||
return false;
|
||||
}
|
||||
v.targets = [];
|
||||
v.targets.push(v.target);
|
||||
outputs[v.source.id+":"+v.sourcePort] = v;
|
||||
return true;
|
||||
});
|
||||
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
|
||||
|
||||
if (candidateInputs.length > 1) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
|
||||
return;
|
||||
}
|
||||
//if (candidateInputs.length == 0) {
|
||||
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
|
||||
// return;
|
||||
//}
|
||||
|
||||
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
||||
if (m) {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
id:subflowId,
|
||||
name:name,
|
||||
in: candidateInputs.map(function(v,i) { var index = i; return {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
x:v.target.x-(v.target.w/2)-80,
|
||||
y:v.target.y,
|
||||
z:subflowId,
|
||||
i:index,
|
||||
id:RED.nodes.id(),
|
||||
wires:[{id:v.target.id}]
|
||||
}}),
|
||||
out: candidateOutputs.map(function(v,i) { var index = i; return {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
x:v.source.x+(v.source.w/2)+80,
|
||||
y:v.source.y,
|
||||
z:subflowId,
|
||||
i:index,
|
||||
id:RED.nodes.id(),
|
||||
wires:[{id:v.source.id,port:v.sourcePort}]
|
||||
}})
|
||||
};
|
||||
RED.nodes.addSubflow(subflow);
|
||||
|
||||
var subflowInstance = {
|
||||
id:RED.nodes.id(),
|
||||
type:"subflow:"+subflow.id,
|
||||
x: center[0],
|
||||
y: center[1],
|
||||
z: RED.workspaces.active(),
|
||||
inputs: subflow.in.length,
|
||||
outputs: subflow.out.length,
|
||||
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
|
||||
changed:true
|
||||
}
|
||||
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
|
||||
RED.editor.validateNode(subflowInstance);
|
||||
RED.nodes.add(subflowInstance);
|
||||
|
||||
candidateInputs.forEach(function(l) {
|
||||
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
|
||||
candidateOutputs.forEach(function(output,i) {
|
||||
output.targets.forEach(function(target) {
|
||||
var link = {source:subflowInstance, sourcePort:i, target: target};
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
subflow.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
subflow.out.forEach(function(output,i) {
|
||||
output.wires.forEach(function(wire) {
|
||||
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
for (i=0;i<removedLinks.length;i++) {
|
||||
RED.nodes.removeLink(removedLinks[i]);
|
||||
}
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
selection.nodes[i].z = subflow.id;
|
||||
}
|
||||
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
nodes:[subflowInstance.id],
|
||||
links:new_links,
|
||||
subflow: subflow,
|
||||
|
||||
activeWorkspace: RED.workspaces.active(),
|
||||
removedLinks: removedLinks,
|
||||
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
|
||||
RED.editor.validateNode(subflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
createSubflow: createSubflow,
|
||||
convertToSubflow: convertToSubflow
|
||||
}
|
||||
})();
|
84
editor/js/ui/tab-config.js
Normal file
@@ -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
|
||||
}
|
||||
})();
|
191
editor/js/ui/tab-info.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 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() {
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
});
|
||||
|
||||
var content = document.createElement("div");
|
||||
content.id = "tab-info";
|
||||
content.style.paddingTop = "4px";
|
||||
content.style.paddingLeft = "4px";
|
||||
content.style.paddingRight = "4px";
|
||||
|
||||
var propertiesExpanded = false;
|
||||
|
||||
function show() {
|
||||
if (!RED.sidebar.containsTab("info")) {
|
||||
RED.sidebar.addTab("info",content,false);
|
||||
}
|
||||
RED.sidebar.show("info");
|
||||
}
|
||||
|
||||
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>';
|
||||
if (node.type != "subflow" && node.name) {
|
||||
table += "<tr><td>Name</td><td> "+node.name+"</td></tr>";
|
||||
}
|
||||
table += "<tr><td>Type</td><td> "+node.type+"</td></tr>";
|
||||
table += "<tr><td>ID</td><td> "+node.id+"</td></tr>";
|
||||
|
||||
var m = /^subflow(:(.+))?$/.exec(node.type);
|
||||
if (m) {
|
||||
var subflowNode;
|
||||
if (m[2]) {
|
||||
subflowNode = RED.nodes.subflow(m[2]);
|
||||
} else {
|
||||
subflowNode = node;
|
||||
}
|
||||
|
||||
table += '<tr class="blank"><td colspan="2">Subflow</td></tr>';
|
||||
|
||||
var userCount = 0;
|
||||
var subflowType = "subflow:"+subflowNode.id;
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type === subflowType) {
|
||||
userCount++;
|
||||
}
|
||||
});
|
||||
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>";
|
||||
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>";
|
||||
}
|
||||
|
||||
if (!m && node.type != "subflow" && node.type != "comment") {
|
||||
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> Properties</a></td></tr>';
|
||||
if (node._def) {
|
||||
for (var n in node._def.defaults) {
|
||||
if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
|
||||
var val = node[n]||"";
|
||||
var type = typeof val;
|
||||
if (type === "string") {
|
||||
if (val.length === 0) {
|
||||
val += '<span style="font-style: italic; color: #ccc;">blank</span>';
|
||||
} else {
|
||||
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 class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table += "</tbody></table><hr/>";
|
||||
if (node.type != "comment") {
|
||||
var helpText = $("script[data-help-name|='"+node.type+"']").html()||"";
|
||||
table += '<div class="node-help">'+helpText+"</div>";
|
||||
}
|
||||
|
||||
if (node._def && node._def.info) {
|
||||
var info = node._def.info;
|
||||
table += '<div class="node-help">'+marked(typeof info === "function" ? info.call(node) : info)+'</div>';
|
||||
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
|
||||
}
|
||||
|
||||
$("#tab-info").html(table);
|
||||
|
||||
$(".node-info-property-header").click(function(e) {
|
||||
var icon = $(this).find("i");
|
||||
if (icon.hasClass("fa-caret-right")) {
|
||||
icon.removeClass("fa-caret-right");
|
||||
icon.addClass("fa-caret-down");
|
||||
$(".node-info-property-row").show();
|
||||
propertiesExpanded = true;
|
||||
} else {
|
||||
icon.addClass("fa-caret-right");
|
||||
icon.removeClass("fa-caret-down");
|
||||
$(".node-info-property-row").hide();
|
||||
propertiesExpanded = false;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function clear() {
|
||||
$("#tab-info").html("");
|
||||
}
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (selection.nodes) {
|
||||
if (selection.nodes.length == 1) {
|
||||
var node = selection.nodes[0];
|
||||
if (node.type === "subflow" && node.direction) {
|
||||
refresh(RED.nodes.subflow(node.z));
|
||||
} else {
|
||||
refresh(node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
if (subflow) {
|
||||
refresh(subflow);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
show: show,
|
||||
refresh:refresh,
|
||||
clear: clear
|
||||
}
|
||||
})();
|
134
editor/js/ui/tabs.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
renameTab: function(id,label) {
|
||||
tabs[id].label = label;
|
||||
var tab = ul.find("a[href='#"+id+"']");
|
||||
tab.attr("title",label);
|
||||
tab.text(label);
|
||||
updateTabWidths();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
create: createTabs
|
||||
}
|
||||
})();
|
184
editor/js/ui/touch/radialMenu.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
1814
editor/js/ui/view.js
Normal file
270
editor/js/ui/workspaces.js
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright 2015 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.workspaces = (function() {
|
||||
|
||||
var activeWorkspace = 0;
|
||||
var workspaceIndex = 0;
|
||||
|
||||
function addWorkspace(ws) {
|
||||
if (ws) {
|
||||
workspace_tabs.addTab(ws);
|
||||
workspace_tabs.resize();
|
||||
} else {
|
||||
var tabId = RED.nodes.id();
|
||||
do {
|
||||
workspaceIndex += 1;
|
||||
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
|
||||
|
||||
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:RED.nodes.dirty()});
|
||||
RED.nodes.dirty(true);
|
||||
}
|
||||
}
|
||||
function deleteWorkspace(ws,force) {
|
||||
if (workspace_tabs.count() == 1) {
|
||||
return;
|
||||
}
|
||||
var nodes = [];
|
||||
if (!force) {
|
||||
nodes = RED.nodes.filterNodes({z:ws.id});
|
||||
}
|
||||
if (force || nodes.length === 0) {
|
||||
removeWorkspace(ws);
|
||||
var historyEvent = RED.nodes.removeWorkspace(ws.id);
|
||||
historyEvent.t = 'delete';
|
||||
historyEvent.dirty = RED.nodes.dirty();
|
||||
historyEvent.workspaces = [ws];
|
||||
RED.history.push(historyEvent);
|
||||
RED.nodes.dirty(true);
|
||||
} else {
|
||||
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
|
||||
$( "#node-dialog-delete-workspace-name" ).text(ws.label);
|
||||
$( "#node-dialog-delete-workspace" ).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");
|
||||
}
|
||||
|
||||
var workspace_tabs = RED.tabs.create({
|
||||
id: "workspace-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.type == "subflow") {
|
||||
$("#workspace-toolbar").show();
|
||||
} else {
|
||||
$("#workspace-toolbar").hide();
|
||||
}
|
||||
var event = {
|
||||
old: activeWorkspace
|
||||
}
|
||||
activeWorkspace = tab.id;
|
||||
event.workspace = activeWorkspace;
|
||||
|
||||
eventHandler.emit("change",event);
|
||||
},
|
||||
ondblclick: function(tab) {
|
||||
if (tab.type != "subflow") {
|
||||
showRenameWorkspaceDialog(tab.id);
|
||||
} else {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(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(".","-"));
|
||||
}
|
||||
});
|
||||
|
||||
$("#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);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
var label = $( "#node-input-workspace-name" ).val();
|
||||
if (workspace.label != label) {
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
RED.nodes.dirty(true);
|
||||
$("#btn-workspace-menu-"+workspace.id.replace(".","-")).text(label);
|
||||
// TODO: update entry in menu
|
||||
}
|
||||
$( 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');
|
||||
deleteWorkspace(workspace,true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function init() {
|
||||
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
|
||||
RED.sidebar.on("resize",workspace_tabs.resize);
|
||||
|
||||
RED.menu.setAction('btn-workspace-delete',function() {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function removeWorkspace(ws) {
|
||||
if (!ws) {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
} else {
|
||||
if (workspace_tabs.contains(ws.id)) {
|
||||
workspace_tabs.removeTab(ws.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
on: eventHandler.on,
|
||||
add: addWorkspace,
|
||||
remove: removeWorkspace,
|
||||
|
||||
edit: function(id) {
|
||||
showRenameWorkspaceDialog(id||activeWorkspace);
|
||||
},
|
||||
contains: function(id) {
|
||||
return workspace_tabs.contains(id);
|
||||
},
|
||||
count: function() {
|
||||
return workspace_tabs.count();
|
||||
},
|
||||
active: function() {
|
||||
return activeWorkspace
|
||||
},
|
||||
show: function(id) {
|
||||
if (!workspace_tabs.contains(id)) {
|
||||
var sf = RED.nodes.subflow(id);
|
||||
if (sf) {
|
||||
addWorkspace({type:"subflow",id:id,label:"Subflow: "+sf.name, closeable: true});
|
||||
}
|
||||
}
|
||||
workspace_tabs.activateTab(id);
|
||||
},
|
||||
refresh: function() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
if (workspace_tabs.contains(sf.id)) {
|
||||
workspace_tabs.renameTab(sf.id,"Subflow: "+sf.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
resize: function() {
|
||||
workspace_tabs.resize();
|
||||
}
|
||||
}
|
||||
})();
|
163
editor/js/user.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Copyright 2014, 2015 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.user = (function() {
|
||||
|
||||
function login(opts,done) {
|
||||
if (typeof opts == 'function') {
|
||||
done = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
var dialog = $('<div id="node-dialog-login" class="hide">'+
|
||||
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img src="node-red-256.png"/></div>'+
|
||||
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
|
||||
'<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
|
||||
'</div>'+
|
||||
'</div>');
|
||||
|
||||
dialog.dialog({
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
modal: true,
|
||||
closeOnEscape: false,
|
||||
width: 600,
|
||||
resizable: false,
|
||||
draggable: false
|
||||
});
|
||||
|
||||
$("#node-dialog-login-fields").empty();
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: "auth/login",
|
||||
success: function(data) {
|
||||
if (data.type == "credentials") {
|
||||
var i=0;
|
||||
for (;i<data.prompts.length;i++) {
|
||||
var field = data.prompts[i];
|
||||
var row = $("<div/>",{class:"form-row"});
|
||||
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
|
||||
$('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
|
||||
row.appendTo("#node-dialog-login-fields");
|
||||
}
|
||||
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
|
||||
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
|
||||
'<a href="#" id="node-dialog-login-submit" tabIndex="'+(i+2)+'">Login</a></div>').appendTo("#node-dialog-login-fields");
|
||||
$("#node-dialog-login-submit").button().click(function( event ) {
|
||||
$("#node-dialog-login-submit").button("option","disabled",true);
|
||||
$("#node-dialog-login-failed").hide();
|
||||
$(".login-spinner").show();
|
||||
|
||||
var body = {
|
||||
client_id: "node-red-editor",
|
||||
grant_type: "password",
|
||||
scope:"*"
|
||||
}
|
||||
for (var i=0;i<data.prompts.length;i++) {
|
||||
var field = data.prompts[i];
|
||||
body[field.id] = $("#node-dialog-login-"+field.id).val();
|
||||
}
|
||||
$.ajax({
|
||||
url:"auth/token",
|
||||
type: "POST",
|
||||
data: body
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.settings.set("auth-tokens",data);
|
||||
$("#node-dialog-login").dialog('destroy').remove();
|
||||
done();
|
||||
}).fail(function(jqXHR,textStatus,errorThrown) {
|
||||
RED.settings.remove("auth-tokens");
|
||||
$("#node-dialog-login-failed").show();
|
||||
}).always(function() {
|
||||
$("#node-dialog-login-submit").button("option","disabled",false);
|
||||
$(".login-spinner").hide();
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
if (opts.cancelable) {
|
||||
$("#node-dialog-login-cancel").button().click(function( event ) {
|
||||
$("#node-dialog-login").dialog('destroy').remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
dialog.dialog("open");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function logout() {
|
||||
$.ajax({
|
||||
url: "auth/revoke",
|
||||
type: "POST",
|
||||
data: {token:RED.settings.get("auth-tokens").access_token},
|
||||
success: function() {
|
||||
RED.settings.remove("auth-tokens");
|
||||
document.location.reload(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateUserMenu() {
|
||||
$("#btn-usermenu-submenu li").remove();
|
||||
if (RED.settings.user.anonymous) {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-login",
|
||||
label:"Login",
|
||||
onselect: function() {
|
||||
RED.user.login({cancelable:true},function() {
|
||||
RED.settings.load(function() {
|
||||
RED.notify("Logged in as "+RED.settings.user.username,"success");
|
||||
updateUserMenu();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-username",
|
||||
label:"<b>"+RED.settings.user.username+"</b>"
|
||||
});
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-logout",
|
||||
label:"Logout",
|
||||
onselect: function() {
|
||||
RED.user.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function init() {
|
||||
if (RED.settings.user) {
|
||||
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
|
||||
.prependTo(".header-toolbar");
|
||||
|
||||
RED.menu.init({id:"btn-usermenu",
|
||||
options: []
|
||||
});
|
||||
updateUserMenu();
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
login: login,
|
||||
logout: logout
|
||||
}
|
||||
|
||||
})();
|
19
editor/js/validators.js
Normal file
@@ -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);}}
|
||||
};
|
1313
editor/sass/style.scss
Normal file
24
editor/vendor/ace/LICENSE
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ajax.org B.V. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
11
editor/vendor/ace/ace.js
vendored
Normal file
5
editor/vendor/ace/ext-language_tools.js
vendored
Normal file
5
editor/vendor/ace/ext-searchbox.js
vendored
Normal file
1
editor/vendor/ace/mode-handlebars.js
vendored
Normal file
1
editor/vendor/ace/mode-html.js
vendored
Normal file
1
editor/vendor/ace/mode-javascript.js
vendored
Normal file
1
editor/vendor/ace/mode-json.js
vendored
Normal file
1
editor/vendor/ace/mode-markdown.js
vendored
Normal file
1
editor/vendor/ace/mode-yaml.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(\:(?:\s+|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*\:(?:\s+|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:"[|>][-+\\d\\s]*$",next:"qqstring"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"}],qqstring:[{token:"string",regex:"(?=(?:(?:\\\\.)|(?:[^:]))*?:)",next:"start"},{token:"string",regex:".+"}]}};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a})
|
1
editor/vendor/ace/snippets/handlebars.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/handlebars",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="handlebars"})
|
1
editor/vendor/ace/snippets/html.js
vendored
Normal file
1
editor/vendor/ace/snippets/javascript.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/javascript",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# Get Elements\nsnippet gett\n getElementsBy${1:TagName}(\'${2}\')${3}\n# Get Element\nsnippet get\n getElementBy${1:Id}(\'${2}\')${3}\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# singleton\nsnippet sing\n function ${1:Singleton} (${2:argument}) {\n var instance;\n $1 = function $1($2) {\n return instance;\n };\n $1.prototype = this;\n instance = new $1();\n instance.constructor = $1;\n\n ${3:// code ...}\n\n return instance;\n }\n# class\nsnippet class\nregex /^\\s*/clas{0,2}/\n var ${1:class} = function(${20}) {\n $40$0\n };\n \n (function() {\n ${60:this.prop = ""}\n }).call(${1:class}.prototype);\n \n exports.${1:class} = ${1:class};\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n\n\n#modules\nsnippet def\n ace.define(function(require, exports, module) {\n "use strict";\n var ${1/.*\\///} = require("${1}");\n \n $TM_SELECTED_TEXT\n });\nsnippet req\nguard ^\\s*\n var ${1/.*\\///} = require("${1}");\n $0\nsnippet requ\nguard ^\\s*\n var ${1/.*\\/(.)/\\u$1/} = require("${1}").${1/.*\\/(.)/\\u$1/};\n $0\n',t.scope="javascript"})
|
1
editor/vendor/ace/snippets/markdown.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/markdown",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Markdown\n\n# Includes octopress (http://octopress.org/) snippets\n\nsnippet [\n [${1:text}](http://${2:address} "${3:title}")\nsnippet [*\n [${1:link}](${2:`@*`} "${3:title}")${4}\n\nsnippet [:\n [${1:id}]: http://${2:url} "${3:title}"\nsnippet [:*\n [${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet \nsnippet ${4}\n\nsnippet ![:\n ![${1:id}]: ${2:url} "${3:title}"\nsnippet ![:*\n ![${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet ===\nregex /^/=+/=*//\n ${PREV_LINE/./=/g}\n \n ${0}\nsnippet ---\nregex /^/-+/-*//\n ${PREV_LINE/./-/g}\n \n ${0}\nsnippet blockquote\n {% blockquote %}\n ${1:quote}\n {% endblockquote %}\n\nsnippet blockquote-author\n {% blockquote ${1:author}, ${2:title} %}\n ${3:quote}\n {% endblockquote %}\n\nsnippet blockquote-link\n {% blockquote ${1:author} ${2:URL} ${3:link_text} %}\n ${4:quote}\n {% endblockquote %}\n\nsnippet bt-codeblock-short\n ```\n ${1:code_snippet}\n ```\n\nsnippet bt-codeblock-full\n ``` ${1:language} ${2:title} ${3:URL} ${4:link_text}\n ${5:code_snippet}\n ```\n\nsnippet codeblock-short\n {% codeblock %}\n ${1:code_snippet}\n {% endcodeblock %}\n\nsnippet codeblock-full\n {% codeblock ${1:title} lang:${2:language} ${3:URL} ${4:link_text} %}\n ${5:code_snippet}\n {% endcodeblock %}\n\nsnippet gist-full\n {% gist ${1:gist_id} ${2:filename} %}\n\nsnippet gist-short\n {% gist ${1:gist_id} %}\n\nsnippet img\n {% img ${1:class} ${2:URL} ${3:width} ${4:height} ${5:title_text} ${6:alt_text} %}\n\nsnippet youtube\n {% youtube ${1:video_id} %}\n\n# The quote should appear only once in the text. It is inherently part of it.\n# See http://octopress.org/docs/plugins/pullquote/ for more info.\n\nsnippet pullquote\n {% pullquote %}\n ${1:text} {" ${2:quote} "} ${3:text}\n {% endpullquote %}\n',t.scope="markdown"})
|
1
editor/vendor/ace/snippets/text.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/text",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="text"})
|
1
editor/vendor/ace/snippets/yaml.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/yaml",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="yaml"})
|
1
editor/vendor/ace/theme-chrome.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
1
editor/vendor/ace/theme-crimson_editor.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/crimson_editor",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssText='.ace-crimson-editor .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-crimson-editor .ace_gutter-layer {width: 100%;text-align: right;}.ace-crimson-editor .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-crimson-editor {background-color: #FFFFFF;color: rgb(64, 64, 64);}.ace-crimson-editor .ace_cursor {color: black;}.ace-crimson-editor .ace_invisible {color: rgb(191, 191, 191);}.ace-crimson-editor .ace_identifier {color: black;}.ace-crimson-editor .ace_keyword {color: blue;}.ace-crimson-editor .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-crimson-editor .ace_constant.ace_language {color: rgb(255, 156, 0);}.ace-crimson-editor .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_invalid {text-decoration: line-through;color: rgb(224, 0, 0);}.ace-crimson-editor .ace_fold {}.ace-crimson-editor .ace_support.ace_function {color: rgb(192, 0, 0);}.ace-crimson-editor .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_support.ace_type,.ace-crimson-editor .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-crimson-editor .ace_keyword.ace_operator {color: rgb(49, 132, 149);}.ace-crimson-editor .ace_string {color: rgb(128, 0, 128);}.ace-crimson-editor .ace_comment {color: rgb(76, 136, 107);}.ace-crimson-editor .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-crimson-editor .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-crimson-editor .ace_constant.ace_numeric {color: rgb(0, 0, 64);}.ace-crimson-editor .ace_variable {color: rgb(0, 64, 128);}.ace-crimson-editor .ace_xml-pe {color: rgb(104, 104, 91);}.ace-crimson-editor .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-crimson-editor .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-crimson-editor .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-crimson-editor .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-crimson-editor .ace_marker-layer .ace_active-line {background: rgb(232, 242, 254);}.ace-crimson-editor .ace_gutter-active-line {background-color : #dcdcdc;}.ace-crimson-editor .ace_meta.ace_tag {color:rgb(28, 2, 255);}.ace-crimson-editor .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-crimson-editor .ace_string.ace_regex {color: rgb(192, 0, 192);}.ace-crimson-editor .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}',t.cssClass="ace-crimson-editor";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
1
editor/vendor/ace/theme-solarized_light.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/solarized_light",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-solarized-light",t.cssText=".ace-solarized-light .ace_gutter {background: #fbf1d3;color: #333}.ace-solarized-light .ace_print-margin {width: 1px;background: #e8e8e8}.ace-solarized-light {background-color: #FDF6E3;color: #586E75}.ace-solarized-light .ace_cursor {color: #000000}.ace-solarized-light .ace_marker-layer .ace_selection {background: rgba(7, 54, 67, 0.09)}.ace-solarized-light.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FDF6E3;border-radius: 2px}.ace-solarized-light .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-solarized-light .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(147, 161, 161, 0.50)}.ace-solarized-light .ace_marker-layer .ace_active-line {background: #EEE8D5}.ace-solarized-light .ace_gutter-active-line {background-color : #EDE5C1}.ace-solarized-light .ace_marker-layer .ace_selected-word {border: 1px solid #073642}.ace-solarized-light .ace_invisible {color: rgba(147, 161, 161, 0.50)}.ace-solarized-light .ace_keyword,.ace-solarized-light .ace_meta,.ace-solarized-light .ace_support.ace_class,.ace-solarized-light .ace_support.ace_type {color: #859900}.ace-solarized-light .ace_constant.ace_character,.ace-solarized-light .ace_constant.ace_other {color: #CB4B16}.ace-solarized-light .ace_constant.ace_language {color: #B58900}.ace-solarized-light .ace_constant.ace_numeric {color: #D33682}.ace-solarized-light .ace_fold {background-color: #268BD2;border-color: #586E75}.ace-solarized-light .ace_entity.ace_name.ace_function,.ace-solarized-light .ace_entity.ace_name.ace_tag,.ace-solarized-light .ace_support.ace_function,.ace-solarized-light .ace_variable,.ace-solarized-light .ace_variable.ace_language {color: #268BD2}.ace-solarized-light .ace_storage {color: #073642}.ace-solarized-light .ace_string {color: #2AA198}.ace-solarized-light .ace_string.ace_regexp {color: #D30102}.ace-solarized-light .ace_comment,.ace-solarized-light .ace_entity.ace_other.ace_attribute-name {color: #93A1A1}.ace-solarized-light .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHjy8NJ/AAjgA5fzQUmBAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
1
editor/vendor/ace/theme-tomorrow.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/tomorrow",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-tomorrow",t.cssText=".ace-tomorrow .ace_gutter {background: #f6f6f6;color: #4D4D4C}.ace-tomorrow .ace_print-margin {width: 1px;background: #f6f6f6}.ace-tomorrow {background-color: #FFFFFF;color: #4D4D4C}.ace-tomorrow .ace_cursor {color: #AEAFAD}.ace-tomorrow .ace_marker-layer .ace_selection {background: #D6D6D6}.ace-tomorrow.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FFFFFF;border-radius: 2px}.ace-tomorrow .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-tomorrow .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #D1D1D1}.ace-tomorrow .ace_marker-layer .ace_active-line {background: #EFEFEF}.ace-tomorrow .ace_gutter-active-line {background-color : #dcdcdc}.ace-tomorrow .ace_marker-layer .ace_selected-word {border: 1px solid #D6D6D6}.ace-tomorrow .ace_invisible {color: #D1D1D1}.ace-tomorrow .ace_keyword,.ace-tomorrow .ace_meta,.ace-tomorrow .ace_storage,.ace-tomorrow .ace_storage.ace_type,.ace-tomorrow .ace_support.ace_type {color: #8959A8}.ace-tomorrow .ace_keyword.ace_operator {color: #3E999F}.ace-tomorrow .ace_constant.ace_character,.ace-tomorrow .ace_constant.ace_language,.ace-tomorrow .ace_constant.ace_numeric,.ace-tomorrow .ace_keyword.ace_other.ace_unit,.ace-tomorrow .ace_support.ace_constant,.ace-tomorrow .ace_variable.ace_parameter {color: #F5871F}.ace-tomorrow .ace_constant.ace_other {color: #666969}.ace-tomorrow .ace_invalid {color: #FFFFFF;background-color: #C82829}.ace-tomorrow .ace_invalid.ace_deprecated {color: #FFFFFF;background-color: #8959A8}.ace-tomorrow .ace_fold {background-color: #4271AE;border-color: #4D4D4C}.ace-tomorrow .ace_entity.ace_name.ace_function,.ace-tomorrow .ace_support.ace_function,.ace-tomorrow .ace_variable {color: #4271AE}.ace-tomorrow .ace_support.ace_class,.ace-tomorrow .ace_support.ace_type {color: #C99E00}.ace-tomorrow .ace_heading,.ace-tomorrow .ace_markup.ace_heading,.ace-tomorrow .ace_string {color: #718C00}.ace-tomorrow .ace_entity.ace_name.ace_tag,.ace-tomorrow .ace_entity.ace_other.ace_attribute-name,.ace-tomorrow .ace_meta.ace_tag,.ace-tomorrow .ace_string.ace_regexp,.ace-tomorrow .ace_variable {color: #C82829}.ace-tomorrow .ace_comment {color: #8E908C}.ace-tomorrow .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
1
editor/vendor/ace/worker-html.js
vendored
Normal file
1
editor/vendor/ace/worker-javascript.js
vendored
Normal file
1
editor/vendor/ace/worker-json.js
vendored
Normal file
9
editor/vendor/bootstrap/css/bootstrap.min.css
vendored
Normal file
BIN
editor/vendor/bootstrap/img/glyphicons-halflings-white.png
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
editor/vendor/bootstrap/img/glyphicons-halflings.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |