Merge pull request #1102 from node-red/0.16

0.16
This commit is contained in:
Nick O'Leary 2017-01-06 14:30:52 +00:00 committed by GitHub
commit f973396821
98 changed files with 13015 additions and 2291 deletions

View File

@ -16,7 +16,6 @@ node_js:
- "7"
- "6"
- "4"
- "0.10"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage
before_script:

View File

@ -98,7 +98,7 @@ module.exports = function(grunt) {
src: [
// Ensure editor source files are concatenated in
// the right order
"editor/js/main.js",
"editor/js/red.js",
"editor/js/events.js",
"editor/js/i18n.js",
"editor/js/settings.js",
@ -110,13 +110,16 @@ module.exports = function(grunt) {
"editor/js/nodes.js",
"editor/js/history.js",
"editor/js/validators.js",
"editor/js/ui/utils.js",
"editor/js/ui/common/editableList.js",
"editor/js/ui/common/menu.js",
"editor/js/ui/common/popover.js",
"editor/js/ui/common/searchBox.js",
"editor/js/ui/common/tabs.js",
"editor/js/ui/common/typedInput.js",
"editor/js/ui/actions.js",
"editor/js/ui/deploy.js",
"editor/js/ui/diff.js",
"editor/js/ui/keyboard.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
@ -131,6 +134,7 @@ module.exports = function(grunt) {
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",
"editor/js/ui/search.js",
"editor/js/ui/typeSearch.js",
"editor/js/ui/subflow.js",
"editor/js/ui/touch/radialMenu.js"
],
@ -150,6 +154,10 @@ module.exports = function(grunt) {
"public/vendor/vendor.css": [
// TODO: resolve relative resource paths in
// bootstrap/FA/jquery
],
"public/vendor/jsonata/jsonata.js": [
"node_modules/jsonata/jsonata.js",
"editor/vendor/jsonata/formatter.js"
]
}
}
@ -157,7 +165,12 @@ module.exports = function(grunt) {
uglify: {
build: {
files: {
'public/red/red.min.js': 'public/red/red.js'
'public/red/red.min.js': 'public/red/red.js',
'public/red/main.min.js': 'public/red/main.js',
'public/vendor/jsonata/jsonata.min.js': 'public/vendor/jsonata/jsonata.js',
'public/vendor/ace/mode-jsonata.js': 'editor/vendor/jsonata/mode-jsonata.js',
'public/vendor/ace/worker-jsonata.js': 'editor/vendor/jsonata/worker-jsonata.js',
'public/vendor/ace/snippets/jsonata.js': 'editor/vendor/jsonata/snippets-jsonata.js'
}
}
},
@ -183,12 +196,18 @@ module.exports = function(grunt) {
'red/api/locales/en-US/editor.json',
'red/runtime/locales/en-US/runtime.json'
]
},
keymaps: {
src: [
'editor/js/keymap.json'
]
}
},
attachCopyright: {
js: {
src: [
'public/red/red.min.js'
'public/red/red.min.js',
'public/red/main.min.js'
]
},
css: {
@ -218,7 +237,7 @@ module.exports = function(grunt) {
files: [
'editor/js/**/*.js'
],
tasks: ['concat','uglify','attachCopyright:js']
tasks: ['copy:build','concat','uglify','attachCopyright:js']
},
sass: {
files: [
@ -234,6 +253,12 @@ module.exports = function(grunt) {
],
tasks: ['jsonlint:messages']
},
keymaps: {
files: [
'editor/js/keymap.json'
],
tasks: ['jsonlint:keymaps','copy:build']
},
misc: {
files: [
'CHANGELOG.md'
@ -267,40 +292,49 @@ module.exports = function(grunt) {
copy: {
build: {
files:[{
cwd: 'editor/images',
src: '**',
expand: true,
dest: 'public/red/images/'
},
{
cwd: 'editor/vendor',
src: [
'ace/**',
//'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
],
expand: true,
dest: 'public/vendor/'
},
{
cwd: 'editor/icons',
src: '**',
expand: true,
dest: 'public/icons/'
},
{
expand: true,
src: ['editor/index.html','editor/favicon.ico'],
dest: 'public/',
flatten: true
},
{
src: 'CHANGELOG.md',
dest: 'public/red/about'
}
files:[
{
src: 'editor/js/main.js',
dest: 'public/red/main.js'
},
{
src: 'editor/js/keymap.json',
dest: 'public/red/keymap.json'
},
{
cwd: 'editor/images',
src: '**',
expand: true,
dest: 'public/red/images/'
},
{
cwd: 'editor/vendor',
src: [
'ace/**',
//'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
],
expand: true,
dest: 'public/vendor/'
},
{
cwd: 'editor/icons',
src: '**',
expand: true,
dest: 'public/icons/'
},
{
expand: true,
src: ['editor/index.html','editor/favicon.ico'],
dest: 'public/',
flatten: true
},
{
src: 'CHANGELOG.md',
dest: 'public/red/about'
}
]
},
release: {
@ -426,7 +460,7 @@ module.exports = function(grunt) {
grunt.registerTask('build',
'Builds editor content',
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']);
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
grunt.registerTask('dev',
'Developer mode: run node-red, watch for source changes and build/restart',

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

View File

@ -16,6 +16,289 @@
RED.history = (function() {
var undo_history = [];
function undoEvent(ev) {
var i;
var len;
var node;
var subflow;
var modifiedTabs = {};
if (ev) {
if (ev.t == 'multi') {
len = ev.events.length;
for (i=len-1;i>=0;i--) {
undoEvent(ev.events[i]);
}
} else if (ev.t == 'replace') {
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
imported[0].forEach(function(n) {
if (ev.changed[n.id]) {
n.changed = true;
}
})
RED.nodes.version(ev.rev);
} else 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]);
}
}
if (ev.subflow) {
if (ev.subflow.instances) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (ev.subflow.hasOwnProperty('changed')) {
subflow = RED.nodes.subflow(ev.subflow.id);
if (subflow) {
subflow.changed = ev.subflow.changed;
}
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[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 && ev.subflow.subflow) {
RED.nodes.addSubflow(ev.subflow.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 (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
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]);
}
}
if (ev.changes) {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
node = RED.nodes.node(i);
if (node) {
for (var d in ev.changes[i]) {
if (ev.changes[i].hasOwnProperty(d)) {
node[d] = ev.changes[i][d];
}
}
node.dirty = true;
}
}
}
}
} 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;
n.n.changed = n.changed;
}
// A move could have caused a link splice
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "edit") {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
if (ev.node._def.defaults[i].type) {
// This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]);
if (currentConfigNode) {
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
}
var newConfigNode = RED.nodes.node(ev.changes[i]);
if (newConfigNode) {
newConfigNode.users.push(ev.node);
}
}
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);
}
}
if (ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
});
} else {
var outputMap;
if (ev.outputMap) {
outputMap = {};
for (var port in ev.outputMap) {
if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== -1) {
outputMap[ev.outputMap[port]] = port;
}
}
}
RED.editor.updateNodeProperties(ev.node,outputMap);
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.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.subflow);
RED.workspaces.remove(ev.subflow.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "reorder") {
if (ev.order) {
RED.workspaces.order(ev.order);
}
}
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();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
}
return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() {
@ -34,260 +317,7 @@ RED.history = (function() {
},
pop: function() {
var ev = undo_history.pop();
var i;
var node;
var subflow;
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]);
}
}
if (ev.subflow) {
if (ev.subflow.instances) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (ev.subflow.hasOwnProperty('changed')) {
subflow = RED.nodes.subflow(ev.subflow.id);
if (subflow) {
subflow.changed = ev.subflow.changed;
}
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[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 && ev.subflow.subflow) {
RED.nodes.addSubflow(ev.subflow.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 (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
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]);
}
}
if (ev.changes) {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
node = RED.nodes.node(i);
if (node) {
for (var d in ev.changes[i]) {
if (ev.changes[i].hasOwnProperty(d)) {
node[d] = ev.changes[i][d];
}
}
node.dirty = true;
}
}
}
}
} 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;
n.n.changed = n.changed;
}
// A move could have caused a link splice
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "edit") {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
if (ev.node._def.defaults[i].type) {
// This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]);
if (currentConfigNode) {
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
}
var newConfigNode = RED.nodes.node(ev.changes[i]);
if (newConfigNode) {
newConfigNode.users.push(ev.node);
}
}
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);
}
}
if (ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
});
} 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.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.subflow);
RED.workspaces.remove(ev.subflow.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "reorder") {
if (ev.order) {
RED.workspaces.order(ev.order);
}
}
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();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
undoEvent(ev);
},
peek: function() {
return undo_history[undo_history.length-1];

View File

@ -23,7 +23,7 @@ RED.i18n = (function() {
dynamicLoad: false,
load:'current',
ns: {
namespaces: ["editor","node-red"],
namespaces: ["editor","node-red","jsonata","infotips"],
defaultNs: "editor"
},
fallbackLng: ['en-US'],

38
editor/js/keymap.json Normal file
View File

@ -0,0 +1,38 @@
{
"*": {
"ctrl-shift-p":"core:manage-palette",
"ctrl-f": "core:search",
"ctrl-=": "core:zoom-in",
"ctrl--": "core:zoom-out",
"ctrl-0": "core:zoom-reset",
"ctrl-enter": "core:confirm-edit-tray",
"ctrl-escape": "core:cancel-edit-tray",
"ctrl-g i": "core:show-info-tab",
"ctrl-g d": "core:show-debug-tab",
"ctrl-g c": "core:show-config-tab"
},
"workspace": {
"ctrl-e": "core:export",
"ctrl-i": "core:import",
"backspace": "core:delete",
"delete": "core:delete",
"enter": "core:edit",
"ctrl-c": "core:copy",
"ctrl-x": "core:cut",
"ctrl-v": "core:paste",
"ctrl-z": "core:undo",
"ctrl-a": "core:select-all",
"shift-?": "core:show-help",
"ctrl-space": "core:toggle-sidebar",
"up": "core:move-selection-up",
"right": "core:move-selection-right",
"down": "core:move-selection-down",
"left": "core:move-selection-left",
"shift-up": "core:step-selection-up",
"shift-right": "core:step-selection-right",
"shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab"
}
}

View File

@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = (function() {
(function() {
function loadNodeList() {
$.ajax({
@ -80,6 +79,23 @@ var RED = (function() {
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
}
var persistentNotifications = {};
RED.comms.subscribe("notification/#",function(topic,msg) {
var parts = topic.split("/");
var notificationId = parts[1];
if (msg.text) {
var text = RED._(msg.text,{default:msg.text});
if (!persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId] = RED.notify(text,msg.type,msg.timeout === undefined,msg.timeout);
} else {
persistentNotifications[notificationId].update(text,msg.timeout);
}
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId].close();
delete persistentNotifications[notificationId];
}
});
RED.comms.subscribe("status/#",function(topic,msg) {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
@ -98,11 +114,9 @@ var RED = (function() {
var i,m;
var typeList;
var info;
if (topic == "node/added") {
var addedTypes = [];
for (i=0;i<msg.length;i++) {
m = msg[i];
msg.forEach(function(m) {
var id = m.id;
RED.nodes.addNodeSet(m);
addedTypes = addedTypes.concat(m.types);
@ -111,7 +125,7 @@ var RED = (function() {
$("body").append(data);
});
});
}
});
if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
@ -172,12 +186,11 @@ var RED = (function() {
}
function loadEditor() {
var menuOptions = [];
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true},
null,
// {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
// {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}},
@ -186,48 +199,47 @@ var RED = (function() {
// {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}}
// ]},
// null,
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true}
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:import"},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:export"},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show});
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
menuOptions.push(null);
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}});
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove}
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
]});
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init();
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:RED.palette.editor.show});
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
menuOptions.push(null);
}
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp});
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"});
menuOptions.push({id:"menu-item-show-tips",label:RED._("menu.label.showTips"),toggle:true,selected:true,onselect:"core:toggle-show-tips"});
menuOptions.push({id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
});
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout });
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
RED.user.init();
RED.library.init();
RED.palette.init();
RED.sidebar.init();
@ -237,15 +249,21 @@ var RED = (function() {
RED.search.init();
RED.view.init();
RED.editor.init();
RED.keyboard.init();
RED.diff.init();
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.add("workspace", /* ? */ 191,{shift:true},function() {RED.keyboard.showHelp();d3.event.preventDefault();});
RED.actions.add("core:show-about", showAbout);
RED.comms.connect();
$("#main-container").show();
$(".header-toolbar").show();
loadNodeList();
}
@ -261,8 +279,4 @@ var RED = (function() {
RED.settings.init(loadEditor);
})
});
return {
};
})();

View File

@ -24,21 +24,13 @@ RED.nodes = (function() {
var workspacesOrder =[];
var subflows = {};
var loadedFlowVersion = null;
var pending = {
deleted: {},
added: {}
};
var initialLoad;
var dirty = false;
function setDirty(d) {
dirty = d;
if (!d) {
pending = {
deleted: {},
added: {}
};
}
RED.events.emit("nodes:change",{dirty:dirty});
}
@ -62,6 +54,9 @@ RED.nodes = (function() {
getNodeList: function() {
return nodeList;
},
getNodeTypes: function() {
return Object.keys(nodeDefinitions);
},
setNodeList: function(list) {
nodeList = [];
for(var i=0;i<list.length;i++) {
@ -170,6 +165,7 @@ RED.nodes = (function() {
configNodes[n.id] = n;
} else {
n.ports = [];
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
if (n.outputs) {
for (var i=0;i<n.outputs;i++) {
n.ports.push(i);
@ -186,8 +182,6 @@ RED.nodes = (function() {
}
nodes.push(n);
}
delete pending.deleted[n.id];
pending.added[n.id] = true;
RED.events.emit('nodes:add',n);
}
function addLink(l) {
@ -253,12 +247,6 @@ RED.nodes = (function() {
if (node && node._def.onremove) {
node._def.onremove.call(n);
}
delete pending.added[id];
pending.deleted[id] = true;
removedNodes.forEach(function(node) {
delete pending.added[node.id];
pending.deleted[node.id] = true;
});
return {links:removedLinks,nodes:removedNodes};
}
@ -271,8 +259,6 @@ RED.nodes = (function() {
function addWorkspace(ws) {
workspaces[ws.id] = ws;
pending.added[ws.id] = true;
delete pending.deleted[ws.id];
ws._def = {
defaults: {
label: {value:""}
@ -310,8 +296,6 @@ RED.nodes = (function() {
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
}
pending.deleted[id] = true;
delete pending.added[id]
return {nodes:removedNodes,links:removedLinks};
}
@ -341,8 +325,6 @@ RED.nodes = (function() {
outputs: sf.out.length
}
subflows[sf.id] = sf;
delete pending.deleted[sf.id];
pending.added[sf.id] = true;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
info: sf.info,
@ -366,8 +348,6 @@ RED.nodes = (function() {
}
function removeSubflow(sf) {
delete subflows[sf.id];
delete pending.added[sf.id];
pending.deleted[sf.id] = true;
registry.removeNodeType("subflow:"+sf.id);
}
@ -694,6 +674,9 @@ RED.nodes = (function() {
if (!$.isArray(newNodes)) {
newNodes = [newNodes];
}
if (!initialLoad) {
initialLoad = JSON.parse(JSON.stringify(newNodes));
}
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
@ -719,17 +702,19 @@ RED.nodes = (function() {
}
var activeWorkspace = RED.workspaces.active();
//TODO: check the z of the subflow instance and check _that_ if it exists
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];
for (i=0;i<newNodes.length;i++) {
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) {
var subflowId = m[1];
var parent = getSubflow(newNodes[i].z || activeWorkspace);
if (parent) {
var err;
if (subflowId === activeSubflow.id) {
if (subflowId === parent.id) {
err = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
}
if (subflowContains(m[1],activeSubflow.id)) {
if (subflowContains(subflowId,parent.id)) {
err = new Error(RED._("notification.errors.cannotAddCircularReference"));
}
if (err) {
@ -1019,8 +1004,9 @@ RED.nodes = (function() {
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]]};
var existingNode = node_map[wires[w2]] || getNode(wires[w2]);
if (existingNode) {
var link = {source:n,sourcePort:w1,target:existingNode};
addLink(link);
new_links.push(link);
}
@ -1161,6 +1147,38 @@ RED.nodes = (function() {
}
}
function clear() {
nodes = [];
links = [];
configNodes = {};
workspacesOrder = [];
var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id)
});
var workspaceIds = Object.keys(workspaces);
workspaceIds.forEach(function(id) {
RED.workspaces.remove(workspaces[id]);
});
defaultWorkspace = null;
RED.nodes.dirty(true);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
// var node_defs = {};
// var nodes = [];
// var configNodes = {};
// var links = [];
// var defaultWorkspace;
// var workspaces = {};
// var workspacesOrder =[];
// var subflows = {};
// var loadedFlowVersion = null;
}
return {
registry:registry,
setNodeList: registry.setNodeList,
@ -1177,6 +1195,7 @@ RED.nodes = (function() {
add: addNode,
remove: removeNode,
clear: clear,
addLink: addLink,
removeLink: removeLink,
@ -1225,14 +1244,19 @@ RED.nodes = (function() {
node: getNode,
version: flowVersion,
originalFlow: function(flow) {
if (flow === undefined) {
return initialLoad;
} else {
initialLoad = flow;
}
},
filterNodes: filterNodes,
filterLinks: filterLinks,
import: importNodes,
pending: function() { return pending },
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,

16
editor/js/red.js Normal file
View File

@ -0,0 +1,16 @@
/**
* Copyright 2013, 2016 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 = {};

35
editor/js/ui/actions.js Normal file
View File

@ -0,0 +1,35 @@
RED.actions = (function() {
var actions = {
}
function addAction(name,handler) {
actions[name] = handler;
}
function removeAction(name) {
delete actions[name];
}
function getAction(name) {
return actions[name];
}
function invokeAction(name) {
if (actions.hasOwnProperty(name)) {
actions[name]();
}
}
function listActions() {
var result = [];
Object.keys(actions).forEach(function(action) {
var shortcut = RED.keyboard.getShortcut(action);
result.push({id:action,scope:shortcut?shortcut.scope:undefined,key:shortcut?shortcut.key:undefined})
})
return result;
}
return {
add: addAction,
remove: removeAction,
get: getAction,
invoke: invokeAction,
list: listActions
}
})();

View File

@ -257,7 +257,7 @@ RED.clipboard = (function() {
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove(/* ESCAPE */ 27);
RED.keyboard.remove("escape");
}
return {
@ -274,13 +274,15 @@ RED.clipboard = (function() {
RED.menu.setDisabled("menu-item-export-library",false);
}
});
RED.keyboard.add("workspace", /* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
RED.keyboard.add("workspace", /* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
RED.actions.add("core:export",exportNodes);
RED.actions.add("core:import",importNodes);
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
RED.keyboard.add("*", /* ESCAPE */ 27,hideDropTarget);
RED.keyboard.add("*", "escape" ,hideDropTarget);
}
});

View File

@ -27,6 +27,9 @@
* - removable : boolean - whether to display delete button on items
* - addItem : function(row,index,itemData) - when an item is added
* - removeItem : function(itemData) - called when an item is removed
* - filter : function(itemData) - called for each item to determine if it should be shown
* - sort : function(itemDataA,itemDataB) - called to sort items
* - scrollOnAdd : boolean - whether to scroll to newly added items
* methods:
* - addItem(itemData)
* - removeItem(itemData)
@ -34,6 +37,9 @@
* - height(height)
* - items()
* - empty()
* - filter(filter)
* - sort(sort)
* - length()
*/
$.widget( "nodered.editableList", {
_create: function() {
@ -234,7 +240,8 @@
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(li);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
li.addClass("red-ui-editableList-item-removable");
deleteButton.click(function() {
deleteButton.click(function(evt) {
evt.preventDefault();
var data = row.data('data');
li.addClass("red-ui-editableList-item-deleting")
li.fadeOut(300, function() {

View File

@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.menu = (function() {
var menuItems = {};
@ -34,17 +31,17 @@ RED.menu = (function() {
var savedStateActive = isSavedStateActive(opt.id);
if (savedStateActive) {
link.addClass("active");
opt.onselect.call(opt, true);
triggerAction(opt.id,true);
} else if (savedStateActive === false) {
link.removeClass("active");
opt.onselect.call(opt, false);
triggerAction(opt.id,false);
} else if (opt.hasOwnProperty("selected")) {
if (opt.selected) {
link.addClass("active");
} else {
link.removeClass("active");
}
opt.onselect.call(opt, opt.selected);
triggerAction(opt.id,opt.selected);
}
}
@ -107,10 +104,12 @@ RED.menu = (function() {
setSelected(opt.id, !selected);
}
} else {
opt.onselect.call(opt);
triggerAction(opt.id);
}
});
setInitialState();
if (opt.toggle) {
setInitialState();
}
} else if (opt.href) {
link.attr("target","_blank").attr("href",opt.href);
} else if (!opt.options) {
@ -164,6 +163,19 @@ RED.menu = (function() {
}
}
function triggerAction(id, args) {
var opt = menuItems[id];
var callback = opt.onselect;
if (typeof opt.onselect === 'string') {
callback = RED.actions.get(opt.onselect);
}
if (callback) {
callback.call(opt,args);
} else {
console.log("No callback for",id,opt.onselect);
}
}
function isSavedStateActive(id) {
return RED.settings.get("menu-" + id);
}
@ -187,11 +199,15 @@ RED.menu = (function() {
$("#"+id).removeClass("active");
}
if (opt && opt.onselect) {
opt.onselect.call(opt,state);
triggerAction(opt.id,state);
}
setSavedState(id, state);
}
function toggleSelected(id) {
setSelected(id,!isSelected(id));
}
function setDisabled(id,state) {
if (state) {
$("#"+id).parent().addClass("disabled");
@ -231,16 +247,6 @@ RED.menu = (function() {
var opt = menuItems[id];
if (opt) {
opt.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]);
// }
// });
}
}
@ -248,6 +254,7 @@ RED.menu = (function() {
init: createMenu,
setSelected: setSelected,
isSelected: isSelected,
toggleSelected: toggleSelected,
setDisabled: setDisabled,
addItem: addItem,
removeItem: removeItem,

View File

@ -105,6 +105,9 @@ RED.tabs = (function() {
if (typeof link === "string") {
link = ul.find("a[href='#"+link+"']");
}
if (link.length === 0) {
return;
}
if (!link.parent().hasClass("active")) {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
@ -126,6 +129,18 @@ RED.tabs = (function() {
},100);
}
}
function activatePreviousTab() {
var previous = ul.find("li.active").prev();
if (previous.length > 0) {
activateTab(previous.find("a"));
}
}
function activateNextTab() {
var next = ul.find("li.active").next();
if (next.length > 0) {
activateTab(next.find("a"));
}
}
function updateTabWidths() {
var tabs = ul.find("li.red-ui-tab");
@ -303,6 +318,8 @@ RED.tabs = (function() {
},
removeTab: removeTab,
activateTab: activateTab,
nextTab: activateNextTab,
previousTab: activatePreviousTab,
resize: updateTabWidths,
count: function() {
return ul.find("li.red-ui-tab").size();

View File

@ -14,87 +14,31 @@
* limitations under the License.
**/
(function($) {
function validateExpression(str) {
var length = str.length;
var start = 0;
var inString = false;
var inBox = false;
var quoteChar;
var v;
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (!inBox) {
return false;
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0 || i===length-1) {
return false;
}
// Next char is a-z
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
return false;
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
return false;
}
if (i===length-1) {
return false;
}
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
return false;
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
return false;
}
if (start != i) {
v = str.substring(start,i);
if (!/^\d+$/.test(v)) {
return false;
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
return false;
}
} else {
if (c === quoteChar) {
// Next char must be a ]
if (!/\]/.test(str[i+1])) {
return false;
}
start = i+1;
inString = false;
}
}
}
if (inBox || inString) {
return false;
}
return true;
}
var allOptions = {
msg: {value:"msg",label:"msg.",validate:validateExpression},
flow: {value:"flow",label:"flow.",validate:validateExpression},
global: {value:"global",label:"global.",validate:validateExpression},
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},
global: {value:"global",label:"global.",validate:RED.utils.validatePropertyExpression},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"},
date: {value:"date",label:"timestamp",hasValue:false}
date: {value:"date",label:"timestamp",hasValue:false},
jsonata: {
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.png",
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
expand:function() {
var that = this;
RED.editor.editExpression({
value: this.value().replace(/\t/g,"\n"),
complete: function(v) {
that.value(v.replace(/\n/g,"\t"));
}
})
}
}
};
var nlsd = false;
@ -117,7 +61,7 @@
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(\d+%)/i.exec(attrStyle)) !== null) {
if ((m = /width\s*:\s*(\d+(%|px))/i.exec(attrStyle)) !== null) {
this.element.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
@ -133,7 +77,7 @@
this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<a href="#"></a>').prependTo(this.uiSelect);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
@ -160,32 +104,72 @@
})
this.selectTrigger.click(function(event) {
event.preventDefault();
if (that.typeList.length > 1) {
that._showMenu(that.menu,that.selectTrigger);
} else {
that.element.focus();
}
that._showTypeMenu();
});
this.selectTrigger.on('keydown',function(evt) {
if (evt.keyCode === 40) {
// Down
that._showTypeMenu();
}
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
})
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<a href="#" class="red-ui-typedInput-option-trigger" style="display:inline-block"><i class="fa fa-sort-desc"></i></a>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span></span>').prependTo(this.optionSelectTrigger);
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
if (that.optionMenu) {
that.optionMenu.css({
minWidth:that.optionSelectLabel.width()
});
that._showMenu(that.optionMenu,that.optionSelectLabel)
that._showOptionSelectMenu();
}).on('keydown', function(evt) {
if (evt.keyCode === 40) {
// Down
that._showOptionSelectMenu();
}
}).on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value);
},
_showTypeMenu: function() {
if (this.typeList.length > 1) {
this._showMenu(this.menu,this.selectTrigger);
this.menu.find("[value='"+this.propertyType+"']").focus();
} else {
this.element.focus();
}
},
_showOptionSelectMenu: function() {
if (this.optionMenu) {
this.optionMenu.css({
minWidth:this.optionSelectLabel.width()
});
this._showMenu(this.optionMenu,this.optionSelectLabel);
var selectedOption = this.optionMenu.find("[value='"+this.value()+"']");
if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first");
}
selectedOption.focus();
}
},
_hideMenu: function(menu) {
$(document).off("mousedown.close-property-select");
menu.hide();
this.element.focus();
if (this.elementDiv.is(":visible")) {
this.element.focus();
} else if (this.optionSelectTrigger.is(":visible")){
this.optionSelectTrigger.focus();
} else {
this.selectTrigger.focus();
}
},
_createMenu: function(opts,callback) {
var that = this;
@ -194,7 +178,7 @@
if (typeof opt === 'string') {
opt = {value:opt,label:opt};
}
var op = $('<a href="#">').attr("value",opt.value).appendTo(menu);
var op = $('<a href="#"></a>').attr("value",opt.value).appendTo(menu);
if (opt.label) {
op.text(opt.label);
}
@ -214,6 +198,21 @@
display: "none",
});
menu.appendTo(document.body);
menu.on('keydown', function(evt) {
if (evt.keyCode === 40) {
// DOWN
$(this).children(":focus").next().focus();
} else if (evt.keyCode === 38) {
// UP
$(this).children(":focus").prev().focus();
} else if (evt.keyCode === 27) {
that._hideMenu(menu);
}
})
return menu;
},
@ -267,13 +266,18 @@
this.uiSelect.width(this.uiWidth);
}
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
this.selectTrigger.css('width',"100%");
this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else {
this.selectTrigger.width('auto');
this.selectTrigger.removeClass("red-ui-typedInput-full-width");
var labelWidth = this._getLabelWidth(this.selectTrigger);
this.elementDiv.css('left',labelWidth+"px");
if (this.optionExpandButton.is(":visible")) {
this.elementDiv.css('right',"22px");
} else {
this.elementDiv.css('right','0');
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.css('left',(labelWidth+5)+"px");
this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
}
}
},
@ -341,6 +345,9 @@
this.selectLabel.text(opt.label);
}
if (opt.options) {
if (this.optionExpandButton) {
this.optionExpandButton.hide();
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
this.elementDiv.hide();
@ -374,6 +381,16 @@
}
this.elementDiv.show();
}
if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show();
this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) {
evt.preventDefault();
opt.expand.call(that);
})
} else {
this.optionExpandButton.hide();
}
this.element.trigger('change',this.propertyType,this.value());
}
if (image) {

View File

@ -30,13 +30,13 @@ RED.deploy = (function() {
var deploymentType = "full";
var currentDiff = null;
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy-icon").attr("src",deploymentTypes[type].img);
}
var currentDiff = null;
/**
* options:
* type: "default" - Button with drop-down options - no further customisation available
@ -98,21 +98,33 @@ RED.deploy = (function() {
height: "auto",
buttons: [
{
text: RED._("deploy.confirm.button.cancel"),
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
// {
// id: "node-dialog-confirm-deploy-review",
// text: RED._("deploy.confirm.button.review"),
// class: "primary",
// click: function() {
// showDiff();
// $( this ).dialog( "close" );
// }
// },
{
id: "node-dialog-confirm-deploy-review",
text: RED._("deploy.confirm.button.review"),
class: "primary disabled",
click: function() {
if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff();
$( this ).dialog( "close" );
}
}
},
{
id: "node-dialog-confirm-deploy-merge",
text: RED._("deploy.confirm.button.merge"),
class: "primary disabled",
click: function() {
RED.diff.mergeDiff(currentDiff);
$( this ).dialog( "close" );
}
},
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
@ -136,10 +148,37 @@ RED.deploy = (function() {
},
open: function() {
if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") {
// $("#node-dialog-confirm-deploy-review").show();
$("#node-dialog-confirm-deploy-deploy").hide();
$("#node-dialog-confirm-deploy-review").addClass('disabled').show();
$("#node-dialog-confirm-deploy-merge").addClass('disabled').show();
currentDiff = null;
$("#node-dialog-confirm-deploy-conflict-checking").show();
$("#node-dialog-confirm-deploy-conflict-auto-merge").hide();
$("#node-dialog-confirm-deploy-conflict-manual-merge").hide();
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
$("#node-dialog-confirm-deploy-conflict-checking").hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
$("#node-dialog-confirm-deploy-conflict-auto-merge").show();
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
$("#node-dialog-confirm-deploy-conflict-manual-merge").show();
}
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
})
$("#node-dialog-confirm-deploy-hide").parent().hide();
} else {
// $("#node-dialog-confirm-deploy-review").hide();
$("#node-dialog-confirm-deploy-deploy").show();
$("#node-dialog-confirm-deploy-review").hide();
$("#node-dialog-confirm-deploy-merge").hide();
$("#node-dialog-confirm-deploy-hide").parent().show();
}
}
@ -157,198 +196,7 @@ RED.deploy = (function() {
}
});
// $("#node-dialog-view-diff").dialog({
// title: RED._('deploy.confirm.button.review'),
// modal: true,
// autoOpen: false,
// buttons: [
// {
// text: RED._("deploy.confirm.button.cancel"),
// click: function() {
// $( this ).dialog( "close" );
// }
// },
// {
// text: RED._("deploy.confirm.button.merge"),
// class: "primary",
// click: function() {
// $( this ).dialog( "close" );
// }
// }
// ],
// open: function() {
// $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)});
// }
// });
// $("#node-dialog-view-diff-diff").editableList({
// addButton: false,
// scrollOnAdd: false,
// addItem: function(container,i,object) {
// var tab = object.tab.n;
// var tabDiv = $('<div>',{class:"node-diff-tab collapsed"}).appendTo(container);
//
// var titleRow = $('<div>',{class:"node-diff-tab-title"}).appendTo(tabDiv);
// titleRow.click(function(evt) {
// evt.preventDefault();
// titleRow.parent().toggleClass('collapsed');
// })
// var chevron = $('<i class="fa fa-angle-down node-diff-chevron ">').appendTo(titleRow);
// var title = $('<span>').html(tab.label||tab.id).appendTo(titleRow);
//
// var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow);
//
// var addedCount = 0;
// var deletedCount = 0;
// var changedCount = 0;
// var conflictedCount = 0;
//
// object.tab.nodes.forEach(function(node) {
// var realNode = RED.nodes.node(node.id);
// var hasChanges = false;
// if (currentDiff.added[node.id]) {
// addedCount++;
// hasChanges = true;
// }
// if (currentDiff.deleted[node.id]) {
// deletedCount++;
// hasChanges = true;
// }
// if (currentDiff.changed[node.id]) {
// changedCount++;
// hasChanges = true;
// }
// if (currentDiff.conflicted[node.id]) {
// conflictedCount++;
// hasChanges = true;
// }
//
// if (hasChanges) {
// var def = RED.nodes.getType(node.type)||{};
// var div = $("<div>",{class:"node-diff-node-entry collapsed"}).appendTo(tabDiv);
// var nodeTitleDiv = $("<div>",{class:"node-diff-node-entry-title"}).appendTo(div);
// nodeTitleDiv.click(function(evt) {
// evt.preventDefault();
// $(this).parent().toggleClass('collapsed');
// })
// var newNode = currentDiff.newConfig.all[node.id];
// var nodePropertiesDiv = $("<div>",{class:"node-diff-node-entry-properties"}).appendTo(div);
//
// var nodePropertiesTable = $("<table>").appendTo(nodePropertiesDiv);
//
// if (node.hasOwnProperty('x')) {
// if (newNode.x !== node.x || newNode.y !== node.y) {
// var currentPosition = node.x+", "+node.y
// var newPosition = newNode.x+", "+newNode.y;
// $("<tr><td>position</td><td>"+currentPosition+"</td><td>"+newPosition+"</td></tr>").appendTo(nodePropertiesTable);
// }
// }
// var properties = Object.keys(node).filter(function(p) { return p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
// if (def.defaults) {
// properties = properties.concat(Object.keys(def.defaults));
// }
// properties.forEach(function(d) {
// var localValue = JSON.stringify(node[d]);
// var remoteValue = JSON.stringify(newNode[d]);
// var originalValue = realNode._config[d];
//
// if (remoteValue !== originalValue) {
// var formattedProperty = formatNodeProperty(node[d]);
// var newFormattedProperty = formatNodeProperty(newNode[d]);
// if (localValue === originalValue) {
// // no conflict change
// } else {
// // conflicting change
// }
// $("<tr><td>"+d+'</td><td class="">'+formattedProperty+'</td><td class="node-diff-property-changed">'+newFormattedProperty+"</td></tr>").appendTo(nodePropertiesTable);
// }
//
// })
// var nodeChevron = $('<i class="fa fa-angle-down node-diff-chevron">').appendTo(nodeTitleDiv);
//
//
// // var leftColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
// // var rightColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
// // rightColumn.html("&nbsp");
//
//
//
// var nodeDiv = $("<div>",{class:"node-diff-node-entry-node"}).appendTo(nodeTitleDiv);
// var colour = def.color;
// var icon_url = "arrow-in.png";
// if (node.type === 'tab') {
// colour = "#C0DEED";
// icon_url = "subflow.png";
// } else if (def.category === 'config') {
// icon_url = "cog.png";
// } else if (node.type === 'unknown') {
// icon_url = "alert.png";
// } else {
// icon_url = def.icon;
// }
// nodeDiv.css('backgroundColor',colour);
//
// var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
// $('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
//
//
//
// var contentDiv = $('<div>',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv);
//
// $('<span>',{class:"node-diff-node-label"}).html(node.label || node.name || node.id).appendTo(contentDiv);
// //$('<div>',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv);
// //$('<div>',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv);
// }
//
// });
//
// var statsInfo = '<span class="node-diff-count">'+object.tab.nodes.length+" nodes"+
// (addedCount+deletedCount+changedCount+conflictedCount > 0 ? " : ":"")+
// "</span> "+
// ((addedCount > 0)?'<span class="node-diff-added">'+addedCount+' added</span> ':'')+
// ((deletedCount > 0)?'<span class="node-diff-deleted">'+deletedCount+' deleted</span> ':'')+
// ((changedCount > 0)?'<span class="node-diff-changed">'+changedCount+' changed</span> ':'')+
// ((conflictedCount > 0)?'<span class="node-diff-conflicted">'+conflictedCount+' conflicts</span>':'');
// stats.html(statsInfo);
//
//
//
// //
// //
// //
// // var node = object.node;
// // var realNode = RED.nodes.node(node.id);
// // var def = RED.nodes.getType(object.node.type)||{};
// // var l = "";
// // if (def && def.label && realNode) {
// // l = def.label;
// // try {
// // l = (typeof l === "function" ? l.call(realNode) : l);
// // } catch(err) {
// // console.log("Definition error: "+node.type+".label",err);
// // }
// // }
// // l = l||node.label||node.name||node.id||"";
// // console.log(node);
// // var div = $('<div>').appendTo(container);
// // div.html(l);
// }
// });
}
function formatNodeProperty(prop) {
var formattedProperty = prop;
if (formattedProperty === null) {
formattedProperty = 'null';
} else if (formattedProperty === undefined) {
formattedProperty = 'undefined';
} else if (typeof formattedProperty === 'object') {
formattedProperty = JSON.stringify(formattedProperty);
}
if (/\n/.test(formattedProperty)) {
formattedProperty = "<pre>"+formattedProperty+"</pre>"
}
return formattedProperty;
}
function getNodeInfo(node) {
@ -393,145 +241,8 @@ RED.deploy = (function() {
$( "#node-dialog-confirm-deploy-conflict" ).show();
$( "#node-dialog-confirm-deploy-type" ).val("conflict");
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
// $("#node-dialog-confirm-deploy-review").append($('<img src="red/images/spin.svg" style="background: rgba(255,255,255,0.8); margin-top: -16px; margin-left: -8px; height:16px; position: absolute; "/>'));
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",0.4);
// $("#node-dialog-confirm-deploy-review").attr("disabled",true).addClass("disabled");
// $.ajax({
// headers: {
// "Accept":"application/json",
// },
// cache: false,
// url: 'flows',
// success: function(nodes) {
// var newNodes = nodes.flows;
// var newRevision = nodes.rev;
// generateDiff(currentNodes,newNodes);
// $("#node-dialog-confirm-deploy-review").attr("disabled",false).removeClass("disabled");
// $("#node-dialog-confirm-deploy-review img").remove();
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",1);
// }
// });
}
// function parseNodes(nodeList) {
// var tabOrder = [];
// var tabs = {};
// var subflows = {};
// var globals = [];
// var all = {};
//
// nodeList.forEach(function(node) {
// all[node.id] = node;
// if (node.type === 'tab') {
// tabOrder.push(node.id);
// tabs[node.id] = {n:node,nodes:[]};
// } else if (node.type === 'subflow') {
// subflows[node.id] = {n:node,nodes:[]};
// }
// });
//
// nodeList.forEach(function(node) {
// if (node.type !== 'tab' && node.type !== 'subflow') {
// if (tabs[node.z]) {
// tabs[node.z].nodes.push(node);
// } else if (subflows[node.z]) {
// subflows[node.z].nodes.push(node);
// } else {
// globals.push(node);
// }
// }
// });
//
// return {
// all: all,
// tabOrder: tabOrder,
// tabs: tabs,
// subflows: subflows,
// globals: globals
// }
// }
// function generateDiff(currentNodes,newNodes) {
// var currentConfig = parseNodes(currentNodes);
// var newConfig = parseNodes(newNodes);
// var pending = RED.nodes.pending();
// var added = {};
// var deleted = {};
// var changed = {};
// var conflicted = {};
//
//
// Object.keys(currentConfig.all).forEach(function(id) {
// var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
// if (!newConfig.all.hasOwnProperty(id)) {
// if (!pending.added.hasOwnProperty(id)) {
// deleted[id] = true;
// conflicted[id] = node.changed;
// }
// } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
// changed[id] = true;
// conflicted[id] = node.changed;
// }
// });
// Object.keys(newConfig.all).forEach(function(id) {
// if (!currentConfig.all.hasOwnProperty(id) && !pending.deleted.hasOwnProperty(id)) {
// added[id] = true;
// }
// });
//
// // console.log("Added",added);
// // console.log("Deleted",deleted);
// // console.log("Changed",changed);
// // console.log("Conflicted",conflicted);
//
// var formatString = function(id) {
// return conflicted[id]?"!":(added[id]?"+":(deleted[id]?"-":(changed[id]?"~":" ")));
// }
// newConfig.tabOrder.forEach(function(tabId) {
// var tab = newConfig.tabs[tabId];
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
// tab.nodes.forEach(function(node) {
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
// })
// if (currentConfig.tabs[tabId]) {
// currentConfig.tabs[tabId].nodes.forEach(function(node) {
// if (deleted[node.id]) {
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
// }
// })
// }
// });
// currentConfig.tabOrder.forEach(function(tabId) {
// if (deleted[tabId]) {
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
// }
// });
//
// currentDiff = {
// currentConfig: currentConfig,
// newConfig: newConfig,
// added: added,
// deleted: deleted,
// changed: changed,
// conflicted: conflicted
// }
// }
// function showDiff() {
// if (currentDiff) {
// var list = $("#node-dialog-view-diff-diff");
// list.editableList('empty');
// var currentConfig = currentDiff.currentConfig;
// currentConfig.tabOrder.forEach(function(tabId) {
// var tab = currentConfig.tabs[tabId];
// list.editableList('addItem',{tab:tab})
// });
// }
// $("#node-dialog-view-diff").dialog("open");
// }
function save(skipValidation,force) {
if (!$("#btn-deploy").hasClass("disabled")) {
if (!skipValidation) {
@ -624,6 +335,7 @@ RED.deploy = (function() {
}).done(function(data,textStatus,xhr) {
RED.nodes.dirty(false);
RED.nodes.version(data.rev);
RED.nodes.originalFlow(nns);
if (hasUnusedConfig) {
RED.notify(
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+

1278
editor/js/ui/diff.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -180,19 +180,31 @@ RED.editor = (function() {
* 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
* @param outputMap - (optional) a map of old->new port numbers if wires should be moved
* @returns {array} the links that were removed due to this update
*/
function updateNodeProperties(node) {
function updateNodeProperties(node, outputMap) {
node.resize = true;
node.dirty = true;
var removedLinks = [];
if (node.ports) {
if (outputMap) {
RED.nodes.eachLink(function(l) {
if (l.source === node && outputMap.hasOwnProperty(l.sourcePort)) {
if (outputMap[l.sourcePort] === -1) {
removedLinks.push(l);
} else {
l.sourcePort = outputMap[l.sourcePort];
}
}
});
}
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) {
if (l.source === node && l.sourcePort >= node.outputs && removedLinks.indexOf(l) === -1) {
removedLinks.push(l);
}
});
@ -482,12 +494,13 @@ RED.editor = (function() {
}
function getEditStackTitle() {
var title = '<ul class="editor-tray-breadcrumbs">';
for (var i=0;i<editStack.length;i++) {
var node = editStack[i];
var label = node.type;
if (node.type === 'subflow') {
if (node.type === '_expression') {
label = "Expression editor";
} else if (node.type === 'subflow') {
label = RED._("subflow.editSubflow",{name:node.name})
} else if (node.type.indexOf("subflow:")===0) {
var subflow = RED.nodes.subflow(node.type.substring(8));
@ -514,6 +527,33 @@ RED.editor = (function() {
return title;
}
function buildEditForm(tray,formId,type,ns) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="'+formId+'" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='"+type+"']").html());
ns = ns||"node-red";
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
return dialogForm;
}
function showEditDialog(node) {
var editing_node = node;
editStack.push(node);
@ -525,6 +565,33 @@ RED.editor = (function() {
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-delete",
class: 'leftButton',
text: RED._("common.label.delete"),
click: function() {
var startDirty = RED.nodes.dirty();
var removedNodes = [];
var removedLinks = [];
var removedEntities = RED.nodes.remove(editing_node.id);
removedNodes.push(editing_node);
removedNodes = removedNodes.concat(removedEntities.nodes);
removedLinks = removedLinks.concat(removedEntities.links);
var historyEvent = {
t:'delete',
nodes:removedNodes,
links:removedLinks,
changes: {},
dirty: startDirty
}
RED.nodes.dirty(true);
RED.view.redraw(true);
RED.history.push(historyEvent);
RED.tray.close();
}
},
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
@ -570,6 +637,7 @@ RED.editor = (function() {
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
var outputMap;
if (editing_node._def.oneditsave) {
var oldValues = {};
@ -654,8 +722,14 @@ RED.editor = (function() {
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
changed = changed || credsChanged;
}
var removedLinks = updateNodeProperties(editing_node);
if (editing_node.hasOwnProperty("_outputs")) {
outputMap = editing_node._outputs;
delete editing_node._outputs;
if (Object.keys(outputMap).length > 0) {
changed = true;
}
}
var removedLinks = updateNodeProperties(editing_node,outputMap);
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
@ -685,6 +759,9 @@ RED.editor = (function() {
dirty:wasDirty,
changed:wasChanged
};
if (outputMap) {
historyEvent.outputMap = outputMap;
}
if (subflowInstances) {
historyEvent.subflow = {
instances:subflowInstances
@ -714,33 +791,13 @@ RED.editor = (function() {
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='"+type+"']").html());
var ns;
if (node._def.set.module === "node-red") {
ns = "node-red";
} else {
ns = node._def.set.id;
}
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
var dialogForm = buildEditForm(tray,"dialog-form",type,ns);
prepareEditDialog(node,node._def,"node-input");
dialogForm.i18n();
},
@ -833,7 +890,6 @@ RED.editor = (function() {
},
open: function(tray) {
var trayHeader = tray.find(".editor-tray-header");
var trayBody = tray.find(".editor-tray-body");
var trayFooter = tray.find(".editor-tray-footer");
if (node_def.hasUsers !== false) {
@ -841,21 +897,8 @@ RED.editor = (function() {
}
trayFooter.append('<span id="node-config-dialog-scope-container"><span id="node-config-dialog-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="node-config-dialog-scope"></select></span>');
var dialogForm = $('<form id="node-config-dialog-edit-form" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='"+type+"']").html());
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
var dialogForm = buildEditForm(tray,"node-config-dialog-edit-form",type,ns);
prepareEditDialog(editing_config_node,node_def,"node-config-input");
if (editing_config_node._def.exclusive) {
$("#node-config-dialog-scope").hide();
@ -1289,30 +1332,7 @@ RED.editor = (function() {
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='subflow-template']").html());
var ns = "node-red";
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
var dialogForm = buildEditForm(tray,"dialog-form","subflow-template");
subflowEditor = RED.editor.createEditor({
id: 'subflow-input-info-editor',
mode: 'ace/mode/markdown',
@ -1348,22 +1368,183 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
function editExpression(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
$("#node-input-expression-help").html("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
editTrayWidthCache[type] = dimensions.width;
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = buildEditForm(tray,'dialog-form','_expression','editor');
var funcSelect = $("#node-input-expression-func");
Object.keys(jsonata.functions).forEach(function(f) {
funcSelect.append($("<option></option>").val(f).text(f));
})
funcSelect.change(function(e) {
var f = $(this).val();
var args = RED._('jsonata:'+f+".args",{defaultValue:''});
var title = "<h5>"+f+"("+args+")</h5>";
var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#node-input-expression-help").html(title+"<p>"+body+"</p>");
})
expressionEditor = RED.editor.createEditor({
id: 'node-input-expression',
value: "",
mode:"ace/mode/jsonata",
options: {
enableBasicAutocompletion:true,
enableSnippets:true,
enableLiveAutocompletion: true
}
});
var currentToken = null;
var currentTokenPos = -1;
var currentFunctionMarker = null;
expressionEditor.getSession().setValue(value||"",-1);
expressionEditor.on("changeSelection", function() {
var c = expressionEditor.getCursorPosition();
var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
currentToken = token;
var r,p;
var scopedFunction = null;
if (token && token.type === 'keyword') {
r = c.row;
scopedFunction = token;
} else {
var depth = 0;
var next = false;
if (token) {
if (token.type === 'paren.rparen') {
// If this is a block of parens ')))', set
// depth to offset against the cursor position
// within the block
currentTokenPos = c.column;
depth = c.column - (token.start + token.value.length);
}
r = c.row;
p = token.index;
} else {
r = c.row-1;
p = -1;
}
while ( scopedFunction === null && r > -1) {
var rowTokens = expressionEditor.getSession().getTokens(r);
if (p === -1) {
p = rowTokens.length-1;
}
while (p > -1) {
var type = rowTokens[p].type;
if (next) {
if (type === 'keyword') {
scopedFunction = rowTokens[p];
// console.log("HIT",scopedFunction);
break;
}
next = false;
}
if (type === 'paren.lparen') {
depth-=rowTokens[p].value.length;
} else if (type === 'paren.rparen') {
depth+=rowTokens[p].value.length;
}
if (depth < 0) {
next = true;
depth = 0;
}
// console.log(r,p,depth,next,rowTokens[p]);
p--;
}
if (!scopedFunction) {
r--;
}
}
}
expressionEditor.session.removeMarker(currentFunctionMarker);
if (scopedFunction) {
//console.log(token,.map(function(t) { return t.type}));
funcSelect.val(scopedFunction.value).change();
}
}
});
dialogForm.i18n();
$("#node-input-expression-func-insert").click(function(e) {
e.preventDefault();
var pos = expressionEditor.getCursorPosition();
var f = funcSelect.val();
var snippet = jsonata.getFunctionSnippet(f);
expressionEditor.insertSnippet(snippet);
expressionEditor.focus();
})
},
close: function() {
editStack.pop();
},
show: function() {}
}
if (editTrayWidthCache.hasOwnProperty(type)) {
trayOptions.width = editTrayWidthCache[type];
}
RED.tray.show(trayOptions);
}
return {
init: function() {
RED.tray.init();
$(window).on('keydown', function(evt) {
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
$("#node-dialog-cancel").click();
$("#node-config-dialog-cancel").click();
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
$("#node-dialog-ok").click();
$("#node-config-dialog-ok").click();
}
RED.actions.add("core:confirm-edit-tray", function() {
$("#node-dialog-ok").click();
$("#node-config-dialog-ok").click();
});
RED.actions.add("core:cancel-edit-tray", function() {
$("#node-dialog-cancel").click();
$("#node-config-dialog-cancel").click();
});
},
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
editExpression: editExpression,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
@ -1398,7 +1579,6 @@ RED.editor = (function() {
}
},100);
}
return editor;
}
}

View File

@ -15,10 +15,101 @@
**/
RED.keyboard = (function() {
var isMac = /Mac/i.test(window.navigator.platform);
var handlers = {};
var partialState;
var keyMap = {
"left":37,
"up":38,
"right":39,
"down":40,
"escape":27,
"enter": 13,
"backspace": 8,
"delete": 46,
"space": 32,
";":186,
"=":187,
",":188,
"-":189,
".":190,
"/":191,
"\\":220,
"'":222,
"?":191 // <- QWERTY specific
}
var metaKeyCodes = {
16:true,
17:true,
18: true,
91:true,
93: true
}
var actionToKeyMap = {}
// FF generates some different keycodes because reasons.
var firefoxKeyCodeMap = {
59:186,
61:187,
173:189
}
function init() {
$.getJSON("red/keymap.json",function(data) {
for (var scope in data) {
if (data.hasOwnProperty(scope)) {
var keys = data[scope];
for (var key in keys) {
if (keys.hasOwnProperty(key)) {
addHandler(scope,key,keys[key]);
}
}
}
}
})
RED.actions.add("core:show-help", showKeyboardHelp);
}
function parseKeySpecifier(key) {
var parts = key.toLowerCase().split("-");
var modifiers = {};
var keycode;
var blank = 0;
for (var i=0;i<parts.length;i++) {
switch(parts[i]) {
case "ctrl":
case "cmd":
modifiers.ctrl = true;
modifiers.meta = true;
break;
case "alt":
modifiers.alt = true;
break;
case "shift":
modifiers.shift = true;
break;
case "":
blank++;
keycode = keyMap["-"];
break;
default:
if (keyMap.hasOwnProperty(parts[i])) {
keycode = keyMap[parts[i]];
} else if (parts[i].length > 1) {
return null;
} else {
keycode = parts[i].toUpperCase().charCodeAt(0);
}
break;
}
}
return [keycode,modifiers];
}
function resolveKeyEvent(evt) {
var slot = handlers;
var slot = partialState||handlers;
if (evt.ctrlKey || evt.metaKey) {
slot = slot.ctrl;
}
@ -28,9 +119,19 @@ RED.keyboard = (function() {
if (slot && evt.altKey) {
slot = slot.alt;
}
if (slot && slot[evt.keyCode]) {
var handler = slot[evt.keyCode];
if (handler.scope && handler.scope !== "*") {
var keyCode = firefoxKeyCodeMap[evt.keyCode] || evt.keyCode;
if (slot && slot[keyCode]) {
var handler = slot[keyCode];
if (!handler.scope) {
if (partialState) {
partialState = null;
return resolveKeyEvent(evt);
} else {
partialState = handler;
evt.preventDefault();
return null;
}
} else if (handler.scope && handler.scope !== "*") {
var target = evt.target;
while (target.nodeName !== 'BODY' && target.id !== handler.scope) {
target = target.parentElement;
@ -39,66 +140,123 @@ RED.keyboard = (function() {
handler = null;
}
}
partialState = null;
return handler;
} else if (partialState) {
partialState = null;
return resolveKeyEvent(evt);
}
}
d3.select(window).on("keydown",function() {
if (metaKeyCodes[d3.event.keyCode]) {
return;
}
var handler = resolveKeyEvent(d3.event);
if (handler && handler.ondown) {
handler.ondown();
}
});
d3.select(window).on("keyup",function() {
var handler = resolveKeyEvent(d3.event);
if (handler && handler.onup) {
handler.onup();
if (typeof handler.ondown === "string") {
RED.actions.invoke(handler.ondown);
} else {
handler.ondown();
}
d3.event.preventDefault();
}
});
function addHandler(scope,key,modifiers,ondown,onup) {
function addHandler(scope,key,modifiers,ondown) {
var mod = modifiers;
var cbdown = ondown;
var cbup = onup;
if (typeof modifiers == "function") {
if (typeof modifiers == "function" || typeof modifiers === "string") {
mod = {};
cbdown = modifiers;
cbup = ondown;
}
var keys = [];
var i=0;
if (typeof key === 'string') {
if (typeof cbdown === 'string') {
actionToKeyMap[cbdown] = {scope:scope,key:key};
}
var parts = key.split(" ");
for (i=0;i<parts.length;i++) {
var parsedKey = parseKeySpecifier(parts[i]);
if (parsedKey) {
keys.push(parsedKey);
} else {
console.log("Unrecognised key specifier:",key)
return;
}
}
} else {
keys.push([key,mod])
}
var slot = handlers;
if (mod.ctrl) {
slot.ctrl = slot.ctrl||{};
slot = slot.ctrl;
for (i=0;i<keys.length;i++) {
key = keys[i][0];
mod = keys[i][1];
if (mod.ctrl) {
slot.ctrl = slot.ctrl||{};
slot = slot.ctrl;
}
if (mod.shift) {
slot.shift = slot.shift||{};
slot = slot.shift;
}
if (mod.alt) {
slot.alt = slot.alt||{};
slot = slot.alt;
}
slot[key] = slot[key] || {};
slot = slot[key];
//slot[key] = {scope: scope, ondown:cbdown};
}
if (mod.shift) {
slot.shift = slot.shift||{};
slot = slot.shift;
}
if (mod.alt) {
slot.alt = slot.alt||{};
slot = slot.alt;
}
slot[key] = {scope: scope, ondown:cbdown, onup:cbup};
slot.scope = scope;
slot.ondown = cbdown;
}
function removeHandler(key,modifiers) {
var mod = modifiers || {};
var keys = [];
var i=0;
if (typeof key === 'string') {
delete actionToKeyMap[key];
var parts = key.split(" ");
for (i=0;i<parts.length;i++) {
var parsedKey = parseKeySpecifier(parts[i]);
if (parsedKey) {
keys.push(parsedKey);
} else {
console.log("Unrecognised key specifier:",key)
return;
}
}
} else {
keys.push([key,mod])
}
var slot = handlers;
if (mod.ctrl) {
slot = slot.ctrl;
}
if (slot && mod.shift) {
slot = slot.shift;
}
if (slot && mod.alt) {
slot = slot.alt;
}
if (slot) {
delete slot[key];
for (i=0;i<keys.length;i++) {
key = keys[i][0];
mod = keys[i][1];
if (mod.ctrl) {
slot = slot.ctrl;
}
if (slot && mod.shift) {
slot = slot.shift;
}
if (slot && mod.alt) {
slot = slot.alt;
}
if (!slot[key]) {
return;
}
slot = slot[key];
}
delete slot.scope;
delete slot.ondown;
}
var dialog = null;
var cmdCtrlKey = '<span class="help-key">'+(isMac?'&#8984;':'Ctrl')+'</span>';
function showKeyboardHelp() {
if (!RED.settings.theme("menu.menu-item-keyboard-shortcuts",true)) {
return;
@ -107,33 +265,30 @@ RED.keyboard = (function() {
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/&#8984;</span> + <span class="help-key">a</span></td><td>'+RED._("keyboard.selectAll")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">a</span></td><td>'+RED._("keyboard.selectAll")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.selectAllConnected")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</td></tr>'+
'<tr><td><span class="help-key">Enter</span></td><td>'+RED._("keyboard.editSelected")+'</td></tr>'+
'<tr><td><span class="help-key">Delete</span> / <span class="help-key">Backspace</span></td><td>'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key"> &#x2190; </span> <span class="help-key"> &#x2191; </span> <span class="help-key"> &#x2192; </span> <span class="help-key"> &#x2193; </span></td><td>'+RED._("keyboard.nudgeNode")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key"> &#x2190; </span></td><td rowspan="4">'+RED._("keyboard.moveNode")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key"> &#x2191; </span></td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key"> &#x2192; </span></td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key"> &#x2193; </span></td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</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/&#8984;</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">.</span></td><td>'+RED._("keyboard.searchBox")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Shift</span> + <span class="help-key">p</span></td><td>'+RED._("keyboard.managePalette")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td rowspan="2">'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td><span class="help-key">Backspace</span></td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">z</span></td><td>'+RED._("keyboard.undoChange")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">f</span></td><td>'+RED._("keyboard.searchBox")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">Shift</span> + <span class="help-key">p</span></td><td>'+RED._("keyboard.managePalette")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">&#x2190</span> <span class="help-key">&#x2191;</span> <span class="help-key">&#x2192;</span> <span class="help-key">&#x2193;</span></td><td>'+RED._("keyboard.nudgeNode")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">&#x2190;</span> <span class="help-key">&#x2191;</span> <span class="help-key">&#x2192;</span> <span class="help-key">&#x2193;</span></td><td>'+RED._("keyboard.moveNode")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
'<tr><td>'+cmdCtrlKey+' + <span class="help-key">z</span></td><td>'+RED._("keyboard.undoChange")+'</td></tr>'+
'</table>'+
'</div>'+
'</div>')
@ -149,11 +304,24 @@ RED.keyboard = (function() {
dialog.dialog("open");
}
function formatKey(key) {
var formattedKey = isMac?key.replace(/ctrl-?/,"&#8984;"):key;
formattedKey = formattedKey.replace(/shift-?/,"&#8679;")
formattedKey = formattedKey.replace(/left/,"&#x2190;")
formattedKey = formattedKey.replace(/up/,"&#x2191;")
formattedKey = formattedKey.replace(/right/,"&#x2192;")
formattedKey = formattedKey.replace(/down/,"&#x2193;")
return '<span class="help-key-block"><span class="help-key">'+formattedKey.split(" ").join('</span> <span class="help-key">')+'</span></span>';
}
return {
init: init,
add: addHandler,
remove: removeHandler,
showHelp: showKeyboardHelp
getShortcut: function(actionName) {
return actionToKeyMap[actionName];
},
formatKey: formatKey
}
})();

View File

@ -410,6 +410,9 @@ RED.library = (function() {
return {
init: function() {
RED.actions.add("core:library-export",exportFlow);
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);

View File

@ -51,11 +51,17 @@ RED.notify = (function() {
n.update = (function() {
var nn = n;
return function(msg) {
return function(msg,timeout) {
nn.innerHTML = msg;
if (timeout !== undefined && timeout > 0) {
window.clearTimeout(nn.timeoutid);
nn.timeoutid = window.setTimeout(nn.close,timeout);
} else {
window.clearTimeout(nn.timeoutid);
}
}
})();
if (!fixed) {
$(n).click((function() {
var nn = n;

View File

@ -304,10 +304,10 @@ RED.palette.editor = (function() {
filterInput.focus();
},250);
RED.events.emit("palette-editor:open");
RED.keyboard.add("*",/* ESCAPE */ 27,function(){hidePaletteEditor();d3.event.preventDefault();});
RED.keyboard.add("*","escape",function(){hidePaletteEditor()});
}
function hidePaletteEditor() {
RED.keyboard.remove("*");
RED.keyboard.remove("escape");
$("#main-container").removeClass("palette-expanded");
$("#header-shade").hide();
$("#editor-shade").hide();
@ -424,8 +424,10 @@ RED.palette.editor = (function() {
RED.events.on("editor:close",function() { disabled = false; });
RED.events.on("search:open",function() { disabled = true; });
RED.events.on("search:close",function() { disabled = false; });
RED.events.on("type-search:open",function() { disabled = true; });
RED.events.on("type-search:close",function() { disabled = false; });
RED.keyboard.add("*", /* p */ 80,{shift:true,ctrl:true},function() {RED.palette.editor.show();d3.event.preventDefault();});
RED.actions.add("core:manage-palette",RED.palette.editor.show);
editorTabs = RED.tabs.create({
id:"palette-editor-tabs",
@ -631,7 +633,7 @@ RED.palette.editor = (function() {
$('<span>').html(RED._("palette.editor.sort")+' ').appendTo(toolBar);
var sortGroup = $('<span class="button-group"></span> ').appendTo(toolBar);
var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
var sortAZ = $('<a href="#" class="sidebar-header-button-toggle selected" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup);
var sortRecent = $('<a href="#" class="sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
@ -754,7 +756,9 @@ RED.palette.editor = (function() {
refreshNodeModule(ns.module);
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
filteredList[i].elements.installButton.hide();
var installButton = filteredList[i].elements.installButton;
installButton.addClass('disabled');
installButton.html(RED._('palette.editor.installed'));
break;
}
}
@ -768,7 +772,9 @@ RED.palette.editor = (function() {
delete nodeEntries[ns.module];
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
filteredList[i].elements.installButton.show();
var installButton = filteredList[i].elements.installButton;
installButton.removeClass('disabled');
installButton.html(RED._('palette.editor.install'));
break;
}
}

View File

@ -45,6 +45,9 @@ RED.search = (function() {
var properties = ['id','type','name','label','info'];
if (n._def && n._def.defaults) {
properties = properties.concat(Object.keys(n._def.defaults));
}
for (var i=0;i<properties.length;i++) {
if (n.hasOwnProperty(properties[i])) {
var v = n[properties[i]];
@ -238,8 +241,11 @@ RED.search = (function() {
}
function show() {
if (disabled) {
return;
}
if (!visible) {
RED.keyboard.add("*",/* ESCAPE */ 27,function(){hide();d3.event.preventDefault();});
RED.keyboard.add("*","escape",function(){hide()});
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
@ -257,7 +263,7 @@ RED.search = (function() {
}
function hide() {
if (visible) {
RED.keyboard.remove(/* ESCAPE */ 27);
RED.keyboard.remove("escape");
visible = false;
$("#header-shade").hide();
$("#editor-shade").hide();
@ -274,11 +280,14 @@ RED.search = (function() {
}
function init() {
RED.keyboard.add("*",/* . */ 190,{ctrl:true},function(){if (!disabled) { show(); } d3.event.preventDefault();});
RED.actions.add("core:search",show);
RED.events.on("editor:open",function() { disabled = true; });
RED.events.on("editor:close",function() { disabled = false; });
RED.events.on("palette-editor:open",function() { disabled = true; });
RED.events.on("palette-editor:close",function() { disabled = false; });
RED.events.on("type-search:open",function() { disabled = true; });
RED.events.on("type-search:close",function() { disabled = false; });

View File

@ -202,12 +202,18 @@ RED.sidebar = (function() {
}
function init () {
RED.keyboard.add("*",/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("menu-item-sidebar",!RED.menu.isSelected("menu-item-sidebar"));d3.event.preventDefault();});
RED.actions.add("core:toggle-sidebar",function(state){
if (state === undefined) {
RED.menu.toggleSelected("menu-item-sidebar");
} else {
toggleSidebar(state);
}
});
showSidebar();
RED.sidebar.info.init();
RED.sidebar.config.init();
// hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { toggleSidebar(); }
if ($(window).width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); }
}
return {

View File

@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,5 +22,6 @@ RED.state = {
EDITING: 5,
EXPORT: 6,
IMPORT: 7,
IMPORT_DRAGGING: 8
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9
}

View File

@ -306,50 +306,13 @@ RED.subflow = (function() {
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.nodes.dirty();
var historyEvent = removeSubflow(RED.workspaces.active());
historyEvent.t = 'delete';
historyEvent.dirty = startDirty;
var activeSubflow = getSubflow();
RED.history.push(historyEvent);
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var removedEntities = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(removedEntities.links);
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
}
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: {
subflow: activeSubflow
},
dirty:startDirty
});
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
});
refreshToolbar(activeSubflow);
@ -362,7 +325,48 @@ RED.subflow = (function() {
$("#chart").css({"margin-top": "0"});
}
function removeSubflow(id) {
var removedNodes = [];
var removedLinks = [];
var activeSubflow = RED.nodes.subflow(id);
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var removedEntities = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(removedEntities.links);
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
}
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
RED.nodes.removeSubflow(activeSubflow);
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
return {
nodes:removedNodes,
links:removedLinks,
subflow: {
subflow: activeSubflow
}
}
}
function init() {
RED.events.on("workspace:change",function(event) {
var activeSubflow = RED.nodes.subflow(event.workspace);
@ -380,6 +384,8 @@ RED.subflow = (function() {
}
});
RED.actions.add("core:create-subflow",createSubflow);
RED.actions.add("core:convert-to-subflow",convertToSubflow);
}
function createSubflow() {
@ -617,6 +623,7 @@ RED.subflow = (function() {
init: init,
createSubflow: createSubflow,
convertToSubflow: convertToSubflow,
removeSubflow: removeSubflow,
refresh: refresh,
removeInput: removeSubflowInput,
removeOutput: removeSubflowOutput

View File

@ -236,10 +236,7 @@ RED.sidebar.config = (function() {
visible: false,
onchange: function() { refreshConfigNodeList(); }
});
RED.menu.setAction('menu-item-config-nodes',function() {
RED.sidebar.show('config');
})
RED.actions.add("core:show-config-tab",function() {RED.sidebar.show('config')});
$("#workspace-config-node-collapse-all").on("click", function(e) {
e.preventDefault();

View File

@ -42,7 +42,7 @@ RED.sidebar.info = (function() {
content: content,
enableOnEdit: true
});
RED.actions.add("core:show-info-tab",show);
}
function show() {
@ -67,13 +67,16 @@ RED.sidebar.info = (function() {
}
function refresh(node) {
var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.node")+'</td></tr>';
tips.stop();
$(content).empty();
var table = $('<table class="node-info"></table>');
var tableBody = $('<tbody>').appendTo(table);
$('<tr class="blank"><td colspan="2">'+RED._("sidebar.info.node")+'</td></tr>').appendTo(tableBody);
if (node.type != "subflow" && node.name) {
table += '<tr><td>'+RED._("common.label.name")+'</td><td>&nbsp;<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'">'+node.name+'</span></td></tr>';
$('<tr><td>'+RED._("common.label.name")+'</td><td>&nbsp;<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'">'+node.name+'</span></td></tr>').appendTo(tableBody);
}
table += "<tr><td>"+RED._("sidebar.info.type")+"</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>";
$("<tr><td>"+RED._("sidebar.info.type")+"</td><td>&nbsp;"+node.type+"</td></tr>").appendTo(tableBody);
$("<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>").appendTo(tableBody);
var m = /^subflow(:(.+))?$/.exec(node.type);
var subflowNode;
@ -84,7 +87,7 @@ RED.sidebar.info = (function() {
subflowNode = node;
}
table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.subflow")+'</td></tr>';
$('<tr class="blank"><td colspan="2">'+RED._("sidebar.info.subflow")+'</td></tr>').appendTo(tableBody);
var userCount = 0;
var subflowType = "subflow:"+subflowNode.id;
@ -93,66 +96,38 @@ RED.sidebar.info = (function() {
userCount++;
}
});
table += '<tr><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>';
table += "<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>";
$('<tr><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>').appendTo(tableBody);
$("<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>").appendTo(tableBody);
}
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> '+RED._("sidebar.info.properties")+'</a></td></tr>';
$('<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> '+RED._("sidebar.info.properties")+'</a></td></tr>').appendTo(tableBody);
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 (val === null || val === undefined) {
val = '<span style="font-style: italic; color: #ccc;">'+RED._("sidebar.info.null")+'</span>';
} else if (type === "string") {
if (val.length === 0) {
val = '<span style="font-style: italic; color: #ccc;">'+RED._("sidebar.info.blank")+'</span>';
} else {
if (val.length > 30) {
val = val.substring(0,30)+" ...";
}
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
} 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
val += "&nbsp;"+i+": "+vv+"<br/>";
}
if (node[n].length > 10) {
val += "&nbsp;... "+RED._("sidebar.info.arrayItems",{count:node[n].length})+"<br/>";
}
val += "]";
} else {
val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
var propRow = $('<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
}
}
}
}
table += "</tbody></table><hr/>";
$(table).appendTo(content);
$("<hr/>").appendTo(content);
if (!subflowNode && node.type != "comment") {
var helpText = $("script[data-help-name$='"+node.type+"']").html()||"";
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(helpText)+'">'+helpText+'</span></div>';
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(helpText)+'">'+helpText+'</span></div>').appendTo(content);
}
if (subflowNode) {
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.info||"")+'">'+marked(subflowNode.info||"")+'</span></div>';
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.info||"")+'">'+marked(subflowNode.info||"")+'</span></div>').appendTo(content);
} else if (node._def && node._def.info) {
var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info);
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(textInfo)+'">'+marked(textInfo)+'</span></div>';
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(textInfo)+'">'+marked(textInfo)+'</span></div>').appendTo(content);
//$('<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
}
$(content).html(table);
$(".node-info-property-header").click(function(e) {
var icon = $(this).find("i");
if (icon.hasClass("fa-caret-right")) {
@ -171,14 +146,98 @@ RED.sidebar.info = (function() {
});
}
var tips = (function() {
var started = false;
var enabled = true;
var startDelay = 1000;
var cycleDelay = 10000;
var startTimeout;
var refreshTimeout;
var tipCount = -1;
RED.actions.add("core:toggle-show-tips",function(state) {
if (state === undefined) {
RED.menu.toggleSelected("menu-item-show-tips");
} else {
enabled = state;
if (enabled) {
if (started) {
startTips();
}
} else {
stopTips();
}
}
});
function setTip() {
var r = Math.floor(Math.random() * tipCount);
var tip = RED._("infotips:info.tip"+r);
var m;
while ((m=/({{(.*?)}})/.exec(tip))) {
var shortcut = RED.keyboard.getShortcut(m[2]);
if (shortcut) {
tip = tip.replace(m[1],RED.keyboard.formatKey(shortcut.key));
} else {
return;
}
}
while ((m=/(\[(.*?)\])/.exec(tip))) {
tip = tip.replace(m[1],RED.keyboard.formatKey(m[2]));
}
$('<div class="node-info-tip hide">'+tip+'</div>').appendTo(content).fadeIn(200);
if (startTimeout) {
startTimeout = null;
refreshTimeout = setInterval(cycleTips,cycleDelay);
}
}
function cycleTips() {
$(".node-info-tip").fadeOut(300,function() {
$(this).remove();
setTip();
})
}
function startTips() {
started = true;
if (enabled) {
if (!startTimeout && !refreshTimeout) {
$(content).html("");
if (tipCount === -1) {
do {
tipCount++;
} while(RED._("infotips:info.tip"+tipCount)!=="infotips:info.tip"+tipCount);
}
startTimeout = setTimeout(setTip,startDelay);
}
}
}
function stopTips() {
started = false;
clearInterval(refreshTimeout);
clearTimeout(startTimeout);
refreshTimeout = null;
startTimeout = null;
$(".node-info-tip").remove();
}
return {
start: startTips,
stop: stopTips
}
})();
function clear() {
$(content).html("");
tips.start();
}
function set(html) {
tips.stop();
$(content).html(html);
}
RED.events.on("view:selection-changed",function(selection) {
if (selection.nodes) {
if (selection.nodes.length == 1) {
@ -202,7 +261,7 @@ RED.sidebar.info = (function() {
return {
init: init,
show: show,
refresh:refresh,
refresh: refresh,
clear: clear,
set: set
}

View File

@ -155,6 +155,8 @@ RED.tray = (function() {
// Delay resetting the flag, so we don't close prematurely
openingTray = false;
},200);
body.find(":focusable:first").focus();
},150);
el.css({right:0});
},0);

304
editor/js/ui/typeSearch.js Normal file
View File

@ -0,0 +1,304 @@
RED.typeSearch = (function() {
var shade;
var disabled = false;
var dialog = null;
var searchInput;
var searchResults;
var searchResultsDiv;
var selected = -1;
var visible = false;
var activeFilter = "";
var addCallback;
var typesUsed = {};
function search(val) {
activeFilter = val.toLowerCase();
var visible = searchResults.editableList('filter');
setTimeout(function() {
selected = 0;
searchResults.children().removeClass('selected');
searchResults.children(":visible:first").addClass('selected');
},100);
}
function ensureSelectedIsVisible() {
var selectedEntry = searchResults.find("li.selected");
if (selectedEntry.length === 1) {
var scrollWindow = searchResults.parent();
var scrollHeight = scrollWindow.height();
var scrollOffset = scrollWindow.scrollTop();
var y = selectedEntry.position().top;
var h = selectedEntry.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-10)},50);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},50);
}
}
}
function createDialog() {
//shade = $('<div>',{class:"red-ui-type-search-shade"}).appendTo("#main-container");
dialog = $("<div>",{id:"red-ui-type-search",class:"red-ui-search red-ui-type-search"}).appendTo("#main-container");
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text">').attr("placeholder",RED._("search.addNode")).appendTo(searchDiv).searchBox({
delay: 50,
change: function() {
search($(this).val());
}
});
searchInput.on('keydown',function(evt) {
var children = searchResults.children(":visible");
if (children.length > 0) {
if (evt.keyCode === 40) {
// Down
if (selected < children.length-1) {
if (selected > -1) {
$(children[selected]).removeClass('selected');
}
selected++;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 38) {
// Up
if (selected > 0) {
if (selected < children.length) {
$(children[selected]).removeClass('selected');
}
selected--;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 13) {
// Enter
var index = Math.max(0,selected);
if (index < children.length) {
// TODO: dips into editableList impl details
confirm($(children[index]).find(".red-ui-editableList-item-content").data('data'));
}
}
}
});
searchResultsDiv = $("<div>",{class:"red-ui-search-results-container"}).appendTo(dialog);
searchResults = $('<ol>',{id:"search-result-list", style:"position: absolute;top: 0;bottom: 0;left: 0;right: 0;"}).appendTo(searchResultsDiv).editableList({
addButton: false,
filter: function(data) {
if (activeFilter === "" ) {
return true;
}
if (data.recent || data.common) {
return false;
}
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
},
addItem: function(container,i,object) {
var def = object.def;
object.index = object.type.toLowerCase();
if (object.separator) {
container.addClass("red-ui-search-result-separator")
}
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = def.color;
var icon_url = "arrow-in.png";
if (def.category === 'config') {
icon_url = "cog.png";
} else {
try {
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
} catch(err) {
console.log("Definition error: "+object.type+".icon",err);
}
}
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
if (def.inputs > 0) {
$('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
}
if (def.outputs > 0) {
$('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
}
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var label = object.label;
object.index += "|"+label.toLowerCase();
$('<div>',{class:"red-ui-search-result-node-label"}).html(label).appendTo(contentDiv);
div.click(function(evt) {
evt.preventDefault();
confirm(object);
});
},
scrollOnAdd: false
});
}
function confirm(def) {
hide();
typesUsed[def.type] = Date.now();
addCallback(def.type);
}
function handleMouseActivity(evt) {
if (visible) {
var t = $(evt.target);
while (t.prop('nodeName').toLowerCase() !== 'body') {
if (t.attr('id') === 'red-ui-type-search') {
return;
}
t = t.parent();
}
hide(true);
}
}
function show(opts) {
if (!visible) {
RED.keyboard.add("*","escape",function(){hide()});
if (dialog === null) {
createDialog();
}
visible = true;
setTimeout(function() {
$(document).on('mousedown.type-search',handleMouseActivity);
$(document).on('mouseup.type-search',handleMouseActivity);
$(document).on('click.type-search',handleMouseActivity);
},200);
} else {
dialog.hide();
searchResultsDiv.hide();
}
refreshTypeList();
addCallback = opts.add;
RED.events.emit("type-search:open");
//shade.show();
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
searchResultsDiv.slideDown();
setTimeout(function() {
searchResultsDiv.find(".red-ui-editableList-container").scrollTop(0);
searchInput.focus();
},100);
}
function hide(fast) {
if (visible) {
RED.keyboard.remove("escape");
visible = false;
if (dialog !== null) {
searchResultsDiv.slideUp(fast?50:200,function() {
dialog.hide();
searchInput.searchBox('value','');
});
//shade.hide();
}
RED.events.emit("type-search:close");
RED.view.focus();
$(document).off('mousedown.type-search');
$(document).off('mouseup.type-search');
$(document).off('click.type-search');
}
}
function getTypeLabel(type, def) {
var label = type;
if (typeof def.paletteLabel !== "undefined") {
try {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
label += " ("+type+")";
} catch(err) {
console.log("Definition error: "+type+".paletteLabel",err);
}
}
return label;
}
function refreshTypeList() {
var i;
searchResults.editableList('empty');
searchInput.searchBox('value','');
selected = -1;
var common = [
'debug','inject','function','change','switch'
];
var recentlyUsed = Object.keys(typesUsed);
recentlyUsed.sort(function(a,b) {
return typesUsed[b]-typesUsed[a];
});
recentlyUsed = recentlyUsed.filter(function(t) {
return common.indexOf(t) === -1;
});
var items = [];
RED.nodes.registry.getNodeTypes().forEach(function(t) {
var def = RED.nodes.getType(t);
if (def.category !== 'config' && t !== 'unknown') {
items.push({type:t,def: def, label:getTypeLabel(t,def)});
}
});
items.sort(function(a,b) {
var al = a.label.toLowerCase();
var bl = b.label.toLowerCase();
if (al < bl) {
return -1;
} else if (al === bl) {
return 0;
} else {
return 1;
}
})
var commonCount = 0;
var item;
for(i=0;i<common.length;i++) {
item = {
type: common[i],
common: true,
def: RED.nodes.getType(common[i])
};
item.label = getTypeLabel(item.type,item.def);
if (i === common.length-1) {
item.separator = true;
}
searchResults.editableList('addItem', item);
}
for(i=0;i<Math.min(5,recentlyUsed.length);i++) {
item = {
type:recentlyUsed[i],
def: RED.nodes.getType(recentlyUsed[i]),
recent: true
};
item.label = getTypeLabel(item.type,item.def);
if (i === recentlyUsed.length-1) {
item.separator = true;
}
searchResults.editableList('addItem', item);
}
for (i=0;i<items.length;i++) {
searchResults.editableList('addItem', items[i]);
}
setTimeout(function() {
selected = 0;
searchResults.children(":first").addClass('selected');
},100);
}
return {
show: show,
hide: hide
};
})();

366
editor/js/ui/utils.js Normal file
View File

@ -0,0 +1,366 @@
/**
* Copyright 2016 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.utils = (function() {
function formatString(str) {
return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;");
}
function sanitize(m) {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
function buildMessageSummaryValue(value) {
var result;
if (Array.isArray(value)) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').html('array['+value.length+']');
} else if (value === null) {
result = $('<span class="debug-message-object-value debug-message-type-null">null</span>');
} else if (typeof value === 'object') {
if (value.hasOwnProperty('type') && value.type === 'Buffer' && value.hasOwnProperty('data')) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').html('buffer['+value.data.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').html('array['+value.length+']');
} else {
result = $('<span class="debug-message-object-value debug-message-type-meta">object</span>');
}
} else if (typeof value === 'string') {
var subvalue;
if (value.length > 30) {
subvalue = sanitize(value.substring(0,30))+"&hellip;";
} else {
subvalue = sanitize(value);
}
result = $('<span class="debug-message-object-value debug-message-type-string"></span>').html('"'+formatString(subvalue)+'"');
} else {
result = $('<span class="debug-message-object-value debug-message-type-other"></span>').text(""+value);
}
return result;
}
function makeExpandable(el,onexpand) {
el.addClass("debug-message-expandable");
el.click(function(e) {
var parent = $(this).parent();
if (parent.hasClass('collapsed')) {
if (onexpand && !parent.hasClass('built')) {
onexpand();
parent.addClass('built');
}
parent.removeClass('collapsed');
} else {
parent.addClass('collapsed');
}
e.preventDefault();
});
}
function buildMessageElement(obj,key,typeHint,hideKey) {
var i;
var e;
var entryObj;
var header;
var headerHead;
var value;
var element = $('<span class="debug-message-element"></span>');
if (!key) {
element.addClass("debug-message-top-level");
}
header = $('<span></span>').appendTo(element);
if (key && !hideKey) {
$('<span class="debug-message-object-key"></span>').text(key).appendTo(header);
$('<span>: </span>').appendTo(header);
}
entryObj = $('<span class="debug-message-object-value"></span>').appendTo(header);
var isArray = Array.isArray(obj);
var isArrayObject = false;
if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__encoded__ && obj.type === 'array') || obj.type === 'Buffer')) {
isArray = true;
isArrayObject = true;
}
if (obj === null || obj === undefined) {
$('<span class="debug-message-type-null">'+obj+'</span>').appendTo(entryObj);
} else if (typeof obj === 'string') {
if (/[\t\n\r]/.test(obj)) {
element.addClass('collapsed');
$('<i class="fa fa-caret-right debug-message-object-handle"></i> ').prependTo(header);
makeExpandable(header, function() {
$('<span class="debug-message-type-meta debug-message-object-type-header"></span>').html(typeHint||'string').appendTo(header);
var row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(element);
$('<pre class="debug-message-type-string"></pre>').text(obj).appendTo(row);
});
}
$('<span class="debug-message-type-string debug-message-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
} else if (typeof obj === 'number') {
e = $('<span class="debug-message-type-number"></span>').text(""+obj).appendTo(entryObj);
if (Number.isInteger(obj) && (obj >= 0)) { // if it's a +ve integer
e.addClass("debug-message-type-number-toggle");
e.click(function(evt) {
var format = $(this).data('format') || "date";
if (format === 'dec') {
$(this).text(""+obj).data('format','date');
} else if ((format === 'date') && (obj.toString().length===13) && (obj<=2147483647000)) {
$(this).text((new Date(obj)).toISOString()).data('format','hex');
} else if ((format === 'date') && (obj.toString().length===10) && (obj<=2147483647)) {
$(this).text((new Date(obj*1000)).toISOString()).data('format','hex');
} else {
$(this).text("0x"+(obj).toString(16)).data('format','dec');
}
evt.preventDefault();
});
}
} else if (isArray) {
element.addClass('collapsed');
var originalLength = obj.length;
if (typeHint) {
var m = /\[(\d+)\]/.exec(typeHint);
if (m) {
originalLength = parseInt(m[1]);
}
}
var data = obj;
var type = 'array';
if (isArrayObject) {
data = obj.data;
if (originalLength === undefined) {
originalLength = data.length;
}
type = obj.type.toLowerCase();
} else if (/buffer/.test(typeHint)) {
type = 'buffer';
}
var fullLength = data.length;
if (originalLength > 0) {
$('<i class="fa fa-caret-right debug-message-object-handle"></i> ').prependTo(header);
var arrayRows = $('<div class="debug-message-array-rows"></div>').appendTo(element);
element.addClass('debug-message-buffer-raw');
makeExpandable(header,function() {
if (!key) {
headerHead = $('<span class="debug-message-type-meta debug-message-object-type-header"></span>').html(typeHint||(type+'['+originalLength+']')).appendTo(header);
}
if (type === 'buffer') {
var stringRow = $('<div class="debug-message-string-rows"></div>').appendTo(element);
var sr = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(stringRow);
var stringEncoding = "";
try {
stringEncoding = String.fromCharCode.apply(null, new Uint16Array(data))
} catch(err) {
console.log(err);
}
$('<pre class="debug-message-type-string"></pre>').text(stringEncoding).appendTo(sr);
var bufferOpts = $('<span class="debug-message-buffer-opts"></span>').appendTo(headerHead);
$('<a href="#"></a>').addClass('selected').html('raw').appendTo(bufferOpts).click(function(e) {
if ($(this).text() === 'raw') {
$(this).text('string');
element.addClass('debug-message-buffer-string').removeClass('debug-message-buffer-raw');
} else {
$(this).text('raw');
element.removeClass('debug-message-buffer-string').addClass('debug-message-buffer-raw');
}
e.preventDefault();
e.stopPropagation();
})
}
var row;
if (fullLength <= 10) {
for (i=0;i<fullLength;i++) {
row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(arrayRows);
buildMessageElement(data[i],""+i,false).appendTo(row);
}
} else {
for (i=0;i<fullLength;i+=10) {
var minRange = i;
row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(arrayRows);
header = $('<span></span>').appendTo(row);
$('<i class="fa fa-caret-right debug-message-object-handle"></i> ').appendTo(header);
makeExpandable(header, (function() {
var min = minRange;
var max = Math.min(fullLength-1,(minRange+9));
var parent = row;
return function() {
for (var i=min;i<=max;i++) {
var row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(parent);
buildMessageElement(data[i],""+i,false).appendTo(row);
}
}
})());
$('<span class="debug-message-object-key"></span>').html("["+minRange+" &hellip; "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
}
if (fullLength < originalLength) {
$('<div class="debug-message-object-entry collapsed"><span class="debug-message-object-key">['+fullLength+' &hellip; '+originalLength+']</span></div>').appendTo(arrayRows);
}
}
});
}
if (key) {
headerHead = $('<span class="debug-message-type-meta f"></span>').html(typeHint||(type+'['+originalLength+']')).appendTo(entryObj);
} else {
headerHead = $('<span class="debug-message-object-header"></span>').appendTo(entryObj);
$('<span>[ </span>').appendTo(headerHead);
var arrayLength = Math.min(originalLength,10);
for (i=0;i<arrayLength;i++) {
buildMessageSummaryValue(data[i]).appendTo(headerHead);
if (i < arrayLength-1) {
$('<span>, </span>').appendTo(headerHead);
}
}
if (originalLength > arrayLength) {
$('<span> &hellip;</span>').appendTo(headerHead);
}
if (arrayLength === 0) {
$('<span class="debug-message-type-meta">empty</span>').appendTo(headerHead);
}
$('<span> ]</span>').appendTo(headerHead);
}
} else if (typeof obj === 'object') {
element.addClass('collapsed');
var keys = Object.keys(obj);
if (key || keys.length > 0) {
$('<i class="fa fa-caret-right debug-message-object-handle"></i> ').prependTo(header);
makeExpandable(header, function() {
if (!key) {
$('<span class="debug-message-type-meta debug-message-object-type-header"></span>').html('object').appendTo(header);
}
for (i=0;i<keys.length;i++) {
var row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(element);
buildMessageElement(obj[keys[i]],keys[i],false).appendTo(row);
}
if (keys.length === 0) {
$('<div class="debug-message-object-entry debug-message-type-meta collapsed"></div>').text("empty").appendTo(element);
}
});
}
if (key) {
$('<span class="debug-message-type-meta"></span>').html('object').appendTo(entryObj);
} else {
headerHead = $('<span class="debug-message-object-header"></span>').appendTo(entryObj);
$('<span>{ </span>').appendTo(headerHead);
var keysLength = Math.min(keys.length,5);
for (i=0;i<keysLength;i++) {
$('<span class="debug-message-object-key"></span>').text(keys[i]).appendTo(headerHead);
$('<span>: </span>').appendTo(headerHead);
buildMessageSummaryValue(obj[keys[i]]).appendTo(headerHead);
if (i < keysLength-1) {
$('<span>, </span>').appendTo(headerHead);
}
}
if (keys.length > keysLength) {
$('<span> &hellip;</span>').appendTo(headerHead);
}
if (keysLength === 0) {
$('<span class="debug-message-type-meta">empty</span>').appendTo(headerHead);
}
$('<span> }</span>').appendTo(headerHead);
}
} else {
$('<span class="debug-message-type-other"></span>').text(""+obj).appendTo(entryObj);
}
return element;
}
function validatePropertyExpression(str) {
// This must be kept in sync with normalisePropertyExpression
// in red/runtime/util.js
var length = str.length;
var start = 0;
var inString = false;
var inBox = false;
var quoteChar;
var v;
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (i != start) {
return false;
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0 || i===length-1) {
return false;
}
// Next char is a-z
if (!/[a-z0-9]/i.test(str[i+1])) {
return false;
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
return false;
}
if (i===length-1) {
return false;
}
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
return false;
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
return false;
}
if (start != i) {
v = str.substring(start,i);
if (!/^\d+$/.test(v)) {
return false;
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
return false;
}
} else {
if (c === quoteChar) {
if (i-start === 0) {
return false;
}
// Next char must be a ]
if (inBox && !/\]/.test(str[i+1])) {
return false;
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
return false;
}
start = i+1;
inString = false;
}
}
}
if (inBox || inString) {
return false;
}
return true;
}
return {
createObjectElement: buildMessageElement,
validatePropertyExpression: validatePropertyExpression
}
})();

View File

@ -244,10 +244,14 @@ RED.view = (function() {
node.el = dragGroup.append("svg:path").attr("class", "drag_line");
drag_lines.push(node);
}
}
function hideDragLines() {
while(drag_lines.length) {
(drag_lines.pop()).el.remove();
var line = drag_lines.pop();
if (line.el) {
line.el.remove();
}
}
}
@ -263,6 +267,7 @@ RED.view = (function() {
}
function init() {
RED.events.on("workspace:change",function(event) {
var chart = $("#chart");
if (event.old !== 0) {
@ -320,70 +325,12 @@ RED.view = (function() {
drop: function( event, ui ) {
d3.event = event;
var selected_tool = ui.draggable[0].type;
var m = /^subflow:(.+)$/.exec(selected_tool);
if (activeSubflow && m) {
var subflowId = m[1];
if (subflowId === activeSubflow.id) {
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
return;
}
if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
return;
}
}
var nn = { id:RED.nodes.id(),z:RED.workspaces.active()};
nn.type = selected_tool;
nn._def = RED.nodes.getType(nn.type);
if (!m) {
nn.inputs = nn._def.inputs || 0;
nn.outputs = nn._def.outputs;
for (var d in nn._def.defaults) {
if (nn._def.defaults.hasOwnProperty(d)) {
if (nn._def.defaults[d].value !== undefined) {
nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
}
}
}
if (nn._def.onadd) {
try {
nn._def.onadd.call(nn);
} catch(err) {
console.log("onadd:",err);
}
}
} else {
var subflow = RED.nodes.subflow(m[1]);
nn.inputs = subflow.in.length;
nn.outputs = subflow.out.length;
}
nn.changed = true;
nn.w = node_width;
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
var historyEvent = {
t:"add",
nodes:[nn.id],
dirty:RED.nodes.dirty()
}
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
historyEvent.subflow = {
id:activeSubflow.id,
changed: activeSubflow.changed,
instances: subflowRefresh.instances
}
}
var result = addNode(selected_tool);
if (!result) {
return;
}
var historyEvent = result.historyEvent;
var nn = result.node;
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var mousePos = d3.touches(this)[0]||d3.mouse(this);
@ -438,29 +385,125 @@ RED.view = (function() {
}
});
RED.keyboard.add("workspace",/* backspace */ 8,function(){deleteSelection();d3.event.preventDefault();});
RED.keyboard.add("workspace",/* delete */ 46,function(){deleteSelection();d3.event.preventDefault();});
RED.keyboard.add("workspace",/* c */ 67,{ctrl:true},function(){copySelection();d3.event.preventDefault();});
RED.keyboard.add("workspace",/* x */ 88,{ctrl:true},function(){copySelection();deleteSelection();d3.event.preventDefault();});
RED.actions.add("core:copy",copySelection);
RED.actions.add("core:cut",function(){copySelection();deleteSelection();});
RED.actions.add("core:paste",function(){importNodes(clipboard);});
RED.actions.add("core:delete",deleteSelection);
RED.actions.add("core:edit",editSelection);
RED.actions.add("core:undo",RED.history.pop);
RED.actions.add("core:select-all",selectAll);
RED.actions.add("core:zoom-in",zoomIn);
RED.actions.add("core:zoom-out",zoomOut);
RED.actions.add("core:zoom-reset",zoomZero);
RED.keyboard.add("workspace",/* z */ 90,{ctrl:true},function(){RED.history.pop();});
RED.keyboard.add("workspace",/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
RED.keyboard.add("*",/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
RED.keyboard.add("*",/* - */ 189,{ctrl:true},function(){zoomOut();d3.event.preventDefault();});
RED.keyboard.add("*",/* 0 */ 48,{ctrl:true},function(){zoomZero();d3.event.preventDefault();});
RED.keyboard.add("workspace",/* v */ 86,{ctrl:true},function(){importNodes(clipboard);d3.event.preventDefault();});
RED.actions.add("core:toggle-show-grid",function(state) {
if (state === undefined) {
RED.menu.toggleSelected("menu-item-view-show-grid");
} else {
toggleShowGrid(state);
}
});
RED.actions.add("core:toggle-snap-grid",function(state) {
if (state === undefined) {
RED.menu.toggleSelected("menu-item-view-snap-grid");
} else {
toggleSnapGrid(state);
}
});
RED.actions.add("core:toggle-status",function(state) {
if (state === undefined) {
RED.menu.toggleSelected("menu-item-status");
} else {
toggleStatus(state);
}
});
RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);});
RED.actions.add("core:step-selection-up", function() { moveSelection(0,-20);});
RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);});
RED.actions.add("core:step-selection-right", function() { moveSelection(20,0);});
RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});
RED.actions.add("core:step-selection-down", function() { moveSelection(0,20);});
RED.actions.add("core:move-selection-left", function() { moveSelection(-1,0);});
RED.actions.add("core:step-selection-left", function() { moveSelection(-20,0);});
}
function addNode(type,x,y) {
var m = /^subflow:(.+)$/.exec(type);
if (activeSubflow && m) {
var subflowId = m[1];
if (subflowId === activeSubflow.id) {
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
return;
}
if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
return;
}
}
var nn = { id:RED.nodes.id(),z:RED.workspaces.active()};
nn.type = type;
nn._def = RED.nodes.getType(nn.type);
if (!m) {
nn.inputs = nn._def.inputs || 0;
nn.outputs = nn._def.outputs;
for (var d in nn._def.defaults) {
if (nn._def.defaults.hasOwnProperty(d)) {
if (nn._def.defaults[d].value !== undefined) {
nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
}
}
}
if (nn._def.onadd) {
try {
nn._def.onadd.call(nn);
} catch(err) {
console.log("onadd:",err);
}
}
} else {
var subflow = RED.nodes.subflow(m[1]);
nn.inputs = subflow.in.length;
nn.outputs = subflow.out.length;
}
nn.changed = true;
nn.w = node_width;
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
var historyEvent = {
t:"add",
nodes:[nn.id],
dirty:RED.nodes.dirty()
}
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
historyEvent.subflow = {
id:activeSubflow.id,
changed: activeSubflow.changed,
instances: subflowRefresh.instances
}
}
}
return {
node: nn,
historyEvent: historyEvent
}
RED.keyboard.add("workspace",/* up */ 38, function() { moveSelection(0,-1);d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* up */ 38, {shift:true}, function() { moveSelection(0,-20); d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* down */ 40, function() { moveSelection(0,1);d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* down */ 40, {shift:true}, function() { moveSelection(0,20); d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* left */ 37, function() { moveSelection(-1,0);d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* left */ 37, {shift:true}, function() { moveSelection(-20,0); d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* right */ 39, function() { moveSelection(1,0);d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add("workspace",/* right */ 39, {shift:true}, function() { moveSelection(20,0); d3.event.preventDefault();},endKeyboardMove);
}
function canvasMouseDown() {
var point;
if (!mousedown_node && !mousedown_link) {
selected_link = null;
updateSelection();
@ -470,19 +513,104 @@ RED.view = (function() {
lasso.remove();
lasso = null;
}
}
if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
if (d3.event.metaKey || d3.event.ctrlKey) {
point = d3.mouse(this);
d3.event.stopPropagation();
var mainPos = $("#main-container").position();
if (mouse_mode !== RED.state.QUICK_JOINING) {
mouse_mode = RED.state.QUICK_JOINING;
$(window).on('keyup',disableQuickJoinEventHandler);
}
RED.typeSearch.show({
x:d3.event.clientX-mainPos.left-node_width/2,
y:d3.event.clientY-mainPos.top-node_height/2,
add: function(type) {
var result = addNode(type);
if (!result) {
return;
}
var nn = result.node;
var historyEvent = result.historyEvent;
nn.x = point[0];
nn.y = point[1];
if (mouse_mode === RED.state.QUICK_JOINING) {
if (drag_lines.length > 0) {
var drag_line = drag_lines[0];
var src = null,dst,src_port;
if (drag_line.portType === 0 && nn.inputs > 0) {
src = drag_line.node;
src_port = drag_line.port;
dst = nn;
} else if (drag_line.portType === 1 && nn.outputs > 0) {
src = nn;
dst = drag_line.node;
src_port = 0;
}
if (src !== null) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
historyEvent.links = [link];
hideDragLines();
if (drag_line.portType === 0 && nn.outputs > 0) {
showDragLines([{node:nn,port:0,portType:0}]);
} else if (drag_line.portType === 1 && nn.inputs > 0) {
showDragLines([{node:nn,port:0,portType:1}]);
} else {
resetMouseVars();
}
} else {
hideDragLines();
resetMouseVars();
}
} else {
if (nn.outputs > 0) {
showDragLines([{node:nn,port:0,portType:0}]);
} else if (nn.inputs > 0) {
showDragLines([{node:nn,port:0,portType:1}]);
} else {
resetMouseVars();
}
}
}
RED.history.push(historyEvent);
RED.nodes.add(nn);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
moving_set.push({n:nn});
updateActiveNodes();
updateSelection();
redraw();
}
});
updateActiveNodes();
updateSelection();
redraw();
}
}
if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
if (!touchStartTime) {
var point = d3.mouse(this);
point = d3.mouse(this);
lasso = vis.append("rect")
.attr("ox",point[0])
.attr("oy",point[1])
.attr("rx",1)
.attr("ry",1)
.attr("x",point[0])
.attr("y",point[1])
.attr("width",0)
.attr("height",0)
.attr("class","lasso");
.attr("ox",point[0])
.attr("oy",point[1])
.attr("rx",1)
.attr("ry",1)
.attr("x",point[0])
.attr("y",point[1])
.attr("width",0)
.attr("height",0)
.attr("class","lasso");
d3.event.preventDefault();
}
}
@ -530,12 +658,12 @@ RED.view = (function() {
return;
}
if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
return;
}
var mousePos;
if (mouse_mode == RED.state.JOINING) {
if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
// update drag line
if (drag_lines.length === 0) {
if (d3.event.shiftKey) {
@ -576,12 +704,17 @@ RED.view = (function() {
portType: (mousedown_port_type===0)?1:0
})
}
showDragLines(links);
mouse_mode = 0;
updateActiveNodes();
redraw();
mouse_mode = RED.state.JOINING;
} else {
if (links.length === 0) {
resetMouseVars();
redraw();
} else {
showDragLines(links);
mouse_mode = 0;
updateActiveNodes();
redraw();
mouse_mode = RED.state.JOINING;
}
} else if (mousedown_node) {
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
}
selected_link = null;
@ -748,6 +881,9 @@ RED.view = (function() {
function canvasMouseUp() {
var i;
var historyEvent;
if (mouse_mode === RED.state.QUICK_JOINING) {
return;
}
if (mousedown_node && mouse_mode == RED.state.JOINING) {
var removedLinks = [];
for (i=0;i<drag_lines.length;i++) {
@ -843,7 +979,7 @@ RED.view = (function() {
}
}
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
RED.keyboard.remove(/* ESCAPE */ 27);
RED.keyboard.remove("escape");
updateActiveNodes();
RED.nodes.dirty(true);
}
@ -987,6 +1123,7 @@ RED.view = (function() {
}
function endKeyboardMove() {
endMoveSet = false;
if (moving_set.length > 0) {
var ns = [];
for (var i=0;i<moving_set.length;i++) {
@ -1001,14 +1138,21 @@ RED.view = (function() {
RED.nodes.dirty(true);
}
}
var endMoveSet = false;
function moveSelection(dx,dy) {
if (moving_set.length > 0) {
if (!endMoveSet) {
$(document).one('keyup',endKeyboardMove);
endMoveSet = true;
}
var minX = 0;
var minY = 0;
var node;
for (var i=0;i<moving_set.length;i++) {
node = moving_set[i];
node.n.changed = true;
node.n.dirty = true;
if (node.ox == null && node.oy == null) {
node.ox = node.n.x;
node.oy = node.n.y;
@ -1031,6 +1175,16 @@ RED.view = (function() {
redraw();
}
}
function editSelection() {
if (moving_set.length > 0) {
var node = moving_set[0].n;
if (node.type === "subflow") {
RED.editor.editSubflow(activeSubflow);
} else {
RED.editor.edit(node);
}
}
}
function deleteSelection() {
if (moving_set.length > 0 || selected_link != null) {
var result;
@ -1165,22 +1319,45 @@ RED.view = (function() {
}
}
function disableQuickJoinEventHandler(evt) {
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
resetMouseVars();
hideDragLines();
redraw();
$(window).off('keyup',disableQuickJoinEventHandler);
}
}
function portMouseDown(d,portType,portIndex) {
//console.log(d,portType,portIndex);
// disable zoom
//vis.call(d3.behavior.zoom().on("zoom"), null);
mousedown_node = d;
mouse_mode = RED.state.JOINING;
mousedown_port_type = portType;
mousedown_port_index = portIndex || 0;
document.body.style.cursor = "crosshair";
if (mouse_mode !== RED.state.QUICK_JOINING) {
mouse_mode = RED.state.JOINING;
document.body.style.cursor = "crosshair";
if (d3.event.ctrlKey || d3.event.metaKey) {
mouse_mode = RED.state.QUICK_JOINING;
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
$(window).on('keyup',disableQuickJoinEventHandler);
}
}
d3.event.stopPropagation();
d3.event.preventDefault();
}
function portMouseUp(d,portType,portIndex) {
var i;
if (mouse_mode === RED.state.QUICK_JOINING) {
if (drag_lines[0].node===d) {
return
}
}
document.body.style.cursor = "";
if (mouse_mode == RED.state.JOINING && drag_lines.length > 0) {
if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) {
if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
RED.nodes.eachNode(function(n) {
if (n.z == RED.workspaces.active()) {
@ -1247,6 +1424,21 @@ RED.view = (function() {
updateActiveNodes();
RED.nodes.dirty(true);
}
if (mouse_mode === RED.state.QUICK_JOINING) {
if (addedLinks.length > 0) {
hideDragLines();
if (portType === 1 && d.outputs > 0) {
showDragLines([{node:d,port:0,portType:0}]);
} else if (portType === 0 && d.inputs > 0) {
showDragLines([{node:d,port:0,portType:1}]);
} else {
resetMouseVars();
}
}
redraw();
return;
}
resetMouseVars();
hideDragLines();
selected_link = null;
@ -1276,7 +1468,7 @@ RED.view = (function() {
//var pos = [touch0.pageX,touch0.pageY];
//RED.touch.radialMenu.show(d3.select(this),pos);
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
RED.keyboard.remove(/* ESCAPE */ 27);
RED.keyboard.remove("escape");
if (activeSpliceLink) {
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@ -1306,6 +1498,9 @@ RED.view = (function() {
resetMouseVars();
d3.event.stopPropagation();
return;
} else if (mouse_mode == RED.state.QUICK_JOINING) {
d3.event.stopPropagation();
return;
}
mousedown_node = d;
var now = Date.now();
@ -1623,14 +1818,14 @@ RED.view = (function() {
nodeMouseUp.call(this,d);
})
.on("mouseover",function(d) {
if (mouse_mode === 0) {
var node = d3.select(this);
node.classed("node_hovered",true);
}
if (mouse_mode === 0) {
var node = d3.select(this);
node.classed("node_hovered",true);
}
})
.on("mouseout",function(d) {
var node = d3.select(this);
node.classed("node_hovered",false);
var node = d3.select(this);
node.classed("node_hovered",false);
});
//node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none");
@ -2270,8 +2465,8 @@ RED.view = (function() {
node.n._def.outputs > 0;
}
}
RED.keyboard.add("*",/* ESCAPE */ 27,function(){
RED.keyboard.remove(/* ESCAPE */ 27);
RED.keyboard.add("*","escape",function(){
RED.keyboard.remove("escape");
clearSelection();
RED.history.pop();
mouse_mode = 0;
@ -2316,6 +2511,24 @@ RED.view = (function() {
}
}
function toggleShowGrid(state) {
if (state) {
grid.style("visibility","visible");
} else {
grid.style("visibility","hidden");
}
}
function toggleSnapGrid(state) {
snapGrid = state;
redraw();
}
function toggleStatus(s) {
showStatus = s;
RED.nodes.eachNode(function(n) { n.dirty = true;});
//TODO: subscribe/unsubscribe here
redraw();
}
return {
init: init,
state:function(state) {
@ -2335,16 +2548,6 @@ RED.view = (function() {
},
focus: focusView,
importNodes: importNodes,
status: function(s) {
if (s == null) {
return showStatus;
} else {
showStatus = s;
RED.nodes.eachNode(function(n) { n.dirty = true;});
//TODO: subscribe/unsubscribe here
redraw();
}
},
calculateTextWidth: calculateTextWidth,
select: function(selection) {
if (typeof selection !== "undefined") {
@ -2371,17 +2574,6 @@ RED.view = (function() {
}
return selection;
},
toggleShowGrid: function(state) {
if (state) {
grid.style("visibility","visible");
} else {
grid.style("visibility","hidden");
}
},
toggleSnapGrid: function(state) {
snapGrid = state;
redraw();
},
scale: function() {
return scaleFactor;
},
@ -2405,7 +2597,6 @@ RED.view = (function() {
node.highlighted = true;
node.dirty = true;
RED.workspaces.show(node.z);
RED.view.redraw();
var screenSize = [$("#chart").width(),$("#chart").height()];
var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
@ -2419,17 +2610,23 @@ RED.view = (function() {
},200);
}
var flash = 22;
var flashFunc = function() {
flash--;
node.highlighted = !node.highlighted;
node.dirty = true;
RED.view.redraw();
if (flash >= 0) {
setTimeout(flashFunc,100);
if (!node._flashing) {
node._flashing = true;
var flash = 22;
var flashFunc = function() {
flash--;
node.dirty = true;
if (flash >= 0) {
node.highlighted = !node.highlighted;
setTimeout(flashFunc,100);
} else {
node.highlighted = false;
delete node._flashing;
}
RED.view.redraw();
}
flashFunc();
}
flashFunc();
} else if (node._def.category === 'config') {
RED.sidebar.config.show(id);
}

View File

@ -170,6 +170,9 @@ RED.workspaces = (function() {
createWorkspaceTabs();
RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.actions.add("core:show-next-tab",workspace_tabs.nextTab);
RED.actions.add("core:show-previous-tab",workspace_tabs.previousTab);
RED.menu.setAction('menu-item-workspace-delete',function() {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
});
@ -177,6 +180,14 @@ RED.workspaces = (function() {
$(window).resize(function() {
workspace_tabs.resize();
});
RED.actions.add("core:add-flow",addWorkspace);
RED.actions.add("core:edit-flow",editWorkspace);
RED.actions.add("core:remove-flow",removeWorkspace);
}
function editWorkspace(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
}
function removeWorkspace(ws) {
@ -201,9 +212,7 @@ RED.workspaces = (function() {
add: addWorkspace,
remove: removeWorkspace,
order: setWorkspaceOrder,
edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
},
edit: editWorkspace,
contains: function(id) {
return workspace_tabs.contains(id);
},

8
editor/sass/ace.scss Normal file
View File

@ -0,0 +1,8 @@
.ace_gutter {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ace_scroller {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}

View File

@ -58,7 +58,7 @@ $workspace-button-color-focus-outline: rgba(85,150,230,0.2);
$typedInput-button-background: #efefef;
$typedInput-button-background-hover: #ddd;
$typedInput-button-background-active: #e3e3e3;
$typedInput-button-background-active: #ddd;
$editor-button-color-primary: #eee;
$editor-button-background-primary: #AD1625;

185
editor/sass/debug.scss Normal file
View File

@ -0,0 +1,185 @@
/**
* Copyright 2016 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.
**/
.debug-window {
padding:0;
margin:0;
background: #fff;
line-height: 20px;
}
.debug-window .debug-message-payload {
font-size: 14px;
}
.debug-content {
position: absolute;
top: 43px;
bottom: 0px;
left:0px;
right: 0px;
overflow-y: scroll;
}
.debug-filter-box {
position:absolute;
top: 42px;
left: 0px;
right: 0px;
background: #f9f9f9;
padding: 10px;
border-bottom: 1px solid #ddd;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.debug-filter-row {
text-align: right;
}
.debug-message {
border-bottom: 1px solid #eee;
border-left: 8px solid #eee;
border-right: 8px solid #eee;
padding: 2px;
}
.debug-message-meta {
background: #fff;
font-size: 10px;
color: #777;
}
.debug-message-date {
padding: 1px 5px 1px 1px;
}
.debug-message-topic {
display: block;
color: #a66;
}
.debug-message-name {
padding: 1px 5px;
color: #777;
}
.debug-message-payload {
display: block;
padding: 2px;
background: #fff;
}
.debug-message-level-log {
border-left-color: #eee;
border-right-color: #eee;
}
.debug-message-level-30 {
border-left-color: #ffdf9d;
border-right-color: #ffdf9d;
}
.debug-message-level-20 {
border-left-color: #f99;
border-right-color: #f99;
}
.debug-message-object-entry {
padding-left: 15px;
padding-bottom: 3px;
padding-top: 3px;
}
.debug-message-element {
color: #333;
font-family: Menlo, monospace;
font-size: 12px !important;
line-height: 1.3em;
}
.debug-message-object-key {
color: #792e90;
}
.debug-message-object-value {
}
.debug-message-object-handle {
color: #666;
font-size: 1em;
width: 1em;
text-align: center;
transition: transform 0.1s ease-in-out;
transform: rotate(90deg);
}
.debug-message-element:not(.debug-message-top-level)>.debug-message-expandable>.debug-message-object-handle {
margin-left: -1em;
}
.debug-message-object-entry>.debug-message-expandable>.debug-message-object-handle {
margin-left: -1em;
}
.debug-message-object-entry.collapsed>span>.debug-message-object-handle {
transform: rotate(0deg);
}
.debug-message-element.collapsed>span>.debug-message-object-handle {
transform: rotate(0deg);
}
.debug-message-object-entry.collapsed > .debug-message-object-entry {
display:none;
}
.debug-message-element.collapsed .debug-message-object-entry {
display:none;
}
.debug-message-element:not(.collapsed)>.debug-message-expandable>.debug-message-object-value>.debug-message-object-header {
display:none;
}
.debug-message-element.collapsed .debug-message-buffer-opts {
display: none;
}
.debug-message-element.collapsed .debug-message-object-type-header {
display:none;
}
.debug-message-object-entry pre {
font-family: Menlo, monospace;
font-size: 12px;
line-height: 1.4em;
margin: 0 0 0 -1em;
}
.debug-message-type-other { color: #2033d6; }
.debug-message-type-string { color: #b72828; }
.debug-message-type-null { color: #666; font-style: italic;}
.debug-message-type-meta { color: #666; font-style: italic;}
.debug-message-type-number { color: #2033d6; };
.debug-message-type-number-toggle { cursor: pointer;}
.debug-message-expandable {
cursor: pointer;
}
.debug-message-expandable:hover {
background: #fefefe;
}
.debug-message-expandable:hover .debug-message-object-handle {
color: #b72828 !important;
}
.debug-message-buffer-opts a {
font-size: 9px;
color: #bbb;
border: 1px solid #bbb;
border-radius: 2px;
padding: 2px 5px;
margin-left: 5px;
}
.debug-message-buffer-opts a:hover {
text-decoration: none;
color: #999;
border: 1px solid #999;
background: #f3f3f3;
}
.debug-message-buffer-raw > .debug-message-string-rows {
display: none;
}
.debug-message-buffer-string > .debug-message-array-rows {
display: none;
}

View File

@ -21,30 +21,67 @@
.red-ui-editableList-container {
border-radius:1px;
padding:0;
background: #f9f9f9;
}
ol {
#node-dialog-view-diff-diff {
position: absolute;
top:10px;
top:80px;
bottom:10px;
left:10px;
right:10px;
li {
background: #f9f9f9;
padding: 0px;
border: none;
min-height: 0;
}
}
.red-ui-editableList-item-content {
padding: 5px;
// padding-bottom: 5px;
}
}
#node-dialog-view-diff-headers {
position: absolute;
left:17px;
right:32px;
top: 55px;
height: 25px;
.node-diff-node-entry-cell:not(:first-child) {
background: #f9f9f9;
text-align: center;
border-top: 1px solid $secondary-border-color;
border-color:$secondary-border-color;
}
.node-diff-node-entry-cell:last-child {
border-right: 1px solid $secondary-border-color;
}
}
.node-diff-toolbar {
position:absolute;
top:0;
left:0;
right:0;
height: 43px;
box-sizing: border-box;
color: #666;
text-align: right;
padding: 8px 10px;
background: #f3f3f3;
border-bottom: 1px solid $secondary-border-color;
white-space: nowrap;
}
.node-diff-tab {
border: 1px solid $secondary-border-color;
border-radius: 3px;
background: #fff;
border: 1px solid #ddd;
border-radius: 1px;
overflow: hidden;
&.collapsed {
.node-diff-tab-title > .node-diff-chevron {
.node-diff-tab-title .node-diff-chevron {
transform: rotate(-90deg);
}
.node-diff-node-entry {
@ -53,21 +90,26 @@
}
}
.node-diff-tab-stats {
position: absolute;
left: 50%;
font-size: 0.9em;
}
.node-diff-chevron {
display: inline-block;
width: 15px;
text-align: center;
margin: 3px 5px 3px 5px;
margin-left: 3px;
transition: transform 0.1s ease-in-out;
}
.node-diff-node-entry {
padding: 0 0 0 5px;
margin-left: 20px;
font-size: 0.9em;
&:first-child {
border-top: 1px solid #eee;
}
&:not(:last-child) {
border-bottom: 1px solid $secondary-border-color;
border-bottom: 1px solid #eee;
}
&.collapsed {
@ -78,25 +120,80 @@
display: none;
}
}
&:not(.collapsed) {
.node-diff-node-entry-cell:not(:first-child) {
//display: none;
}
.node-diff-node-entry-cell:first-child {
//width: 100%
}
}
table {
border-collapse: collapse;
width: 100%;
table-layout:fixed;
border-collapse: collapse;
table-layout:fixed;
// Fix for table-layout: fixed on safari:
max-width: none;
width: auto;
min-width: 100%;
}
td, th {
border: 1px solid $secondary-border-color;
padding: 3px 5px;
padding: 0 0 0 3px;
text-align: left;
overflow-x: auto;
}
td:nth-child(1) {
width: 150px;
tr {
vertical-align: top;
&:first-child td {
white-space:nowrap;
overflow:hidden;
}
}
td:first-child {
width: 140px;
}
td:not(:first-child) {
width: calc(50% - 150px);
width: calc( 100% - 140px);
}
td {
.node-diff-status {
margin-left: 0;
}
}
tr:not(.node-diff-property-header) {
.node-diff-status {
width: 12px;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
margin-right: 5px;
}
}
}
.node-diff-three-way {
.node-diff-node-entry-cell {
width: calc((100% - 220px) / 2);
&:first-child {
width: 220px;
}
}
td:not(:first-child) {
width: calc( (100% - 140px) / 2);
}
.node-diff-node-entry {
.node-diff-node-entry-cell {
width: calc((100% + 20px - 220px) / 2);
&:first-child {
width: 200px;
}
}
}
}
.node-diff-column {
display:inline-block;
height:100%;
@ -108,18 +205,26 @@
border-right: 1px solid $secondary-border-color
}
}
.node-diff-tab-title {
padding: 3px 3px 3px 0;
background: #f6f6f6;
cursor: pointer;
padding: 0;
// background: #f6f6f6;
}
.node-diff-tab-title-meta {
vertical-align: middle;
display: inline-block;
padding-top: 2px;
}
.node-diff-node-entry-header {
cursor: pointer;
}
.node-diff-node-entry-node {
vertical-align: middle;
display: inline-block;
margin: 5px;
width: 24px;
height: 20px;
width: 18px;
height: 15px;
background: #ddd;
border-radius: 2px;
border: 1px solid #999;
@ -128,25 +233,89 @@
background-size: contain;
position: relative;
.palette-icon {
width: 16px;
.palette_icon {
background-position: 49% 50%;
width: 15px;
}
.palette_icon_container {
width: 24px;
width: 18px;
}
}
.node-diff-tab-empty {
.node-diff-chevron i {
display: none;
}
.node-diff-tab-title {
cursor: default;
}
}
.node-diff-node-deleted {
//background: #fadddd;
cursor: default !important;
.node-diff-status {
color: #f80000;
}
.node-diff-node-entry-node {
opacity: 0.5;
}
.node-diff-node-description {
opacity: 0.5;
text-decoration: line-through;
}
}
.node-diff-node-added {
//background: #eefaee;
cursor: default !important;
.node-diff-status {
color: #009900;
}
}
.node-diff-node-moved {
//background: #eefaee;
.node-diff-status {
color: #3f81b3;
}
}
.node-diff-node-changed {
//background: #fff2ca;
.node-diff-status {
color: #f89406;
}
}
.node-diff-node-unchanged {
//background: #fff2ca;
.node-diff-status {
color: #bbb;
}
}
.node-diff-node-conflict {
.node-diff-status {
color: #9b45ce;
}
}
.node-diff-node-entry-title {
cursor: pointer;
display: inline-block;
.node-diff-status {
margin-left: 15px;
}
}
.node-diff-node-entry-properties {
margin-left: 30px;
margin-right: 8px;
margin-bottom:8px;
margin: 5px ;
color: #666;
}
.node-diff-status {
display: inline-block;
height: 20px;
margin-left: 5px;
vertical-align: top;
margin-top: 6px;
margin-bottom: 6px;
text-align: center;
}
.node-diff-node-description {
color: $form-text-color;
margin-left: 5px;
margin-right: 5px;
padding-top: 5px;
display: inline-block;
@ -156,10 +325,192 @@
clear: both;
}
}
.node-diff-node-meta {
float: right;
//font-size: 0.9em;
color: #999;
margin-top: 7px;
margin-right: 10px;
}
.node-diff-count { color: #999}
.node-diff-added { color: #009900}
.node-diff-deleted { color: #f80000}
.node-diff-changed { color: #f89406}
.node-diff-conflicted { color: purple}
.node-diff-node-entry-cell {
display: inline-block;
vertical-align: top;
box-sizing: border-box;
width: calc( (100% - 20px) / 2);
height: 32px;
border-left: 1px solid #eee;
padding-top: 2px;
white-space: nowrap;
overflow: hidden;
position: relative;
}
.node-diff-empty {
background: #f3f3f3;
background: repeating-linear-gradient(
20deg,
#fff, #fff 5px,
#f6f6f6 5px,
#f6f6f6 10px
);
}
.node-diff-node-entry-cell:first-child {
border-left: none;
}
.node-diff-property-cell-label {
margin-left: 20px;
vertical-align: top;
box-sizing: border-box;
padding-left: 8px;
width: 120px;
}
.node-diff-property-wires {
display: inline-block;
.node-diff-node-entry-node {
width: 18px;
height: 15px;
}
.palette_icon_container {
width: 18px;
}
.palette_icon {
width: 15px;
}
ul,li,ol {
background: none !important;
}
ul {
vertical-align: middle;
display: inline-block;
margin-left: 5px;
}
li {
list-style-type: none !important;
}
ol {
font-size: 0.9em;
margin: 0;
& > span {
vertical-align: middle;
display: inline-block;
width: 30px;
text-align: center;
}
& > li:not(:last-child) {
border-bottom: 1px solid #999;
}
}
}
.node-diff-node-props .node-diff-node-entry-cell:first-child {
padding: 6px 0px;
span:not(.node-diff-chevron) {
margin-left: 5px;
}
}
.node-diff-property-cell {
// vertical-align: top;
// display:inline-block;
//
// box-sizing: border-box;
// padding: 1px 5px;
//min-height: 30px;
&.node-diff-node-changed {
background: #fff2e1 !important;
}
&.node-diff-node-conflict {
background: #ffdad4 !important;
}
}
.node-diff-selectbox {
position: absolute;
top:0;
right:0;
bottom:0;
width: 35px;
text-align: center;
border-left: 1px solid #eee;
margin:0;
input {
margin-top: 8px;
}
&:hover {
background: #f3f3f3;
}
}
.node-diff-node-entry-conflict.node-diff-select-remote {
.node-diff-node-remote {
background: #e7ffe3;
label {
border-left-color: #b8daad;
}
}
.node-diff-node-local {
background: #ffe1e1;
label {
border-left-color: #e4bcbc;
}
}
}
.node-diff-node-entry-conflict.node-diff-select-local {
.node-diff-node-local {
background: #e7ffe3;
label {
border-left-color: #b8daad;
}
}
.node-diff-node-remote {
background: #ffe1e1;
label {
border-left-color: #e4bcbc;
}
}
}
#node-dialog-confirm-deploy {
.node-dialog-confirm-row {
text-align: left; padding-top: 10px;
}
ul {
font-size: 0.9em;
width: 400px;
margin: 10px auto;
text-align: left;
}
.node-dialog-confirm-conflict-row {
img {
vertical-align:middle;
height: 30px;
margin-right: 10px;
}
i {
vertical-align:middle;
text-align: center;
font-size: 30px;
width: 30px;
margin-right: 10px;
}
div {
vertical-align: middle;
width: calc(100% - 60px);
display:inline-block;
}
}
}
#node-diff-toolbar-resolved-conflicts .node-diff-status {
margin:0;
}

View File

@ -168,12 +168,7 @@
color: $workspace-button-color;
}
#palette-shade, #editor-shade, #header-shade, #sidebar-shade {
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background: $shade-color;
@include shade;
z-index: 2;
}
#sidebar-shade {

View File

@ -170,6 +170,11 @@
stroke-dasharray:8, 3;
}
.node_quickadd * {
stroke-dasharray: 12,3;
}
.node_status_label {
@include disable-selection;
stroke-width: 0;
@ -183,6 +188,13 @@
stroke: $port-selected-color;
fill: $port-selected-color;
}
.port_quick_link {
stroke: $port-selected-color;
fill: $port-selected-color;
}
.subflowport {
stroke-dasharray: 5,5;
fill: #eee;

View File

@ -37,4 +37,6 @@
font-family: Courier, monospace;
box-shadow: #999 1px 1px 1px;
}
.help-key-block {
white-space: nowrap;
}

View File

@ -70,7 +70,6 @@
&.selected:not(.disabled) {
color: $workspace-button-color-selected !important;
background: $workspace-button-background-active;
cursor: default;
}
.button-group &:not(:first-child) {
border-left: none;
@ -86,6 +85,12 @@
outline: 1px solid $workspace-button-color-focus-outline;
}
}
.button-group:not(:last-child) {
margin-right: 10px;
}
@mixin workspace-button-toggle {
@include workspace-button;
color: $workspace-button-toggle-color !important;
@ -97,6 +102,7 @@
border-bottom-width: 2px;
border-bottom-color: $form-input-border-selected-color;
margin-bottom: 0;
cursor: default;
}
&.disabled {
color: $workspace-button-toggle-color-disabled !important;
@ -115,6 +121,12 @@
height: 25px;
line-height: 23px;
padding: 0 10px;
.button-group:not(:last-child) {
margin-right: 5px;
}
}
@mixin component-footer-button {
@ -128,9 +140,28 @@
padding: 0 5px;
}
}
@mixin component-footer-button-toggle {
@include workspace-button-toggle;
font-size: 11px;
line-height: 17px;
height: 18px;
&.text-button {
width: auto;
padding: 0 5px;
}
}
@mixin component-shadow {
border: 1px solid $secondary-border-color;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
}
@mixin shade {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: $shade-color;
}

View File

@ -72,10 +72,6 @@
padding: 8px 10px;
border-bottom: 1px solid $primary-border-color;
text-align: right;
.button-group {
margin-right: 10px;
}
}
.palette-module-button-group {
position: absolute;
@ -86,12 +82,7 @@
}
}
.palette-module-shade {
position: absolute;
top: 0;
bottom:0;
left:0;
right:0;
background: $shade-color;
@include shade;
text-align: center;
padding-top: 20px;
}

View File

@ -29,6 +29,77 @@
ol {
}
}
.red-ui-type-search-shade {
@include shade;
z-index: 20;
position: fixed;
background: rgba(255,255,255,0.05);
}
.red-ui-type-search {
box-shadow: 0 1px 6px -3px black;
background: none;
width: 300px;
margin-left: 0px;
//height: 75px;
border: none;
.red-ui-search-container {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border: 1px dashed #aaa;
border-bottom: none;
padding: 0;
}
.red-ui-search-results-container {
display: none;
height: 150px;
.red-ui-editableList-container {
border: 1px dashed #aaa;
border-top: 1px solid #ccc;
}
}
.red-ui-search-result {
padding: 2px 2px 2px 5px;
font-size: 13px;
border-left-width: 3px;
border-right-width: 3px;
}
.red-ui-search-result-separator {
border-bottom: 3px solid #ddd;
}
.red-ui-search-result-node {
position: relative;
width: 18px;
height: 15px;
margin-top: 1px;
}
.red-ui-search-result-node-port {
position: absolute;
border-radius: 2px;
border: 1px solid #999;
width: 6px;
height: 7px;
top:4px;
left:-4px;
background: #eee;
box-sizing: border-box;
}
.red-ui-search-result-node-output{
left: 16px;
}
.palette_icon_container {
width: 18px;
}
.palette_icon {
width: 15px;
}
.red-ui-search-result-description {
margin-left:28px;
}
.red-ui-search-result-node-label {
color: #999;
}
}
.red-ui-search-container {
padding: 3px;
border-bottom: 1px solid $secondary-border-color;
@ -37,19 +108,21 @@
position:relative;
height: 300px;
padding: 5px;
background: $background-color;
background: #f9f9f9;
.red-ui-editableList-container {
background: white;
border-radius: 2px;
padding: 0;
background: $background-color;
background: #f9f9f9;
li {
padding: 0;
&.selected .red-ui-search-result {
border-color: $primary-border-color;
&.selected {
background: #efefef;
.red-ui-search-result {
border-left-color:#999;
border-right-color:#999;
}
}
}
}
}
@ -58,11 +131,19 @@
display: block;
cursor: pointer;
color: $form-text-color;
border: 2px solid white;
border-left: 3px solid #fff;
border-right: 3px solid #fff;
&:hover {
text-decoration: none;
border-color: $primary-border-color;
color: $form-text-color;
background: #efefef;
border-left-color:#efefef;
border-right-color:#efefef;
}
&:after {
content: "";
display: table;
clear: both;
}
}
@ -78,6 +159,11 @@
background-repeat: no-repeat;
background-size: contain;
position: relative;
.palette_icon_container {
border-right: none;
}
}
.red-ui-search-result-description {
margin-left: 40px;

View File

@ -71,6 +71,7 @@
padding: 8px 10px;
background: #f3f3f3;
border-bottom: 1px solid $secondary-border-color;
white-space: nowrap;
}
#sidebar-footer {
@ -80,6 +81,9 @@
.sidebar-footer-button {
@include component-footer-button;
}
.sidebar-footer-button-toggle {
@include component-footer-button-toggle;
}
.sidebar-header-button {
@include workspace-button;
font-size: 13px;
@ -97,10 +101,5 @@
}
.sidebar-shade {
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background: $shade-color;
@include shade;
}

View File

@ -21,6 +21,7 @@
@import "jquery";
@import "bootstrap";
@import "ace";
@import "dropdownMenu";
@ -53,6 +54,7 @@
@import "keyboard";
@import "debug";
body {
font-size: 14px;
@ -119,3 +121,7 @@ pre code {
background-color: transparent;
border: 0;
}
.hide {
display: none;
}

View File

@ -35,7 +35,7 @@ table.node-info tr.blank td {
padding-left: 0px;
}
table.node-info td:first-child{
color: #000;
color: #666;
vertical-align: top;
width: 90px;
padding: 3px;
@ -81,3 +81,18 @@ div.node-info {
margin: 8px auto;
}
}
.node-info-tip {
position: absolute;
top: 40%;
left:0;
right:0;
padding: 20px;
font-size: 16px;
text-align: center;
line-height: 1.9em;
color : #bbb;
background-color: #fff;
@include disable-selection;
cursor: default;
}

View File

@ -49,7 +49,10 @@
border-color: $form-input-focus-color !important;
}
a {
button {
text-align: left;
border: none;
position: absolute;
box-sizing: border-box;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
@ -66,6 +69,9 @@
margin-right:4px;
margin-top: 1px;
vertical-align: middle;
&.fa-ellipsis-h {
top: -1px;
}
}
&.disabled {
cursor: default;
@ -82,44 +88,65 @@
&:not(.disabled):hover {
text-decoration: none;
background: $typedInput-button-background-hover;
span {
background: $typedInput-button-background-hover;
}
}
&:focus {
text-decoration: none;
outline: none;
box-shadow: inset 0 0 0 1px $form-input-focus-color;
}
&:not(.disabled):active {
background: $typedInput-button-background-active;
text-decoration: none;
}
&.red-ui-typedInput-full-width {
width: 100%;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
a.red-ui-typedInput-option-trigger {
button.red-ui-typedInput-option-expand {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
right: 0;
}
button.red-ui-typedInput-option-trigger {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 5px 0 0;
padding: 0 0 0 0;
position:absolute;
left:0;
top:0;
bottom:0;
right:0;
width: calc( 100% );
i {
position:absolute;
right: 4px;
top: 7px;
}
span {
.red-ui-typedInput-option-label {
background:#fff;
position:absolute;
left:0;
right:23px;
top: 0;
padding: 0 5px 0 5px;
}
.red-ui-typedInput-option-caret {
top: 0;
position: absolute;
right: 0;
width: 17px;
}
&:focus {
box-shadow: none;
}
&:focus .red-ui-typedInput-option-caret {
box-shadow: inset 0 0 0 1px $form-input-focus-color;
}
}
}
.red-ui-typedInput-options {
@ -139,12 +166,12 @@
}
&:focus {
text-decoration: none;
background: $typedInput-button-background-active;
outline: none;
}
&:active {
background: $typedInput-button-background-active;
text-decoration: none;
background: $typedInput-button-background-active;
}
}
}

View File

@ -87,19 +87,28 @@
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-invalid-list"></ul>
<div id="node-dialog-confirm-deploy-config" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
<ul id="node-dialog-confirm-deploy-invalid-list"></ul>
</div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
<div id="node-dialog-confirm-deploy-unknown" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul id="node-dialog-confirm-deploy-unknown-list"></ul>
</div>
<div id="node-dialog-confirm-deploy-conflict" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.conflict;[append]deploy.confirm.confirm">
<div id="node-dialog-confirm-deploy-conflict" class="node-dialog-confirm-row">
<div style="margin-left: 40px; margin-bottom: 10px;">
<span data-i18n="deploy.confirm.conflict"></span>
</div>
<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">
<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>
</div>
<div id="node-dialog-confirm-deploy-conflict-auto-merge" class="node-dialog-confirm-conflict-row">
<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>
</div>
<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">
<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>
</div>
</div>
</form>
</div>
<div id="node-dialog-view-diff" class="hide">
<ol id="node-dialog-view-diff-diff"></ol>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
@ -159,10 +168,25 @@
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</script>
<script type="text/x-red" data-template-name="_expression">
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-expression"></div>
</div>
<div class="form-row">
<label for="node-input-expression-func" data-i18n="expressionEditor.functions"></label>
<select id="node-input-expression-func"></select>
<button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button>
<div style="min-height: 200px;" id="node-input-expression-help"></div>
</div>
</script>
<script src="vendor/vendor.js"></script>
<script src="vendor/jsonata/jsonata.min.js"></script>
<script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script>
<script src="{{ asset.red }}"></script>
<script src="{{ asset.main }}"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 434 KiB

147
editor/vendor/jsonata/formatter.js vendored Normal file
View File

@ -0,0 +1,147 @@
(function() {
function indentLine(str,length) {
if (length <= 0) {
return str;
}
var i = (new Array(length)).join(" ");
str = str.replace(/^\s*/,i);
return str;
}
function formatExpression(str) {
var length = str.length;
var start = 0;
var inString = false;
var inBox = false;
var quoteChar;
var list = [];
var stack = [];
var frame;
var v;
var matchingBrackets = {
"(":")",
"[":"]",
"{":"}"
}
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
inString = true;
quoteChar = c;
frame = {type:"string",pos:i};
list.push(frame);
stack.push(frame);
} else if (c === ";") {
frame = {type:";",pos:i};
list.push(frame);
} else if (c === ",") {
frame = {type:",",pos:i};
list.push(frame);
} else if (/[\(\[\{]/.test(c)) {
frame = {type:"open-block",char:c,pos:i};
list.push(frame);
stack.push(frame);
} else if (/[\}\)\]]/.test(c)) {
var oldFrame = stack.pop();
if (matchingBrackets[oldFrame.char] !== c) {
//console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
return str;
}
//console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos);
oldFrame.width = i-oldFrame.pos;
frame = {type:"close-block",pos:i,char:c,width:oldFrame.width}
list.push(frame);
}
} else {
if (c === quoteChar) {
// Next char must be a ]
inString = false;
stack.pop();
}
}
}
// console.log(stack);
var result = str;
var indent = 0;
var offset = 0;
var pre,post,indented;
var longStack = [];
list.forEach(function(f) {
if (f.type === ";" || f.type === ",") {
if (longStack[longStack.length-1]) {
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
}
} else if (f.type === "open-block") {
if (f.width > 30) {
longStack.push(true);
indent += 4;
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
} else {
longStack.push(false);
}
} else if (f.type === "close-block") {
if (f.width > 30) {
indent -= 4;
pre = result.substring(0,offset+f.pos);
post = result.substring(offset+f.pos);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
}
longStack.pop();
}
})
//console.log(result);
return result;
}
jsonata.format = formatExpression;
jsonata.functions =
{
'$append':{ args:['array','array'] },
'$average':{ args:['value'] },
'$boolean':{ args:['value'] },
'$count':{ args:['array'] },
'$exists':{ args:['value'] },
'$join':{ args:['array','separator'] },
'$keys':{ args:['object'] },
'$length':{ args:['string'] },
'$lookup':{ args:['object','key'] },
'$lowercase':{ args:['string'] },
'$map':{ args:[] },
'$max':{ args:['array'] },
'$min':{ args:['array'] },
'$not':{ args:['value'] },
'$number':{ args:['value'] },
'$reduce':{ args:[] },
'$split':{ args:['string','separator','limit'] },
'$spread':{ args:['object'] },
'$string':{ args:['value'] },
'$substring':{ args:['string','start','length'] },
'$substringAfter':{ args:['string','chars'] },
'$substringBefore':{ args:['string','chars'] },
'$sum':{ args:['array'] },
'$uppercase':{ args:['string'] }
}
jsonata.getFunctionSnippet = function(fn) {
var snippetText = "";
if (jsonata.functions.hasOwnProperty(fn)) {
var def = jsonata.functions[fn];
snippetText = "\\"+fn+"(";
if (def.args) {
snippetText += def.args.map(function(a,i) { return "${"+(i+1)+":"+a+"}"}).join(", ");
}
snippetText += ")\n"
}
return snippetText;
}
})();

134
editor/vendor/jsonata/mode-jsonata.js vendored Normal file
View File

@ -0,0 +1,134 @@
define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules","ace/worker/worker_client","ace/mode/text"], function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var WorkerClient = require("../worker/worker_client").WorkerClient;
var jsonataFunctions = Object.keys(jsonata.functions);
// sort in length order (long->short) otherwise substringAfter gets matched
// as substring etc.
jsonataFunctions.sort(function(A,B) {
return B.length-A.length;
});
jsonataFunctions = jsonataFunctions.join("|").replace(/\$/g,"\\$");
var JSONataHighlightRules = function() {
var keywordMapper = this.createKeywordMapper({
"keyword.operator":
"and|or|in",
"constant.language":
"null|Infinity|NaN|undefined",
"constant.language.boolean":
"true|false",
"storage.type":
"function"
}, "identifier");
this.$rules = {
"start" : [
{
token : "string",
regex : "'(?=.)",
next : "qstring"
},
{
token : "string",
regex : '"(?=.)',
next : "qqstring"
},
{
token : "constant.numeric", // hex
regex : /0(?:[xX][0-9a-fA-F]+|[bB][01]+)\b/
},
{
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
},
{ token: "keyword",
regex: /λ/
},
{
token: "keyword",
regex: jsonataFunctions
},
{
token : keywordMapper,
regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
},
{
token : "punctuation.operator",
regex : /[.](?![.])/
},
{
token : "keyword.operator",
regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/,
next : "start"
},
{
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
},
{
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
},
{
token : "paren.rparen",
regex : /[\])}]/
}
],
"qqstring" : [
{
token : "string",
regex : '"|$',
next : "start"
}, {
defaultToken: "string"
}
],
"qstring" : [
{
token : "string",
regex : "'|$",
next : "start"
}, {
defaultToken: "string"
}
]
};
};
oop.inherits(JSONataHighlightRules, TextHighlightRules);
var TextMode = require("./text").Mode;
var Mode = function() {
this.HighlightRules = JSONataHighlightRules;
};
oop.inherits(Mode, TextMode);
(function() {
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], "ace/mode/jsonata_worker", "JSONataWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(e) {
session.setAnnotations(e.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/jsonata";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@ -0,0 +1,11 @@
define("ace/snippets/jsonata",["require","exports","module"], function(require, exports, module) {
"use strict";
var snippetText = "";
for (var fn in jsonata.functions) {
if (jsonata.functions.hasOwnProperty(fn)) {
snippetText += "# "+fn+"\nsnippet "+fn+"\n\t"+jsonata.getFunctionSnippet(fn)+"\n"
}
}
exports.snippetText = snippetText;
exports.scope = "jsonata";
});

4236
editor/vendor/jsonata/worker-jsonata.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -185,7 +185,7 @@
return false;
}
} else if (ptype === 'flow' || ptype === 'global' ) {
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i.test(v);
return RED.utils.validatePropertyExpression(v);
} else if (ptype === 'num') {
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
}

View File

@ -41,12 +41,14 @@
<p>The button to the right of the node will toggle its output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".</p>
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
<p>Optionally can show the complete <code>msg</code> object, and send messages to the console log.</p>
<p>Optionally can show the complete <code>msg</code> object, and send messages to the console log (⇶).</p>
<p>In addition any calls to node.warn or node.error will appear here.</p>
</script>
<script src="debug/view/debug-utils.js"></script>
<script type="text/javascript">
(function() {
var subWindow = null;
RED.nodes.registerType('debug',{
category: 'output',
defaults: {
@ -56,10 +58,12 @@
complete: {value:"false", required:true}
},
label: function() {
var suffix = "";
if (this.console === true || this.console === "true") { suffix = " ⇶"; }
if (this.complete === true || this.complete === "true") {
return this.name||"msg";
return (this.name||"msg") + suffix;
} else {
return this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete);
return (this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete)) + suffix;
}
},
labelStyle: function() {
@ -98,33 +102,52 @@
}
},
onpaletteadd: function() {
var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="sidebar-header">'+
'<span class="button-group">'+
'<a class="sidebar-header-button-toggle selected" id="debug-tab-filter-all" href="#"><span data-i18n="node-red:debug.sidebar.filterAll"></span></a>'+
'<a class="sidebar-header-button-toggle" id="debug-tab-filter-current" href="#"><span data-i18n="node-red:debug.sidebar.filterCurrent"></span></a> '+
'</span>'+
'<span><a id="debug-tab-clear" title="clear log" class="sidebar-header-button" href="#"><i class="fa fa-trash"></i></a></span></div>').appendTo(content);
var messages = $('<div id="debug-content"/>').appendTo(content);
content.i18n();
var options = {
messageMouseEnter: function(sourceId) {
if (sourceId) {
var n = RED.nodes.node(sourceId);
if (n) {
n.highlighted = true;
n.dirty = true;
}
RED.view.redraw();
}
},
messageMouseLeave: function(sourceId) {
if (sourceId) {
var n = RED.nodes.node(sourceId);
if (n) {
n.highlighted = false;
n.dirty = true;
}
RED.view.redraw();
}
},
messageSourceClick: function(sourceId) {
RED.view.reveal(sourceId);
},
clear: function() {
RED.nodes.eachNode(function(node) {
node.highlighted = false;
node.dirty = true;
});
RED.view.redraw();
}
}
var uiComponents = RED.debug.init(options);
RED.sidebar.addTab({
id: "debug",
label: this._("debug.sidebar.label"),
name: this._("debug.sidebar.name"),
content: content,
content: uiComponents.content,
toolbar: uiComponents.footer,
enableOnEdit: true
});
RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug')});
function getTimestamp() {
var d = new Date();
return d.toLocaleString();
}
var sbc = document.getElementById("debug-content");
var filter = false;
var messageCount = 0;
var that = this;
RED._debug = function(msg) {
that.handleDebugMessage("",{
@ -132,125 +155,76 @@
msg:msg
});
}
function sanitize(m) {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
var refreshMessageList = function() {
$(".debug-message").each(function() {
$(this).toggleClass('hide',filter&&!$(this).hasClass('debug-message-flow-'+RED.workspaces.active()));
});
this.refreshMessageList = function() {
RED.debug.refreshMessageList(RED.workspaces.active());
if (subWindow) {
try {
subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*")
} catch(err) {
console.log(err);
}
}
}
this.handleDebugMessage = function(t,o) {
var msg = document.createElement("div");
var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (sourceNode) {
o._source = {id:sourceNode.id,z:sourceNode.z};
msg.onmouseover = function() {
msg.style.borderRightColor = "#999";
var n = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (n) {
n.highlighted = true;
n.dirty = true;
}
RED.view.redraw();
};
msg.onmouseout = function() {
msg.style.borderRightColor = "";
var n = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (n) {
n.highlighted = false;
n.dirty = true;
}
RED.view.redraw();
};
msg.onclick = function() {
var node = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (node) {
RED.view.reveal(node.id);
}
};
//console.log(o);
var name = sanitize(((o.name?o.name:o.id)||"").toString());
var topic = sanitize((o.topic||"").toString());
var property = sanitize(o.property?o.property:'');
var payload = sanitize((o.msg||"").toString());
var format = sanitize((o.format||"").toString());
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'') +
((sourceNode&&sourceNode.z)?((" debug-message-flow-"+sourceNode.z+((filter&&(RED.workspaces.active()!==sourceNode.z))?" hide":""))):"");
msg.innerHTML = '<span class="debug-message-date">'+
getTimestamp()+'</span>'+
(name?'<span class="debug-message-name">'+name:'')+
'</span>';
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
var errorLvlType = 'error';
var errorLvl = 20;
if (o.hasOwnProperty("level") && o.level === 30) {
errorLvl = 30;
errorLvlType = 'warn';
}
msg.className = 'debug-message debug-message-level-' + errorLvl;
msg.innerHTML += '<span class="debug-message-topic">function : (' + errorLvlType + ')</span>';
} else {
msg.innerHTML += '<span class="debug-message-topic">'+
(o.topic?topic+' : ':'')+
(o.property?'msg.'+property:'msg')+" : "+format+
'</span>';
}
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
var atBottom = (sbc.scrollHeight-messages.height()-sbc.scrollTop) < 5;
messageCount++;
messages.append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
messageCount--;
}
if (atBottom) {
$(sbc).scrollTop(sbc.scrollHeight);
RED.debug.handleDebugMessage(o);
if (subWindow) {
try {
subWindow.postMessage({event:"message",msg:o},"*")
} catch(err) {
console.log(err);
}
}
};
RED.comms.subscribe("debug",this.handleDebugMessage);
$("#debug-tab-clear").click(function() {
$(".debug-message").remove();
messageCount = 0;
RED.nodes.eachNode(function(node) {
node.highlighted = false;
node.dirty = true;
});
RED.view.redraw();
});
$('#debug-tab-filter-all').on("click",function(e) {
e.preventDefault();
if (filter) {
$(this).addClass('selected');
$('#debug-tab-filter-current').removeClass('selected');
filter = !filter;
refreshMessageList();
}
});
$('#debug-tab-filter-current').on("click",function(e) {
e.preventDefault();
if (!filter) {
$(this).addClass('selected');
$('#debug-tab-filter-all').removeClass('selected');
filter = !filter;
refreshMessageList();
}
});
RED.events.on("workspace:change", refreshMessageList);
RED.events.on("workspace:change", this.refreshMessageList);
$("#debug-tab-open").click(function(e) {
e.preventDefault();
subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
subWindow.onload = function() {
subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
}
});
$(window).unload(function() {
if (subWindow) {
try {
subWindow.close()
} catch(err) {
console.log(err);
}
}
});
this.handleWindowMessage = function(evt) {
var msg = evt.data;
if (msg.event === "mouseEnter") {
options.messageMouseEnter(msg.id);
} else if (msg.event === "mouseLeave") {
options.messageMouseLeave(msg.id);
} else if (msg.event === "mouseClick") {
options.messageSourceClick(msg.id);
} else if (msg.event === "clear") {
options.clear();
}
}
window.addEventListener('message',this.handleWindowMessage);
},
onpaletteremove: function() {
RED.comms.unsubscribe("debug",this.handleDebugMessage);
RED.sidebar.removeTab("debug");
RED.events.off("workspace:change", refreshMessageList);
RED.events.off("workspace:change", this.refreshMessageList);
window.removeEventListener("message",this.handleWindowMessage);
RED.actions.remove("core:show-debug");
delete RED._debug;
},
oneditprepare: function() {
@ -282,58 +256,5 @@
}
});
})();
</script>
<style>
#debug-content {
position: absolute;
top: 43px;
bottom: 0px;
left:0px;
right: 0px;
overflow-y: scroll;
}
.debug-message {
cursor: pointer;
border-bottom: 1px solid #eee;
border-left: 8px solid #eee;
border-right: 8px solid #eee;
padding: 2px;
}
.debug-message-date {
background: #fff;
font-size: 9px;
color: #aaa;
padding: 1px 5px 1px 1px;
}
.debug-message-topic {
display: block;
background: #fff;
padding: 1px;
font-size: 10px;
color: #a66;
}
.debug-message-name {
background: #fff;
padding: 1px 5px;
font-size: 9px;
color: #aac;
}
.debug-message-payload {
display: block;
padding: 2px;
background: #fff;
}
.debug-message-level-log {
border-left-color: #eee;
border-right-color: #eee;
}
.debug-message-level-30 {
border-left-color: #ffdf9d;
border-right-color: #ffdf9d;
}
.debug-message-level-20 {
border-left-color: #f99;
border-right-color: #f99;
}
</style>

View File

@ -18,6 +18,8 @@ module.exports = function(RED) {
"use strict";
var util = require("util");
var events = require("events");
var path = require("path");
var safeJSONStringify = require("json-stringify-safe");
var debuglength = RED.settings.debugMaxLength||1000;
var useColors = false;
// util.inspect.styles.boolean = "red";
@ -79,30 +81,62 @@ module.exports = function(RED) {
msg.format = "error";
msg.msg = msg.msg.toString();
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer ["+msg.msg.length+"]";
msg.format = "buffer["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength);
}
} else if (msg.msg && typeof msg.msg === 'object') {
var seen = [];
var seenAts = [];
try {
msg.format = msg.msg.constructor.name || "Object";
} catch(err) {
msg.format = "Object";
}
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array ["+msg.msg.length+"]";
}
if (isArray || (msg.format === "Object")) {
msg.msg = JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
if (/error/i.test(msg.format)) {
msg.msg = JSON.stringify({
name: msg.msg.name,
message: msg.msg.message
});
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.slice(0,debuglength);
}
}
if (isArray || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key[0] === '_' && key !== "_msgid") {
return undefined;
}
if (key === '_req' || key === '_res') {
return "[internal]"
}
if (value instanceof Error) {
return value.toString()
}
if (util.isArray(value) && value.length > debuglength) {
value = {
__encoded__: true,
type: "array",
data: value.slice(0,debuglength),
length: value.length
}
}
if (typeof value === 'string') {
if (value.length > debuglength) {
return value.substring(0,debuglength)+"...";
}
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
}
}
seen = null;
} else if (typeof msg.msg === "boolean") {
@ -118,13 +152,14 @@ module.exports = function(RED) {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string ["+msg.msg.length+"]";
msg.msg = msg.msg;
}
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
}
}
// if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.substr(0,debuglength) +" ....";
// }
RED.comms.publish("debug",msg);
}
@ -153,4 +188,12 @@ module.exports = function(RED) {
res.sendStatus(404);
}
});
RED.httpAdmin.get("/debug/view/*",RED.auth.needsPermission("debug.read"),function(req,res) {
var options = {
root: __dirname + '/lib/debug/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
};

View File

@ -53,7 +53,7 @@
(on the 3rd output).</p>
<p>The optional append gets added to the command after <code>msg.payload</code> - so you can do
things like pipe the result to another command.</p>
<p>Parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
<p>Commands or parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
<p>The blue status icon will be visible while the node is active.</p>
<p>If running a Python app you may need to use the <code>-u</code> parameter to stop the output being buffered.</p>

View File

@ -49,6 +49,7 @@ module.exports = function(RED) {
// slice whole line by spaces (trying to honour quotes);
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
var cmd = arg.shift();
if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); }
/* istanbul ignore else */
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
child = spawn(cmd,arg);

View File

@ -36,6 +36,7 @@
<option value="javascript">Javascript</option>
<option value="css">CSS</option>
<option value="markdown">Markdown</option>
<option value="yaml">YAML</option>
<option value="text">none</option>
</select>
</div>
@ -66,6 +67,7 @@
}</pre>
<p>The resulting property will be:
<pre>Hello Fred. Today is Monday</pre>
<p>It is possible to use property from flow context or global context. Just use <code>{{flow.name}}</code> or <code>{{global.name}}</code>.
<p>By default, mustache will escape any HTML entities in the values it substitutes.
To prevent this, use <code>{{{triple}}}</code> braces.
</script>

View File

@ -18,6 +18,39 @@ module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
/**
* Custom Mustache Context capable to resolve message property and node
* flow and global context
*/
function NodeContext(msg, nodeContext) {
this.msgContext = new mustache.Context(msg);
this.nodeContext = nodeContext;
}
NodeContext.prototype = new mustache.Context();
NodeContext.prototype.lookup = function (name) {
// try message first:
var value = this.msgContext.lookup(name);
if (value !== undefined) {
return value;
}
// try node context:
var dot = name.indexOf(".");
if (dot > 0) {
var contextName = name.substr(0, dot);
var variableName = name.substr(dot + 1);
if (contextName === "flow" && this.nodeContext.flow) {
return this.nodeContext.flow.get(variableName);
}
else if (contextName === "global" && this.nodeContext.global) {
return this.nodeContext.global.get(variableName);
}
}
}
function TemplateNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
@ -31,7 +64,7 @@ module.exports = function(RED) {
try {
var value;
if (node.syntax === "mustache") {
value = mustache.render(node.template,msg);
value = mustache.render(node.template, new NodeContext(msg, node.context()));
} else {
value = node.template;
}

View File

@ -0,0 +1,285 @@
/**
* Copyright 2016 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.
**/
if (!RED) {
var RED = {}
}
RED.debug = (function() {
var config;
var messageList;
var messageTable;
var filter = false;
var view = 'list';
var messages = [];
var messagesByNode = {};
var sbc;
var activeWorkspace;
function init(_config) {
config = _config;
var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="sidebar-header">'+
'<span class="button-group"><a id="debug-tab-filter" class="sidebar-header-button" href="#"><i class="fa fa-filter"></i></a></span>'+
'<span class="button-group"><a id="debug-tab-clear" title="clear log" class="sidebar-header-button" href="#"><i class="fa fa-trash"></i></a></span></div>').appendTo(content);
var footerToolbar = $('<div>'+
// '<span class="button-group">'+
// '<a class="sidebar-footer-button-toggle text-button selected" id="debug-tab-view-list" href="#"><span data-i18n="">list</span></a>'+
// '<a class="sidebar-footer-button-toggle text-button" id="debug-tab-view-table" href="#"><span data-i18n="">table</span></a> '+
// '</span>'+
'<span class="button-group"><a id="debug-tab-open" title="open in new window" class="sidebar-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' +
'</div>');
messageList = $('<div class="debug-content debug-content-list"/>').appendTo(content);
sbc = messageList[0];
messageTable = $('<div class="debug-content debug-content-table hide"/>').appendTo(content);
var filterDialog = $('<div class="debug-filter-box hide">'+
'<div class="debug-filter-row">'+
'<span class="button-group">'+
'<a class="sidebar-header-button-toggle selected" id="debug-tab-filter-all" href="#"><span data-i18n="node-red:debug.sidebar.filterAll">all flows</span></a>'+
'<a class="sidebar-header-button-toggle" id="debug-tab-filter-current" href="#"><span data-i18n="node-red:debug.sidebar.filterCurrent">current flow</span></a> '+
'</span>'+
'</div>'+
'</div>').appendTo(content);
try {
content.i18n();
} catch(err) {
console.log("TODO: i18n library support");
}
filterDialog.find('#debug-tab-filter-all').on("click",function(e) {
e.preventDefault();
if (filter) {
$(this).addClass('selected');
$('#debug-tab-filter-current').removeClass('selected');
filter = !filter;
refreshMessageList();
}
});
filterDialog.find('#debug-tab-filter-current').on("click",function(e) {
e.preventDefault();
if (!filter) {
$(this).addClass('selected');
$('#debug-tab-filter-all').removeClass('selected');
filter = !filter;
refreshMessageList();
}
});
// $('#debug-tab-view-list').on("click",function(e) {
// e.preventDefault();
// if (!$(this).hasClass('selected')) {
// $(this).addClass('selected');
// $('#debug-tab-view-table').removeClass('selected');
// showMessageList();
// }
// });
// $('#debug-tab-view-table').on("click",function(e) {
// e.preventDefault();
// if (!$(this).hasClass('selected')) {
// $(this).addClass('selected');
// $('#debug-tab-view-list').removeClass('selected');
// showMessageTable();
// }
// });
toolbar.find('#debug-tab-filter').on("click",function(e) {
e.preventDefault();
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
filterDialog.slideUp(200);
} else {
$(this).addClass('selected');
filterDialog.slideDown(200);
}
})
toolbar.find("#debug-tab-clear").click(function(e) {
e.preventDefault();
$(".debug-message").remove();
messageCount = 0;
config.clear();
});
return {
content: content,
footer: footerToolbar
}
}
function getTimestamp() {
var d = new Date();
return d.toLocaleString();
}
function sanitize(m) {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
function refreshMessageList(_activeWorkspace) {
if (_activeWorkspace) {
activeWorkspace = _activeWorkspace;
}
$(".debug-message").each(function() {
$(this).toggleClass('hide',filter&&!$(this).hasClass('debug-message-flow-'+activeWorkspace));
});
}
function refreshMessageTable() {
}
function showMessageList() {
view = 'list';
messageTable.hide();
messageTable.empty();
messages.forEach(function(m) {
messageList.append(m.el);
})
messageList.show();
}
function showMessageTable() {
view = 'table';
messageList.hide();
messageList.empty();
Object.keys(messagesByNode).forEach(function(id) {
var m = messagesByNode[id];
var msg = m.el;
var sourceNode = m.source;
if (sourceNode) {
var wrapper = $("<div>",{id:"debug-message-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable);
wrapper.append(msg);
}
});
messageTable.show();
}
function formatString(str) {
return str.replace(/\n/g,"&crarr;").replace(/\t/g,"&rarr;");
}
function handleDebugMessage(o) {
var msg = document.createElement("div");
var sourceNode = o._source;
msg.onmouseenter = function() {
msg.style.borderRightColor = "#999";
if (o._source) {
config.messageMouseEnter(o._source.id);
}
};
msg.onmouseleave = function() {
msg.style.borderRightColor = "";
if (o._source) {
config.messageMouseLeave(o._source.id);
}
};
var name = sanitize(((o.name?o.name:o.id)||"").toString());
var topic = sanitize((o.topic||"").toString());
var property = sanitize(o.property?o.property:'');
var payload = o.msg;
var format = sanitize((o.format||"").toString());
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'') +
((sourceNode&&sourceNode.z)?((" debug-message-flow-"+sourceNode.z+((filter&&(activeWorkspace!==sourceNode.z))?" hide":""))):"");
var metaRow = $('<div class="debug-message-meta"></div>').appendTo(msg);
$('<span class="debug-message-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) {
$('<a>',{href:"#",class:"debug-message-name"}).html('node: '+sourceNode.id)
.appendTo(metaRow)
.click(function(evt) {
evt.preventDefault();
config.messageSourceClick(sourceNode.id);
});
} else if (name) {
$('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow);
}
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
var errorLvlType = 'error';
var errorLvl = 20;
if (o.hasOwnProperty("level") && o.level === 30) {
errorLvl = 30;
errorLvlType = 'warn';
}
$(msg).addClass('debug-message-level-' + errorLvl);
$('<span class="debug-message-topic">function : (' + errorLvlType + ')</span>').appendTo(metaRow);
} else {
$('<span class="debug-message-topic">'+
(o.topic?topic+' : ':'')+
(o.property?'msg.'+property:'msg')+" : "+format+
'</span>').appendTo(metaRow);
}
if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number'||/error/i.test(format) ) {
payload = JSON.parse(payload);
} else if (format === 'null') {
payload = null;
} else if (format === 'undefined') {
payload = undefined;
} else if (/^buffer/.test(format)) {
var buffer = payload;
payload = [];
for (var c = 0; c < buffer.length; c += 2) {
payload.push(parseInt(buffer.substr(c, 2), 16));
}
}
var el = $('<span class="debug-message-payload"></span>').appendTo(msg);
RED.utils.createObjectElement(payload,/*true*/null,format).appendTo(el);
var atBottom = (sbc.scrollHeight-messageList.height()-sbc.scrollTop) < 5;
var m = {
el: msg
};
messages.push(m);
if (sourceNode) {
m.source = sourceNode;
messagesByNode[sourceNode.id] = m;
}
if (view == "list") {
messageList.append(msg);
} else {
if (sourceNode) {
var wrapper = $("#debug-message-source-"+sourceNode.id.replace(/\./g,"_"));
if (wrapper.length === 0 ) {
wrapper = $("<div>",{id:"debug-message-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable);
}
wrapper.empty();
wrapper.append(msg);
}
}
if (messages.length === 100) {
m = messages.shift();
if (view === "list") {
m.el.remove();
}
}
if (atBottom) {
messageList.scrollTop(sbc.scrollHeight);
}
}
return {
init: init,
refreshMessageList:refreshMessageList,
handleDebugMessage: handleDebugMessage
}
})();

View File

@ -0,0 +1,28 @@
$(function() {
var options = {
messageMouseEnter: function(sourceId) {
window.opener.postMessage({event:"mouseEnter",id:sourceId},'*');
},
messageMouseLeave: function(sourceId) {
window.opener.postMessage({event:"mouseLeave",id:sourceId},'*');
},
messageSourceClick: function(sourceId) {
window.opener.postMessage({event:"mouseClick",id:sourceId},'*');
},
clear: function() {
window.opener.postMessage({event:"clear"},'*');
}
}
var uiComponents = RED.debug.init(options);
$(".debug-window").append(uiComponents.content);
window.addEventListener('message',function(evt) {
if (evt.data.event === "message") {
RED.debug.handleDebugMessage(evt.data.msg);
} else if (evt.data.event === "workspaceChange") {
RED.debug.refreshMessageList(evt.data.activeWorkspace);
}
},false);
});

View File

@ -0,0 +1,13 @@
<html>
<head>
<link rel="stylesheet" href="../../red/style.min.css">
<link rel="stylesheet" href="../../vendor/font-awesome/css/font-awesome.min.css">
<title>Node-RED Debug Tools</title>
</head>
<body class="debug-window">
</body>
<script src="../../vendor/vendor.js"></script>
<script src="../../red/red.js"></script>
<script src="debug-utils.js"></script>
<script src="debug.js"></script>
</html>

View File

@ -90,6 +90,7 @@
<li><code>payload</code> is the body of the response</li>
<li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
<li><code>headers</code> is an object containing the response headers</li>
<li><code>responseUrl</code> is the url of the server that responds</li>
</ul>
<p><b>Note</b>: If you need to configure a proxy please add <b>http_proxy=...</b> to your environment variables and restart Node-RED.</p>
</script>

View File

@ -159,6 +159,7 @@ module.exports = function(RED) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.responseUrl = res.responseUrl;
msg.payload = "";
// msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {

View File

@ -502,6 +502,9 @@
"null":"is null",
"nnull":"is not null",
"else":"otherwise"
},
"errors": {
"invalid-expr": "Invalid expression: __error__"
}
},
"change": {
@ -614,6 +617,13 @@
"dropped-error": "Failed to convert payload"
}
},
"yaml": {
"errors": {
"dropped-object": "Ignored non-object payload",
"dropped": "Ignored unsupported payload type",
"dropped-error": "Failed to convert payload"
}
},
"xml": {
"label": {
"represent": "Represent XML tag attributes as a property named",

View File

@ -64,7 +64,7 @@
var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global']});
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata']});
var operators = [
{v:"eq",t:"=="},
{v:"neq",t:"!="},
@ -115,7 +115,10 @@
$("#node-input-rule-container").css('min-height','250px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var rule = opt;
if (!opt.hasOwnProperty('r')) {
opt.r = {};
}
var rule = opt.r;
if (!rule.hasOwnProperty('t')) {
rule.t = 'eq';
}
@ -126,10 +129,10 @@
for (var d in operators) {
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
}
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num',previousValueType]});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> ');
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
@ -177,6 +180,12 @@
selectField.change();
},
removeItem: function(opt) {
if (opt.hasOwnProperty('i')) {
var removedList = $("#node-input-rule-container").data('removedList')||[];
removedList.push(opt.i);
$("#node-input-rule-container").data('removedList',removedList);
}
var rules = $("#node-input-rule-container").editableList('items');
rules.each(function(i) { $(this).find(".node-input-rule-index").html(i+1); });
},
@ -191,15 +200,21 @@
for (var i=0;i<this.rules.length;i++) {
var rule = this.rules[i];
$("#node-input-rule-container").editableList('addItem',rule);
$("#node-input-rule-container").editableList('addItem',{r:rule,i:i});
}
},
oneditsave: function() {
var rules = $("#node-input-rule-container").editableList('items');
var ruleset;
var node = this;
node.rules= [];
node.rules = [];
var changedOutputs = {};
var removedList = $("#node-input-rule-container").data('removedList')||[];
removedList.forEach(function(i) {
changedOutputs[i] = -1;
});
rules.each(function(i) {
var ruleData = $(this).data('data');
var rule = $(this);
var type = rule.find("select").val();
var r = {t:type};
@ -217,8 +232,14 @@
r.case = rule.find(".node-input-rule-case").prop("checked");
}
}
if (ruleData.hasOwnProperty('i')) {
if (ruleData.i !== i) {
changedOutputs[ruleData.i] = i;
}
}
node.rules.push(r);
});
this._outputs = changedOutputs;
this.outputs = node.rules.length;
this.propertyType = $("#node-input-property").typedInput('type');
},

View File

@ -16,6 +16,9 @@
module.exports = function(RED) {
"use strict";
var jsonata = require('jsonata');
var operators = {
'eq': function(a, b) { return a == b; },
'neq': function(a, b) { return a != b; },
@ -38,9 +41,20 @@ module.exports = function(RED) {
this.rules = n.rules || [];
this.property = n.property;
this.propertyType = n.propertyType || "msg";
if (this.propertyType === 'jsonata') {
try {
this.property = jsonata(this.property);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
}
this.checkall = n.checkall || "true";
this.previousValue = null;
var node = this;
var valid = true;
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
if (!rule.vt) {
@ -54,6 +68,13 @@ module.exports = function(RED) {
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
}
} else if (rule.vt === "jsonata") {
try {
rule.v = jsonata(rule.v);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
valid = false;
}
}
if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) {
@ -65,14 +86,30 @@ module.exports = function(RED) {
}
if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') {
try {
rule.v2 = jsonata(rule.v2);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
valid = false;
}
}
}
}
if (!valid) {
return;
}
this.on('input', function (msg) {
var onward = [];
try {
var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
var prop;
if (node.propertyType === 'jsonata') {
prop = node.property.evaluate({msg:msg});
} else {
prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
}
var elseflag = true;
for (var i=0; i<node.rules.length; i+=1) {
var rule = node.rules[i];
@ -80,12 +117,26 @@ module.exports = function(RED) {
var v1,v2;
if (rule.vt === 'prev') {
v1 = node.previousValue;
} else if (rule.vt === 'jsonata') {
try {
v1 = rule.v.evaluate({msg:msg});
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else {
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
}
v2 = rule.v2;
if (rule.v2t === 'prev') {
v2 = node.previousValue;
} else if (rule.v2t === 'jsonata') {
try {
v2 = rule.v2.evaluate({msg:msg});
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else if (typeof v2 !== 'undefined') {
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
}

View File

@ -141,22 +141,22 @@
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
}
var propertyName = $('<input/>',{style:"width:250px",class:"node-input-rule-property-name",type:"text"})
var propertyName = $('<input/>',{class:"node-input-rule-property-name",type:"text"})
.appendTo(row1)
.typedInput({types:['msg','flow','global']});
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)
.appendTo(row2);
var propertyValue = $('<input/>',{style:"width:250px",class:"node-input-rule-property-value",type:"text"})
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','date']});
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','date','jsonata']});
var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(search)
.appendTo(row3_1);
var fromValue = $('<input/>',{style:"width:250px",class:"node-input-rule-property-search-value",type:"text"})
var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']});
@ -164,14 +164,14 @@
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(replace)
.appendTo(row3_2);
var toValue = $('<input/>',{style:"width:250px",class:"node-input-rule-property-replace-value",type:"text"})
var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)
.appendTo(row4);
var moveValue = $('<input/>',{style:"width:250px",class:"node-input-rule-property-move-value",type:"text"})
var moveValue = $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
.appendTo(row4)
.typedInput({default:'msg',types:['msg','flow','global']});

View File

@ -16,6 +16,7 @@
module.exports = function(RED) {
"use strict";
var jsonata = require("jsonata");
function ChangeNode(n) {
RED.nodes.createNode(this, n);
@ -85,6 +86,13 @@ module.exports = function(RED) {
}
} else if (rule.tot === 'bool') {
rule.to = /^true$/i.test(rule.to);
} else if (rule.tot === 'jsonata') {
try {
rule.to = jsonata(rule.to);
} catch(e) {
valid = false;
this.error(RED._("change.errors.invalid-from",{error:e.message}));
}
}
}
@ -107,6 +115,8 @@ module.exports = function(RED) {
value = node.context().global.get(rule.to);
} else if (rule.tot === 'date') {
value = Date.now();
} else if (rule.tot === 'jsonata') {
value = rule.to.evaluate({msg:msg});
}
if (rule.t === 'change') {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {

View File

@ -16,7 +16,6 @@
module.exports = function(RED) {
"use strict";
var util = require("util");
function JSONNode(n) {
RED.nodes.createNode(this,n);
@ -36,9 +35,7 @@ module.exports = function(RED) {
msg.payload = JSON.stringify(msg.payload);
node.send(msg);
}
catch(e) {
node.error(RED._("json.errors.dropped-error"));
}
catch(e) { node.error(RED._("json.errors.dropped-error")); }
}
else { node.warn(RED._("json.errors.dropped-object")); }
}

View File

@ -0,0 +1,32 @@
<script type="text/x-red" data-template-name="yaml">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="yaml">
<p>A function that parses the <code>msg.payload</code> to convert a YAML string to/from a javascript object. Places the result back into the payload.</p>
<p>If the input is a YAML string it tries to parse it to a javascript object.</p>
<p>If the input is a javascript object it creates a YAML string.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('yaml',{
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "parser-yaml.png",
label: function() {
return this.name||"yaml";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -0,0 +1,35 @@
module.exports = function(RED) {
"use strict";
var yaml = require('js-yaml');
function YAMLNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload === "string") {
try {
msg.payload = yaml.load(msg.payload);
node.send(msg);
}
catch(e) { node.error(e.message,msg); }
}
else if (typeof msg.payload === "object") {
if (!Buffer.isBuffer(msg.payload)) {
try {
msg.payload = yaml.dump(msg.payload);
node.send(msg);
}
catch(e) {
node.error(RED._("yaml.errors.dropped-error"));
}
}
else { node.warn(RED._("yaml.errors.dropped-object")); }
}
else { node.warn(RED._("yaml.errors.dropped")); }
}
else { node.send(msg); } // If no payload - just pass it on.
});
}
RED.nodes.registerType("yaml",YAMLNode);
};

View File

@ -45,8 +45,8 @@ module.exports = function(RED) {
data = new Buffer(data);
if (this.overwriteFile === "true") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
fs.writeFile(filename, data, "binary", function (err) {
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
//fs.writeFile(filename, data, "binary", function (err) {
fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {
@ -71,8 +71,8 @@ module.exports = function(RED) {
}
else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
fs.appendFile(filename, data, "binary", function (err) {
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
//fs.appendFile(filename, data, "binary", function (err) {
fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {

View File

@ -26,35 +26,38 @@
"editor", "messaging", "iot", "ibm", "flow"
],
"dependencies": {
"basic-auth": "1.0.4",
"bcryptjs": "2.3.0",
"basic-auth": "1.1.0",
"bcryptjs": "2.4.0",
"body-parser": "1.15.2",
"cheerio":"0.22.0",
"clone": "2.0.0",
"clone": "2.1.0",
"cookie-parser": "1.4.3",
"cors":"2.8.1",
"cron":"1.1.1",
"cron":"1.2.1",
"express": "4.14.0",
"follow-redirects":"0.2.0",
"fs-extra": "0.30.0",
"follow-redirects":"1.2.1",
"fs-extra": "1.0.0",
"fs.notify":"0.0.4",
"i18next":"1.10.6",
"is-utf8":"0.2.1",
"js-yaml": "3.7.0",
"json-stringify-safe":"5.0.1",
"jsonata":"1.0.10",
"media-typer": "0.3.0",
"mqtt": "1.14.1",
"mustache": "2.2.1",
"mqtt": "1.*",
"mustache": "2.3.0",
"nopt": "3.0.6",
"oauth2orize":"1.5.0",
"oauth2orize":"1.6.0",
"on-headers":"1.0.1",
"passport":"0.3.2",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"raw-body":"2.1.7",
"semver": "5.3.0",
"sentiment":"1.0.6",
"uglify-js":"2.7.3",
"sentiment":"2.1.0",
"uglify-js":"2.7.5",
"when": "3.7.7",
"ws": "0.8.1",
"ws": "1.1.1",
"xml2js":"0.4.17",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
@ -62,31 +65,30 @@
"node-red-node-rbe":"0.1.*"
},
"optionalDependencies": {
"node-red-node-serialport":"0.4.*",
"bcrypt":"0.8.7"
"bcrypt":"~1.0.1"
},
"devDependencies": {
"grunt": "1.0.1",
"grunt-chmod": "1.1.1",
"grunt-cli": "1.2.0",
"grunt-concurrent":"2.3.1",
"grunt-contrib-clean":"1.0.0",
"grunt-contrib-compress": "1.3.0",
"grunt-contrib-concat":"1.0.1",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-jshint": "1.0.0",
"grunt-contrib-uglify": "2.0.0",
"grunt-contrib-watch":"1.0.0",
"grunt-jsonlint":"1.1.0",
"grunt-nodemon":"0.4.2",
"grunt-sass":"1.2.1",
"grunt-simple-mocha": "0.4.1",
"mocha": "3.1.1",
"should": "8.4.0",
"sinon": "1.17.6",
"supertest": "2.0.0"
"grunt": "~1.0.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-concurrent":"~2.3.1",
"grunt-contrib-clean":"~1.0.0",
"grunt-contrib-compress": "~1.3.0",
"grunt-contrib-concat":"~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~2.0.0",
"grunt-contrib-watch":"~1.0.0",
"grunt-jsonlint":"~1.1.0",
"grunt-nodemon":"~0.4.2",
"grunt-sass":"~1.2.1",
"grunt-simple-mocha": "~0.4.1",
"mocha": "~3.2.0",
"should": "^8.4.0",
"sinon": "^1.17.6",
"supertest": "^2.0.0"
},
"engines": {
"node": ">=0.10"
"node": ">=4"
}
}

View File

@ -32,7 +32,9 @@ var lastSentTime;
function handleStatus(event) {
publish("status/"+event.id,event.status,true);
}
function handleRuntimeEvent(event) {
publish("notification/"+event.id,event,true);
}
function init(_server,runtime) {
server = _server;
settings = runtime.settings;
@ -40,6 +42,9 @@ function init(_server,runtime) {
runtime.events.removeListener("node-status",handleStatus);
runtime.events.on("node-status",handleStatus);
runtime.events.removeListener("runtime-event",handleRuntimeEvent);
runtime.events.on("runtime-event",handleRuntimeEvent);
}
function start() {

View File

@ -165,7 +165,12 @@ function init(_server,_runtime) {
}
}
function start() {
return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){
var catalogPath = path.resolve(path.join(__dirname,"locales"));
return i18n.registerMessageCatalogs([
{namespace: "editor", dir: catalogPath, file:"editor.json"},
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
]).then(function(){
comms.start();
});
}

View File

@ -42,13 +42,13 @@ module.exports = {
if (settings.flowFilePretty) {
safeSettings.flowFilePretty = settings.flowFilePretty;
}
if (!runtime.nodes.paletteEditorEnabled()) {
safeSettings.editorTheme = safeSettings.editorTheme || {};
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
safeSettings.editorTheme.palette.editable = false;
}
res.json(safeSettings);
}
}

View File

@ -53,7 +53,8 @@
"keyboardShortcuts": "Keyboard shortcuts",
"login": "Login",
"logout": "Logout",
"editPalette":"Manage palette"
"editPalette":"Manage palette",
"showTips": "Show tips"
}
},
"user": {
@ -66,7 +67,8 @@
"warning": "<strong>Warning</strong>: __message__",
"warnings": {
"undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow"
"nodeActionDisabled": "node actions disabled within subflow",
"missing-types": "Flows stopped due to missing node types. Check logs for details."
},
"error": "<strong>Error</strong>: __message__",
@ -129,9 +131,31 @@
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:",
"confirm": "Are you sure you want to deploy?",
"conflict": "The server is running a more recent set of flows."
"conflict": "The server is running a more recent set of flows.",
"conflictChecking": "Checking to see if the changes can be merged automatically",
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed."
}
},
"diff": {
"unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts",
"type": {
"added": "added",
"changed": "changed",
"unchanged": "unchanged",
"deleted": "deleted",
"flowDeleted": "flow deleted",
"flowAdded": "flow added",
"movedTo": "moved to __id__",
"movedFrom": "moved from __id__"
},
"nodeCount": "__count__ node",
"nodeCount_plural": "__count__ nodes",
"local":"Local",
"remote":"Remote"
},
"subflow": {
"editSubflow": "Edit flow template: __name__",
"edit": "Edit flow template",
@ -167,11 +191,12 @@
"selectAll": "Select all nodes",
"selectAllConnected": "Select all connected nodes",
"addRemoveNode": "Add/remove node from selection",
"editSelected": "Edit selected node",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export nodes",
"nudgeNode": "Move selected node(s) by a small amount",
"moveNode": "Move selected node(s) by a large amount",
"nudgeNode": "Move selected nodes (1px)",
"moveNode": "Move selected nodes (20px)",
"toggleSidebar": "Toggle sidebar",
"deleteNode": "Delete selected nodes or link",
"copyNode": "Copy selected nodes",
@ -316,6 +341,11 @@
"add": "add"
},
"search": {
"empty": "No matches found"
"empty": "No matches found",
"addNode": "add a node..."
},
"expressionEditor": {
"functions": "Functions",
"insert": "Insert"
}
}

View File

@ -0,0 +1,32 @@
{
"info": {
"tip0" : "You can remove the selected nodes or links with {{core:delete}}",
"tip1" : "Search for nodes using {{core:search}}",
"tip2" : "{{core:toggle-sidebar}} will toggle the view of this sidebar",
"tip3" : "You can manage your palette of nodes with {{core:manage-palette}}",
"tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can been accessed from the menu or with {{core:show-config-tab}}",
"tip5" : "Enable or disable these tips from the option in the menu",
"tip6" : "Move the selected nodes using the [left] [up] [down] and [right] keys. Hold [shift] to nudge them further",
"tip7" : "Dragging a node onto a wire will splice it into the link",
"tip8" : "Export the selected nodes, or the current tab with {{core:export}}",
"tip9" : "Import a flow by dragging its JSON into the editor, or with {{core:import}}",
"tip10" : "[shift] [click] and drag on a node port to move all of the attached wires or just the selected one",
"tip11" : "Show the Info tab with {{core:show-info-tab}} or the Debug tab with {{core:show-debug-tab}}",
"tip12" : "[ctrl] [click] in the workspace to open the quick-add dialog",
"tip13" : "Hold down [ctrl] when you [click] on a node port to enable quick-wiring",
"tip14" : "Hold down [shift] when you [click] on a node to also select all of its connected nodes",
"tip15" : "Hold down [ctrl] when you [click] on a node to add or remove it from the current selection",
"tip16" : "Switch flow tabs with {{core:show-previous-tab}} and {{core:show-next-tab}}",
"tip17" : "You can confirm your changes in the node edit tray with {{core:confirm-edit-tray}} or cancel them with {{core:cancel-edit-tray}}"
},
"info-tbd": {
"tip1" : "Press the <code>Deploy</code> button above to start the flow running. You can choose to deploy the whole flow or just the changes.",
"tip2" : "Options like <b>Show grid</b> and <b>Snap to grid</b> are under the menu icon<br/><i class='fa fa-bars'></i> <i class='fa fa-caret-right'></i> <b>View</b>",
"tip4" : "<i class='fa fa-bars'></i> <i class='fa fa-caret-right'></i> <b>Manage palette</b> can be used to find, add and remove extra nodes.",
"tip5" : "Nodes may install examples under<br/><i class='fa fa-bars'></i> <i class='fa fa-caret-right'></i> <b>Import</b> <i class='fa fa-caret-right'></i> <b>Examples</b>",
"tip6" : "Lots of example flows can be found on <a href='http://flows.nodered.org' target='_blank'>flows.nodered.org</a><br/>They can then be imported by drag and drop to the workspace.",
"tip7" : "<b>Shift-click and drag</b> on a connector to move all the attached wires in one go.",
"tip8" : "The <b>Node-RED Dashboard</b> package can be used to create simple User Interfaces.",
"tip10": "Got a question?<br/>Join us on <a href='https://node-red.slack.com/' target='_blank'>Slack</a><br/>or the<br/><a href='https://groups.google.com/forum/#!forum/node-red' target='_blank'>Node-RED Google group</a>"
}
}

View File

@ -0,0 +1,98 @@
{
"$string": {
"args": "arg",
"desc": "Casts the *arg* parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function"
},
"$length": {
"args": "str",
"desc": "Returns the number of characters in the string `str`. An error is thrown if `str` is not a string."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Returns a string containing the characters in the first parameter `str` starting at position `start` (zero-offset). If `length` is specified, then the substring will contain maximum `length` characters. If `start` is negative then it indicates the number of characters from the end of `str`."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Returns the substring before the first occurrence of the character sequence chars in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Returns the substring after the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$uppercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to uppercase."
},
"$lowercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to lowercase."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Splits the `str` parameter into an array of substrings. It is an error if `str` is not a string. The optional `separator` parameter specifies the characters within the `str` about which it should be split. If `separator` is not specified, then the empty string is assumed, and `str` will be split into an array of single characters. It is an error if `separator` is not a string. The optional `limit` parameter is a number that specifies the maximum number of substrings to include in the resultant array. Any additional substrings are discarded. If `limit` is not specified, then `str` is fully split with no limit to the size of the resultant array. It is an error if `limit` is not a non-negative number."
},
"$join": {
"args": "array[, separator]",
"desc": "Joins an array of component strings into a single concatenated string with each component string separated by the optional `separator` parameter. It is an error if the input `array` contains an item which isn't a string. If `separator` is not specified, then it is assumed to be the empty string, i.e. no `separator` between the component strings. It is an error if `separator` is not a string."
},
"$number": {
"args": "arg",
"desc": "Casts the `arg` parameter to a number using the following casting rules:\n\n - Numbers are unchanged\n - Strings that contain a sequence of characters that represent a legal JSON number are converted to that number\n - All other values cause an error to be thrown."
},
"$sum": {
"args": "array",
"desc": "Returns the arithmetic sum of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$max": {
"args": "array",
"desc": "Returns the maximum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$min": {
"args": "array",
"desc": "Returns the minimum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$average": {
"args": "array",
"desc": "Returns the mean value of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$boolean": {
"args": "arg",
"desc": "Casts the argument to a Boolean using the following rules:\n\n - `Boolean` : unchanged\n - `string`: empty : `false`\n - `string`: non-empty : `true`\n - `number`: `0` : `false`\n - `number`: non-zero : `true`\n - `null` : `false`\n - `array`: empty : `false`\n - `array`: contains a member that casts to `true` : `true`\n - `array`: all members cast to `false` : `false`\n - `object`: empty : `false`\n - `object`: non-empty : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "Returns Boolean NOT on the argument. `arg` is first cast to a boolean"
},
"$exists": {
"args": "arg",
"desc": "Returns Boolean `true` if the `arg` expression evaluates to a value, or `false` if the expression does not match anything (e.g. a path to a non-existent field reference)."
},
"$count": {
"args": "array",
"desc": "Returns the number of items in the array"
},
"$append": {
"args": "array, array",
"desc": "Appends two arrays"
},
"$keys": {
"args": "object",
"desc": "Returns an array containing the keys in the object. If the argument is an array of objects, then the array returned contains a de-duplicated list of all the keys in all of the objects."
},
"$lookup": {
"args": "object, key",
"desc": "Returns the value associated with key in object. If the first argument is an array of objects, then all of the objects in the array are searched, and the values associated with all occurrences of key are returned."
},
"$spread": {
"args": "object",
"desc": "Splits an object containing key/value pairs into an array of objects, each of which has a single key/value pair from the input object. If the parameter is an array of objects, then the resultant array contains an object for every key/value pair in every object in the supplied array."
}
}

View File

@ -31,7 +31,9 @@ var defaultContext = {
image: "red/images/node-red.png"
},
asset: {
red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js"
red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js",
main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js",
}
};

View File

@ -24,6 +24,13 @@ var defaultLang = "en-US";
var resourceMap = {};
var resourceCache = {};
function registerMessageCatalogs(catalogs) {
var promises = catalogs.map(function(catalog) {
return registerMessageCatalog(catalog.namespace,catalog.dir,catalog.file);
});
return when.settle(promises);
}
function registerMessageCatalog(namespace,dir,file) {
return when.promise(function(resolve,reject) {
resourceMap[namespace] = { basedir:dir, file:file};
@ -109,9 +116,10 @@ function getCatalog(namespace,lang) {
var obj = module.exports = {
init: init,
registerMessageCatalog: registerMessageCatalog,
registerMessageCatalogs: registerMessageCatalogs,
catalog: getCatalog,
i: i18n,
defaultLang:defaultLang
defaultLang: defaultLang
}
obj['_'] = function() {

View File

@ -92,7 +92,7 @@ function start() {
reportMetrics();
}, settings.runtimeMetricInterval||15000);
}
console.log("\n\n"+log._("runtime.welcome")+"\n===================\n");
log.info("\n\n"+log._("runtime.welcome")+"\n===================\n");
if (settings.version) {
log.info(log._("runtime.version",{component:"Node-RED",version:"v"+settings.version}));
}

View File

@ -58,6 +58,7 @@ function init(runtime) {
log.info(log._("nodes.flows.registered-missing", {type:type}));
activeFlowConfig.missingTypes.splice(i,1);
if (activeFlowConfig.missingTypes.length === 0 && started) {
events.emit("runtime-event",{id:"runtime-state"});
start();
}
}
@ -238,6 +239,7 @@ function start(type,diff,muteLog) {
log.info(log._("nodes.flows.missing-type-install-2"));
log.info(" "+settings.userDir);
}
events.emit("runtime-event",{id:"runtime-state",type:"warning",text:"notification.warnings.missing-types"});
return when.resolve();
}
if (!muteLog) {
@ -287,6 +289,8 @@ function start(type,diff,muteLog) {
}
}
events.emit("nodes-started");
events.emit("runtime-event",{id:"runtime-state"});
if (!muteLog) {
if (diff) {
log.info(log._("nodes.flows.started-modified-"+type));

View File

@ -15,6 +15,7 @@
**/
var clone = require("clone");
var jsonata = require("jsonata");
function generateId() {
return (1+Math.random()*4294967295).toString(16);
@ -128,6 +129,9 @@ function compareObjects(obj1,obj2) {
}
function normalisePropertyExpression(str) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
var length = str.length;
var parts = [];
var start = 0;
@ -139,7 +143,7 @@ function normalisePropertyExpression(str) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (!inBox) {
if (i != start) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
}
inString = true;
@ -200,10 +204,15 @@ function normalisePropertyExpression(str) {
}
} else {
if (c === quoteChar) {
if (i-start === 0) {
throw new Error("Invalid property expression: zero-length string at position "+start);
}
parts.push(str.substring(start,i));
// Next char must be a ]
if (!/\]/.test(str[i+1])) {
// If inBox, next char must be a ]. Otherwise it may be [ or .
if (inBox && !/\]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
}
start = i+1;
inString = false;
@ -310,6 +319,8 @@ function evaluateNodeProperty(value, type, node, msg) {
return node.context().global.get(value);
} else if (type === 'bool') {
return /^true$/i.test(value);
} else if (type === 'jsonata') {
return jsonata(value).evaluate({msg:msg});
}
return value;
}

View File

@ -49,7 +49,7 @@ describe('debug node', function() {
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",data:{id:"n1",name:"Debug",msg:"test",
format:"string [4]",property:"payload"}
format:"string[4]",property:"payload"}
});
}, done);
});
@ -64,7 +64,7 @@ describe('debug node', function() {
n1.emit("input", {payload:"test"});
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",data:{id:"n1",msg:"test",property:"payload",format:"string [4]"}
topic:"debug",data:{id:"n1",msg:"test",property:"payload",format:"string[4]"}
});
count++;
}, function() {
@ -108,7 +108,7 @@ describe('debug node', function() {
n1.emit("input", {payload:"test", foo:"bar"});
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",data:{id:"n1",msg:"bar",property:"foo",format:"string [3]"}
topic:"debug",data:{id:"n1",msg:"bar",property:"foo",format:"string[3]"}
});
}, done);
});
@ -122,7 +122,7 @@ describe('debug node', function() {
n1.emit("input", {payload:"test", foo: {bar: "bar"}});
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",data:{id:"n1",msg:"bar",property:"foo.bar",format:"string [3]"}
topic:"debug",data:{id:"n1",msg:"bar",property:"foo.bar",format:"string[3]"}
});
}, done);
});
@ -194,7 +194,7 @@ describe('debug node', function() {
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",
data:{id:"n1",msg: '[\n 0,\n 1,\n 2,\n 3\n]',format:"array [4]",
data:{id:"n1",msg: '[\n 0,\n 1,\n 2,\n 3\n]',format:"array[4]",
property:"payload"}
});
}, done);
@ -214,7 +214,7 @@ describe('debug node', function() {
topic:"debug",
data:{
id:"n1",
msg:'{\n "name": "bar",\n "o": "[circular]"\n}',
msg:'{\n "name": "bar",\n "o": "[Circular ~]"\n}',
property:"payload",format:"Object"
}
});
@ -222,7 +222,7 @@ describe('debug node', function() {
});
});
it('should truncated a long message', function(done) {
it('should truncate a long message', function(done) {
var flow = [{id:"n1", type:"debug" }];
helper.load(debugNode, flow, function() {
var n1 = helper.getNode("n1");
@ -234,9 +234,9 @@ describe('debug node', function() {
topic:"debug",
data:{
id:"n1",
msg: Array(1001).join("X")+' ....',
msg: Array(1001).join("X")+'...',
property:"payload",
format:"string [1001]"
format:"string[1001]"
}
});
}, done);
@ -256,7 +256,7 @@ describe('debug node', function() {
id:"n1",
msg: '48454c4c4f',
property:"payload",
format: "buffer [5]"
format: "buffer[5]"
}
});
}, done);
@ -277,7 +277,7 @@ describe('debug node', function() {
});
}, function(msg) {
JSON.parse(msg).should.eql({
topic:"debug",data:{id:"n1",msg:"message 2",property:"payload",format:"string [9]"}
topic:"debug",data:{id:"n1",msg:"message 2",property:"payload",format:"string[9]"}
});
}, done);
});

View File

@ -43,6 +43,52 @@ describe('template node', function() {
});
});
it('should modify payload from flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.context().flow.set("value","foo");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify payload from global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{global.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.context().global.set("value","foo");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should handle missing node context', function(done) {
// this is artificial test because in flow there is missing z property (probably never happen in real usage)
var flow = [{id:"n1",type:"template", field:"payload", template:"payload={{flow.value}},{{global.value}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=,');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify payload in plain text mode', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", syntax:"plain", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
@ -57,32 +103,36 @@ describe('template node', function() {
});
});
xit('should modify flow context', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
it('should modify flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
setTimeout( function() {
console.log(n2);
console.log(n2.context().global.get("payload"));
//c.should.equal(1); // should only have had one output.
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in flow context
n2.context().flow.get("payload").should.equal("payload=foo");
done();
},50);
});
n1.receive({payload:"foo",topic: "bar"});
});
});
xit('should modify global context', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
it('should modify global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
setTimeout( function() {
console.log(n2);
console.log(n2.context().global.get("payload"));
//c.should.equal(1); // should only have had one output.
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in global context
n2.context().global.get("payload").should.equal("payload=foo");
done();
},50);
});
n1.receive({payload:"foo",topic: "bar"});
});
});

View File

@ -27,7 +27,7 @@ describe('XML node', function() {
afterEach(function() {
helper.unload();
});
it('should be loaded', function(done) {
var flow = [{id:"xmlNode1", type:"xml", name: "xmlNode" }];
helper.load(xmlNode, flow, function() {
@ -56,7 +56,27 @@ describe('XML node', function() {
n1.receive({payload:string,topic: "bar"});
});
});
it('should convert a valid xml string to a javascript object with options', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
helper.load(xmlNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.payload.should.have.property('employees');
msg.payload.employees.should.have.property('firstName');
should.equal(msg.payload.employees.firstName[0], 'John');
msg.payload.employees.should.have.property('lastName');
should.equal(msg.payload.employees.lastName[0], 'Smith');
done();
});
var string = '<employees><firstName>John</firstName><lastName>Smith</lastName></employees>';
n1.receive({payload:string, topic:"bar", options:{trim:true}});
});
});
it('should convert a javascript object to an xml string', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
@ -73,7 +93,24 @@ describe('XML node', function() {
n1.receive({payload:obj,topic: "bar"});
});
});
it('should convert a javascript object to an xml string with options', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
helper.load(xmlNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
var index = msg.payload.indexOf('<employees>\n <firstName>John</firstName>\n <lastName>Smith</lastName>\n</employees>');
index.should.be.above(-1);
done();
});
var obj = {"employees":{"firstName":["John"],"lastName":["Smith"] }};
n1.receive({payload:obj, topic:"bar", options:{headless:true}});
});
});
it('should log an error if asked to parse an invalid xml string', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
@ -89,7 +126,6 @@ describe('XML node', function() {
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Error: Attribute without value");
done();
} catch(err) {
done(err);

View File

@ -0,0 +1,154 @@
/**
* 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.
**/
var should = require("should");
var yamlNode = require("../../../../nodes/core/parsers/70-YAML.js");
var helper = require("../../helper.js");
describe('YAML node', function() {
before(function(done) {
helper.startServer(done);
});
afterEach(function() {
helper.unload();
});
it('should be loaded', function(done) {
var flow = [{id:"yamlNode1", type:"yaml", name: "yamlNode" }];
helper.load(yamlNode, flow, function() {
var yamlNode1 = helper.getNode("yamlNode1");
yamlNode1.should.have.property('name', 'yamlNode');
done();
});
});
it('should convert a valid yaml string to a javascript object', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.payload.should.have.property('employees');
msg.payload.employees[0].should.have.property('firstName', 'John');
msg.payload.employees[0].should.have.property('lastName', 'Smith');
done();
});
var yamlString = "employees:\n - firstName: John\n lastName: Smith\n";
yn1.receive({payload:yamlString,topic: "bar"});
});
});
it('should convert a javascript object to a yaml string', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
should.equal(msg.payload, "employees:\n - firstName: John\n lastName: Smith\n");
done();
});
var obj = {employees:[{firstName:"John", lastName:"Smith"}]};
yn1.receive({payload:obj});
});
});
it('should convert an array to a yaml string', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
should.equal(msg.payload, "- 1\n- 2\n- 3\n");
done();
});
var obj = [1,2,3];
yn1.receive({payload:obj});
});
});
it('should log an error if asked to parse an invalid yaml string', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
try {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"});
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "yaml";
});
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.should.startWith("end of the stream");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) {
done(err);
}
});
});
it('should log an error if asked to parse something thats not yaml or js', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "yaml";
});
logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.eql('yaml.errors.dropped');
logEvents[1][0].should.have.a.property('msg');
logEvents[1][0].msg.toString().should.eql('yaml.errors.dropped');
logEvents[2][0].should.have.a.property('msg');
logEvents[2][0].msg.toString().should.eql('yaml.errors.dropped-object');
done();
} catch(err) {
done(err);
}
},150);
yn1.receive({payload:true});
yn1.receive({payload:1});
yn1.receive({payload:new Buffer("a")});
});
});
it('should pass straight through if no payload set', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.not.have.property('payload');
done();
});
yn1.receive({topic: "bar"});
});
});
});

View File

@ -332,6 +332,10 @@ describe("red/util", function() {
it("pass a.0.c",function() { testABC("a.0.c",['a',0,'c']); })
it("pass a['a.b[0]'].c",function() { testABC("a['a.b[0]'].c",['a','a.b[0]','c']); })
it("pass a[0][0][0]",function() { testABC("a[0][0][0]",['a',0,0,0]); })
it("pass '1.2.3.4'",function() { testABC("'1.2.3.4'",['1.2.3.4']); })
it("pass 'a.b'[1]",function() { testABC("'a.b'[1]",['a.b',1]); })
it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); })
it('pass a.$b.c',function() { testABC('a.$b.c',['a','$b','c']); })
it('pass a["$b"].c',function() { testABC('a["$b"].c',['a','$b','c']); })
@ -355,5 +359,8 @@ describe("red/util", function() {
it("fail a. b",function() { testInvalid("a. b"); })
it("fail a.b",function() { testInvalid(" a.b"); })
it("fail a[0].[1]",function() { testInvalid("a[0].[1]"); })
it("fail a['']",function() { testInvalid("a['']"); })
it("fail 'a.b'c",function() { testInvalid("'a.b'c"); })
});
});