pull out editor-client and editor-api

This commit is contained in:
Nick O'Leary
2018-08-17 22:10:54 +01:00
parent 6b79c6135f
commit e57d8ba0ef
287 changed files with 245 additions and 294 deletions

View File

@@ -0,0 +1,178 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.comms = (function() {
var errornotification = null;
var clearErrorTimer = null;
var connectCountdownTimer = null;
var connectCountdown = 10;
var subscriptions = {};
var ws;
var pendingAuth = false;
var reconnectAttempts = 0;
var active = false;
function connectWS() {
active = true;
var wspath;
if (RED.settings.apiRootUrl) {
var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl);
if (m) {
console.log(m);
wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms";
}
} else {
var path = location.hostname;
var port = location.port;
if (port.length !== 0) {
path = path+":"+port;
}
path = path+document.location.pathname;
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
}
var auth_tokens = RED.settings.get("auth-tokens");
pendingAuth = (auth_tokens!=null);
function completeConnection() {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
ws.send(JSON.stringify({subscribe:t}));
}
}
}
ws = new WebSocket(wspath);
ws.onopen = function() {
reconnectAttempts = 0;
if (errornotification) {
clearErrorTimer = setTimeout(function() {
errornotification.close();
errornotification = null;
},1000);
}
if (pendingAuth) {
ws.send(JSON.stringify({auth:auth_tokens.access_token}));
} else {
completeConnection();
}
}
ws.onmessage = function(event) {
var message = JSON.parse(event.data);
for (var m = 0; m < message.length; m++) {
var msg = message[m];
if (pendingAuth && msg.auth) {
if (msg.auth === "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.auth === "fail") {
// anything else is an error...
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
}
}
else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
}
}
}
}
}
}
}
};
ws.onclose = function() {
if (!active) {
return;
}
if (clearErrorTimer) {
clearTimeout(clearErrorTimer);
clearErrorTimer = null;
}
reconnectAttempts++;
if (reconnectAttempts < 10) {
setTimeout(connectWS,1000);
if (reconnectAttempts > 5 && errornotification == null) {
errornotification = RED.notify(RED._("notification.errors.lostConnection"),"error",true);
}
} else if (reconnectAttempts < 20) {
setTimeout(connectWS,2000);
} else {
connectCountdown = 60;
connectCountdownTimer = setInterval(function() {
connectCountdown--;
if (connectCountdown === 0) {
errornotification.update(RED._("notification.errors.lostConnection"));
clearInterval(connectCountdownTimer);
connectWS();
} else {
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
errornotification.update(msg);
$(errornotification).find("a").click(function(e) {
e.preventDefault();
errornotification.update(RED._("notification.errors.lostConnection"));
clearInterval(connectCountdownTimer);
connectWS();
})
}
},1000);
}
}
}
function subscribe(topic,callback) {
if (subscriptions[topic] == null) {
subscriptions[topic] = [];
}
subscriptions[topic].push(callback);
if (ws && ws.readyState == 1) {
ws.send(JSON.stringify({subscribe:topic}));
}
}
function unsubscribe(topic,callback) {
if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) {
if (subscriptions[topic][i] === callback) {
subscriptions[topic].splice(i,1);
break;
}
}
if (subscriptions[topic].length === 0) {
delete subscriptions[topic];
}
}
}
return {
connect: connectWS,
subscribe: subscribe,
unsubscribe:unsubscribe
}
})();

View File

@@ -0,0 +1,53 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.events = (function() {
var handlers = {};
function on(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
}
function off(evt,func) {
var handler = handlers[evt];
if (handler) {
for (var i=0;i<handler.length;i++) {
if (handler[i] === func) {
handler.splice(i,1);
return;
}
}
}
}
function emit(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
try {
handlers[evt][i](arg);
} catch(err) {
console.log("RED.events.emit error: ["+evt+"] "+(err.toString()));
console.log(err);
}
}
}
}
return {
on: on,
off: off,
emit: emit
}
})();

View File

@@ -0,0 +1,332 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.history = (function() {
var undo_history = [];
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.moved = n.moved;
}
// 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 && ev.node._def.defaults[i] && 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.editor.validateNode(ev.node);
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);
RED.editor.validateNode(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() {
for (var i=0;i<undo_history.length;i++) {
undo_history[i].dirty = true;
}
},
list: function() {
return undo_history
},
depth: function() {
return undo_history.length;
},
push: function(ev) {
undo_history.push(ev);
},
pop: function() {
var ev = undo_history.pop();
undoEvent(ev);
},
peek: function() {
return undo_history[undo_history.length-1];
},
clear: function() {
undo_history = [];
}
}
})();

View File

@@ -0,0 +1,89 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.i18n = (function() {
var apiRootUrl;
return {
init: function(options, done) {
apiRootUrl = options.apiRootUrl||"";
i18n.init({
resGetPath: apiRootUrl+'locales/__ns__?lng=__lng__',
dynamicLoad: false,
load:'current',
ns: {
namespaces: ["editor","node-red","jsonata","infotips"],
defaultNs: "editor"
},
fallbackLng: ['en-US'],
useCookie: false
},function() {
done();
});
RED["_"] = function() {
return i18n.t.apply(null,arguments);
}
},
loadNodeCatalog: function(namespace,done) {
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
var toLoad = languageList.length;
languageList.forEach(function(lang) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: apiRootUrl+'nodes/'+namespace+'/messages?lng='+lang,
success: function(data) {
i18n.addResourceBundle(lang,namespace,data);
toLoad--;
if (toLoad === 0) {
done();
}
}
});
})
},
loadNodeCatalogs: function(done) {
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
var toLoad = languageList.length;
languageList.forEach(function(lang) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: apiRootUrl+'nodes/messages?lng='+lang,
success: function(data) {
var namespaces = Object.keys(data);
namespaces.forEach(function(ns) {
i18n.addResourceBundle(lang,ns,data[ns]);
});
toLoad--;
if (toLoad === 0) {
done();
}
}
});
})
}
}
})();

View File

@@ -0,0 +1,44 @@
{
"*": {
"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",
"ctrl-g x": "core:show-context-tab",
"ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar",
"ctrl-,": "core:show-user-settings",
"ctrl-alt-r": "core:show-remote-diff",
"ctrl-alt-n": "core:new-project",
"ctrl-alt-o": "core:open-project",
"ctrl-g v": "core:show-version-control-tab"
},
"workspace": {
"backspace": "core:delete-selection",
"delete": "core:delete-selection",
"enter": "core:edit-selected-node",
"ctrl-c": "core:copy-selection-to-internal-clipboard",
"ctrl-x": "core:cut-selection-to-internal-clipboard",
"ctrl-v": "core:paste-from-internal-clipboard",
"ctrl-z": "core:undo",
"ctrl-a": "core:select-all-nodes",
"shift-?": "core:show-help",
"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

@@ -0,0 +1,24 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname;
}
RED.init({
apiRootUrl: ""
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,533 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = (function() {
function appendNodeConfig(nodeConfig,done) {
done = done || function(){};
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
var moduleId;
if (m) {
moduleId = m[1];
} else {
moduleId = "unknown";
}
try {
var hasDeferred = false;
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
nodeConfigEls.find("script").each(function(i,el) {
var srcUrl = $(el).attr('src');
if (srcUrl && !/^\s*(https?:|\/|\.)/.test(srcUrl)) {
$(el).remove();
var newScript = document.createElement("script");
newScript.onload = function() { $("body").append(nodeConfigEls); done() }
$('body').append(newScript);
newScript.src = RED.settings.apiRootUrl+srcUrl;
hasDeferred = true;
}
})
if (!hasDeferred) {
$("body").append(nodeConfigEls);
done();
}
} catch(err) {
RED.notify(RED._("notification.errors.failedToAppendNode",{module:moduleId, error:err.toString()}),{
type: "error",
timeout: 10000
});
console.log("["+moduleId+"] "+err.toString());
done();
}
}
function loadNodeList() {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'nodes',
success: function(data) {
RED.nodes.setNodeList(data);
RED.i18n.loadNodeCatalogs(function() {
loadIconList(loadNodes);
});
}
});
}
function loadIconList(done) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'icons',
success: function(data) {
RED.nodes.setIconSets(data);
if (done) {
done();
}
}
});
}
function loadNodes() {
$.ajax({
headers: {
"Accept":"text/html"
},
cache: false,
url: 'nodes',
success: function(data) {
var configs = data.trim().split(/(?=<!-- --- \[red-module:\S+\] --- -->)/);
var stepConfig = function() {
if (configs.length === 0) {
$("body").i18n();
$("#palette > .palette-spinner").hide();
$(".palette-scroll").removeClass("hide");
$("#palette-search").removeClass("hide");
loadFlows(function() {
if (RED.settings.theme("projects.enabled",false)) {
RED.projects.refresh(function(activeProject) {
RED.sidebar.info.refresh()
if (!activeProject) {
// Projects enabled but no active project
RED.menu.setDisabled('menu-item-projects-open',true);
RED.menu.setDisabled('menu-item-projects-settings',true);
if (activeProject === false) {
// User previously decline the migration to projects.
} else { // null/undefined
RED.projects.showStartup();
}
}
completeLoad();
});
} else {
// Projects disabled by the user
RED.sidebar.info.refresh()
completeLoad();
}
});
} else {
var config = configs.shift();
appendNodeConfig(config,stepConfig);
}
}
stepConfig();
}
});
}
function loadFlows(done) {
$.ajax({
headers: {
"Accept":"application/json",
},
cache: false,
url: 'flows',
success: function(nodes) {
if (nodes) {
var currentHash = window.location.hash;
RED.nodes.version(nodes.rev);
RED.nodes.import(nodes.flows);
RED.nodes.dirty(false);
RED.view.redraw(true);
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
}
}
done();
}
});
}
function completeLoad() {
var persistentNotifications = {};
RED.comms.subscribe("notification/#",function(topic,msg) {
var parts = topic.split("/");
var notificationId = parts[1];
if (notificationId === "runtime-deploy") {
// handled in ui/deploy.js
return;
}
if (notificationId === "node") {
// handled below
return;
}
if (notificationId === "project-update") {
RED.nodes.clear();
RED.history.clear();
RED.view.redraw(true);
RED.projects.refresh(function() {
loadFlows(function() {
var project = RED.projects.getActiveProject();
var message = {
"change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
"merge-abort": RED._("notification.project.merge-abort"),
"loaded": RED._("notification.project.loaded", {project: msg.project}),
"updated": RED._("notification.project.updated", {project: msg.project}),
"pull": RED._("notification.project.pull", {project: msg.project}),
"revert": RED._("notification.project.revert", {project: msg.project}),
"merge-complete": RED._("notification.project.merge-complete")
}[msg.action];
RED.notify("<p>"+message+"</p>");
RED.sidebar.info.refresh()
});
});
return;
}
if (msg.text) {
msg.default = msg.text;
var text = RED._(msg.text,msg);
var options = {
type: msg.type,
fixed: msg.timeout === undefined,
timeout: msg.timeout,
id: notificationId
}
if (notificationId === "runtime-state") {
if (msg.error === "missing-types") {
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
if (!!RED.projects.getActiveProject()) {
options.buttons = [
{
text: RED._("notification.label.manage-project-dep"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.settings.show('deps');
}
}
]
// } else if (RED.settings.theme('palette.editable') !== false) {
} else {
options.buttons = [
{
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
}
} else if (msg.error === "credentials_load_failed") {
if (RED.settings.theme("projects.enabled",false)) {
// projects enabled
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup credentials",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showCredentialsPrompt();
}
}
]
}
} else {
options.buttons = [
{
text: "Close",
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
}
} else if (msg.error === "missing_flow_file") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup project files",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showFilesPrompt();
}
}
]
}
} else if (msg.error === "missing_package_file") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Create default package file",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile();
}
}
]
}
} else if (msg.error === "project_empty") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "No thanks",
click: function() {
persistentNotifications[notificationId].hideNotification();
}
},
{
text: "Create default project files",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultFileSet();
}
}
]
}
} else if (msg.error === "git_merge_conflict") {
RED.nodes.clear();
RED.sidebar.versionControl.refresh(true);
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Show merge conflicts",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges();
}
}
]
}
}
}
if (!persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId] = RED.notify(text,options);
} else {
persistentNotifications[notificationId].update(text,options);
}
} 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]);
if (node) {
if (msg.hasOwnProperty("text")) {
if (msg.text[0] !== ".") {
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
}
}
node.status = msg;
node.dirty = true;
RED.view.redraw();
}
});
RED.comms.subscribe("notification/node/#",function(topic,msg) {
var i,m;
var typeList;
var info;
if (topic == "notification/node/added") {
var addedTypes = [];
msg.forEach(function(m) {
var id = m.id;
RED.nodes.addNodeSet(m);
addedTypes = addedTypes.concat(m.types);
RED.i18n.loadNodeCatalog(id, function() {
$.get('nodes/'+id, function(data) {
appendNodeConfig(data);
});
});
});
if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
}
loadIconList();
} else if (topic == "notification/node/removed") {
for (i=0;i<msg.length;i++) {
m = msg[i];
info = RED.nodes.removeNodeSet(m.id);
if (info.added) {
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
}
}
loadIconList();
} else if (topic == "notification/node/enabled") {
if (msg.types) {
info = RED.nodes.getNodeSet(msg.id);
if (info.added) {
RED.nodes.enableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
} else {
$.get('nodes/'+msg.id, function(data) {
appendNodeConfig(data);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
});
}
}
} else if (topic == "notification/node/disabled") {
if (msg.types) {
RED.nodes.disableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
}
} else if (topic == "node/upgraded") {
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
}
// Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary();
});
}
function showAbout() {
$.get('red/about', function(data) {
var aboutHeader = '<div style="text-align:center;">'+
'<img width="50px" src="red/images/node-red-icon.svg" />'+
'</div>';
RED.sidebar.info.set(aboutHeader+marked(data));
RED.sidebar.info.show();
});
}
function loadEditor() {
var menuOptions = [];
if (RED.settings.theme("projects.enabled",false)) {
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[
{id:"menu-item-projects-new",label:"New",disabled:false,onselect:"core:new-project"},
{id:"menu-item-projects-open",label:"Open",disabled:false,onselect:"core:open-project"},
{id:"menu-item-projects-settings",label:"Project Settings",disabled:false,onselect:"core:show-project-settings"}
]});
}
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
// {id:"menu-item-status",setting:"node-show-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("")}}},
// {id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}},
// {id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}},
// {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:"core:toggle-sidebar", selected: true},
null
]});
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:"core:show-import-dialog"},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
{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:"core:search"});
menuOptions.push(null);
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:"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:"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) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
menuOptions.push(null);
}
menuOptions.push({id:"menu-item-user-settings",label:RED._("menu.label.settings"),onselect:"core:show-user-settings"});
menuOptions.push(null);
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"});
menuOptions.push({id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
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: "core:show-about" });
RED.view.init();
RED.userSettings.init();
RED.user.init();
RED.library.init();
RED.keyboard.init();
RED.palette.init();
if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init();
} else {
console.log("Palette editor disabled");
}
RED.sidebar.init();
if (RED.settings.theme("projects.enabled",false)) {
RED.projects.init();
} else {
console.log("Projects disabled");
}
RED.subflow.init();
RED.workspaces.init();
RED.clipboard.init();
RED.search.init();
RED.editor.init();
RED.diff.init();
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.notifications.init();
RED.actions.add("core:show-about", showAbout);
RED.nodes.init();
RED.comms.connect();
$("#main-container").show();
$(".header-toolbar").show();
loadNodeList();
}
var initialised = false;
function init(options) {
if (initialised) {
throw new Error("RED already initialised");
}
initialised = true;
ace.require("ace/ext/language_tools");
options = options || {};
options.apiRootUrl = options.apiRootUrl || "";
if (options.apiRootUrl && !/\/$/.test(options.apiRootUrl)) {
options.apiRootUrl = options.apiRootUrl+"/";
}
RED.i18n.init(options, function() {
RED.settings.init(options, loadEditor);
})
}
return {
init: init
}
})();

View File

@@ -0,0 +1,217 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.settings = (function () {
var loadedSettings = {};
var userSettings = {};
var settingsDirty = false;
var pendingSave;
var hasLocalStorage = function () {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
};
var set = function (key, value) {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.setItem(key, JSON.stringify(value));
} else {
userSettings[key] = value;
saveUserSettings();
}
};
/**
* If the key is not set in the localStorage it returns <i>undefined</i>
* Else return the JSON parsed value
* @param key
* @returns {*}
*/
var get = function (key) {
if (!hasLocalStorage()) {
return undefined;
}
if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key));
} else {
return userSettings[key];
}
};
var remove = function (key) {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.removeItem(key);
} else {
delete userSettings[key];
saveUserSettings();
}
};
var setProperties = function(data) {
for (var prop in loadedSettings) {
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
delete RED.settings[prop];
}
}
for (prop in data) {
if (data.hasOwnProperty(prop)) {
RED.settings[prop] = data[prop];
}
}
loadedSettings = data;
};
var setUserSettings = function(data) {
userSettings = data;
}
var init = function (options, done) {
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
if (accessTokenMatch) {
var accessToken = accessTokenMatch[1];
RED.settings.set("auth-tokens",{access_token: accessToken});
window.location.search = "";
}
RED.settings.apiRootUrl = options.apiRootUrl;
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
// Only attach auth header for requests to relative paths
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
if (options.apiRootUrl) {
settings.url = options.apiRootUrl+settings.url;
}
var auth_tokens = RED.settings.get("auth-tokens");
if (auth_tokens) {
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
}
jqXHR.setRequestHeader("Node-RED-API-Version","v2");
}
}
});
load(done);
}
var load = function(done) {
$.ajax({
headers: {
"Accept": "application/json"
},
dataType: "json",
cache: false,
url: 'settings',
success: function (data) {
setProperties(data);
if (!RED.settings.user || RED.settings.user.anonymous) {
RED.settings.remove("auth-tokens");
}
console.log("Node-RED: " + data.version);
loadUserSettings(done);
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
window.location.search = "";
}
RED.user.login(function() { load(done); });
} else {
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
}
}
});
};
function loadUserSettings(done) {
$.ajax({
headers: {
"Accept": "application/json"
},
dataType: "json",
cache: false,
url: 'settings/user',
success: function (data) {
setUserSettings(data);
done();
},
error: function(jqXHR,textStatus,errorThrown) {
console.log("Unexpected error loading user settings:",jqXHR.status,textStatus);
}
});
}
function saveUserSettings() {
if (RED.user.hasPermission("settings.write")) {
if (pendingSave) {
clearTimeout(pendingSave);
}
pendingSave = setTimeout(function() {
pendingSave = null;
$.ajax({
method: 'POST',
contentType: 'application/json',
url: 'settings/user',
data: JSON.stringify(userSettings),
success: function (data) {
},
error: function(jqXHR,textStatus,errorThrown) {
console.log("Unexpected error saving user settings:",jqXHR.status,textStatus);
}
});
},300);
}
}
function theme(property,defaultValue) {
if (!RED.settings.editorTheme) {
return defaultValue;
}
var parts = property.split(".");
var v = RED.settings.editorTheme;
try {
for (var i=0;i<parts.length;i++) {
v = v[parts[i]];
}
if (v === undefined) {
return defaultValue;
}
return v;
} catch(err) {
return defaultValue;
}
}
return {
init: init,
load: load,
loadUserSettings: loadUserSettings,
set: set,
get: get,
remove: remove,
theme: theme
}
})();

View File

@@ -0,0 +1,130 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.text = {};
RED.text.bidi = (function() {
var textDir = "";
var LRE = "\u202A",
RLE = "\u202B",
PDF = "\u202C";
function isRTLValue(stringValue) {
var length = stringValue.length;
for (var i=0;i<length;i++) {
if (isBidiChar(stringValue.charCodeAt(i))) {
return true;
}
else if(isLatinChar(stringValue.charCodeAt(i))) {
return false;
}
}
return false;
}
function isBidiChar(c) {
return (c >= 0x05d0 && c <= 0x05ff)||
(c >= 0x0600 && c <= 0x065f)||
(c >= 0x066a && c <= 0x06ef)||
(c >= 0x06fa && c <= 0x07ff)||
(c >= 0xfb1d && c <= 0xfdff)||
(c >= 0xfe70 && c <= 0xfefc);
}
function isLatinChar(c){
return (c > 64 && c < 91)||(c > 96 && c < 123)
}
/**
* Determines the text direction of a given string.
* @param value - the string
*/
function resolveBaseTextDir(value) {
if (textDir == "auto") {
if (isRTLValue(value)) {
return "rtl";
} else {
return "ltr";
}
}
else {
return textDir;
}
}
function onInputChange() {
$(this).attr("dir", resolveBaseTextDir($(this).val()));
}
/**
* Adds event listeners to the Input to ensure its text-direction attribute
* is properly set based on its content.
* @param input - the input field
*/
function prepareInput(input) {
input.on("keyup",onInputChange).on("paste",onInputChange).on("cut",onInputChange);
// Set the initial text direction
onInputChange.call(input);
}
/**
* Enforces the text direction of a given string by adding
* UCC (Unicode Control Characters)
* @param value - the string
*/
function enforceTextDirectionWithUCC(value) {
if (value) {
var dir = resolveBaseTextDir(value);
if (dir == "ltr") {
return LRE + value + PDF;
}
else if (dir == "rtl") {
return RLE + value + PDF;
}
}
return value;
}
/**
* Enforces the text direction for all the spans with style bidiAware under
* workspace or sidebar div
*/
function enforceTextDirectionOnPage() {
$("#workspace").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).html()));
});
$("#sidebar").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).text()));
});
}
/**
* Sets the text direction preference
* @param dir - the text direction preference
*/
function setTextDirection(dir) {
textDir = dir;
RED.nodes.eachNode(function(n) { n.dirty = true;});
RED.view.redraw();
RED.palette.refresh();
enforceTextDirectionOnPage();
}
return {
setTextDirection: setTextDirection,
enforceTextDirectionWithUCC: enforceTextDirectionWithUCC,
resolveBaseTextDir: resolveBaseTextDir,
prepareInput: prepareInput
}
})();

File diff suppressed because it is too large Load Diff

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,user:shortcut?shortcut.user:undefined})
})
return result;
}
return {
add: addAction,
remove: removeAction,
get: getAction,
invoke: invokeAction,
list: listActions
}
})();

View File

@@ -0,0 +1,379 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.clipboard = (function() {
var dialog;
var dialogContainer;
var exportNodesDialog;
var importNodesDialog;
var disabled = false;
function setupDialogs() {
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
class: "primary",
text: RED._("common.label.close"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-copy",
class: "primary",
text: RED._("clipboard.export.copy"),
click: function() {
$("#clipboard-export").select();
document.execCommand("copy");
document.getSelection().removeAllRanges();
RED.notify(RED._("clipboard.nodesExported"));
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-ok",
class: "primary",
text: RED._("common.label.import"),
click: function() {
RED.view.importNodes($("#clipboard-import").val(),$("#import-tab > a.selected").attr('id') === 'import-tab-new');
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
},
close: function(e) {
}
});
dialogContainer = dialog.children(".dialog-form");
exportNodesDialog =
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.export.copy"></label>'+
'<span id="export-range-group" class="button-group">'+
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 4px;font-family: monospace; font-size: 12px; background:#f3f3f3; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-row" style="text-align: right;">'+
'<span id="export-format-group" class="button-group">'+
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
'</span>'+
'</div>';
importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>'+
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
'<span id="import-tab" class="button-group">'+
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
'</span>'+
'</div>';
}
function validateImport() {
var importInput = $("#clipboard-import");
var v = importInput.val();
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
try {
JSON.parse(v);
importInput.removeClass("input-error");
importInput.val(v);
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
importInput.addClass("input-error");
}
$("#clipboard-dialog-ok").button("disable");
}
}
function importNodes() {
if (disabled) {
return;
}
dialogContainer.empty();
dialogContainer.append($(importNodesDialog));
dialogContainer.i18n();
$("#clipboard-dialog-ok").show();
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-close").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(validateImport);
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
$("#import-tab > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
});
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
}
function exportNodes() {
if (disabled) {
return;
}
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
dialogContainer.i18n();
var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini";
$("#export-format-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var flow = $("#clipboard-export").val();
if (flow.length > 0) {
var nodes = JSON.parse(flow);
format = $(this).attr('id');
if (format === 'export-format-full') {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
$("#clipboard-export").val(flow);
$("#clipboard-export").focus();
}
});
$("#export-range-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var type = $(this).attr('id');
var flow = "";
var nodes = null;
if (type === 'export-range-selected') {
var selection = RED.view.selection();
// Don't include the subflow meta-port nodes in the exported selection
nodes = RED.nodes.createExportableNodeSet(selection.nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'export-range-flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'export-range-full') {
nodes = RED.nodes.createCompleteNodeSet(false);
}
if (nodes !== null) {
if (format === "export-format-full") {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
}
if (flow.length > 0) {
$("#export-copy").removeClass('disabled');
} else {
$("#export-copy").addClass('disabled');
}
$("#clipboard-export").val(flow);
$("#clipboard-export").focus();
})
$("#clipboard-dialog-ok").hide();
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-close").hide();
var selection = RED.view.selection();
if (selection.nodes) {
$("#export-range-selected").click();
} else {
$("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click();
}
if (format === "export-format-full") {
$("#export-format-full").click();
} else {
$("#export-format-mini").click();
}
$("#clipboard-export")
.focus(function() {
var textarea = $(this);
textarea.select();
textarea.mouseup(function() {
textarea.unbind("mouseup");
return false;
})
});
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
$("#clipboard-export").focus();
if (!document.queryCommandSupported("copy")) {
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-close").show();
} else {
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-copy").show();
}
}
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove("escape");
}
function copyText(value,element,msg) {
var truncated = false;
if (typeof value !== "string" ) {
value = JSON.stringify(value, function(key,value) {
if (value !== null && typeof value === 'object') {
if (value.__enc__) {
if (value.hasOwnProperty('data') && value.hasOwnProperty('length')) {
truncated = value.data.length !== value.length;
return value.data;
}
if (value.type === 'function' || value.type === 'internal') {
return undefined
}
if (value.type === 'number') {
// Handle NaN and Infinity - they are not permitted
// in JSON. We can either substitute with a String
// representation or null
return null;
}
}
}
return value;
});
}
if (truncated) {
msg += "_truncated";
}
$("#clipboard-hidden").val(value).select();
var result = document.execCommand("copy");
if (result && element) {
var popover = RED.popover.create({
target: element,
direction: 'left',
size: 'small',
content: RED._(msg)
});
setTimeout(function() {
popover.close();
},1000);
popover.open();
}
return result;
}
return {
init: function() {
setupDialogs();
$('<input type="text" id="clipboard-hidden">').appendTo("body");
RED.actions.add("core:show-export-dialog",exportNodes);
RED.actions.add("core:show-import-dialog",importNodes);
RED.events.on("editor:open",function() { disabled = true; });
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; });
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
RED.keyboard.add("*", "escape" ,hideDropTarget);
}
});
$('#dropTarget').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
event.preventDefault();
}
})
.on("dragleave",function(event) {
hideDropTarget();
})
.on("drop",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
var data = event.originalEvent.dataTransfer.getData("text/plain");
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
RED.view.importNodes(data);
} else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
var files = event.originalEvent.dataTransfer.files;
if (files.length === 1) {
var file = files[0];
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
RED.view.importNodes(e.target.result);
};
})(file);
reader.readAsText(file);
}
}
hideDropTarget();
event.preventDefault();
});
},
import: importNodes,
export: exportNodes,
copyText: copyText
}
})();

View File

@@ -0,0 +1,131 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
(function($) {
$.widget( "nodered.checkboxSet", {
_create: function() {
var that = this;
this.uiElement = this.element.wrap( "<span>" ).parent();
this.uiElement.addClass("red-ui-checkboxSet");
if (this.options.parent) {
this.parent = this.options.parent;
this.parent.checkboxSet('addChild',this.element);
}
this.children = [];
this.partialFlag = false;
this.stateValue = 0;
var initialState = this.element.prop('checked');
this.options = [
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
];
if (initialState) {
this.options[1].show();
} else {
this.options[0].show();
}
this.element.change(function() {
if (this.checked) {
that.options[0].hide();
that.options[1].show();
that.options[2].hide();
} else {
that.options[1].hide();
that.options[0].show();
that.options[2].hide();
}
var isChecked = this.checked;
that.children.forEach(function(child) {
child.checkboxSet('state',isChecked,false,true);
})
})
this.uiElement.click(function(e) {
e.stopPropagation();
// state returns null for a partial state. Clicking on that should
// result in false.
that.state((that.state()===false)?true:false);
})
if (this.parent) {
this.parent.checkboxSet('updateChild',this);
}
},
_destroy: function() {
if (this.parent) {
this.parent.checkboxSet('removeChild',this.element);
}
},
addChild: function(child) {
var that = this;
this.children.push(child);
},
removeChild: function(child) {
var index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index,1);
}
},
updateChild: function(child) {
var checkedCount = 0;
this.children.forEach(function(c,i) {
if (c.checkboxSet('state') === true) {
checkedCount++;
}
});
if (checkedCount === 0) {
this.state(false,true);
} else if (checkedCount === this.children.length) {
this.state(true,true);
} else {
this.state(null,true);
}
},
disable: function() {
this.uiElement.addClass('disabled');
},
state: function(state,suppressEvent,suppressParentUpdate) {
if (arguments.length === 0) {
return this.partialFlag?null:this.element.is(":checked");
} else {
this.partialFlag = (state === null);
var trueState = this.partialFlag||state;
this.element.prop('checked',trueState);
if (state === true) {
this.options[0].hide();
this.options[1].show();
this.options[2].hide();
} else if (state === false) {
this.options[2].hide();
this.options[1].hide();
this.options[0].show();
} else if (state === null) {
this.options[0].hide();
this.options[1].hide();
this.options[2].show();
}
if (!suppressEvent) {
this.element.trigger('change',null);
}
if (!suppressParentUpdate && this.parent) {
this.parent.checkboxSet('updateChild',this);
}
}
}
})
})(jQuery);

View File

@@ -0,0 +1,332 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
(function($) {
/**
* options:
* - addButton : boolean|string - text for add label, default 'add'
* - height : number|'auto'
* - resize : function - called when list as a whole is resized
* - resizeItem : function(item) - called to resize individual item
* - sortable : boolean|string - string is the css selector for handle
* - sortItems : function(items) - when order of items changes
* - connectWith : css selector of other sortables
* - 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)
* - width(width)
* - height(height)
* - items()
* - empty()
* - filter(filter)
* - sort(sort)
* - length()
*/
$.widget( "nodered.editableList", {
_create: function() {
var that = this;
this.element.addClass('red-ui-editableList-list');
this.uiWidth = this.element.width();
this.uiContainer = this.element
.wrap( "<div>" )
.parent();
if (this.options.header) {
this.options.header.addClass("red-ui-editableList-header");
this.borderContainer = this.uiContainer.wrap("<div>").parent();
this.borderContainer.prepend(this.options.header);
this.topContainer = this.borderContainer.wrap("<div>").parent();
} else {
this.topContainer = this.uiContainer.wrap("<div>").parent();
}
this.topContainer.addClass('red-ui-editableList');
if (this.options.class) {
this.topContainer.addClass(this.options.class);
}
if (this.options.addButton !== false) {
var addLabel;
if (typeof this.options.addButton === 'string') {
addLabel = this.options.addButton
} else {
if (RED && RED._) {
addLabel = RED._("editableList.add");
} else {
addLabel = 'add';
}
}
$('<a href="#" class="editor-button editor-button-small red-ui-editableList-addButton" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
.appendTo(this.topContainer)
.click(function(evt) {
evt.preventDefault();
that.addItem({});
});
}
if (this.element.css("position") === "absolute") {
["top","left","bottom","right"].forEach(function(s) {
var v = that.element.css(s);
if (v!=="auto" && v!=="") {
that.topContainer.css(s,v);
that.uiContainer.css(s,"0");
that.element.css(s,'auto');
}
})
this.element.css("position","static");
this.topContainer.css("position","absolute");
this.uiContainer.css("position","absolute");
}
if (this.options.header) {
this.borderContainer.addClass("red-ui-editableList-border");
} else {
this.uiContainer.addClass("red-ui-editableList-border");
}
this.uiContainer.addClass("red-ui-editableList-container");
this.uiHeight = this.element.height();
this.activeFilter = this.options.filter||null;
this.activeSort = this.options.sort||null;
this.scrollOnAdd = this.options.scrollOnAdd;
if (this.scrollOnAdd === undefined) {
this.scrollOnAdd = true;
}
var minHeight = this.element.css("minHeight");
if (minHeight !== '0px') {
this.uiContainer.css("minHeight",minHeight);
this.element.css("minHeight",0);
}
var maxHeight = this.element.css("maxHeight");
if (maxHeight !== '0px') {
this.uiContainer.css("maxHeight",maxHeight);
this.element.css("maxHeight",null);
}
if (this.options.height !== 'auto') {
this.uiContainer.css("overflow-y","scroll");
if (!isNaN(this.options.height)) {
this.uiHeight = this.options.height;
}
}
this.element.height('auto');
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(\d+%)/i.exec(attrStyle)) !== null) {
this.element.width('100%');
this.uiContainer.width(m[1]);
}
if (this.options.sortable) {
var handle = (typeof this.options.sortable === 'string')?
this.options.sortable :
".red-ui-editableList-item-handle";
var sortOptions = {
axis: "y",
update: function( event, ui ) {
if (that.options.sortItems) {
that.options.sortItems(that.items());
}
},
handle:handle,
cursor: "move",
tolerance: "pointer",
forcePlaceholderSize:true,
placeholder: "red-ui-editabelList-item-placeholder",
start: function(e, ui){
ui.placeholder.height(ui.item.height()-4);
}
};
if (this.options.connectWith) {
sortOptions.connectWith = this.options.connectWith;
}
this.element.sortable(sortOptions);
}
this._resize();
// this.menu = this._createMenu(this.types, function(v) { that.type(v) });
// this.type(this.options.default||this.types[0].value);
},
_resize: function() {
var currentFullHeight = this.topContainer.height();
var innerHeight = this.uiContainer.height();
var delta = currentFullHeight - innerHeight;
if (this.uiHeight !== 0) {
this.uiContainer.height(this.uiHeight-delta);
}
if (this.options.resize) {
this.options.resize();
}
if (this.options.resizeItem) {
var that = this;
this.element.children().each(function(i) {
that.options.resizeItem($(this).find(".red-ui-editableList-item-content"),i);
});
}
},
_destroy: function() {
},
_refreshFilter: function() {
var that = this;
var count = 0;
if (!this.activeFilter) {
return this.element.children().show();
}
var items = this.items();
items.each(function (i,el) {
var data = el.data('data');
try {
if (that.activeFilter(data)) {
el.parent().show();
count++;
} else {
el.parent().hide();
}
} catch(err) {
console.log(err);
el.parent().show();
count++;
}
});
return count;
},
_refreshSort: function() {
if (this.activeSort) {
var items = this.element.children();
var that = this;
items.sort(function(A,B) {
return that.activeSort($(A).find(".red-ui-editableList-item-content").data('data'),$(B).find(".red-ui-editableList-item-content").data('data'));
});
$.each(items,function(idx,li) {
that.element.append(li);
})
}
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
},
height: function(desiredHeight) {
this.uiHeight = desiredHeight;
this._resize();
},
addItem: function(data) {
var that = this;
data = data || {};
var li = $('<li>');
var added = false;
if (this.activeSort) {
var items = this.items();
var skip = false;
items.each(function(i,el) {
if (added) { return }
var itemData = el.data('data');
if (that.activeSort(data,itemData) < 0) {
li.insertBefore(el.closest("li"));
added = true;
}
});
}
if (!added) {
li.appendTo(this.element);
}
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
row.data('data',data);
if (this.options.sortable === true) {
$('<i class="red-ui-editableList-item-handle fa fa-bars"></i>').appendTo(li);
li.addClass("red-ui-editableList-item-sortable");
}
if (this.options.removable) {
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(evt) {
evt.preventDefault();
var data = row.data('data');
li.addClass("red-ui-editableList-item-deleting")
li.fadeOut(300, function() {
$(this).remove();
if (that.options.removeItem) {
that.options.removeItem(data);
}
});
});
}
if (this.options.addItem) {
var index = that.element.children().length-1;
setTimeout(function() {
that.options.addItem(row,index,data);
if (that.activeFilter) {
try {
if (!that.activeFilter(data)) {
li.hide();
}
} catch(err) {
}
}
if (!that.activeSort && that.scrollOnAdd) {
setTimeout(function() {
that.uiContainer.scrollTop(that.element.height());
},0);
}
},0);
}
},
addItems: function(items) {
for (var i=0; i<items.length;i++) {
this.addItem(items[i]);
}
},
removeItem: function(data) {
var items = this.element.children().filter(function(f) {
return data === $(this).find(".red-ui-editableList-item-content").data('data');
});
items.remove();
if (this.options.removeItem) {
this.options.removeItem(data);
}
},
items: function() {
return this.element.children().map(function(i) { return $(this).find(".red-ui-editableList-item-content"); });
},
empty: function() {
this.element.empty();
},
filter: function(filter) {
if (filter !== undefined) {
this.activeFilter = filter;
}
return this._refreshFilter();
},
sort: function(sort) {
if (sort !== undefined) {
this.activeSort = sort;
}
return this._refreshSort();
},
length: function() {
return this.element.children().length;
}
});
})(jQuery);

View File

@@ -0,0 +1,267 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.menu = (function() {
var menuItems = {};
function createMenuItem(opt) {
var item;
if (opt !== null && opt.id) {
var themeSetting = RED.settings.theme("menu."+opt.id);
if (themeSetting === false) {
return null;
}
}
function setInitialState() {
var savedStateActive = RED.settings.get("menu-" + opt.id);
if (opt.setting) {
// May need to migrate pre-0.17 setting
if (savedStateActive !== null) {
RED.settings.set(opt.setting,savedStateActive);
RED.settings.remove("menu-" + opt.id);
} else {
savedStateActive = RED.settings.get(opt.setting);
}
}
if (savedStateActive) {
link.addClass("active");
triggerAction(opt.id,true);
} else if (savedStateActive === false) {
link.removeClass("active");
triggerAction(opt.id,false);
} else if (opt.hasOwnProperty("selected")) {
if (opt.selected) {
link.addClass("active");
} else {
link.removeClass("active");
}
triggerAction(opt.id,opt.selected);
}
}
if (opt === null) {
item = $('<li class="divider"></li>');
} else {
item = $('<li></li>');
if (opt.group) {
item.addClass("menu-group-"+opt.group);
}
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
if (opt.toggle) {
linkContent += '<i class="fa fa-square pull-left"></i>';
linkContent += '<i class="fa fa-check-square pull-left"></i>';
}
if (opt.icon !== undefined) {
if (/\.png/.test(opt.icon)) {
linkContent += '<img src="'+opt.icon+'"/> ';
} else {
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
}
}
if (opt.sublabel) {
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
} else {
linkContent += '<span class="menu-label">'+opt.label+'</span>'
}
linkContent += '</a>';
var link = $(linkContent).appendTo(item);
menuItems[opt.id] = opt;
if (opt.onselect) {
link.click(function(e) {
e.preventDefault();
if ($(this).parent().hasClass("disabled")) {
return;
}
if (opt.toggle) {
var selected = isSelected(opt.id);
if (typeof opt.toggle === "string") {
if (!selected) {
for (var m in menuItems) {
if (menuItems.hasOwnProperty(m)) {
var mi = menuItems[m];
if (mi.id != opt.id && opt.toggle == mi.toggle) {
setSelected(mi.id,false);
}
}
}
setSelected(opt.id,true);
}
} else {
setSelected(opt.id, !selected);
}
} else {
triggerAction(opt.id);
}
});
if (opt.toggle) {
setInitialState();
}
} else if (opt.href) {
link.attr("target","_blank").attr("href",opt.href);
} else if (!opt.options) {
item.addClass("disabled");
link.click(function(event) {
event.preventDefault();
});
}
if (opt.options) {
item.addClass("dropdown-submenu pull-left");
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
for (var i=0;i<opt.options.length;i++) {
var li = createMenuItem(opt.options[i]);
if (li) {
li.appendTo(submenu);
}
}
}
if (opt.disabled) {
item.addClass("disabled");
}
}
return item;
}
function createMenu(options) {
var menuParent = $("#"+options.id);
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"});
if (menuParent.length === 1) {
topMenu.insertAfter(menuParent);
}
var lastAddedSeparator = false;
for (var i=0;i<options.options.length;i++) {
var opt = options.options[i];
if (opt !== null || !lastAddedSeparator) {
var li = createMenuItem(opt);
if (li) {
li.appendTo(topMenu);
lastAddedSeparator = (opt === null);
}
}
}
return topMenu;
}
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 isSelected(id) {
return $("#" + id).hasClass("active");
}
function setSelected(id,state) {
if (isSelected(id) == state) {
return;
}
var opt = menuItems[id];
if (state) {
$("#"+id).addClass("active");
} else {
$("#"+id).removeClass("active");
}
if (opt && opt.onselect) {
triggerAction(opt.id,state);
}
RED.settings.set(opt.setting||("menu-"+opt.id), state);
}
function toggleSelected(id) {
setSelected(id,!isSelected(id));
}
function setDisabled(id,state) {
if (state) {
$("#"+id).parent().addClass("disabled");
} else {
$("#"+id).parent().removeClass("disabled");
}
}
function addItem(id,opt) {
var item = createMenuItem(opt);
if (opt.group) {
var groupItems = $("#"+id+"-submenu").children(".menu-group-"+opt.group);
if (groupItems.length === 0) {
item.appendTo("#"+id+"-submenu");
} else {
for (var i=0;i<groupItems.length;i++) {
var groupItem = groupItems[i];
var label = $(groupItem).find(".menu-label").html();
if (opt.label < label) {
$(groupItem).before(item);
break;
}
}
if (i === groupItems.length) {
item.appendTo("#"+id+"-submenu");
}
}
} else {
item.appendTo("#"+id+"-submenu");
}
}
function removeItem(id) {
$("#"+id).parent().remove();
}
function setAction(id,action) {
var opt = menuItems[id];
if (opt) {
opt.onselect = action;
}
}
return {
init: createMenu,
setSelected: setSelected,
isSelected: isSelected,
toggleSelected: toggleSelected,
setDisabled: setDisabled,
addItem: addItem,
removeItem: removeItem,
setAction: setAction
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
}
})();

View File

@@ -0,0 +1,81 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.panels = (function() {
function createPanel(options) {
var container = options.container || $("#"+options.id);
var children = container.children();
if (children.length !== 2) {
throw new Error("Container must have exactly two children");
}
container.addClass("red-ui-panels");
var separator = $('<div class="red-ui-panels-separator"></div>').insertAfter(children[0]);
var startPosition;
var panelHeights = [];
var modifiedHeights = false;
var panelRatio;
separator.draggable({
axis: "y",
containment: container,
scroll: false,
start:function(event,ui) {
var height = container.height();
startPosition = ui.position.top;
panelHeights = [$(children[0]).height(),$(children[1]).height()];
},
drag: function(event,ui) {
var height = container.height();
var delta = ui.position.top-startPosition;
var newHeights = [panelHeights[0]+delta,panelHeights[1]-delta];
$(children[0]).height(newHeights[0]);
$(children[1]).height(newHeights[1]);
if (options.resize) {
options.resize(newHeights[0],newHeights[1]);
}
ui.position.top -= delta;
panelRatio = newHeights[0]/height;
},
stop:function(event,ui) {
modifiedHeights = true;
}
});
return {
resize: function(height) {
var panelHeights = [$(children[0]).height(),$(children[1]).height()];
container.height(height);
if (modifiedHeights) {
var topPanelHeight = panelRatio*height;
var bottomPanelHeight = height - topPanelHeight - 48;
panelHeights = [topPanelHeight,bottomPanelHeight];
$(children[0]).height(panelHeights[0]);
$(children[1]).height(panelHeights[1]);
}
if (options.resize) {
options.resize(panelHeights[0],panelHeights[1]);
}
}
}
}
return {
create: createPanel
}
})();

View File

@@ -0,0 +1,171 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.popover = (function() {
var deltaSizes = {
"default": {
top: 10,
leftRight: 17,
leftLeft: 25,
leftBottom: 8,
},
"small": {
top: 5,
leftRight: 17,
leftLeft: 16,
leftBottom: 3,
}
}
function createPopover(options) {
var target = options.target;
var direction = options.direction || "right";
var trigger = options.trigger;
var content = options.content;
var delay = options.delay;
var autoClose = options.autoClose;
var width = options.width||"auto";
var size = options.size||"default";
if (!deltaSizes[size]) {
throw new Error("Invalid RED.popover size value:",size);
}
var timer = null;
var active;
var div;
var openPopup = function(instant) {
if (active) {
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>');
if (size !== "default") {
div.addClass("red-ui-popover-size-"+size);
}
if (typeof content === 'function') {
var result = content.call(res);
if (result === null) {
return;
}
if (typeof result === 'string') {
div.text(result);
} else {
div.append(result);
}
} else {
div.html(content);
}
if (width !== "auto") {
div.width(width);
}
div.appendTo("body");
var targetPos = target.offset();
var targetWidth = target.outerWidth();
var targetHeight = target.outerHeight();
var divHeight = div.height();
var divWidth = div.width();
if (direction === 'right') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left+targetWidth+deltaSizes[size].leftRight});
} else if (direction === 'left') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth});
} else if (direction === 'bottom') {
div.css({top: targetPos.top+targetHeight+deltaSizes[size].top,left:targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom});
}
if (instant) {
div.show();
} else {
div.fadeIn("fast");
}
}
}
var closePopup = function(instant) {
if (!active) {
if (div) {
if (instant) {
$(this).remove();
} else {
div.fadeOut("fast",function() {
$(this).remove();
});
}
div = null;
}
}
}
if (trigger === 'hover') {
target.on('mouseenter',function(e) {
clearTimeout(timer);
active = true;
timer = setTimeout(openPopup,delay.show);
});
target.on('mouseleave', function(e) {
if (timer) {
clearTimeout(timer);
}
active = false;
setTimeout(closePopup,delay.hide);
});
} else if (trigger === 'click') {
target.click(function(e) {
e.preventDefault();
e.stopPropagation();
active = !active;
if (!active) {
closePopup();
} else {
openPopup();
}
});
} else if (autoClose) {
setTimeout(function() {
active = false;
closePopup();
},autoClose);
}
var res = {
setContent: function(_content) {
content = _content;
return res;
},
open: function (instant) {
active = true;
openPopup(instant);
return res;
},
close: function (instant) {
active = false;
closePopup(instant);
return res;
}
}
return res;
}
return {
create: createPopover,
tooltip: function(target,content) {
RED.popover.create({
target:target,
trigger: "hover",
size: "small",
direction: "bottom",
content: content,
delay: { show: 550, hide: 10 }
});
}
}
})();

View File

@@ -0,0 +1,99 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
(function($) {
$.widget( "nodered.searchBox", {
_create: function() {
var that = this;
this.currentTimeout = null;
this.lastSent = "";
this.element.val("");
this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container");
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) {
e.preventDefault();
that.element.val("");
that._change("",true);
that.element.focus();
});
this.resultCount = $('<span>',{class:"red-ui-searchBox-resultCount hide"}).appendTo(this.uiContainer);
this.element.val("");
this.element.on("keydown",function(evt) {
if (evt.keyCode === 27) {
that.element.val("");
}
})
this.element.on("keyup",function(evt) {
that._change($(this).val());
});
this.element.on("focus",function() {
$("body").one("mousedown",function() {
that.element.blur();
});
});
},
_change: function(val,instant) {
var fireEvent = false;
if (val === "") {
this.clearButton.hide();
fireEvent = true;
} else {
this.clearButton.show();
fireEvent = (val.length >= (this.options.minimumLength||0));
}
var current = this.element.val();
fireEvent = fireEvent && current !== this.lastSent;
if (fireEvent) {
if (!instant && this.options.delay > 0) {
clearTimeout(this.currentTimeout);
var that = this;
this.currentTimeout = setTimeout(function() {
that.lastSent = that.element.val();
that._trigger("change");
},this.options.delay);
} else {
this._trigger("change");
}
}
},
value: function(val) {
if (val === undefined) {
return this.element.val();
} else {
this.element.val(val);
this._change(val);
}
},
count: function(val) {
if (val === undefined || val === null || val === "") {
this.resultCount.text("").hide();
} else {
this.resultCount.text(val).show();
}
},
change: function() {
this._trigger("change");
}
});
})(jQuery);

View File

@@ -0,0 +1,161 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.stack = (function() {
function createStack(options) {
var container = options.container;
container.addClass("red-ui-stack");
var contentHeight = 0;
var entries = [];
var visible = true;
// TODO: make this a singleton function - and watch out for stacks no longer
// in the DOM
var resizeStack = function() {
if (entries.length > 0) {
var headerHeight = 0;
entries.forEach(function(entry) {
headerHeight += entry.header.outerHeight();
});
var height = container.innerHeight();
contentHeight = height - headerHeight - (entries.length-1);
entries.forEach(function(e) {
e.contentWrap.height(contentHeight);
});
}
}
if (options.fill && options.singleExpanded) {
$(window).resize(resizeStack);
$(window).focus(resizeStack);
}
return {
add: function(entry) {
entries.push(entry);
entry.container = $('<div class="palette-category">').appendTo(container);
if (!visible) {
entry.container.hide();
}
var header = $('<div class="palette-header"></div>').appendTo(entry.container);
entry.header = header;
entry.contentWrap = $('<div></div>',{style:"position:relative"}).appendTo(entry.container);
if (options.fill) {
entry.contentWrap.css("height",contentHeight);
}
entry.content = $('<div></div>').appendTo(entry.contentWrap);
if (entry.collapsible !== false) {
header.click(function() {
if (options.singleExpanded) {
if (!entry.isExpanded()) {
for (var i=0;i<entries.length;i++) {
if (entries[i].isExpanded()) {
entries[i].collapse();
}
}
entry.expand();
}
} else {
entry.toggle();
}
});
var icon = $('<i class="fa fa-angle-down"></i>').appendTo(header);
if (entry.expanded) {
entry.container.addClass("palette-category-expanded");
icon.addClass("expanded");
} else {
entry.contentWrap.hide();
}
} else {
$('<i style="opacity: 0.5;" class="fa fa-angle-down expanded"></i>').appendTo(header);
header.css("cursor","default");
}
entry.title = $('<span></span>').html(entry.title).appendTo(header);
entry.toggle = function() {
if (entry.isExpanded()) {
entry.collapse();
return false;
} else {
entry.expand();
return true;
}
};
entry.expand = function() {
if (!entry.isExpanded()) {
if (entry.onexpand) {
entry.onexpand.call(entry);
}
if (options.singleExpanded) {
entries.forEach(function(e) {
if (e !== entry) {
e.collapse();
}
})
}
icon.addClass("expanded");
entry.container.addClass("palette-category-expanded");
entry.contentWrap.slideDown(200);
return true;
}
};
entry.collapse = function() {
if (entry.isExpanded()) {
icon.removeClass("expanded");
entry.container.removeClass("palette-category-expanded");
entry.contentWrap.slideUp(200);
return true;
}
};
entry.isExpanded = function() {
return entry.container.hasClass("palette-category-expanded");
};
if (options.fill && options.singleExpanded) {
resizeStack();
}
return entry;
},
hide: function() {
visible = false;
entries.forEach(function(entry) {
entry.container.hide();
});
return this;
},
show: function() {
visible = true;
entries.forEach(function(entry) {
entry.container.show();
});
return this;
},
resize: function() {
if (resizeStack) {
resizeStack();
}
}
}
}
return {
create: createStack
}
})();

View File

@@ -0,0 +1,508 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.tabs = (function() {
var defaultTabIcon = "fa fa-lemon-o";
function createTabs(options) {
var tabs = {};
var pinnedTabsCount = 0;
var currentTabWidth;
var currentActiveTabWidth = 0;
var collapsibleMenu;
var ul = options.element || $("#"+options.id);
var wrapper = ul.wrap( "<div>" ).parent();
var scrollContainer = ul.wrap( "<div>" ).parent();
wrapper.addClass("red-ui-tabs");
if (options.vertical) {
wrapper.addClass("red-ui-tabs-vertical");
}
if (options.addButton && typeof options.addButton === 'function') {
wrapper.addClass("red-ui-tabs-add");
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
addButton.find('a').click(function(evt) {
evt.preventDefault();
options.addButton();
})
}
var scrollLeft;
var scrollRight;
if (options.scrollable) {
wrapper.addClass("red-ui-tabs-scrollable");
scrollContainer.addClass("red-ui-tabs-scroll-container");
scrollContainer.scroll(updateScroll);
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
}
if (options.collapsible) {
// var dropDown = $('<div>',{class:"red-ui-tabs-select"}).appendTo(wrapper);
// ul.hide();
wrapper.addClass("red-ui-tabs-collapsible");
var collapsedButtonsRow = $('<div class="red-ui-tab-link-buttons"></div>').appendTo(wrapper);
var selectButton = $('<a href="#"><i class="fa fa-caret-down"></i></a>').appendTo(collapsedButtonsRow);
selectButton.addClass("red-ui-tab-link-button-menu")
selectButton.click(function(evt) {
evt.preventDefault();
if (!collapsibleMenu) {
var pinnedOptions = [];
var options = [];
ul.children().each(function(i,el) {
var id = $(el).data('tabId');
var opt = {
id:"red-ui-tabs-menu-option-"+id,
icon: tabs[id].iconClass || defaultTabIcon,
label: tabs[id].name,
onselect: function() {
activateTab(id);
}
};
if (tabs[id].pinned) {
pinnedOptions.push(opt);
} else {
options.push(opt);
}
});
options = pinnedOptions.concat(options);
collapsibleMenu = RED.menu.init({id:"debug-message-option-menu",options: options});
collapsibleMenu.css({
position: "absolute"
})
collapsibleMenu.on('mouseleave', function(){ $(this).hide() });
collapsibleMenu.on('mouseup', function() { $(this).hide() });
collapsibleMenu.appendTo("body");
}
var elementPos = selectButton.offset();
collapsibleMenu.css({
top: (elementPos.top+selectButton.height()-20)+"px",
left: (elementPos.left - collapsibleMenu.width() + selectButton.width())+"px"
})
collapsibleMenu.toggle();
})
}
function scrollEventHandler(evt,dir) {
evt.preventDefault();
if ($(this).hasClass('disabled')) {
return;
}
var currentScrollLeft = scrollContainer.scrollLeft();
scrollContainer.animate( { scrollLeft: dir }, 100);
var interval = setInterval(function() {
var newScrollLeft = scrollContainer.scrollLeft()
if (newScrollLeft === currentScrollLeft) {
clearInterval(interval);
return;
}
currentScrollLeft = newScrollLeft;
scrollContainer.animate( { scrollLeft: dir }, 100);
},100);
$(this).one('mouseup',function() {
clearInterval(interval);
})
}
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
function onTabClick() {
if (options.onclick) {
options.onclick(tabs[$(this).attr('href').slice(1)]);
}
activateTab($(this));
return false;
}
function updateScroll() {
if (ul.children().length !== 0) {
var sl = scrollContainer.scrollLeft();
var scWidth = scrollContainer.width();
var ulWidth = ul.width();
if (sl === 0) {
scrollLeft.hide();
} else {
scrollLeft.show();
}
if (sl === ulWidth-scWidth) {
scrollRight.hide();
} else {
scrollRight.show();
}
}
}
function onTabDblClick() {
if (options.ondblclick) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
}
return false;
}
function activateTab(link) {
if (typeof link === "string") {
link = ul.find("a[href='#"+link+"']");
}
if (link.length === 0) {
return;
}
if (!link.parent().hasClass("active")) {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active");
var parentId = link.parent().attr('id');
wrapper.find(".red-ui-tab-link-button").removeClass("active selected");
$("#"+parentId+"-link-button").addClass("active selected");
if (options.scrollable) {
var pos = link.parent().position().left;
if (pos-21 < 0) {
scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300);
} else if (pos + 120 > scrollContainer.width()) {
scrollContainer.animate( { scrollLeft: '+='+(pos + 140-scrollContainer.width()) }, 300);
}
}
if (options.onchange) {
options.onchange(tabs[link.attr('href').slice(1)]);
}
updateTabWidths();
setTimeout(function() {
ul.children().css({"transition": ""});
},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() {
if (options.vertical) {
return;
}
var tabs = ul.find("li.red-ui-tab");
var width = wrapper.width();
var tabCount = tabs.size();
var tabWidth;
if (options.collapsible) {
tabWidth = width - collapsedButtonsRow.width()-10;
if (tabWidth < 198) {
var delta = 198 - tabWidth;
var b = collapsedButtonsRow.find("a:last").prev();
while (b.is(":not(:visible)")) {
b = b.prev();
}
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
b.hide();
}
tabWidth = width - collapsedButtonsRow.width()-10;
} else {
var space = width - 198 - collapsedButtonsRow.width();
if (space > 40) {
collapsedButtonsRow.find("a:not(:visible):first").show();
tabWidth = width - collapsedButtonsRow.width()-10;
}
}
tabs.css({width:tabWidth});
} else {
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%";
if (options.scrollable) {
tabWidth = Math.max(tabWidth,140);
currentTabWidth = tabWidth+"px";
currentActiveTabWidth = 0;
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
ul.width(listWidth);
updateScroll();
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
} else {
currentActiveTabWidth = 0;
}
}
if (options.collapsible) {
console.log(currentTabWidth);
}
tabs.css({width:currentTabWidth});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
} else {
ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show();
ul.find(".red-ui-tab-label").css({paddingLeft:""})
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
}
}
}
ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick);
setTimeout(function() {
updateTabWidths();
},0);
function removeTab(id) {
var li = ul.find("a[href='#"+id+"']").parent();
if (li.hasClass("active")) {
var tab = li.prev();
if (tab.size() === 0) {
tab = li.next();
}
activateTab(tab.find("a"));
}
li.remove();
if (tabs[id].pinned) {
pinnedTabsCount--;
}
if (options.onremove) {
options.onremove(tabs[id]);
}
delete tabs[id];
updateTabWidths();
collapsibleMenu = null;
}
return {
addTab: function(tab) {
tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id);
if (options.maximumTabWidth) {
li.css("maxWidth",options.maximumTabWidth+"px");
}
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
} else if (tab.iconClass) {
$('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
}
var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link);
span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label));
if (options.collapsible) {
li.addClass("red-ui-tab-pinned");
var pinnedLink = $('<a href="#'+tab.id+'" class="red-ui-tab-link-button"></a>');
if (tab.pinned) {
if (pinnedTabsCount === 0) {
pinnedLink.prependTo(collapsedButtonsRow)
} else {
pinnedLink.insertAfter(collapsedButtonsRow.find("a.red-ui-tab-link-button-pinned:last"));
}
} else {
pinnedLink.insertBefore(collapsedButtonsRow.find("a:last"));
}
pinnedLink.attr('id',li.attr('id')+"-link-button");
if (tab.iconClass) {
$('<i>',{class:tab.iconClass}).appendTo(pinnedLink);
} else {
$('<i>',{class:defaultTabIcon}).appendTo(pinnedLink);
}
pinnedLink.click(function(evt) {
evt.preventDefault();
activateTab(tab.id);
});
if (tab.pinned) {
pinnedLink.addClass("red-ui-tab-link-button-pinned");
pinnedTabsCount++;
}
RED.popover.tooltip($(pinnedLink), tab.name);
}
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.append('<i class="fa fa-times" />');
closeLink.on("click",function(event) {
event.preventDefault();
removeTab(tab.id);
});
}
if (options.onadd) {
options.onadd(tab);
}
link.attr("title",tab.label);
if (ul.find("li.red-ui-tab").size() == 1) {
activateTab(link);
}
if (options.onreorder) {
var originalTabOrder;
var tabDragIndex;
var tabElements = [];
var startDragIndex;
li.draggable({
axis:"x",
distance: 20,
start: function(event,ui) {
originalTabOrder = [];
tabElements = [];
ul.children().each(function(i) {
tabElements[i] = {
el:$(this),
text: $(this).text(),
left: $(this).position().left,
width: $(this).width()
};
if ($(this).is(li)) {
tabDragIndex = i;
startDragIndex = i;
}
originalTabOrder.push($(this).data("tabId"));
});
ul.children().each(function(i) {
if (i!==tabDragIndex) {
$(this).css({
position: 'absolute',
left: tabElements[i].left+"px",
width: tabElements[i].width+2,
transition: "left 0.3s"
});
}
})
if (!li.hasClass('active')) {
li.css({'zIndex':1});
}
},
drag: function(event,ui) {
ui.position.left += tabElements[tabDragIndex].left+scrollContainer.scrollLeft();
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2 - scrollContainer.scrollLeft();
for (var i=0;i<tabElements.length;i++) {
if (i === tabDragIndex) {
continue;
}
if (tabCenter > tabElements[i].left && tabCenter < tabElements[i].left+tabElements[i].width) {
if (i < tabDragIndex) {
tabElements[i].left += tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertBefore(tabElements[i].el);
} else {
tabElements[i].left -= tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertAfter(tabElements[i].el);
}
tabElements[i].el.css({left:tabElements[i].left+"px"});
tabElements.splice(i, 0, tabElements.splice(tabDragIndex, 1)[0]);
tabDragIndex = i;
break;
}
}
},
stop: function(event,ui) {
ul.children().css({position:"relative",left:"",transition:""});
if (!li.hasClass('active')) {
li.css({zIndex:""});
}
updateTabWidths();
if (startDragIndex !== tabDragIndex) {
options.onreorder(originalTabOrder, $.makeArray(ul.children().map(function() { return $(this).data('tabId');})));
}
activateTab(tabElements[tabDragIndex].el.data('tabId'));
}
})
}
setTimeout(function() {
updateTabWidths();
},10);
collapsibleMenu = null;
},
removeTab: removeTab,
activateTab: activateTab,
nextTab: activateNextTab,
previousTab: activatePreviousTab,
resize: updateTabWidths,
count: function() {
return ul.find("li.red-ui-tab").size();
},
contains: function(id) {
return ul.find("a[href='#"+id+"']").length > 0;
},
renameTab: function(id,label) {
tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths();
},
order: function(order) {
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
if (existingTabOrder.length !== order.length) {
return
}
var i;
var match = true;
for (i=0;i<order.length;i++) {
if (order[i] !== existingTabOrder[i]) {
match = false;
break;
}
}
if (match) {
return;
}
var existingTabMap = {};
var existingTabs = ul.children().detach().each(function() {
existingTabMap[$(this).data("tabId")] = $(this);
});
for (i=0;i<order.length;i++) {
existingTabMap[order[i]].appendTo(ul);
}
}
}
}
return {
create: createTabs
}
})();

View File

@@ -0,0 +1,692 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
(function($) {
var contextParse = function(v) {
var parts = RED.utils.parseContextKey(v);
return {
option: parts.store,
value: parts.key
}
}
var contextExport = function(v,opt) {
if (!opt) {
return v;
}
var store = ((typeof opt === "string")?opt:opt.value)
if (store !== RED.settings.context.default) {
return "#:("+store+")::"+v;
} else {
return v;
}
}
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
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;}},
expand: function() {
var that = this;
var value = this.value();
try {
value = JSON.stringify(JSON.parse(value),null,4);
} catch(err) {
}
RED.editor.editJSON({
value: value,
complete: function(v) {
var value = v;
try {
value = JSON.stringify(JSON.parse(v));
} catch(err) {
}
that.value(value);
}
})
}
},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"},
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"));
}
})
}
},
bin: {
value: "bin",
label: "buffer",
icon: "red/images/typedInput/bin.png",
expand: function() {
var that = this;
RED.editor.editBuffer({
value: this.value(),
complete: function(v) {
that.value(v);
}
})
}
},
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.png"
}
};
var nlsd = false;
$.widget( "nodered.typedInput", {
_create: function() {
try {
if (!nlsd && RED && RED._) {
for (var i in allOptions) {
if (allOptions.hasOwnProperty(i)) {
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
}
}
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'}
})
if (contextOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
}
}
nlsd = true;
var that = this;
this.disarmClick = false;
this.input = $('<input type="text"></input>');
this.input.insertAfter(this.element);
this.input.val(this.element.val());
this.element.addClass('red-ui-typedInput');
this.uiWidth = this.element.outerWidth();
this.elementDiv = this.input.wrap("<div>").parent().addClass('red-ui-typedInput-input');
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) {
this.input.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
} else {
this.uiSelect.width(this.uiWidth);
}
["Right","Left"].forEach(function(d) {
var m = that.element.css("margin"+d);
that.uiSelect.css("margin"+d,m);
that.input.css("margin"+d,0);
});
this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden');
this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
$('<i class="red-ui-typedInput-icon fa fa-sort-desc"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
this.types(this.options.types);
if (this.options.typeField) {
this.typeField = $(this.options.typeField).hide();
var t = this.typeField.val();
if (t && this.typeMap[t]) {
this.options.default = t;
}
} else {
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
}
this.input.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.input.on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
});
this.input.on('change', function() {
that.validate();
that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value());
})
this.selectTrigger.click(function(event) {
event.preventDefault();
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 = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
RED.popover.tooltip(this.optionSelectLabel,function() {
return that.optionValue;
});
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
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="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value);
}catch(err) {
console.log(err.stack);
}
},
_showTypeMenu: function() {
if (this.typeList.length > 1) {
this._showMenu(this.menu,this.selectTrigger);
this.menu.find("[value='"+this.propertyType+"']").focus();
} else {
this.input.focus();
}
},
_showOptionSelectMenu: function() {
if (this.optionMenu) {
this.optionMenu.css({
minWidth:this.optionSelectLabel.width()
});
this._showMenu(this.optionMenu,this.optionSelectTrigger);
var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first");
}
selectedOption.focus();
}
},
_hideMenu: function(menu) {
$(document).off("mousedown.close-property-select");
menu.hide();
if (this.elementDiv.is(":visible")) {
this.input.focus();
} else if (this.optionSelectTrigger.is(":visible")){
this.optionSelectTrigger.focus();
} else {
this.selectTrigger.focus();
}
},
_createMenu: function(opts,callback) {
var that = this;
var menu = $("<div>").addClass("red-ui-typedInput-options");
opts.forEach(function(opt) {
if (typeof opt === 'string') {
opt = {value:opt,label:opt};
}
var op = $('<a href="#"></a>').attr("value",opt.value).appendTo(menu);
if (opt.label) {
op.text(opt.label);
}
if (opt.icon) {
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(op);
} else if (opt.icon.indexOf("/") !== -1) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
} else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
}
} else {
op.css({paddingLeft: "18px"});
}
if (!opt.icon && !opt.label) {
op.text(opt.value);
}
op.click(function(event) {
event.preventDefault();
callback(opt.value);
that._hideMenu(menu);
});
});
menu.css({
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;
},
_showMenu: function(menu,relativeTo) {
if (this.disarmClick) {
this.disarmClick = false;
return
}
var that = this;
var pos = relativeTo.offset();
var height = relativeTo.height();
var menuHeight = menu.height();
var top = (height+pos.top-3);
if (top+menuHeight > $(window).height()) {
top -= (top+menuHeight)-$(window).height()+5;
}
menu.css({
top: top+"px",
left: (2+pos.left)+"px",
});
menu.slideDown(100);
this._delay(function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
$(document).on("mousedown.close-property-select", function(event) {
if(!$(event.target).closest(menu).length) {
that._hideMenu(menu);
}
if ($(event.target).closest(relativeTo).length) {
that.disarmClick = true;
event.preventDefault();
}
})
});
},
_getLabelWidth: function(label) {
var labelWidth = label.outerWidth();
if (labelWidth === 0) {
var container = $('<div class="red-ui-typedInput-container"></div>').css({
position:"absolute",
top:0,
left:-1000
}).appendTo(document.body);
var newTrigger = label.clone().appendTo(container);
labelWidth = newTrigger.outerWidth();
container.remove();
}
return labelWidth;
},
_resize: function() {
if (this.uiWidth !== null) {
this.uiSelect.width(this.uiWidth);
}
var type = this.typeMap[this.propertyType];
if (type && type.hasValue === false) {
this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else {
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');
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
// if (this.optionSelectTrigger) {
// this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
// }
if (this.optionSelectTrigger) {
if (type && type.options && type.hasValue === true) {
this.optionSelectLabel.css({'left':'auto'})
var lw = this._getLabelWidth(this.optionSelectLabel);
this.optionSelectTrigger.css({'width':(23+lw)+"px"});
this.elementDiv.css('right',(23+lw)+"px");
this.input.css({
'border-top-right-radius': 0,
'border-bottom-right-radius': 0
});
} else {
this.optionSelectLabel.css({'left':'0'})
this.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'});
if (!this.optionExpandButton.is(":visible")) {
this.elementDiv.css({'right':0});
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
}
}
}
},
_updateOptionSelectLabel: function(o) {
var opt = this.typeMap[this.propertyType];
this.optionSelectLabel.empty();
if (o.icon) {
if (o.icon.indexOf("<") === 0) {
$(o.icon).prependTo(this.optionSelectLabel);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:o.icon,style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else {
// icon class
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
}
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else {
this.optionSelectLabel.text(o.value);
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
}
},
_destroy: function() {
if (this.optionMenu) {
this.optionMenu.remove();
}
this.menu.remove();
},
types: function(types) {
var that = this;
var currentType = this.type();
this.typeMap = {};
this.typeList = types.map(function(opt) {
var result;
if (typeof opt === 'string') {
result = allOptions[opt];
} else {
result = opt;
}
that.typeMap[result.value] = result;
return result;
});
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
this.selectTrigger.find(".fa-sort-desc").toggle(this.typeList.length > 1)
if (this.menu) {
this.menu.remove();
}
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value);
} else {
this.propertyType = null;
this.type(currentType);
}
setTimeout(function() {that._resize();},0);
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
},
value: function(value) {
if (!arguments.length) {
var v = this.input.val();
if (this.typeMap[this.propertyType].export) {
v = this.typeMap[this.propertyType].export(v,this.optionValue)
}
return v;
} else {
var selectedOption;
if (this.typeMap[this.propertyType].options) {
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) {
var op = this.typeMap[this.propertyType].options[i];
if (typeof op === "string") {
if (op === value) {
selectedOption = this.activeOptions[op];
break;
}
} else if (op.value === value) {
selectedOption = op;
break;
}
}
if (!selectedOption) {
selectedOption = {value:""}
}
this._updateOptionSelectLabel(selectedOption)
}
this.input.val(value);
this.input.trigger('change',this.type(),value);
}
},
type: function(type) {
if (!arguments.length) {
return this.propertyType;
} else {
var that = this;
var opt = this.typeMap[type];
if (opt && this.propertyType !== type) {
this.propertyType = type;
if (this.typeField) {
this.typeField.val(type);
}
this.selectLabel.empty();
var image;
if (opt.icon) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
} else {
this.selectLabel.text(opt.label);
}
if (opt.options) {
if (this.optionExpandButton) {
this.optionExpandButton.hide();
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
if (!opt.hasValue) {
this.elementDiv.hide();
} else {
this.elementDiv.show();
}
this.activeOptions = {};
opt.options.forEach(function(o) {
if (typeof o === 'string') {
that.activeOptions[o] = {label:o,value:o};
} else {
that.activeOptions[o.value] = o;
}
});
if (!that.activeOptions.hasOwnProperty(that.optionValue)) {
that.optionValue = null;
}
this.optionMenu = this._createMenu(opt.options,function(v){
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
});
var op;
if (!opt.hasValue) {
var currentVal = this.input.val();
var validValue = false;
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string" && op === currentVal) {
that._updateOptionSelectLabel({value:currentVal});
validValue = true;
break;
} else if (op.value === currentVal) {
that._updateOptionSelectLabel(op);
validValue = true;
break;
}
}
if (!validValue) {
op = opt.options[0];
if (typeof op === "string") {
this.value(op);
that._updateOptionSelectLabel({value:op});
} else {
this.value(op.value);
that._updateOptionSelectLabel(op);
}
}
} else {
var selectedOption = this.optionValue||opt.options[0];
if (opt.parse) {
var parts = opt.parse(this.input.val());
if (parts.option) {
selectedOption = parts.option;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
parts.option = Object.keys(this.activeOptions)[0];
selectedOption = parts.option
}
}
this.input.val(parts.value);
if (opt.export) {
this.element.val(opt.export(parts.value,parts.option||selectedOption));
}
}
if (typeof selectedOption === "string") {
this.optionValue = selectedOption;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
selectedOption = Object.keys(this.activeOptions)[0];
}
if (!selectedOption) {
this.optionSelectTrigger.hide();
} else {
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
}
} else if (selectedOption) {
this.optionValue = selectedOption.value;
this._updateOptionSelectLabel(selectedOption);
} else {
this.optionSelectTrigger.hide();
}
}
}
} else {
if (this.optionMenu) {
this.optionMenu.remove();
this.optionMenu = null;
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide();
}
if (opt.hasValue === false) {
this.oldValue = this.input.val();
this.input.val("");
this.elementDiv.hide();
} else {
if (this.oldValue !== undefined) {
this.input.val(this.oldValue);
delete this.oldValue;
}
this.elementDiv.show();
}
if (this.optionExpandButton) {
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.input.trigger('change',this.propertyType,this.value());
}
if (image) {
image.onload = function() { that._resize(); }
image.onerror = function() { that._resize(); }
} else {
this._resize();
}
}
}
},
validate: function() {
var result;
var value = this.value();
var type = this.type();
if (this.typeMap[type] && this.typeMap[type].validate) {
var val = this.typeMap[type].validate;
if (typeof val === 'function') {
result = val(value);
} else {
result = val.test(value);
}
} else {
result = true;
}
if (result) {
this.uiSelect.removeClass('input-error');
} else {
this.uiSelect.addClass('input-error');
}
return result;
},
show: function() {
this.uiSelect.show();
this._resize();
},
hide: function() {
this.uiSelect.hide();
}
});
})(jQuery);

View File

@@ -0,0 +1,454 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.deploy = (function() {
var deploymentTypes = {
"full":{img:"red/images/deploy-full-o.png"},
"nodes":{img:"red/images/deploy-nodes-o.png"},
"flows":{img:"red/images/deploy-flows-o.png"}
}
var ignoreDeployWarnings = {
unknown: false,
unusedConfig: false,
invalid: false
}
var deploymentType = "full";
var deployInflight = false;
var currentDiff = null;
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy-icon").attr("src",deploymentTypes[type].img);
}
/**
* options:
* type: "default" - Button with drop-down options - no further customisation available
* type: "simple" - Button without dropdown. Customisations:
* label: the text to display - default: "Deploy"
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.png"
*/
function init(options) {
options = options || {};
var type = options.type || "default";
if (type == "default") {
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
'<span class="deploy-button-content">'+
'<img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> '+
'<span>'+RED._("deploy.deploy")+'</span>'+
'</span>'+
'<span class="deploy-button-spinner hide">'+
'<img src="red/images/spin.svg"/>'+
'</span>'+
'</a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar");
RED.menu.init({id:"btn-deploy-options",
options: [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
]
});
} else if (type == "simple") {
var label = options.label || RED._("deploy.deploy");
var icon = 'red/images/deploy-full-o.png';
if (options.hasOwnProperty('icon')) {
icon = options.icon;
}
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
'<span class="deploy-button-content">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span>'+
'</span>'+
'<span class="deploy-button-spinner hide">'+
'<img src="red/images/spin.svg"/>'+
'</span>'+
'</a>'+
'</span></li>').prependTo(".header-toolbar");
}
$('#btn-deploy').click(function(event) {
event.preventDefault();
save();
});
RED.actions.add("core:deploy-flows",save);
RED.events.on('nodes:change',function(state) {
if (state.dirty) {
window.onbeforeunload = function() {
return RED._("deploy.confirm.undeployedChanges");
}
$("#btn-deploy").removeClass("disabled");
} else {
window.onbeforeunload = null;
$("#btn-deploy").addClass("disabled");
}
});
var activeNotifyMessage;
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
if (!activeNotifyMessage) {
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
activeNotifyMessage = RED.notify(message,{
modal: true,
fixed: true,
buttons: [
{
text: RED._('deploy.confirm.button.ignore'),
click: function() {
activeNotifyMessage.close();
activeNotifyMessage = null;
}
},
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeNotifyMessage.close();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
activeNotifyMessage = null;
}
}
]
});
}
});
}
function getNodeInfo(node) {
var tabLabel = "";
if (node.z) {
var tab = RED.nodes.workspace(node.z);
if (!tab) {
tab = RED.nodes.subflow(node.z);
tabLabel = tab.name;
} else {
tabLabel = tab.label;
}
}
var label = RED.utils.getNodeLabel(node,node.id);
return {tab:tabLabel,type:node.type,label:label};
}
function sortNodeInfo(A,B) {
if (A.tab < B.tab) { return -1;}
if (A.tab > B.tab) { return 1;}
if (A.type < B.type) { return -1;}
if (A.type > B.type) { return 1;}
if (A.name < B.name) { return -1;}
if (A.name > B.name) { return 1;}
return 0;
}
function resolveConflict(currentNodes, activeDeploy) {
var message = $('<div>');
$('<p data-i18n="deploy.confirm.conflict"></p>').appendTo(message);
var conflictCheck = $('<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>').appendTo(message);
var conflictAutoMerge = $('<div class="node-dialog-confirm-conflict-row">'+
'<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>'+
'</div>').hide().appendTo(message);
var conflictManualMerge = $('<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>').hide().appendTo(message);
message.i18n();
currentDiff = null;
var buttons = [
{
text: RED._("common.label.cancel"),
click: function() {
conflictNotification.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();
conflictNotification.close();
}
}
},
{
id: "node-dialog-confirm-deploy-merge",
text: RED._("deploy.confirm.button.merge"),
class: "primary disabled",
click: function() {
if (!$("#node-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff);
conflictNotification.close();
}
}
}
];
if (activeDeploy) {
buttons.push({
id: "node-dialog-confirm-deploy-overwrite",
text: RED._("deploy.confirm.button.overwrite"),
class: "primary",
click: function() {
save(true,activeDeploy);
conflictNotification.close();
}
})
}
var conflictNotification = RED.notify(message,{
modal: true,
fixed: true,
width: 600,
buttons: buttons
});
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
})
}
function cropList(list) {
if (list.length > 5) {
var remainder = list.length - 5;
list = list.slice(0,5);
list.push(RED._("deploy.confirm.plusNMore",{count:remainder}));
}
return list;
}
function save(skipValidation,force) {
if (!$("#btn-deploy").hasClass("disabled")) {
if (!RED.user.hasPermission("flows.write")) {
RED.notify(RED._("user.errors.deploy"),"error");
return;
}
if (!skipValidation) {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
var unknownNodes = [];
var invalidNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
if (!node.valid) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0 && (node._def.hasUsers !== false)) {
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}
});
var showWarning = false;
var notificationMessage;
var notificationButtons = [];
var notification;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
showWarning = true;
invalidNodes.sort(sortNodeInfo);
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"})).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
}
if (showWarning) {
notificationButtons.unshift(
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
}
);
notification = RED.notify(notificationMessage,{
modal: true,
fixed: true,
buttons:notificationButtons
});
return;
}
}
var nns = RED.nodes.createCompleteNodeSet();
var startTime = Date.now();
$(".deploy-button-content").css('opacity',0);
$(".deploy-button-spinner").show();
$("#btn-deploy").addClass("disabled");
var data = {flows:nns};
if (!force) {
data.rev = RED.nodes.version();
}
deployInflight = true;
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
$.ajax({
url:"flows",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type":deploymentType
}
}).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>'+
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
} else {
RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
}
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
if(node.credentials) {
delete node.credentials;
}
});
RED.nodes.eachConfig(function (confNode) {
confNode.changed = false;
if (confNode.credentials) {
delete confNode.credentials;
}
});
RED.nodes.eachSubflow(function(subflow) {
subflow.changed = false;
});
RED.nodes.eachWorkspace(function(ws) {
ws.changed = false;
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.events.emit("deploy");
}).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true);
$("#btn-deploy").removeClass("disabled");
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
} else if (xhr.status === 409) {
resolveConflict(nns, true);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
} else {
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
}
}).always(function() {
deployInflight = false;
var delta = Math.max(0,300-(Date.now()-startTime));
setTimeout(function() {
$(".deploy-button-content").css('opacity',1);
$(".deploy-button-spinner").hide();
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
},delta);
});
}
}
return {
init: init,
setDeployInflight: function(state) {
deployInflight = state;
}
}
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._buffer = (function() {
var template = '<script type="text/x-red" data-template-name="_buffer"><div id="node-input-buffer-panels"><div id="node-input-buffer-panel-str" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-buffer-str"></div></div></div><div id="node-input-buffer-panel-bin" class="red-ui-panel"><div class="form-row node-text-editor-row" style="margin-top: 10px"><div class="node-text-editor" id="node-input-buffer-bin"></div></div></div></div></script>';
function stringToUTF8Array(str) {
var data = [];
var i=0, l = str.length;
for (i=0; i<l; i++) {
var char = str.charCodeAt(i);
if (char < 0x80) {
data.push(char);
} else if (char < 0x800) {
data.push(0xc0 | (char >> 6));
data.push(0x80 | (char & 0x3f));
} else if (char < 0xd800 || char >= 0xe000) {
data.push(0xe0 | (char >> 12));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
} else {
i++;
char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff));
data.push(0xf0 | (char >>18));
data.push(0x80 | ((char>>12) & 0x3f));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
}
}
return data;
}
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_buffer"
RED.view.state(RED.state.EDITING);
var bufferStringEditor = [];
var bufferBinValue;
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(JSON.stringify(bufferBinValue));
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
bufferStringEditor = RED.editor.createEditor({
id: 'node-input-buffer-str',
value: "",
mode:"ace/mode/text"
});
bufferStringEditor.getSession().setValue(value||"",-1);
bufferBinEditor = RED.editor.createEditor({
id: 'node-input-buffer-bin',
value: "",
mode:"ace/mode/text",
readOnly: true
});
var changeTimer;
var buildBuffer = function(data) {
var valid = true;
var isString = typeof data === 'string';
var binBuffer = [];
if (isString) {
bufferBinValue = stringToUTF8Array(data);
} else {
bufferBinValue = data;
}
var i=0,l=bufferBinValue.length;
var c = 0;
for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]);
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false;
break;
}
if (i>0) {
if (i%8 === 0) {
if (i%16 === 0) {
binBuffer.push("\n");
} else {
binBuffer.push(" ");
}
} else {
binBuffer.push(" ");
}
}
binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase());
}
if (valid) {
$("#node-input-buffer-type-string").toggle(isString);
$("#node-input-buffer-type-array").toggle(!isString);
bufferBinEditor.setValue(binBuffer.join(""),1);
}
return valid;
}
var bufferStringUpdate = function() {
var value = bufferStringEditor.getValue();
var isValidArray = false;
if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) {
isValidArray = true;
try {
var data = JSON.parse(value);
isValidArray = buildBuffer(data);
} catch(err) {
isValidArray = false;
}
}
if (!isValidArray) {
buildBuffer(value);
}
}
bufferStringEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(bufferStringUpdate,200);
});
bufferStringUpdate();
dialogForm.i18n();
panels = RED.panels.create({
id:"node-input-buffer-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-buffer-panel-str");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-str").css("height",(p1Height-5)+"px");
bufferStringEditor.resize();
var p2 = $("#node-input-buffer-panel-bin");
editorRow = $(p2.children()[0]);
p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-bin").css("height",(p2Height-5)+"px");
bufferBinEditor.resize();
}
});
$(".node-input-buffer-type").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("bufferEditor.modeDesc"));
RED.sidebar.info.show();
})
},
close: function() {
if (options.onclose) {
options.onclose();
}
bufferStringEditor.destroy();
bufferBinEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,325 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._expression = (function() {
var template = '<script type="text/x-red" data-template-name="_expression"><div id="node-input-expression-panels"><div id="node-input-expression-panel-expr" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-expression-legacy"><i class="fa fa-exclamation-circle"></i> <span data-i18n="expressionEditor.compatMode"></span></span><button id="node-input-expression-reformat" class="editor-button editor-button-small"><span data-i18n="expressionEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-expression"></div></div></div><div id="node-input-expression-panel-info" class="red-ui-panel"><div class="form-row"><ul id="node-input-expression-tabs"></ul><div id="node-input-expression-tab-help" class="node-input-expression-tab-content hide"><div><select id="node-input-expression-func"></select><button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button></div><div id="node-input-expression-help"></div></div><div id="node-input-expression-tab-test" class="node-input-expression-tab-content hide"><div><span style="display: inline-block; width: calc(50% - 5px);"><span data-i18n="expressionEditor.data"></span><button style="float: right; margin-right: 5px;" id="node-input-example-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></span><span style="display: inline-block; width: calc(50% - 5px);" data-i18n="expressionEditor.result"></span></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-data"></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-result"></div></div></div></div></div></script>';
var expressionTestCache = {};
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var expressionTestCacheId = options.parent||"_";
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var testDataEditor;
var testResultEditor
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
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").text("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
trayBody.addClass("node-input-expression-editor")
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'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();
});
$("#node-input-expression-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = jsonata.format(v);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
var tabs = RED.tabs.create({
element: $("#node-input-expression-tabs"),
onchange:function(tab) {
$(".node-input-expression-tab-content").hide();
tab.content.show();
trayOptions.resize();
}
})
tabs.addTab({
id: 'expression-help',
label: RED._('expressionEditor.functionReference'),
content: $("#node-input-expression-tab-help")
});
tabs.addTab({
id: 'expression-tests',
label: RED._('expressionEditor.test'),
content: $("#node-input-expression-tab-test")
});
testDataEditor = RED.editor.createEditor({
id: 'node-input-expression-test-data',
value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
mode:"ace/mode/json",
lineNumbers: false
});
var changeTimer;
$(".node-input-expression-legacy").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc"));
RED.sidebar.info.show();
})
var testExpression = function() {
var value = testDataEditor.getValue();
var parsedData;
var currentExpression = expressionEditor.getValue();
var expr;
var usesContext = false;
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
$(".node-input-expression-legacy").toggle(legacyMode);
try {
expr = jsonata(currentExpression);
expr.assign('flowContext',function(val) {
usesContext = true;
return null;
});
expr.assign('globalContext',function(val) {
usesContext = true;
return null;
});
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
return;
}
try {
parsedData = JSON.parse(value);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()}))
return;
}
try {
var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
if (usesContext) {
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
return;
}
var formattedResult;
if (result !== undefined) {
formattedResult = JSON.stringify(result,null,4);
} else {
formattedResult = RED._("expressionEditor.noMatch");
}
testResultEditor.setValue(formattedResult,-1);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
}
}
testDataEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
expressionTestCache[expressionTestCacheId] = testDataEditor.getValue();
});
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
});
testResultEditor = RED.editor.createEditor({
id: 'node-input-expression-test-result',
value: "",
mode:"ace/mode/json",
lineNumbers: false,
readOnly: true
});
panels = RED.panels.create({
id:"node-input-expression-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-expression-panel-expr");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-expression").css("height",(p1Height-5)+"px");
expressionEditor.resize();
var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child");
p2Height -= p2.outerHeight(true) + 20;
$(".node-input-expression-tab-content").height(p2Height);
$("#node-input-expression-test-data").css("height",(p2Height-5)+"px");
testDataEditor.resize();
$("#node-input-expression-test-result").css("height",(p2Height-5)+"px");
testResultEditor.resize();
}
});
$("#node-input-example-reformat").click(function(evt) {
evt.preventDefault();
var v = testDataEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
testDataEditor.getSession().setValue(v||"",-1);
});
testExpression();
},
close: function() {
if (options.onclose) {
options.onclose();
}
expressionEditor.destroy();
testDataEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,102 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._js = (function() {
var template = '<script type="text/x-red" data-template-name="_js"><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-js"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_js"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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);
}
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-js',
mode: 'ace/mode/javascript',
value: value,
globals: {
msg:true,
context:true,
RED: true,
util: true,
flow: true,
global: true,
console: true,
Buffer: true,
setTimeout: true,
clearTimeout: true,
setInterval: true,
clearInterval: true
}
});
if (options.cursor) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,118 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._json = (function() {
var template = '<script type="text/x-red" data-template-name="_json"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_json"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var checkValid = function() {
var v = expressionEditor.getValue();
try {
JSON.parse(v);
$("#node-dialog-ok").removeClass('disabled');
return true;
} catch(err) {
$("#node-dialog-ok").addClass('disabled');
return false;
}
}
var trayOptions = {
title: options.title,
width: "inherit",
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() {
if (options.requireValid && !checkValid()) {
return;
}
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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 = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-json',
value: "",
mode:"ace/mode/json"
});
expressionEditor.getSession().setValue(value||"",-1);
if (options.requireValid) {
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(checkValid,200);
});
checkValid();
}
$("#node-input-json-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,90 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._markdown = (function() {
var template = '<script type="text/x-red" data-template-name="_markdown"><div class="form-row" id="node-input-markdown-title" style="margin-bottom: 3px; text-align: right;"></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_markdown"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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 = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-markdown',
value: value,
mode:"ace/mode/markdown"
});
if (options.header) {
options.header.appendTo(tray.find('#node-input-markdown-title'));
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,553 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.keyboard = (function() {
var 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 = {}
var defaultKeyMap = {};
// FF generates some different keycodes because reasons.
var firefoxKeyCodeMap = {
59:186,
61:187,
173:189
}
function migrateOldKeymap() {
if ('localStorage' in window && window['localStorage'] !== null) {
var oldKeyMap = localStorage.getItem("keymap");
if (oldKeyMap !== null) {
localStorage.removeItem("keymap");
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.keymap = JSON.parse(oldKeyMap);
RED.settings.set('editor',currentEditorSettings);
}
}
}
function init() {
// Migrate from pre-0.18
migrateOldKeymap();
var currentEditorSettings = RED.settings.get('editor') || {};
var userKeymap = currentEditorSettings.keymap || {};
$.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)) {
if (!userKeymap.hasOwnProperty(keys[key])) {
addHandler(scope,key,keys[key],false);
}
defaultKeyMap[keys[key]] = {
scope:scope,
key:key,
user:false
};
}
}
}
}
for (var action in userKeymap) {
if (userKeymap.hasOwnProperty(action)) {
var obj = userKeymap[action];
if (obj.hasOwnProperty('key')) {
addHandler(obj.scope, obj.key, action, true);
}
}
}
});
RED.userSettings.add({
id:'keyboard',
title: RED._("keyboard.keyboard"),
get: getSettingsPane,
focus: function() {
setTimeout(function() {
$("#user-settings-tab-keyboard-filter").focus();
},200);
}
})
}
function revertToDefault(action) {
var currentAction = actionToKeyMap[action];
if (currentAction) {
removeHandler(currentAction.key);
}
if (defaultKeyMap.hasOwnProperty(action)) {
var obj = defaultKeyMap[action];
addHandler(obj.scope, obj.key, action, false);
}
}
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 = partialState||handlers;
if (evt.ctrlKey || evt.metaKey) {
slot = slot.ctrl;
}
if (slot && evt.shiftKey) {
slot = slot.shift;
}
if (slot && evt.altKey) {
slot = slot.alt;
}
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 if (Object.keys(handler).length > 0) {
partialState = handler;
evt.preventDefault();
return null;
} else {
return null;
}
} else if (handler.scope && handler.scope !== "*") {
var target = evt.target;
while (target.nodeName !== 'BODY' && target.id !== handler.scope) {
target = target.parentElement;
}
if (target.nodeName === 'BODY') {
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) {
if (typeof handler.ondown === "string") {
RED.actions.invoke(handler.ondown);
} else {
handler.ondown();
}
d3.event.preventDefault();
}
});
function addHandler(scope,key,modifiers,ondown) {
var mod = modifiers;
var cbdown = ondown;
if (typeof modifiers == "function" || typeof modifiers === "string") {
mod = {};
cbdown = modifiers;
}
var keys = [];
var i=0;
if (typeof key === 'string') {
if (typeof cbdown === 'string') {
actionToKeyMap[cbdown] = {scope:scope,key:key};
if (typeof ondown === 'boolean') {
actionToKeyMap[cbdown].user = ondown;
}
}
var parts = key.split(" ");
for (i=0;i<parts.length;i++) {
var parsedKey = parseKeySpecifier(parts[i]);
if (parsedKey) {
keys.push(parsedKey);
} else {
return;
}
}
} else {
keys.push([key,mod])
}
var slot = handlers;
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};
}
slot.scope = scope;
slot.ondown = cbdown;
}
function removeHandler(key,modifiers) {
var mod = modifiers || {};
var keys = [];
var i=0;
if (typeof key === 'string') {
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;
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];
}
if (typeof slot.ondown === "string") {
if (typeof modifiers === 'boolean' && modifiers) {
actionToKeyMap[slot.ondown] = {user: modifiers}
} else {
delete actionToKeyMap[slot.ondown];
}
}
delete slot.scope;
delete slot.ondown;
}
var cmdCtrlKey = '<span class="help-key">'+(isMac?'&#8984;':'Ctrl')+'</span>';
function formatKey(key) {
var formattedKey = isMac?key.replace(/ctrl-?/,"&#8984;"):key;
formattedKey = isMac?formattedKey.replace(/alt-?/,"&#8997;"):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>';
}
function validateKey(key) {
key = key.trim();
var parts = key.split(" ");
for (i=0;i<parts.length;i++) {
var parsedKey = parseKeySpecifier(parts[i]);
if (!parsedKey) {
return false;
}
}
return true;
}
function editShortcut(e) {
e.preventDefault();
var container = $(this);
var object = container.data('data');
if (!container.hasClass('keyboard-shortcut-entry-expanded')) {
endEditShortcut();
var key = container.find(".keyboard-shortcut-entry-key");
var scope = container.find(".keyboard-shortcut-entry-scope");
container.addClass('keyboard-shortcut-entry-expanded');
var keyInput = $('<input type="text">').attr('placeholder',RED._('keyboard.unassigned')).val(object.key||"").appendTo(key);
keyInput.on("keyup",function(e) {
if (e.keyCode === 13) {
return endEditShortcut();
}
var currentVal = $(this).val();
currentVal = currentVal.trim();
var valid = (currentVal === "" || RED.keyboard.validateKey(currentVal));
$(this).toggleClass("input-error",!valid);
})
var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
scopeSelect.i18n();
scopeSelect.val(object.scope||'*');
var div = $('<div class="keyboard-shortcut-edit button-group-vertical"></div>').appendTo(scope);
var okButton = $('<button class="editor-button editor-button-small"><i class="fa fa-check"></i></button>').appendTo(div);
var revertButton = $('<button class="editor-button editor-button-small"><i class="fa fa-reply"></i></button>').appendTo(div);
okButton.click(function(e) {
e.stopPropagation();
endEditShortcut();
});
revertButton.click(function(e) {
e.stopPropagation();
RED.keyboard.revertToDefault(object.id);
container.empty();
container.removeClass('keyboard-shortcut-entry-expanded');
var shortcut = RED.keyboard.getShortcut(object.id);
var userKeymap = RED.settings.get('keymap') || {};
var currentEditorSettings = RED.settings.get('editor') || {};
var userKeymap = currentEditorSettings.keymap || {};
userKeymap[object.id] = null;
currentEditorSettings.keymap = userKeymap;
RED.settings.set('editor',currentEditorSettings);
var obj = {
id:object.id,
scope:shortcut?shortcut.scope:undefined,
key:shortcut?shortcut.key:undefined,
user:shortcut?shortcut.user:undefined
}
buildShortcutRow(container,obj);
})
keyInput.focus();
}
}
function endEditShortcut(cancel) {
var container = $('.keyboard-shortcut-entry-expanded');
if (container.length === 1) {
var object = container.data('data');
var keyInput = container.find(".keyboard-shortcut-entry-key input");
var scopeSelect = container.find(".keyboard-shortcut-entry-scope select");
if (!cancel) {
var key = keyInput.val().trim();
var scope = scopeSelect.val();
var valid = (key === "" || RED.keyboard.validateKey(key));
if (valid) {
var current = RED.keyboard.getShortcut(object.id);
if ((!current && key) || (current && (current.scope !== scope || current.key !== key))) {
var keyDiv = container.find(".keyboard-shortcut-entry-key");
var scopeDiv = container.find(".keyboard-shortcut-entry-scope");
keyDiv.empty();
scopeDiv.empty();
if (object.key) {
RED.keyboard.remove(object.key,true);
}
container.find(".keyboard-shortcut-entry-text i").css("opacity",1);
if (key === "") {
keyDiv.parent().addClass("keyboard-shortcut-entry-unassigned");
keyDiv.append($('<span>').text(RED._('keyboard.unassigned')) );
delete object.key;
delete object.scope;
} else {
keyDiv.parent().removeClass("keyboard-shortcut-entry-unassigned");
keyDiv.append(RED.keyboard.formatKey(key))
$("<span>").text(scope).appendTo(scopeDiv);
object.key = key;
object.scope = scope;
RED.keyboard.add(object.scope,object.key,object.id,true);
}
var currentEditorSettings = RED.settings.get('editor') || {};
var userKeymap = currentEditorSettings.keymap || {};
userKeymap[object.id] = RED.keyboard.getShortcut(object.id);
currentEditorSettings.keymap = userKeymap;
RED.settings.set('editor',currentEditorSettings);
}
}
}
keyInput.remove();
scopeSelect.remove();
$('.keyboard-shortcut-edit').remove();
container.removeClass('keyboard-shortcut-entry-expanded');
}
}
function buildShortcutRow(container,object) {
var item = $('<div class="keyboard-shortcut-entry">').appendTo(container);
container.data('data',object);
var text = object.id.replace(/(^.+:([a-z]))|(-([a-z]))/g,function() {
if (arguments[5] === 0) {
return arguments[2].toUpperCase();
} else {
return " "+arguments[4].toUpperCase();
}
});
var label = $('<div>').addClass("keyboard-shortcut-entry-text").text(text).appendTo(item);
var user = $('<i class="fa fa-user"></i>').prependTo(label);
if (!object.user) {
user.css("opacity",0);
}
var key = $('<div class="keyboard-shortcut-entry-key">').appendTo(item);
if (object.key) {
key.append(RED.keyboard.formatKey(object.key));
} else {
item.addClass("keyboard-shortcut-entry-unassigned");
key.append($('<span>').text(RED._('keyboard.unassigned')) );
}
var scope = $('<div class="keyboard-shortcut-entry-scope">').appendTo(item);
$("<span>").text(object.scope === '*'?'global':object.scope||"").appendTo(scope);
container.click(editShortcut);
}
function getSettingsPane() {
var pane = $('<div id="user-settings-tab-keyboard"></div>');
$('<div class="keyboard-shortcut-entry keyboard-shortcut-list-header">'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input id="user-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key" data-i18n="keyboard.shortcut"></div>'+
'<div class="keyboard-shortcut-entry-scope" data-i18n="keyboard.scope"></div>'+
'</div>').appendTo(pane);
pane.find("input").searchBox({
delay: 100,
change: function() {
var filterValue = $(this).val().trim();
if (filterValue === "") {
shortcutList.editableList('filter', null);
} else {
filterValue = filterValue.replace(/\s/g,"");
shortcutList.editableList('filter', function(data) {
return data.id.toLowerCase().replace(/^.*:/,"").replace("-","").indexOf(filterValue) > -1;
})
}
}
});
var shortcutList = $('<ol class="keyboard-shortcut-list"></ol>').css({
position: "absolute",
top: "32px",
bottom: "0",
left: "0",
right: "0"
}).appendTo(pane).editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(container,i,object) {
buildShortcutRow(container,object);
},
});
var shortcuts = RED.actions.list();
shortcuts.sort(function(A,B) {
var Aid = A.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
var Bid = B.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
return Aid.localeCompare(Bid);
});
shortcuts.forEach(function(s) {
shortcutList.editableList('addItem',s);
});
return pane;
}
return {
init: init,
add: addHandler,
remove: removeHandler,
getShortcut: function(actionName) {
return actionToKeyMap[actionName];
},
revertToDefault: revertToDefault,
formatKey: formatKey,
validateKey: validateKey
}
})();

View File

@@ -0,0 +1,501 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.library = (function() {
var exportToLibraryDialog;
var elementPrefix = "node-input-";
var _librarySaveConfirm = '<div id="node-dialog-library-save-confirm" class="hide"><form class="form-horizontal"><div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content"></div></form></div>';
var _librarySave = '<div id="node-dialog-library-save" class="hide"><form class="form-horizontal"><div class="form-row"><label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label><input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder"></div><div class="form-row"><label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label><input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder"></div></form></div>';
var _libraryLookup = '<div id="node-dialog-library-lookup" class="hide"><form class="form-horizontal"><div class="form-row"><ul id="node-dialog-library-breadcrumbs" class="breadcrumb"><li class="active"><a href="#" data-i18n="[append]library.breadcrumb"></a></li></ul></div><div class="form-row"><div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;"><div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div></div><div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;"><div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div></div></div></form></div>';
function loadFlowLibrary() {
$.getJSON("library/flows",function(data) {
//console.log(data);
var buildMenu = function(data,root) {
var i;
var li;
var a;
var ul = document.createElement("ul");
if (root === "") {
ul.id = "menu-item-import-library-submenu";
}
ul.className = "dropdown-menu";
if (data.d) {
for (i in data.d) {
if (data.d.hasOwnProperty(i)) {
li = document.createElement("li");
li.className = "dropdown-submenu pull-left";
a = document.createElement("a");
a.href="#";
var label = i.replace(/^@.*\//,"").replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
a.innerHTML = label;
li.appendChild(a);
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
ul.appendChild(li);
}
}
}
if (data.f) {
for (i in data.f) {
if (data.f.hasOwnProperty(i)) {
li = document.createElement("li");
a = document.createElement("a");
a.href="#";
a.innerHTML = data.f[i];
a.flowName = root+(root!==""?"/":"")+data.f[i];
a.onclick = function() {
$.get('library/flows/'+this.flowName, function(data) {
RED.view.importNodes(data);
});
};
li.appendChild(a);
ul.appendChild(li);
}
}
}
return ul;
};
var examples;
if (data.d && data.d._examples_) {
examples = data.d._examples_;
delete data.d._examples_;
}
var menu = buildMenu(data,"");
$("#menu-item-import-examples").remove();
if (examples) {
RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
$("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
}
//TODO: need an api in RED.menu for this
$("#menu-item-import-library-submenu").replaceWith(menu);
});
}
function createUI(options) {
var libraryData = {};
var selectedLibraryItem = null;
var libraryEditor = null;
elementPrefix = options.elementPrefix || "node-input-";
// Orion editor has set/getText
// ACE editor has set/getValue
// normalise to set/getValue
if (options.editor.setText) {
// Orion doesn't like having pos passed in, so proxy the call to drop it
options.editor.setValue = function(text,pos) {
options.editor.setText.call(options.editor,text);
}
}
if (options.editor.getText) {
options.editor.getValue = options.editor.getText;
}
function buildFileListItem(item) {
var li = document.createElement("li");
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
return li;
}
function buildFileList(root,data) {
var ul = document.createElement("ul");
var li;
for (var i=0; i<data.length; i++) {
var v = data[i];
if (typeof v === "string") {
// directory
li = buildFileListItem(v);
li.onclick = (function () {
var dirName = v;
return function(e) {
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
$("a",bcli).click(function(e) {
$(this).parent().nextAll().remove();
$.getJSON("library/"+options.url+root+dirName,function(data) {
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
});
e.stopPropagation();
});
var bc = $("#node-dialog-library-breadcrumbs");
$(".active",bc).removeClass("active");
bc.append(bcli);
$.getJSON("library/"+options.url+root+dirName,function(data) {
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
});
}
})();
li.innerHTML = '<i class="fa fa-folder"></i> '+v+"</i>";
ul.appendChild(li);
} else {
// file
li = buildFileListItem(v);
li.innerHTML = v.name;
li.onclick = (function() {
var item = v;
return function(e) {
$(".list-selected",ul).removeClass("list-selected");
$(this).addClass("list-selected");
$.get("library/"+options.url+root+item.fn, function(data) {
selectedLibraryItem = item;
libraryEditor.setValue(data,-1);
});
}
})();
ul.appendChild(li);
}
}
return ul;
}
$('#'+elementPrefix+"name").css("width","calc(100% - 52px)").after(
'<div class="btn-group" style="margin-left:5px;">'+
'<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
'<ul class="dropdown-menu pull-right" role="menu">'+
'<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">'+RED._("library.openLibrary")+'</a></li>'+
'<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">'+RED._("library.saveToLibrary")+'</a></li>'+
'</ul></div>'
);
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
$("#node-select-library").children().remove();
var bc = $("#node-dialog-library-breadcrumbs");
bc.children().first().nextAll().remove();
libraryEditor.setValue('',-1);
$.getJSON("library/"+options.url,function(data) {
$("#node-select-library").append(buildFileList("/",data));
$("#node-dialog-library-breadcrumbs a").click(function(e) {
$(this).parent().nextAll().remove();
$("#node-select-library").children().first().replaceWith(buildFileList("/",data));
e.stopPropagation();
});
$( "#node-dialog-library-lookup" ).dialog( "open" );
});
e.preventDefault();
});
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
//var found = false;
var name = $("#"+elementPrefix+"name").val().replace(/(^\s*)|(\s*$)/g,"");
//var buildPathList = function(data,root) {
// var paths = [];
// if (data.d) {
// for (var i in data.d) {
// var dn = root+(root==""?"":"/")+i;
// var d = {
// label:dn,
// files:[]
// };
// for (var f in data.d[i].f) {
// d.files.push(data.d[i].f[f].fn.split("/").slice(-1)[0]);
// }
// paths.push(d);
// paths = paths.concat(buildPathList(data.d[i],root+(root==""?"":"/")+i));
// }
// }
// return paths;
//};
$("#node-dialog-library-save-folder").attr("value","");
var filename = name.replace(/[^\w-]/g,"-");
if (filename === "") {
filename = "unnamed-"+options.type;
}
$("#node-dialog-library-save-filename").attr("value",filename+".js");
//var paths = buildPathList(libraryData,"");
//$("#node-dialog-library-save-folder").autocomplete({
// minLength: 0,
// source: paths,
// select: function( event, ui ) {
// $("#node-dialog-library-save-filename").autocomplete({
// minLength: 0,
// source: ui.item.files
// });
// }
//});
$( "#node-dialog-library-save" ).dialog( "open" );
e.preventDefault();
});
libraryEditor = ace.edit('node-select-library-text');
libraryEditor.setTheme("ace/theme/tomorrow");
if (options.mode) {
libraryEditor.getSession().setMode(options.mode);
}
libraryEditor.setOptions({
readOnly: true,
highlightActiveLine: false,
highlightGutterLine: false
});
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
libraryEditor.$blockScrolling = Infinity;
$( "#node-dialog-library-lookup" ).dialog({
title: RED._("library.typeLibrary", {type:options.type}),
modal: true,
autoOpen: false,
width: 800,
height: 450,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.load"),
class: "primary",
click: function() {
if (selectedLibraryItem) {
for (var i=0; i<options.fields.length; i++) {
var field = options.fields[i];
$("#"+elementPrefix+field).val(selectedLibraryItem[field]);
}
options.editor.setValue(libraryEditor.getValue(),-1);
}
$( this ).dialog( "close" );
}
}
],
open: function(e) {
var form = $("form",this);
form.height(form.parent().height()-30);
$("#node-select-library-text").height("100%");
$(".form-row:last-child",form).children().height(form.height()-60);
},
resize: function(e) {
var form = $("form",this);
form.height(form.parent().height()-30);
$(".form-row:last-child",form).children().height(form.height()-60);
}
});
function saveToLibrary(overwrite) {
var name = $("#"+elementPrefix+"name").val().replace(/(^\s*)|(\s*$)/g,"");
if (name === "") {
name = RED._("library.unnamedType",{type:options.type});
}
var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
if (filename === "" || !/.+\.js$/.test(filename)) {
RED.notify(RED._("library.invalidFilename"),"warning");
return;
}
var fullpath = pathname+(pathname===""?"":"/")+filename;
if (!overwrite) {
//var pathnameParts = pathname.split("/");
//var exists = false;
//var ds = libraryData;
//for (var pnp in pathnameParts) {
// if (ds.d && pathnameParts[pnp] in ds.d) {
// ds = ds.d[pathnameParts[pnp]];
// } else {
// ds = null;
// break;
// }
//}
//if (ds && ds.f) {
// for (var f in ds.f) {
// if (ds.f[f].fn == fullpath) {
// exists = true;
// break;
// }
// }
//}
//if (exists) {
// $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
// $("#node-dialog-library-save-confirm").dialog( "open" );
// return;
//}
}
var queryArgs = [];
var data = {};
for (var i=0; i<options.fields.length; i++) {
var field = options.fields[i];
if (field == "name") {
data.name = name;
} else {
data[field] = $("#"+elementPrefix+field).val();
}
}
data.text = options.editor.getValue();
$.ajax({
url:"library/"+options.url+'/'+fullpath,
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
RED.notify(RED._("library.savedType", {type:options.type}),"success");
}).fail(function(xhr,textStatus,err) {
if (xhr.status === 401) {
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
} else {
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
}
});
}
$( "#node-dialog-library-save-confirm" ).dialog({
title: RED._("library.saveToLibrary"),
modal: true,
autoOpen: false,
width: 530,
height: 230,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.save"),
class: "primary",
click: function() {
saveToLibrary(true);
$( this ).dialog( "close" );
}
}
]
});
$( "#node-dialog-library-save" ).dialog({
title: RED._("library.saveToLibrary"),
modal: true,
autoOpen: false,
width: 530,
height: 230,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.save"),
class: "primary",
click: function() {
saveToLibrary(false);
$( this ).dialog( "close" );
}
}
]
});
}
function exportFlow() {
//TODO: don't rely on the main dialog
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
$("#node-input-library-filename").attr('nodes',JSON.stringify(nns));
exportToLibraryDialog.dialog( "open" );
}
return {
init: function() {
$(_librarySave).appendTo(document.body);
$(_librarySaveConfirm).appendTo(document.body);
$(_libraryLookup).appendTo(document.body);
RED.actions.add("core:library-export",exportFlow);
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export-library",false);
}
});
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
exportToLibraryDialog = $('<div id="library-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
title: RED._("library.exportToLibrary"),
buttons: [
{
id: "library-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "library-dialog-ok",
class: "primary",
text: RED._("common.label.export"),
click: function() {
//TODO: move this to RED.library
var flowName = $("#node-input-library-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-library-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify(RED._("library.savedNodes"),"success");
}).fail(function(xhr,textStatus,err) {
if (xhr.status === 401) {
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
} else {
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
}
});
}
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
},
close: function(e) {
}
});
exportToLibraryDialog.children(".dialog-form").append($(
'<div class="form-row">'+
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
'<input type="text" id="node-input-library-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">'+
'<input type="text" style="display: none;" />'+ // Second hidden input to prevent submit on Enter
'</div>'
));
},
create: createUI,
loadFlowLibrary: loadFlowLibrary,
export: exportFlow
}
})();

View File

@@ -0,0 +1,249 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.notifications = (function() {
/*
// Example usage for a modal dialog with buttons
var myNotification = RED.notify("This is the message to display",{
modal: true,
fixed: true,
type: 'warning',
buttons: [
{
text: "cancel",
click: function(e) {
myNotification.close();
}
},
{
text: "okay",
class:"primary",
click: function(e) {
myNotification.close();
}
}
]
});
*/
var persistentNotifications = {};
var currentNotifications = [];
var c = 0;
function notify(msg,type,fixed,timeout) {
var options = {};
if (type !== null && typeof type === 'object') {
options = type;
fixed = options.fixed;
timeout = options.timeout;
type = options.type;
}
if (options.modal) {
$("#full-shade").show();
}
if (currentNotifications.length > 4) {
var ll = currentNotifications.length;
for (var i = 0;ll > 4 && i<currentNotifications.length;i+=1) {
var notifiction = currentNotifications[i];
if (!notifiction.fixed) {
window.clearTimeout(notifiction.timeoutid);
notifiction.close();
ll -= 1;
}
}
}
var n = document.createElement("div");
n.id="red-notification-"+c;
n.className = "notification";
n.fixed = fixed;
if (type) {
n.className = "notification notification-"+type;
}
if (options.width) {
var parentWidth = $("#notifications").width();
if (options.width > parentWidth) {
var margin = -(options.width-parentWidth)/2;
$(n).css({
width: options.width+"px",
marginLeft: margin+"px"
})
}
}
n.style.display = "none";
if (typeof msg === "string") {
if (!/<p>/i.test(msg)) {
msg = "<p>"+msg+"</p>";
}
n.innerHTML = msg;
} else {
$(n).append(msg);
}
if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
options.buttons.forEach(function(buttonDef) {
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) {
b.attr('id',buttonDef.id);
}
if (buttonDef.class) {
b.addClass(buttonDef.class);
}
})
}
$("#notifications").append(n);
$(n).slideDown(300);
n.close = (function() {
var nn = n;
return function() {
if (nn.closed) {
return;
}
nn.closed = true;
currentNotifications.splice(currentNotifications.indexOf(nn),1);
if (options.id) {
delete persistentNotifications[options.id];
if (Object.keys(persistentNotifications).length === 0) {
notificationButtonWrapper.hide();
}
}
$(nn).slideUp(300, function() {
nn.parentNode.removeChild(nn);
});
if (options.modal) {
$("#full-shade").hide();
}
};
})();
n.hideNotification = (function() {
var nn = n;
return function() {
if (nn.closed) {
return
}
nn.hidden = true;
$(nn).slideUp(300);
}
})();
n.showNotification = (function() {
var nn = n;
return function() {
if (nn.closed || !nn.hidden) {
return
}
nn.hidden = false;
$(nn).slideDown(300);
}
})();
n.update = (function() {
var nn = n;
return function(msg,options) {
if (typeof msg === "string") {
if (!/<p>/i.test(msg)) {
msg = "<p>"+msg+"</p>";
}
nn.innerHTML = msg;
} else {
$(nn).empty().append(msg);
}
var timeout;
if (typeof options === 'number') {
timeout = options;
} else if (options !== undefined) {
timeout = options.timeout;
if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
options.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) {
b.attr('id',buttonDef.id);
}
if (buttonDef.class) {
b.addClass(buttonDef.class);
}
})
}
}
if (timeout !== undefined && timeout > 0) {
window.clearTimeout(nn.timeoutid);
nn.timeoutid = window.setTimeout(nn.close,timeout);
} else {
window.clearTimeout(nn.timeoutid);
}
if (nn.hidden) {
nn.showNotification();
}
}
})();
if (!fixed) {
$(n).click((function() {
var nn = n;
return function() {
nn.close();
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.close,timeout||5000);
}
currentNotifications.push(n);
if (options.id) {
persistentNotifications[options.id] = n;
notificationButtonWrapper.show();
}
c+=1;
return n;
}
RED.notify = notify;
function hidePersistent() {
for(var i in persistentNotifications) {
if (persistentNotifications.hasOwnProperty(i)) {
persistentNotifications[i].hideNotification();
}
}
}
function showPersistent() {
for(var i in persistentNotifications) {
if (persistentNotifications.hasOwnProperty(i)) {
persistentNotifications[i].showNotification();
}
}
}
var notificationButtonWrapper;
return {
init: function() {
notificationButtonWrapper = $('<li>'+
'<a id="btn-notifications" class="button" href="#">'+
'<i class="fa fa-warning"></i>'+
'</a>'+
'</li>').prependTo(".header-toolbar").hide();
$('#btn-notifications').click(function() {
showPersistent();
})
},
notify: notify
}
})();

View File

@@ -0,0 +1,967 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.palette.editor = (function() {
var disabled = false;
var editorTabs;
var filterInput;
var searchInput;
var nodeList;
var packageList;
var loadedList = [];
var filteredList = [];
var loadedIndex = {};
var typesInUse = {};
var nodeEntries = {};
var eventTimers = {};
var activeFilter = "";
function semVerCompare(A,B) {
var aParts = A.split(".").map(function(m) { return parseInt(m);});
var bParts = B.split(".").map(function(m) { return parseInt(m);});
for (var i=0;i<3;i++) {
var j = aParts[i]-bParts[i];
if (j<0) { return -1 }
if (j>0) { return 1 }
}
return 0;
}
function delayCallback(start,callback) {
var delta = Date.now() - start;
if (delta < 300) {
delta = 300;
} else {
delta = 0;
}
setTimeout(function() {
callback();
},delta);
}
function changeNodeState(id,state,shade,callback) {
shade.show();
var start = Date.now();
$.ajax({
url:"nodes/"+id,
type: "PUT",
data: JSON.stringify({
enabled: state
}),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
delayCallback(start,function() {
shade.hide();
callback();
});
}).fail(function(xhr,textStatus,err) {
delayCallback(start,function() {
shade.hide();
callback(xhr);
});
})
}
function installNodeModule(id,version,callback) {
var requestBody = {
module: id
};
if (version) {
requestBody.version = version;
}
$.ajax({
url:"nodes",
type: "POST",
data: JSON.stringify(requestBody),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
});
}
function removeNodeModule(id,callback) {
$.ajax({
url:"nodes/"+id,
type: "DELETE"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
})
}
function refreshNodeModuleList() {
for (var id in nodeEntries) {
if (nodeEntries.hasOwnProperty(id)) {
_refreshNodeModule(id);
}
}
}
function refreshNodeModule(module) {
if (!eventTimers.hasOwnProperty(module)) {
eventTimers[module] = setTimeout(function() {
delete eventTimers[module];
_refreshNodeModule(module);
},100);
}
}
function getContrastingBorder(rgbColor){
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
if (parts) {
var r = parseInt(parts[1]);
var g = parseInt(parts[2]);
var b = parseInt(parts[3]);
var yiq = ((r*299)+(g*587)+(b*114))/1000;
if (yiq > 160) {
r = Math.floor(r*0.8);
g = Math.floor(g*0.8);
b = Math.floor(b*0.8);
return "rgb("+r+","+g+","+b+")";
}
}
return rgbColor;
}
function formatUpdatedAt(dateString) {
var now = new Date();
var d = new Date(dateString);
var delta = (Date.now() - new Date(dateString).getTime())/1000;
if (delta < 60) {
return RED._('palette.editor.times.seconds');
}
delta = Math.floor(delta/60);
if (delta < 10) {
return RED._('palette.editor.times.minutes');
}
if (delta < 60) {
return RED._('palette.editor.times.minutesV',{count:delta});
}
delta = Math.floor(delta/60);
if (delta < 24) {
return RED._('palette.editor.times.hoursV',{count:delta});
}
delta = Math.floor(delta/24);
if (delta < 7) {
return RED._('palette.editor.times.daysV',{count:delta})
}
var weeks = Math.floor(delta/7);
var days = delta%7;
if (weeks < 4) {
return RED._('palette.editor.times.weeksV',{count:weeks})
}
var months = Math.floor(weeks/4);
weeks = weeks%4;
if (months < 12) {
return RED._('palette.editor.times.monthsV',{count:months})
}
var years = Math.floor(months/12);
months = months%12;
if (months === 0) {
return RED._('palette.editor.times.yearsV',{count:years})
} else {
return RED._('palette.editor.times.year'+(years>1?'s':'')+'MonthsV',{y:years,count:months})
}
}
function _refreshNodeModule(module) {
if (!nodeEntries.hasOwnProperty(module)) {
nodeEntries[module] = {info:RED.nodes.registry.getModule(module)};
var index = [module];
for (var s in nodeEntries[module].info.sets) {
if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
index.push(s);
index = index.concat(nodeEntries[module].info.sets[s].types)
}
}
nodeEntries[module].index = index.join(",").toLowerCase();
nodeList.editableList('addItem', nodeEntries[module]);
} else {
var moduleInfo = nodeEntries[module].info;
var nodeEntry = nodeEntries[module].elements;
if (nodeEntry) {
var activeTypeCount = 0;
var typeCount = 0;
var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
for (var setName in moduleInfo.sets) {
if (moduleInfo.sets.hasOwnProperty(setName)) {
var inUseCount = 0;
var set = moduleInfo.sets[setName];
var setElements = nodeEntry.sets[setName];
if (set.err) {
errorCount++;
$("<li>").text(set.err).appendTo(nodeEntry.errorList);
}
if (set.enabled) {
activeTypeCount += set.types.length;
}
typeCount += set.types.length;
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
var t = moduleInfo.sets[setName].types[i];
inUseCount += (typesInUse[t]||0);
var swatch = setElements.swatches[t];
if (set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
swatch.css({background:RED.utils.getNodeColor(t,def)});
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
} else {
swatch.css({background:"#eee",border:"1px dashed #999"})
}
} else {
swatch.css({background:"#eee",border:"1px dashed #999"})
}
}
nodeEntries[module].setUseCount[setName] = inUseCount;
nodeEntries[module].totalUseCount += inUseCount;
if (inUseCount > 0) {
setElements.enableButton.text(RED._('palette.editor.inuse'));
setElements.enableButton.addClass('disabled');
} else {
setElements.enableButton.removeClass('disabled');
if (set.enabled) {
setElements.enableButton.text(RED._('palette.editor.disable'));
} else {
setElements.enableButton.text(RED._('palette.editor.enable'));
}
}
setElements.setRow.toggleClass("palette-module-set-disabled",!set.enabled);
}
}
if (errorCount === 0) {
nodeEntry.errorRow.hide()
} else {
nodeEntry.errorRow.show();
}
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
if (nodeEntries[module].totalUseCount > 0) {
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
nodeEntry.enableButton.addClass('disabled');
nodeEntry.removeButton.hide();
} else {
nodeEntry.enableButton.removeClass('disabled');
if (moduleInfo.local) {
nodeEntry.removeButton.css('display', 'inline-block');
}
if (activeTypeCount === 0) {
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
} else {
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
}
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
}
}
if (moduleInfo.pending_version) {
nodeEntry.versionSpan.html(moduleInfo.version+' <i class="fa fa-long-arrow-right"></i> '+moduleInfo.pending_version).appendTo(nodeEntry.metaRow)
nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').show();
} else if (loadedIndex.hasOwnProperty(module)) {
if (semVerCompare(loadedIndex[module].version,moduleInfo.version) === 1) {
nodeEntry.updateButton.show();
nodeEntry.updateButton.text(RED._('palette.editor.update',{version:loadedIndex[module].version}));
} else {
nodeEntry.updateButton.hide();
}
} else {
nodeEntry.updateButton.hide();
}
}
}
function filterChange(val) {
activeFilter = val.toLowerCase();
var visible = nodeList.editableList('filter');
var size = nodeList.editableList('length');
if (val === "") {
filterInput.searchBox('count');
} else {
filterInput.searchBox('count',visible+" / "+size);
}
}
var catalogueCount;
var catalogueLoadStatus = [];
var catalogueLoadStart;
var catalogueLoadErrors = false;
var activeSort = sortModulesAZ;
function handleCatalogResponse(err,catalog,index,v) {
catalogueLoadStatus.push(err||v);
if (!err) {
if (v.modules) {
v.modules.forEach(function(m) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
})
loadedList = loadedList.concat(v.modules);
}
searchInput.searchBox('count',loadedList.length);
} else {
catalogueLoadErrors = true;
}
if (catalogueCount > 1) {
$(".palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>"+catalogueLoadStatus.length+"/"+catalogueCount);
}
if (catalogueLoadStatus.length === catalogueCount) {
if (catalogueLoadErrors) {
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000);
}
var delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#palette-module-install-shade").hide();
},Math.max(delta,0));
}
}
function initInstallTab() {
if (loadedList.length === 0) {
loadedList = [];
loadedIndex = {};
packageList.editableList('empty');
$(".palette-module-shade-status").text(RED._('palette.editor.loading'));
var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
catalogueLoadStatus = [];
catalogueLoadErrors = false;
catalogueCount = catalogues.length;
if (catalogues.length > 1) {
$(".palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>0/"+catalogues.length);
}
$("#palette-module-install-shade").show();
catalogueLoadStart = Date.now();
var handled = 0;
catalogues.forEach(function(catalog,index) {
$.getJSON(catalog, {_: new Date().getTime()},function(v) {
handleCatalogResponse(null,catalog,index,v);
refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) {
handleCatalogResponse(jqxhr,catalog,index);
}).always(function() {
handled++;
if (handled === catalogueCount) {
searchInput.searchBox('change');
}
})
});
}
}
function refreshFilteredItems() {
packageList.editableList('empty');
var currentFilter = searchInput.searchBox('value').trim();
if (currentFilter === ""){
packageList.editableList('addItem',{count:loadedList.length})
return;
}
filteredList.sort(activeSort);
for (var i=0;i<Math.min(10,filteredList.length);i++) {
packageList.editableList('addItem',filteredList[i]);
}
if (filteredList.length === 0) {
packageList.editableList('addItem',{});
}
if (filteredList.length > 10) {
packageList.editableList('addItem',{start:10,more:filteredList.length-10})
}
}
function sortModulesAZ(A,B) {
return A.info.id.localeCompare(B.info.id);
}
function sortModulesRecent(A,B) {
return -1 * (A.info.timestamp-B.info.timestamp);
}
function init() {
if (RED.settings.theme('palette.editable') === false) {
return;
}
createSettingsPane();
RED.userSettings.add({
id:'palette',
title: RED._("palette.editor.palette"),
get: getSettingsPane,
close: function() {
settingsPane.detach();
},
focus: function() {
editorTabs.resize();
setTimeout(function() {
filterInput.focus();
},200);
}
})
RED.actions.add("core:manage-palette",function() {
RED.userSettings.show('palette');
});
RED.events.on('registry:module-updated', function(ns) {
refreshNodeModule(ns.module);
});
RED.events.on('registry:node-set-enabled', function(ns) {
refreshNodeModule(ns.module);
});
RED.events.on('registry:node-set-disabled', function(ns) {
refreshNodeModule(ns.module);
});
RED.events.on('registry:node-type-added', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
}
});
RED.events.on('registry:node-type-removed', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
}
});
RED.events.on('registry:node-set-added', function(ns) {
refreshNodeModule(ns.module);
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
var installButton = filteredList[i].elements.installButton;
installButton.addClass('disabled');
installButton.text(RED._('palette.editor.installed'));
break;
}
}
});
RED.events.on('registry:node-set-removed', function(ns) {
var module = RED.nodes.registry.getModule(ns.module);
if (!module) {
var entry = nodeEntries[ns.module];
if (entry) {
nodeList.editableList('removeItem', entry);
delete nodeEntries[ns.module];
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
var installButton = filteredList[i].elements.installButton;
installButton.removeClass('disabled');
installButton.text(RED._('palette.editor.install'));
break;
}
}
}
}
});
RED.events.on('nodes:add', function(n) {
if (!/^subflow:/.test(n.type)) {
typesInUse[n.type] = (typesInUse[n.type]||0)+1;
if (typesInUse[n.type] === 1) {
var ns = RED.nodes.registry.getNodeSetForType(n.type);
refreshNodeModule(ns.module);
}
}
})
RED.events.on('nodes:remove', function(n) {
if (typesInUse.hasOwnProperty(n.type)) {
typesInUse[n.type]--;
if (typesInUse[n.type] === 0) {
delete typesInUse[n.type];
var ns = RED.nodes.registry.getNodeSetForType(n.type);
refreshNodeModule(ns.module);
}
}
})
}
var settingsPane;
function getSettingsPane() {
initInstallTab();
editorTabs.activateTab('nodes');
return settingsPane;
}
function createSettingsPane() {
settingsPane = $('<div id="user-settings-tab-palette"></div>');
var content = $('<div id="palette-editor">'+
'<ul id="palette-editor-tabs"></ul>'+
'</div>').appendTo(settingsPane);
editorTabs = RED.tabs.create({
element: settingsPane.find('#palette-editor-tabs'),
onchange:function(tab) {
content.find(".palette-editor-tab").hide();
tab.content.show();
if (filterInput) {
filterInput.searchBox('value',"");
}
if (searchInput) {
searchInput.searchBox('value',"");
}
if (tab.id === 'install') {
if (searchInput) {
searchInput.focus();
}
} else {
if (filterInput) {
filterInput.focus();
}
}
},
minimumActiveTabWidth: 110
});
var modulesTab = $('<div>',{class:"palette-editor-tab"}).appendTo(content);
editorTabs.addTab({
id: 'nodes',
label: RED._('palette.editor.tab-nodes'),
content: modulesTab
})
var filterDiv = $('<div>',{class:"palette-search"}).appendTo(modulesTab);
filterInput = $('<input type="text" data-i18n="[placeholder]palette.filter"></input>')
.appendTo(filterDiv)
.searchBox({
delay: 200,
change: function() {
filterChange($(this).val());
}
});
nodeList = $('<ol>',{id:"palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({
addButton: false,
scrollOnAdd: false,
sort: function(A,B) {
return A.info.name.localeCompare(B.info.name);
},
filter: function(data) {
if (activeFilter === "" ) {
return true;
}
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
},
addItem: function(container,i,object) {
var entry = object.info;
if (entry) {
var headerRow = $('<div>',{class:"palette-module-header"}).appendTo(container);
var titleRow = $('<div class="palette-module-meta palette-module-name"><i class="fa fa-cube"></i></div>').appendTo(headerRow);
$('<span>').text(entry.name).appendTo(titleRow);
var metaRow = $('<div class="palette-module-meta palette-module-version"><i class="fa fa-tag"></i></div>').appendTo(headerRow);
var versionSpan = $('<span>').text(entry.version).appendTo(metaRow);
var errorRow = $('<div class="palette-module-meta palette-module-errors"><i class="fa fa-warning"></i></div>').hide().appendTo(headerRow);
var errorList = $('<ul class="palette-module-error-list"></ul>').appendTo(errorRow);
var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
var setButton = $('<a href="#" class="editor-button editor-button-small palette-module-set-button"><i class="fa fa-angle-right palette-module-node-chevron"></i> </a>').appendTo(buttonRow);
var setCount = $('<span>').appendTo(setButton);
var buttonGroup = $('<div>',{class:"palette-module-button-group"}).appendTo(buttonRow);
var updateButton = $('<a href="#" class="editor-button editor-button-small"></a>').text(RED._('palette.editor.update')).appendTo(buttonGroup);
updateButton.attr('id','up_'+Math.floor(Math.random()*1000000000));
updateButton.click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled')) {
return;
}
update(entry,loadedIndex[entry.name].version,container,function(err){});
})
var removeButton = $('<a href="#" class="editor-button editor-button-small"></a>').text(RED._('palette.editor.remove')).appendTo(buttonGroup);
removeButton.attr('id','up_'+Math.floor(Math.random()*1000000000));
removeButton.click(function(evt) {
evt.preventDefault();
remove(entry,container,function(err){});
})
if (!entry.local) {
removeButton.hide();
}
var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').text(RED._('palette.editor.disableall')).appendTo(buttonGroup);
var contentRow = $('<div>',{class:"palette-module-content"}).appendTo(container);
var shade = $('<div class="palette-module-shade hide"><img src="red/images/spin.svg" class="palette-spinner"/></div>').appendTo(container);
object.elements = {
updateButton: updateButton,
removeButton: removeButton,
enableButton: enableButton,
errorRow: errorRow,
errorList: errorList,
setCount: setCount,
container: container,
shade: shade,
versionSpan: versionSpan,
sets: {}
}
setButton.click(function(evt) {
evt.preventDefault();
if (container.hasClass('expanded')) {
container.removeClass('expanded');
contentRow.slideUp();
} else {
container.addClass('expanded');
contentRow.slideDown();
}
})
var setList = Object.keys(entry.sets)
setList.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
});
setList.forEach(function(setName) {
var set = entry.sets[setName];
var setRow = $('<div>',{class:"palette-module-set"}).appendTo(contentRow);
var buttonGroup = $('<div>',{class:"palette-module-set-button-group"}).appendTo(setRow);
var typeSwatches = {};
set.types.forEach(function(t) {
var typeDiv = $('<div>',{class:"palette-module-type"}).appendTo(setRow);
typeSwatches[t] = $('<span>',{class:"palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"palette-module-type-node"}).text(t).appendTo(typeDiv);
})
var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').appendTo(buttonGroup);
enableButton.click(function(evt) {
evt.preventDefault();
if (object.setUseCount[setName] === 0) {
var currentSet = RED.nodes.registry.getNodeSet(set.id);
shade.show();
var newState = !currentSet.enabled
changeNodeState(set.id,newState,shade,function(xhr){
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
}
}
});
}
})
object.elements.sets[set.name] = {
setRow: setRow,
enableButton: enableButton,
swatches: typeSwatches
};
});
enableButton.click(function(evt) {
evt.preventDefault();
if (object.totalUseCount === 0) {
changeNodeState(entry.name,(container.hasClass('disabled')),shade,function(xhr){
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.installFailed',{module: id,message:xhr.responseJSON.message}));
}
}
});
}
})
refreshNodeModule(entry.name);
} else {
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
}
}
});
var installTab = $('<div>',{class:"palette-editor-tab hide"}).appendTo(content);
editorTabs.addTab({
id: 'install',
label: RED._('palette.editor.tab-install'),
content: installTab
})
var toolBar = $('<div>',{class:"palette-editor-toolbar"}).appendTo(installTab);
var searchDiv = $('<div>',{class:"palette-search"}).appendTo(installTab);
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
.appendTo(searchDiv)
.searchBox({
delay: 300,
change: function() {
var searchTerm = $(this).val().trim().toLowerCase();
if (searchTerm.length > 0) {
filteredList = loadedList.filter(function(m) {
return (m.index.indexOf(searchTerm) > -1);
}).map(function(f) { return {info:f}});
refreshFilteredItems();
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
} else {
searchInput.searchBox('count',loadedList.length);
packageList.editableList('empty');
packageList.editableList('addItem',{count:loadedList.length});
}
}
});
$('<span>').text(RED._("palette.editor.sort")+' ').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);
sortAZ.click(function(e) {
e.preventDefault();
if ($(this).hasClass("selected")) {
return;
}
$(this).addClass("selected");
sortRecent.removeClass("selected");
activeSort = sortModulesAZ;
refreshFilteredItems();
});
sortRecent.click(function(e) {
e.preventDefault();
if ($(this).hasClass("selected")) {
return;
}
$(this).addClass("selected");
sortAZ.removeClass("selected");
activeSort = sortModulesRecent;
refreshFilteredItems();
});
var refreshSpan = $('<span>').appendTo(toolBar);
var refreshButton = $('<a href="#" class="sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
refreshButton.click(function(e) {
e.preventDefault();
loadedList = [];
loadedIndex = {};
initInstallTab();
})
packageList = $('<ol>',{style:"position: absolute;top: 78px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(container,i,object) {
if (object.count) {
$('<div>',{class:"red-ui-search-empty"}).text(RED._('palette.editor.moduleCount',{count:object.count})).appendTo(container);
return
}
if (object.more) {
container.addClass('palette-module-more');
var moreRow = $('<div>',{class:"palette-module-header palette-module"}).appendTo(container);
var moreLink = $('<a href="#"></a>').text(RED._('palette.editor.more',{count:object.more})).appendTo(moreRow);
moreLink.click(function(e) {
e.preventDefault();
packageList.editableList('removeItem',object);
for (var i=object.start;i<Math.min(object.start+10,object.start+object.more);i++) {
packageList.editableList('addItem',filteredList[i]);
}
if (object.more > 10) {
packageList.editableList('addItem',{start:object.start+10, more:object.more-10})
}
})
return;
}
if (object.info) {
var entry = object.info;
var headerRow = $('<div>',{class:"palette-module-header"}).appendTo(container);
var titleRow = $('<div class="palette-module-meta"><i class="fa fa-cube"></i></div>').appendTo(headerRow);
$('<span>',{class:"palette-module-name"}).text(entry.name||entry.id).appendTo(titleRow);
$('<a target="_blank" class="palette-module-link"><i class="fa fa-external-link"></i></a>').attr('href',entry.url).appendTo(titleRow);
var descRow = $('<div class="palette-module-meta"></div>').appendTo(headerRow);
$('<div>',{class:"palette-module-description"}).text(entry.description).appendTo(descRow);
var metaRow = $('<div class="palette-module-meta"></div>').appendTo(headerRow);
$('<span class="palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
$('<span class="palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
var buttonGroup = $('<div>',{class:"palette-module-button-group"}).appendTo(buttonRow);
var installButton = $('<a href="#" class="editor-button editor-button-small"></a>').text(RED._('palette.editor.install')).appendTo(buttonGroup);
installButton.click(function(e) {
e.preventDefault();
if (!$(this).hasClass('disabled')) {
install(entry,container,function(xhr) {});
}
})
if (nodeEntries.hasOwnProperty(entry.id)) {
installButton.addClass('disabled');
installButton.text(RED._('palette.editor.installed'));
}
object.elements = {
installButton:installButton
}
} else {
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
}
}
});
$('<div id="palette-module-install-shade" class="palette-module-shade hide"><div class="palette-module-shade-status"></div><img src="red/images/spin.svg" class="palette-spinner"/></div>').appendTo(installTab);
}
function update(entry,version,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));
return;
}
var notification = RED.notify(RED._("palette.editor.confirm.update.body",{module:entry.name}),{
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
},
{
text: RED._("palette.editor.confirm.button.update"),
class: "primary palette-module-install-confirm-button-update",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
installNodeModule(entry.name,version,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message}));
}
}
done(xhr);
});
notification.close();
}
}
]
})
}
function remove(entry,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));
return;
}
var notification = RED.notify(RED._("palette.editor.confirm.remove.body",{module:entry.name}),{
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
},
{
text: RED._("palette.editor.confirm.button.remove"),
class: "primary palette-module-install-confirm-button-remove",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
removeNodeModule(entry.name, function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message}));
}
}
})
notification.close();
}
}
]
})
}
function install(entry,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));
return;
}
var buttons = [
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
}
];
if (entry.url) {
buttons.push({
text: RED._("palette.editor.confirm.button.review"),
class: "primary palette-module-install-confirm-button-install",
click: function() {
var url = entry.url||"";
window.open(url);
}
});
}
buttons.push({
text: RED._("palette.editor.confirm.button.install"),
class: "primary palette-module-install-confirm-button-install",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
installNodeModule(entry.id,entry.version,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}));
}
}
done(xhr);
});
notification.close();
}
});
var notification = RED.notify(RED._("palette.editor.confirm.install.body",{module:entry.id}),{
modal: true,
fixed: true,
buttons: buttons
})
}
return {
init: init,
install: install
}
})();

View File

@@ -0,0 +1,537 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var categoryContainers = {};
function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
}
}
function createCategoryContainer(originalCategory,category, labelId) {
var label = RED._(labelId, {defaultValue:category});
label = (label || category).replace(/_/g, " ");
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
'<div class="palette-content" id="palette-base-category-'+category+'">'+
'<div id="palette-'+category+'-input"></div>'+
'<div id="palette-'+category+'-output"></div>'+
'<div id="palette-'+category+'-function"></div>'+
'</div>'+
'</div>').appendTo("#palette-container");
catDiv.data('category',originalCategory);
catDiv.data('label',label);
categoryContainers[category] = {
container: catDiv,
close: function() {
catDiv.removeClass("palette-open");
catDiv.addClass("palette-closed");
$("#palette-base-category-"+category).slideUp();
$("#palette-header-"+category+" i").removeClass("expanded");
},
open: function() {
catDiv.addClass("palette-open");
catDiv.removeClass("palette-closed");
$("#palette-base-category-"+category).slideDown();
$("#palette-header-"+category+" i").addClass("expanded");
},
toggle: function() {
if (catDiv.hasClass("palette-open")) {
categoryContainers[category].close();
} else {
categoryContainers[category].open();
}
}
};
$("#palette-header-"+category).on('click', function(e) {
categoryContainers[category].toggle();
});
}
function setLabel(type, el,label, info) {
var nodeWidth = 82;
var nodeHeight = 25;
var lineHeight = 20;
var portHeight = 10;
var words = label.split(/[ -]/);
var displayLines = [];
var currentLine = words[0];
var currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
for (var i=1;i<words.length;i++) {
var newWidth = RED.view.calculateTextWidth(currentLine+" "+words[i], "palette_label", 0);
if (newWidth < nodeWidth) {
currentLine += " "+words[i];
currentLineWidth = newWidth;
} else {
displayLines.push(currentLine);
currentLine = words[i];
currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
}
}
displayLines.push(currentLine);
var lines = displayLines.join("<br/>");
var multiLineNodeHeight = 8+(lineHeight*displayLines.length);
el.css({height:multiLineNodeHeight+"px"});
var labelElement = el.find(".palette_label");
labelElement.html(lines).attr('dir', RED.text.bidi.resolveBaseTextDir(lines));
el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
var popOverContent;
try {
var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
if (label != type) {
l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b><br/><i>"+type+"</i></p>";
}
popOverContent = $(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) {
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2);
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
// NON-NLS: internal debug
console.log("Error generating pop-over label for ",type);
console.log(err.toString());
popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
}
el.data('popover').setContent(popOverContent);
}
function setIcon(element,sf) {
var iconElement = element.find(".palette_icon");
var icon_url = RED.utils.getNodeIcon(sf._def,sf);
iconElement.attr("style", "background-image: url("+icon_url+")");
}
function escapeNodeType(nt) {
return nt.replace(" ","_").replace(".","_").replace(":","_");
}
function addNodeType(nt,def) {
var nodeTypeId = escapeNodeType(nt);
if ($("#palette_node_"+nodeTypeId).length) {
return;
}
if (exclusion.indexOf(def.category)===-1) {
var originalCategory = def.category;
var category = def.category.replace(/ /g,"_");
var rootCategory = category.split("-")[0];
var d = document.createElement("div");
d.id = "palette_node_"+nodeTypeId;
d.type = nt;
var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
if (typeof def.paletteLabel !== "undefined") {
try {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
} catch(err) {
console.log("Definition error: "+nt+".paletteLabel",err);
}
}
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
d.className="palette_node";
if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
}
d.style.backgroundColor = RED.utils.getNodeColor(nt,def);
if (def.outputs > 0) {
var portOut = document.createElement("div");
portOut.className = "palette_port palette_port_output";
d.appendChild(portOut);
}
if (def.inputs > 0) {
var portIn = document.createElement("div");
portIn.className = "palette_port palette_port_input";
d.appendChild(portIn);
}
createCategory(def.category,rootCategory,category,(coreCategories.indexOf(rootCategory) !== -1)?"node-red":def.set.id);
$("#palette-"+category).append(d);
$(d).data('category',rootCategory);
d.onmousedown = function(e) { e.preventDefault(); };
var popover = RED.popover.create({
target:$(d),
trigger: "hover",
width: "300px",
content: "hi",
delay: { show: 750, hide: 50 }
});
$(d).data('popover',popover);
// $(d).popover({
// title:d.type,
// placement:"right",
// trigger: "hover",
// delay: { show: 750, hide: 50 },
// html: true,
// container:'body'
// });
$(d).click(function() {
RED.view.focus();
var helpText;
if (nt.indexOf("subflow:") === 0) {
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
} else {
helpText = $("script[data-help-name='"+d.type+"']").html()||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
}
RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp"));
});
var chart = $("#chart");
var chartOffset = chart.offset();
var chartSVG = $("#chart>svg").get(0);
var activeSpliceLink;
var mouseX;
var mouseY;
var spliceTimer;
var paletteWidth;
var paletteTop;
$(d).draggable({
helper: 'clone',
appendTo: 'body',
revert: true,
revertDuration: 50,
containment:'#main-container',
start: function() {
paletteWidth = $("#palette").width();
paletteTop = $("#palette").parent().position().top + $("#palette-container").position().top;
RED.view.focus();
},
stop: function() { d3.select('.link_splice').classed('link_splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
drag: function(e,ui) {
// TODO: this is the margin-left of palette node. Hard coding
// it here makes me sad
//console.log(ui.helper.position());
ui.position.left += 17.5;
if (def.inputs > 0 && def.outputs > 0) {
mouseX = ui.position.left-paletteWidth+(ui.helper.width()/2) - chartOffset.left + chart.scrollLeft();
mouseY = ui.position.top-paletteTop+(ui.helper.height()/2) - chartOffset.top + chart.scrollTop();
if (!spliceTimer) {
spliceTimer = setTimeout(function() {
var nodes = [];
var bestDistance = Infinity;
var bestLink = null;
if (chartSVG.getIntersectionList) {
var svgRect = chartSVG.createSVGRect();
svgRect.x = mouseX;
svgRect.y = mouseY;
svgRect.width = 1;
svgRect.height = 1;
nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
} else {
// Firefox doesn't do getIntersectionList and that
// makes us sad
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
}
for (var i=0;i<nodes.length;i++) {
if (d3.select(nodes[i]).classed('link_background')) {
var length = nodes[i].getTotalLength();
for (var j=0;j<length;j+=10) {
var p = nodes[i].getPointAtLength(j);
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
if (d2 < 200 && d2 < bestDistance) {
bestDistance = d2;
bestLink = nodes[i];
}
}
}
}
if (activeSpliceLink && activeSpliceLink !== bestLink) {
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
}
if (bestLink) {
d3.select(bestLink.parentNode).classed('link_splice',true)
} else {
d3.select('.link_splice').classed('link_splice',false);
}
if (activeSpliceLink !== bestLink) {
if (bestLink) {
$(ui.helper).data('splice',d3.select(bestLink).data()[0]);
} else {
$(ui.helper).removeData('splice');
}
}
activeSpliceLink = bestLink;
spliceTimer = null;
},200);
}
}
}
});
var nodeInfo = null;
if (nt.indexOf("subflow:") === 0) {
$(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = marked(def.info||"");
}
setLabel(nt,$(d),label,nodeInfo);
var categoryNode = $("#palette-container-"+category);
if (categoryNode.find(".palette_node").length === 1) {
categoryContainers[category].open();
}
}
}
function removeNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
var paletteNode = $("#palette_node_"+nodeTypeId);
var categoryNode = paletteNode.closest(".palette-category");
paletteNode.remove();
if (categoryNode.find(".palette_node").length === 0) {
if (categoryNode.find("i").hasClass("expanded")) {
categoryNode.find(".palette-content").slideToggle();
categoryNode.find("i").toggleClass("expanded");
}
}
}
function hideNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
var paletteNode = $("#palette_node_"+nodeTypeId);
paletteNode.hide();
var categoryNode = paletteNode.closest(".palette-category");
var cl = categoryNode.find(".palette_node");
var c = 0;
for (var i = 0; i < cl.length; i++) {
if ($(cl[i]).css('display') === 'none') { c += 1; }
}
if (c === cl.length) { categoryNode.hide(); }
}
function showNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
var paletteNode = $("#palette_node_"+nodeTypeId);
var categoryNode = paletteNode.closest(".palette-category");
categoryNode.show();
paletteNode.show();
}
function refreshNodeTypes() {
RED.nodes.eachSubflow(function(sf) {
var paletteNode = $("#palette_node_subflow_"+sf.id.replace(".","_"));
var portInput = paletteNode.find(".palette_port_input");
var portOutput = paletteNode.find(".palette_port_output");
if (portInput.length === 0 && sf.in.length > 0) {
var portIn = document.createElement("div");
portIn.className = "palette_port palette_port_input";
paletteNode.append(portIn);
} else if (portInput.length !== 0 && sf.in.length === 0) {
portInput.remove();
}
if (portOutput.length === 0 && sf.out.length > 0) {
var portOut = document.createElement("div");
portOut.className = "palette_port palette_port_output";
paletteNode.append(portOut);
} else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category');
var newCategory = (sf.category||"subflows");
if (currentCategory !== newCategory) {
var category = newCategory.replace(/ /g,"_");
createCategory(newCategory,category,category,"node-red");
var currentCategoryNode = paletteNode.closest(".palette-category");
var newCategoryNode = $("#palette-"+category);
newCategoryNode.append(paletteNode);
if (newCategoryNode.find(".palette_node").length === 1) {
categoryContainers[category].open();
}
paletteNode.data('category',newCategory);
if (currentCategoryNode.find(".palette_node").length === 0) {
if (currentCategoryNode.find("i").hasClass("expanded")) {
currentCategoryNode.find(".palette-content").slideToggle();
currentCategoryNode.find("i").toggleClass("expanded");
}
}
}
});
}
function filterChange(val) {
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
$("#palette-container .palette_node").each(function(i,el) {
var currentLabel = $(el).find(".palette_label").text();
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
$(this).show();
} else {
$(this).hide();
}
});
for (var category in categoryContainers) {
if (categoryContainers.hasOwnProperty(category)) {
if (categoryContainers[category].container
.find(".palette_node")
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
categoryContainers[category].close();
} else {
categoryContainers[category].open();
}
}
}
}
function init() {
RED.events.on('registry:node-type-added', function(nodeType) {
var def = RED.nodes.getType(nodeType);
addNodeType(nodeType,def);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
});
RED.events.on('registry:node-type-removed', function(nodeType) {
removeNodeType(nodeType);
});
RED.events.on('registry:node-set-enabled', function(nodeSet) {
for (var j=0;j<nodeSet.types.length;j++) {
showNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def && def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
}
});
RED.events.on('registry:node-set-disabled', function(nodeSet) {
for (var j=0;j<nodeSet.types.length;j++) {
hideNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
});
RED.events.on('registry:node-set-removed', function(nodeSet) {
if (nodeSet.added) {
for (var j=0;j<nodeSet.types.length;j++) {
removeNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
}
});
$("#palette > .palette-spinner").show();
$("#palette-search input").searchBox({
delay: 100,
change: function() {
filterChange($(this).val());
}
})
var categoryList = coreCategories;
if (RED.settings.paletteCategories) {
categoryList = RED.settings.paletteCategories;
} else if (RED.settings.theme('palette.categories')) {
categoryList = RED.settings.theme('palette.categories');
}
if (!Array.isArray(categoryList)) {
categoryList = coreCategories
}
categoryList.forEach(function(category){
createCategoryContainer(category, category, "palette.label."+category);
});
$("#palette-collapse-all").on("click", function(e) {
e.preventDefault();
for (var cat in categoryContainers) {
if (categoryContainers.hasOwnProperty(cat)) {
categoryContainers[cat].close();
}
}
});
$("#palette-expand-all").on("click", function(e) {
e.preventDefault();
for (var cat in categoryContainers) {
if (categoryContainers.hasOwnProperty(cat)) {
categoryContainers[cat].open();
}
}
});
}
function getCategories() {
var categories = [];
$("#palette-container .palette-category").each(function(i,d) {
categories.push({id:$(d).data('category'),label:$(d).data('label')});
})
return categories;
}
return {
init: init,
add:addNodeType,
remove:removeNodeType,
hide:hideNodeType,
show:showNodeType,
refresh:refreshNodeTypes,
getCategories: getCategories
};
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,418 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.projects.userSettings = (function() {
var gitUsernameInput;
var gitEmailInput;
function createGitUserSection(pane) {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.user = currentGitSettings.user || {};
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.committerDetail")).appendTo(pane);
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
}
function createSSHKeySection(pane) {
var container = $('<div class="user-settings-section"></div>').appendTo(pane);
var popover;
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(container);
var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip"));
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>')
.appendTo(subtitle)
.click(function(evt) {
addKeyButton.attr('disabled',true);
saveButton.attr('disabled',true);
// bg.children().removeClass("selected");
// addLocalButton.click();
addKeyDialog.slideDown(200);
keyNameInput.focus();
});
var validateForm = function() {
var valid = /^[a-zA-Z0-9\-_]+$/.test(keyNameInput.val());
keyNameInput.toggleClass('input-error',keyNameInputChanged&&!valid);
// var selectedButton = bg.find(".selected");
// if (selectedButton[0] === addLocalButton[0]) {
// valid = valid && localPublicKeyPathInput.val().length > 0 && localPrivateKeyPathInput.val().length > 0;
// } else if (selectedButton[0] === uploadButton[0]) {
// valid = valid && publicKeyInput.val().length > 0 && privateKeyInput.val().length > 0;
// } else if (selectedButton[0] === generateButton[0]) {
var passphrase = passphraseInput.val();
var validPassphrase = passphrase.length === 0 || passphrase.length >= 8;
passphraseInput.toggleClass('input-error',!validPassphrase);
if (!validPassphrase) {
passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.passphraseShort"));
} else if (passphrase.length === 0) {
passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.optional"));
} else {
passphraseInputSubLabel.text("");
}
valid = valid && validPassphrase;
// }
saveButton.attr('disabled',!valid);
if (popover) {
popover.close();
popover = null;
}
};
var row = $('<div class="user-settings-row"></div>').appendTo(container);
var addKeyDialog = $('<div class="projects-dialog-list-dialog"></div>').hide().appendTo(row);
$('<div class="projects-dialog-list-dialog-header">').text(RED._("editor:sidebar.project.userSettings.addSshKey")).appendTo(addKeyDialog);
var addKeyDialogBody = $('<div>').appendTo(addKeyDialog);
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<div style="color:#aaa;"></div>').appendTo(row).text(RED._("editor:sidebar.project.userSettings.addSshKeyTip"));
// var bg = $('<div></div>',{class:"button-group", style:"text-align: center"}).appendTo(row);
// var addLocalButton = $('<button class="editor-button toggle selected">use local key</button>').appendTo(bg);
// var uploadButton = $('<button class="editor-button toggle">upload key</button>').appendTo(bg);
// var generateButton = $('<button class="editor-button toggle">generate key</button>').appendTo(bg);
// bg.children().click(function(e) {
// e.preventDefault();
// if ($(this).hasClass("selected")) {
// return;
// }
// bg.children().removeClass("selected");
// $(this).addClass("selected");
// if (this === addLocalButton[0]) {
// addLocalKeyPane.show();
// generateKeyPane.hide();
// uploadKeyPane.hide();
// } else if (this === uploadButton[0]) {
// addLocalKeyPane.hide();
// generateKeyPane.hide();
// uploadKeyPane.show();
// } else if (this === generateButton[0]){
// addLocalKeyPane.hide();
// generateKeyPane.show();
// uploadKeyPane.hide();
// }
// validateForm();
// })
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.name")).appendTo(row);
var keyNameInputChanged = false;
var keyNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
keyNameInputChanged = true;
validateForm();
});
$('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.nameRule")+'</small></label>').appendTo(row).find("small");
var generateKeyPane = $('<div>').appendTo(addKeyDialogBody);
row = $('<div class="user-settings-row"></div>').appendTo(generateKeyPane);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.passphrase")).appendTo(row);
var passphraseInput = $('<input type="password">').appendTo(row).on("change keyup paste",validateForm);
var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.optional")+'</small></label>').appendTo(row).find("small");
// var addLocalKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
// $('<label for=""></label>').text('Public key').appendTo(row);
// var localPublicKeyPathInput = $('<input type="text">').appendTo(row).on("change keyup paste",validateForm);
// $('<label class="projects-edit-form-sublabel"><small>Public key file path, for example: ~/.ssh/id_rsa.pub</small></label>').appendTo(row).find("small");
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
// $('<label for=""></label>').text('Private key').appendTo(row);
// var localPrivateKeyPathInput = $('<input type="text">').appendTo(row).on("change keyup paste",validateForm);
// $('<label class="projects-edit-form-sublabel"><small>Private key file path, for example: ~/.ssh/id_rsa</small></label>').appendTo(row).find("small");
//
// var uploadKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
// row = $('<div class="user-settings-row"></div>').appendTo(uploadKeyPane);
// $('<label for=""></label>').text('Public key').appendTo(row);
// var publicKeyInput = $('<textarea>').appendTo(row).on("change keyup paste",validateForm);
// $('<label class="projects-edit-form-sublabel"><small>Paste in public key contents, for example: ~/.ssh/id_rsa.pub</small></label>').appendTo(row).find("small");
// row = $('<div class="user-settings-row"></div>').appendTo(uploadKeyPane);
// $('<label for=""></label>').text('Private key').appendTo(row);
// var privateKeyInput = $('<textarea>').appendTo(row).on("change keyup paste",validateForm);
// $('<label class="projects-edit-form-sublabel"><small>Paste in private key contents, for example: ~/.ssh/id_rsa</small></label>').appendTo(row).find("small");
var hideEditForm = function() {
addKeyButton.attr('disabled',false);
addKeyDialog.hide();
keyNameInput.val("");
keyNameInputChanged = false;
passphraseInput.val("");
// localPublicKeyPathInput.val("");
// localPrivateKeyPathInput.val("");
// publicKeyInput.val("");
// privateKeyInput.val("");
if (popover) {
popover.close();
popover = null;
}
}
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(addKeyDialog);
$('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.cancel")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.generate")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
var spinner = utils.addSpinnerOverlay(addKeyDialog).addClass('projects-dialog-spinner-contain');
var payload = {
name: keyNameInput.val()
};
// var selectedButton = bg.find(".selected");
// if (selectedButton[0] === addLocalButton[0]) {
// payload.type = "local";
// payload.publicKeyPath = localPublicKeyPathInput.val();
// payload.privateKeyPath = localPrivateKeyPathInput.val();
// } else if (selectedButton[0] === uploadButton[0]) {
// payload.type = "upload";
// payload.publicKey = publicKeyInput.val();
// payload.privateKey = privateKeyInput.val();
// } else if (selectedButton[0] === generateButton[0]) {
payload.type = "generate";
payload.comment = gitEmailInput.val();
payload.password = passphraseInput.val();
payload.size = 4096;
// }
var done = function(err) {
spinner.remove();
if (err) {
return;
}
hideEditForm();
}
// console.log(JSON.stringify(payload,null,4));
RED.deploy.setDeployInflight(true);
utils.sendRequest({
url: "settings/user/keys",
type: "POST",
responses: {
0: function(error) {
done(error);
},
200: function(data) {
refreshSSHKeyList(payload.name);
done();
},
400: {
'unexpected_error': function(error) {
console.log(error);
done(error);
}
},
}
},payload);
});
row = $('<div class="user-settings-row projects-dialog-list"></div>').appendTo(container);
var emptyItem = { empty: true };
var expandKey = function(container,entry) {
var row = $('<div class="projects-dialog-ssh-public-key">',{style:"position:relative"}).appendTo(container);
var keyBox = $('<pre>',{style:"min-height: 80px"}).appendTo(row);
var spinner = utils.addSpinnerOverlay(keyBox).addClass('projects-dialog-spinner-contain');
var options = {
url: 'settings/user/keys/'+entry.name,
type: "GET",
responses: {
200: function(data) {
keyBox.text(data.publickey);
spinner.remove();
},
400: {
'unexpected_error': function(error) {
console.log(error);
spinner.remove();
}
},
}
}
utils.sendRequest(options);
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(row);
$('<button class="editor-button editor-button-small">'+RED._("editor:sidebar.project.userSettings.copyPublicKey")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
try {
evt.stopPropagation();
evt.preventDefault();
document.getSelection().selectAllChildren(keyBox[0]);
var ret = document.execCommand('copy');
document.getSelection().empty();
} catch(err) {
}
});
return row;
}
var keyList = $('<ol class="projects-dialog-ssh-key-list">').appendTo(row).editableList({
height: 'auto',
addButton: false,
scrollOnAdd: false,
addItem: function(row,index,entry) {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) {
container.addClass('red-ui-search-empty');
container.text(RED._("editor:sidebar.project.userSettings.noSshKeys"));
return;
}
var topRow = $('<div class="projects-dialog-ssh-key-header">').appendTo(container);
$('<span class="entry-icon"><i class="fa fa-key"></i></span>').appendTo(topRow);
$('<span class="entry-name">').text(entry.name).appendTo(topRow);
var tools = $('<span class="button-row entry-tools">').appendTo(topRow);
var expandedRow;
topRow.click(function(e) {
if (expandedRow) {
expandedRow.slideUp(200,function() {
expandedRow.remove();
expandedRow = null;
})
} else {
expandedRow = expandKey(container,entry);
}
})
if (!entry.system) {
$('<button class="editor-button editor-button-small"><i class="fa fa-trash"></i></button>')
.appendTo(tools)
.click(function(e) {
e.stopPropagation();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify(RED._("editor:sidebar.project.userSettings.deleteConfirm", {name:entry.name}), {
type: 'warning',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.cancel"),
click: function() {
spinner.remove();
notification.close();
}
},
{
text: RED._("editor:sidebar.project.userSettings.delete"),
click: function() {
notification.close();
var url = "settings/user/keys/"+entry.name;
var options = {
url: url,
type: "DELETE",
responses: {
200: function(data) {
row.fadeOut(200,function() {
keyList.editableList('removeItem',entry);
setTimeout(spinner.remove, 100);
if (keyList.editableList('length') === 0) {
keyList.editableList('addItem',emptyItem);
}
});
},
400: {
'unexpected_error': function(error) {
console.log(error);
spinner.remove();
}
},
}
}
utils.sendRequest(options);
}
}
]
});
});
}
if (entry.expand) {
expandedRow = expandKey(container,entry);
}
}
});
var refreshSSHKeyList = function(justAdded) {
$.getJSON("settings/user/keys",function(result) {
if (result.keys) {
result.keys.sort(function(A,B) {
return A.name.localeCompare(B.name);
});
keyList.editableList('empty');
result.keys.forEach(function(key) {
if (key.name === justAdded) {
key.expand = true;
}
keyList.editableList('addItem',key);
});
if (keyList.editableList('length') === 0) {
keyList.editableList('addItem',emptyItem);
}
}
})
}
refreshSSHKeyList();
}
function createSettingsPane(activeProject) {
var pane = $('<div id="user-settings-tab-gitconfig" class="project-settings-tab-pane node-help"></div>');
createGitUserSection(pane);
createSSHKeySection(pane);
return pane;
}
var utils;
function init(_utils) {
utils = _utils;
RED.userSettings.add({
id:'gitconfig',
title: RED._("editor:sidebar.project.userSettings.gitConfig"),
get: createSettingsPane,
close: function() {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.user = currentGitSettings.user || {};
currentGitSettings.user.name = gitUsernameInput.val();
currentGitSettings.user.email = gitEmailInput.val();
RED.settings.set('git', currentGitSettings);
}
});
}
return {
init: init,
};
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,298 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.search = (function() {
var disabled = false;
var dialog = null;
var searchInput;
var searchResults;
var selected = -1;
var visible = false;
var index = {};
var keys = [];
var results = [];
function indexProperty(node,label,property) {
if (typeof property === 'string' || typeof property === 'number') {
property = (""+property).toLowerCase();
index[property] = index[property] || {};
index[property][node.id] = {node:node,label:label};
} else if (Array.isArray(property)) {
property.forEach(function(prop) {
indexProperty(node,label,prop);
})
} else if (typeof property === 'object') {
for (var prop in property) {
if (property.hasOwnProperty(prop)) {
indexProperty(node,label,property[prop])
}
}
}
}
function indexNode(n) {
var l = RED.utils.getNodeLabel(n);
if (l) {
l = (""+l).toLowerCase();
index[l] = index[l] || {};
index[l][n.id] = {node:n,label:l}
}
l = l||n.label||n.name||n.id||"";
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])) {
indexProperty(n, l, n[properties[i]]);
}
}
}
function indexWorkspace() {
index = {};
RED.nodes.eachWorkspace(indexNode);
RED.nodes.eachSubflow(indexNode);
RED.nodes.eachConfig(indexNode);
RED.nodes.eachNode(indexNode);
keys = Object.keys(index);
keys.sort();
keys.forEach(function(key) {
index[key] = Object.keys(index[key]).map(function(id) {
return index[key][id];
})
})
}
function search(val) {
searchResults.editableList('empty');
selected = -1;
results = [];
if (val.length > 0) {
val = val.toLowerCase();
var i;
var j;
var list = [];
var nodes = {};
for (i=0;i<keys.length;i++) {
var key = keys[i];
var kpos = keys[i].indexOf(val);
if (kpos > -1) {
for (j=0;j<index[key].length;j++) {
var node = index[key][j];
nodes[node.node.id] = nodes[node.node.id] = node;
nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
}
}
}
list = Object.keys(nodes);
list.sort(function(A,B) {
return nodes[A].index - nodes[B].index;
});
for (i=0;i<list.length;i++) {
results.push(nodes[list[i]]);
}
if (results.length > 0) {
for (i=0;i<Math.min(results.length,25);i++) {
searchResults.editableList('addItem',results[i])
}
} else {
searchResults.editableList('addItem',{});
}
}
}
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() {
dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#main-container");
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text" data-i18n="[placeholder]menu.label.searchInput">').appendTo(searchDiv).searchBox({
delay: 200,
change: function() {
search($(this).val());
}
});
searchInput.on('keydown',function(evt) {
var children;
if (results.length > 0) {
if (evt.keyCode === 40) {
// Down
children = searchResults.children();
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
children = searchResults.children();
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
if (results.length > 0) {
reveal(results[Math.max(0,selected)].node);
}
}
}
});
searchInput.i18n();
var searchResultsDiv = $("<div>",{class:"red-ui-search-results-container"}).appendTo(dialog);
searchResults = $('<ol>',{id:"search-result-list", style:"position: absolute;top: 5px;bottom: 5px;left: 5px;right: 5px;"}).appendTo(searchResultsDiv).editableList({
addButton: false,
addItem: function(container,i,object) {
var node = object.node;
if (node === undefined) {
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
} else {
var def = node._def;
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = RED.utils.getNodeColor(node.type,def);
var icon_url = RED.utils.getNodeIcon(def,node);
if (node.type === 'tab') {
colour = "#C0DEED";
}
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
if (node.z) {
var workspace = RED.nodes.workspace(node.z);
if (!workspace) {
workspace = RED.nodes.subflow(node.z);
workspace = "subflow:"+workspace.name;
} else {
workspace = "flow:"+workspace.label;
}
$('<div>',{class:"red-ui-search-result-node-flow"}).text(workspace).appendTo(contentDiv);
}
$('<div>',{class:"red-ui-search-result-node-label"}).text(object.label || node.id).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-type"}).text(node.type).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-id"}).text(node.id).appendTo(contentDiv);
div.click(function(evt) {
evt.preventDefault();
reveal(node);
});
}
},
scrollOnAdd: false
});
}
function reveal(node) {
hide();
RED.view.reveal(node.id);
}
function show() {
if (disabled) {
return;
}
if (!visible) {
RED.keyboard.add("*","escape",function(){hide()});
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
$("#sidebar-separator").hide();
indexWorkspace();
if (dialog === null) {
createDialog();
}
dialog.slideDown(300);
RED.events.emit("search:open");
visible = true;
}
searchInput.focus();
}
function hide() {
if (visible) {
RED.keyboard.remove("escape");
visible = false;
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
$("#sidebar-separator").show();
if (dialog !== null) {
dialog.slideUp(200,function() {
searchInput.searchBox('value','');
});
}
RED.events.emit("search:close");
}
}
function init() {
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("type-search:open",function() { disabled = true; });
RED.events.on("type-search:close",function() { disabled = false; });
$("#header-shade").on('mousedown',hide);
$("#editor-shade").on('mousedown',hide);
$("#palette-shade").on('mousedown',hide);
$("#sidebar-shade").on('mousedown',hide);
}
return {
init: init,
show: show,
hide: hide
};
})();

View File

@@ -0,0 +1,235 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar = (function() {
//$('#sidebar').tabs();
var sidebar_tabs = RED.tabs.create({
id:"sidebar-tabs",
onchange:function(tab) {
$("#sidebar-content").children().hide();
$("#sidebar-footer").children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
$(tab.wrapper).show();
if (tab.toolbar) {
$(tab.toolbar).show();
}
},
onremove: function(tab) {
$(tab.wrapper).hide();
if (tab.onremove) {
tab.onremove.call(tab);
}
},
// minimumActiveTabWidth: 70,
collapsible: true
// scrollable: true
});
var knownTabs = {
};
function addTab(title,content,closeable,visible) {
var options;
if (typeof title === "string") {
// TODO: legacy support in case anyone uses this...
options = {
id: content.id,
label: title,
name: title,
content: content,
closeable: closeable,
visible: visible
}
} else if (typeof title === "object") {
options = title;
}
delete options.closeable;
options.wrapper = $('<div>',{style:"height:100%"}).appendTo("#sidebar-content")
options.wrapper.append(options.content);
options.wrapper.hide();
if (!options.enableOnEdit) {
options.shade = $('<div>',{class:"sidebar-shade hide"}).appendTo(options.wrapper);
}
if (options.toolbar) {
$("#sidebar-footer").append(options.toolbar);
$(options.toolbar).hide();
}
var id = options.id;
RED.menu.addItem("menu-item-view-menu",{
id:"menu-item-view-menu-"+options.id,
label:options.name,
onselect:function() {
showSidebar(options.id);
},
group: "sidebar-tabs"
});
options.iconClass = options.iconClass || "fa fa-square-o"
knownTabs[options.id] = options;
if (options.visible !== false) {
sidebar_tabs.addTab(knownTabs[options.id]);
}
}
function removeTab(id) {
sidebar_tabs.removeTab(id);
$(knownTabs[id].wrapper).remove();
if (knownTabs[id].footer) {
knownTabs[id].footer.remove();
}
delete knownTabs[id];
RED.menu.removeItem("menu-item-view-menu-"+id);
}
var sidebarSeparator = {};
$("#sidebar-separator").draggable({
axis: "x",
start:function(event,ui) {
sidebarSeparator.closing = false;
sidebarSeparator.opening = false;
var winWidth = $(window).width();
sidebarSeparator.start = ui.position.left;
sidebarSeparator.chartWidth = $("#workspace").width();
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true;
var newChartRight = 7;
$("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight);
$("#editor-stack").css("right",newChartRight+1);
$("#sidebar").width(0);
RED.menu.setSelected("menu-item-sidebar",true);
RED.events.emit("sidebar:resize");
}
sidebarSeparator.width = $("#sidebar").width();
},
drag: function(event,ui) {
var d = ui.position.left-sidebarSeparator.start;
var newSidebarWidth = sidebarSeparator.width-d;
if (sidebarSeparator.opening) {
newSidebarWidth -= 3;
}
if (newSidebarWidth > 150) {
if (sidebarSeparator.chartWidth+d < 200) {
ui.position.left = 200+sidebarSeparator.start-sidebarSeparator.chartWidth;
d = ui.position.left-sidebarSeparator.start;
newSidebarWidth = sidebarSeparator.width-d;
}
}
if (newSidebarWidth < 150) {
if (!sidebarSeparator.closing) {
$("#sidebar").addClass("closing");
sidebarSeparator.closing = true;
}
if (!sidebarSeparator.opening) {
newSidebarWidth = 150;
ui.position.left = sidebarSeparator.width-(150 - sidebarSeparator.start);
d = ui.position.left-sidebarSeparator.start;
}
} else if (newSidebarWidth > 150 && (sidebarSeparator.closing || sidebarSeparator.opening)) {
sidebarSeparator.closing = false;
$("#sidebar").removeClass("closing");
}
var newChartRight = sidebarSeparator.chartRight-d;
$("#workspace").css("right",newChartRight);
$("#editor-stack").css("right",newChartRight+1);
$("#sidebar").width(newSidebarWidth);
sidebar_tabs.resize();
RED.events.emit("sidebar:resize");
},
stop:function(event,ui) {
if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing");
RED.menu.setSelected("menu-item-sidebar",false);
if ($("#sidebar").width() < 180) {
$("#sidebar").width(180);
$("#workspace").css("right",187);
$("#editor-stack").css("right",188);
}
}
$("#sidebar-separator").css("left","auto");
$("#sidebar-separator").css("right",($("#sidebar").width()+2)+"px");
RED.events.emit("sidebar:resize");
}
});
function toggleSidebar(state) {
if (!state) {
$("#main-container").addClass("sidebar-closed");
} else {
$("#main-container").removeClass("sidebar-closed");
sidebar_tabs.resize();
}
RED.events.emit("sidebar:resize");
}
function showSidebar(id) {
if (id) {
if (!containsTab(id)) {
sidebar_tabs.addTab(knownTabs[id]);
}
sidebar_tabs.activateTab(id);
if (!RED.menu.isSelected("menu-item-sidebar")) {
RED.menu.setSelected("menu-item-sidebar",true);
}
}
}
function containsTab(id) {
return sidebar_tabs.contains(id);
}
function init () {
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();
RED.sidebar.context.init();
// hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); }
}
return {
init: init,
addTab: addTab,
removeTab: removeTab,
show: showSidebar,
containsTab: containsTab,
toggleSidebar: toggleSidebar,
}
})();

View File

@@ -0,0 +1,28 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.state = {
DEFAULT: 0,
MOVING: 1,
JOINING: 2,
MOVING_ACTIVE: 3,
ADDING: 4,
EDITING: 5,
EXPORT: 6,
IMPORT: 7,
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9,
PANNING: 10
}

View File

@@ -0,0 +1,639 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.subflow = (function() {
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow"><div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div></script>';
var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template"><div class="form-row"><i class="fa fa-tag"></i><label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name"></div><div class="form-row" style="margin-bottom: 0px;"><label for="subflow-input-info" data-i18n="editor:subflow.info"></label><a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a></div><div class="form-row node-text-editor-row"><div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div></div><div class="form-row form-tips" id="subflow-dialog-user-count"></div></script>';
function getSubflow() {
return RED.nodes.subflow(RED.workspaces.active());
}
function findAvailableSubflowIOPosition(subflow,isInput) {
var pos = {x:50,y:30};
if (!isInput) {
pos.x += 110;
}
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
var port;
if (i < subflow.out.length) {
port = subflow.out[i];
} else {
port = subflow.in[i-subflow.out.length];
}
if (port.x == pos.x && port.y == pos.y) {
pos.x += 55;
i=0;
}
}
return pos;
}
function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (subflow.in.length === 1) {
return;
}
var position = findAvailableSubflowIOPosition(subflow,true);
var newInput = {
type:"subflow",
direction:"in",
z:subflow.id,
i:subflow.in.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldInCount = subflow.in.length;
subflow.in.push(newInput);
subflow.dirty = true;
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
var result = refresh(true);
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
inputCount: oldInCount,
instances: result.instances
}
};
RED.history.push(historyEvent);
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-input-add").addClass("active");
$("#workspace-subflow-input-remove").removeClass("active");
}
function removeSubflowInput() {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow.in.length === 0) {
return;
}
var removedInput = activeSubflow.in[0];
var removedInputLinks = [];
RED.nodes.eachLink(function(l) {
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == removedInput.i) {
removedInputLinks.push(l);
} else if (l.target.type == "subflow:"+activeSubflow.id) {
removedInputLinks.push(l);
}
});
removedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
activeSubflow.in = [];
$("#workspace-subflow-input-add").removeClass("active");
$("#workspace-subflow-input-remove").addClass("active");
activeSubflow.changed = true;
return {subflowInputs: [ removedInput ], links:removedInputLinks};
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow,false);
var newOutput = {
type:"subflow",
direction:"out",
z:subflow.id,
i:subflow.out.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldOutCount = subflow.out.length;
subflow.out.push(newOutput);
subflow.dirty = true;
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
var result = refresh(true);
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
outputCount: oldOutCount,
instances: result.instances
}
};
RED.history.push(historyEvent);
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-output .spinner-value").text(subflow.out.length);
}
function removeSubflowOutput(removedSubflowOutputs) {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow.out.length === 0) {
return;
}
if (typeof removedSubflowOutputs === "undefined") {
removedSubflowOutputs = [activeSubflow.out[activeSubflow.out.length-1]];
}
var removedLinks = [];
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
for (i=0;i<removedSubflowOutputs.length;i++) {
var output = removedSubflowOutputs[i];
activeSubflow.out.splice(output.i,1);
var subflowRemovedLinks = [];
var subflowMovedLinks = [];
RED.nodes.eachLink(function(l) {
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
subflowRemovedLinks.push(l);
}
if (l.source.type == "subflow:"+activeSubflow.id) {
if (l.sourcePort == output.i) {
subflowRemovedLinks.push(l);
} else if (l.sourcePort > output.i) {
subflowMovedLinks.push(l);
}
}
});
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
removedLinks = removedLinks.concat(subflowRemovedLinks);
for (var j=output.i;j<activeSubflow.out.length;j++) {
activeSubflow.out[j].i--;
activeSubflow.out[j].dirty = true;
}
}
activeSubflow.changed = true;
return {subflowOutputs: removedSubflowOutputs, links: removedLinks}
}
function refresh(markChange) {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
refreshToolbar(activeSubflow);
var subflowInstances = [];
if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
subflowInstances.push({
id: n.id,
changed: n.changed
});
if (markChange) {
n.changed = true;
}
n.inputs = activeSubflow.in.length;
n.outputs = activeSubflow.out.length;
while (n.outputs < n.ports.length) {
n.ports.pop();
}
n.resize = true;
n.dirty = true;
RED.editor.updateNodeProperties(n);
});
RED.editor.validateNode(activeSubflow);
return {
instances: subflowInstances
}
}
}
function refreshToolbar(activeSubflow) {
if (activeSubflow) {
$("#workspace-subflow-input-add").toggleClass("active", activeSubflow.in.length !== 0);
$("#workspace-subflow-input-remove").toggleClass("active",activeSubflow.in.length === 0);
$("#workspace-subflow-output .spinner-value").text(activeSubflow.out.length);
}
}
function showWorkspaceToolbar(activeSubflow) {
var toolbar = $("#workspace-toolbar");
toolbar.empty();
$('<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowProperties"><i class="fa fa-pencil"></i> </a>').appendTo(toolbar);
$('<span style="margin-left: 5px;" data-i18n="subflow.input"></span> '+
'<div style="display: inline-block;" class="button-group">'+
'<a id="workspace-subflow-input-remove" class="button active" href="#">0</a>'+
'<a id="workspace-subflow-input-add" class="button" href="#">1</a>'+
'</div>').appendTo(toolbar);
$('<span style="margin-left: 5px;" data-i18n="subflow.output"></span> <div id="workspace-subflow-output" style="display: inline-block;" class="button-group spinner-group">'+
'<a id="workspace-subflow-output-remove" class="button" href="#"><i class="fa fa-minus"></i></a>'+
'<div class="spinner-value">3</div>'+
'<a id="workspace-subflow-output-add" class="button" href="#"><i class="fa fa-plus"></i></a>'+
'</div>').appendTo(toolbar);
// $('<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
// $('<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
$('<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar);
toolbar.i18n();
$("#workspace-subflow-output-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
var wasChanged = activeSubflow.changed;
var result = removeSubflowOutput();
if (result) {
var inst = refresh(true);
RED.history.push({
t:'delete',
links:result.links,
subflowOutputs: result.subflowOutputs,
changed: wasChanged,
dirty:wasDirty,
subflow: {
instances: inst.instances
}
});
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw(true);
}
});
$("#workspace-subflow-output-add").click(function(event) {
event.preventDefault();
addSubflowOutput();
});
$("#workspace-subflow-input-add").click(function(event) {
event.preventDefault();
addSubflowInput();
});
$("#workspace-subflow-input-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
var wasChanged = activeSubflow.changed;
activeSubflow.changed = true;
var result = removeSubflowInput();
if (result) {
var inst = refresh(true);
RED.history.push({
t:'delete',
links:result.links,
changed: wasChanged,
subflowInputs: result.subflowInputs,
dirty:wasDirty,
subflow: {
instances: inst.instances
}
});
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw(true);
}
});
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
event.preventDefault();
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var startDirty = RED.nodes.dirty();
var historyEvent = removeSubflow(RED.workspaces.active());
historyEvent.t = 'delete';
historyEvent.dirty = startDirty;
RED.history.push(historyEvent);
});
refreshToolbar(activeSubflow);
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
}
function hideWorkspaceToolbar() {
$("#workspace-toolbar").hide().empty();
$("#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);
if (activeSubflow) {
showWorkspaceToolbar(activeSubflow);
} else {
hideWorkspaceToolbar();
}
});
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-subflow-convert",true);
} else {
RED.menu.setDisabled("menu-item-subflow-convert",false);
}
});
RED.actions.add("core:create-subflow",createSubflow);
RED.actions.add("core:convert-to-subflow",convertToSubflow);
$(_subflowEditTemplate).appendTo(document.body);
$(_subflowTemplateEditTemplate).appendTo(document.body);
}
function createSubflow() {
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
info:"",
in: [],
out: []
};
RED.nodes.addSubflow(subflow);
RED.history.push({
t:'createSubflow',
subflow: {
subflow:subflow
},
dirty:RED.nodes.dirty()
});
RED.workspaces.show(subflowId);
RED.nodes.dirty(true);
}
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
return;
}
var i,n;
var nodes = {};
var new_links = [];
var removedLinks = [];
var candidateInputs = [];
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
selection.nodes[0].y];
for (i=0;i<selection.nodes.length;i++) {
n = selection.nodes[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x),
Math.min(boundingBox[1],n.y),
Math.max(boundingBox[2],n.x),
Math.max(boundingBox[3],n.y)
]
}
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection
}
if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection
candidateOutputs.push(link);
removedLinks.push(link);
}
if (!nodes[link.source.id] && nodes[link.target.id]) {
// An inbound link
candidateInputs.push(link);
candidateInputNodes[link.target.id] = link.target;
removedLinks.push(link);
}
});
var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) {
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
return false;
}
v.targets = [];
v.targets.push(v.target);
outputs[v.source.id+":"+v.sourcePort] = v;
return true;
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (Object.keys(candidateInputNodes).length > 1) {
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
return;
}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
info:"",
in: Object.keys(candidateInputNodes).map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80,
y:candidateInputNodes[v].y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:candidateInputNodes[v].id}]
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.source.x+(v.source.w/2)+80,
y:v.source.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.source.id,port:v.sourcePort}]
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
id:RED.nodes.id(),
type:"subflow:"+subflow.id,
x: center[0],
y: center[1],
z: RED.workspaces.active(),
inputs: subflow.in.length,
outputs: subflow.out.length,
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
changed:true
}
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
RED.nodes.addLink(link);
});
candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target};
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.out.forEach(function(output,i) {
output.wires.forEach(function(wire) {
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
new_links.push(link);
RED.nodes.addLink(link);
});
});
for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<selection.nodes.length;i++) {
n = selection.nodes[i];
if (/^link /.test(n.type)) {
n.links = n.links.filter(function(id) {
var isLocalLink = nodes.hasOwnProperty(id);
if (!isLocalLink) {
var otherNode = RED.nodes.node(id);
if (otherNode && otherNode.links) {
var i = otherNode.links.indexOf(n.id);
if (i > -1) {
otherNode.links.splice(i,1);
}
}
}
return isLocalLink;
});
}
n.z = subflow.id;
}
RED.history.push({
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: {
subflow: subflow
},
activeWorkspace: RED.workspaces.active(),
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
});
RED.view.select(null);
RED.editor.validateNode(subflow);
RED.nodes.dirty(true);
RED.view.redraw(true);
}
return {
init: init,
createSubflow: createSubflow,
convertToSubflow: convertToSubflow,
removeSubflow: removeSubflow,
refresh: refresh,
removeInput: removeSubflowInput,
removeOutput: removeSubflowOutput
}
})();

View File

@@ -0,0 +1,314 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar.config = (function() {
var content = document.createElement("div");
content.className = "sidebar-node-config";
$('<div class="button-group sidebar-header">'+
'<a class="sidebar-header-button-toggle selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+
'<a class="sidebar-header-button-toggle" id="workspace-config-node-filter-unused" href="#"><span data-i18n="sidebar.config.filterUnused"></span></a> '+
'</div>'
).appendTo(content);
var toolbar = $('<div>'+
'<a class="sidebar-footer-button" id="workspace-config-node-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a> '+
'<a class="sidebar-footer-button" id="workspace-config-node-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>'+
'</div>');
var globalCategories = $("<div>").appendTo(content);
var flowCategories = $("<div>").appendTo(content);
var subflowCategories = $("<div>").appendTo(content);
var showUnusedOnly = false;
var categories = {};
function getOrCreateCategory(name,parent,label) {
name = name.replace(/\./i,"-");
if (!categories[name]) {
var container = $('<div class="palette-category workspace-config-node-category" id="workspace-config-node-category-'+name+'"></div>').appendTo(parent);
var header = $('<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container);
if (label) {
$('<span class="config-node-label"/>').text(label).appendTo(header);
} else {
$('<span class="config-node-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
}
$('<span class="config-node-filter-info"></span>').appendTo(header);
category = $('<ul class="palette-content config-node-list"></ul>').appendTo(container);
container.i18n();
var icon = header.find("i");
var result = {
label: label,
list: category,
size: function() {
return result.list.find("li:not(.config_node_none)").length
},
open: function(snap) {
if (!icon.hasClass("expanded")) {
icon.addClass("expanded");
if (snap) {
result.list.show();
} else {
result.list.slideDown();
}
}
},
close: function(snap) {
if (icon.hasClass("expanded")) {
icon.removeClass("expanded");
if (snap) {
result.list.hide();
} else {
result.list.slideUp();
}
}
},
isOpen: function() {
return icon.hasClass("expanded");
}
};
header.on('click', function(e) {
if (result.isOpen()) {
result.close();
} else {
result.open();
}
});
categories[name] = result;
} else {
if (categories[name].label !== label) {
categories[name].list.parent().find('.config-node-label').text(label);
categories[name].label = label;
}
}
return categories[name];
}
function createConfigNodeList(id,nodes) {
var category = getOrCreateCategory(id.replace(/\./i,"-"))
var list = category.list;
nodes.sort(function(A,B) {
if (A.type < B.type) { return -1;}
if (A.type > B.type) { return 1;}
return 0;
});
if (showUnusedOnly) {
var hiddenCount = nodes.length;
nodes = nodes.filter(function(n) {
return n._def.hasUsers!==false && n.users.length === 0;
})
hiddenCount = hiddenCount - nodes.length;
if (hiddenCount > 0) {
list.parent().find('.config-node-filter-info').text(RED._('sidebar.config.filtered',{count:hiddenCount})).show();
} else {
list.parent().find('.config-node-filter-info').hide();
}
} else {
list.parent().find('.config-node-filter-info').hide();
}
list.empty();
if (nodes.length === 0) {
$('<li class="config_node_none" data-i18n="sidebar.config.none">NONE</li>').i18n().appendTo(list);
category.close(true);
} else {
var currentType = "";
nodes.forEach(function(node) {
var label = RED.utils.getNodeLabel(node,node.id);
if (node.type != currentType) {
$('<li class="config_node_type">'+node.type+'</li>').appendTo(list);
currentType = node.type;
}
var entry = $('<li class="palette_node config_node palette_node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
$('<div class="palette_label"></div>').text(label).appendTo(entry);
if (node._def.hasUsers !== false) {
var iconContainer = $('<div/>',{class:"palette_icon_container palette_icon_container_right"}).text(node.users.length).appendTo(entry);
if (node.users.length === 0) {
entry.addClass("config_node_unused");
}
}
entry.on('click',function(e) {
RED.sidebar.info.refresh(node);
});
entry.on('dblclick',function(e) {
RED.editor.editConfig("", node.type, node.id);
});
var userArray = node.users.map(function(n) { return n.id });
entry.on('mouseover',function(e) {
RED.nodes.eachNode(function(node) {
if( userArray.indexOf(node.id) != -1) {
node.highlighted = true;
node.dirty = true;
}
});
RED.view.redraw();
});
entry.on('mouseout',function(e) {
RED.nodes.eachNode(function(node) {
if(node.highlighted) {
node.highlighted = false;
node.dirty = true;
}
});
RED.view.redraw();
});
});
category.open(true);
}
}
function refreshConfigNodeList() {
var validList = {"global":true};
getOrCreateCategory("global",globalCategories);
RED.nodes.eachWorkspace(function(ws) {
validList[ws.id.replace(/\./g,"-")] = true;
getOrCreateCategory(ws.id,flowCategories,ws.label);
})
RED.nodes.eachSubflow(function(sf) {
validList[sf.id.replace(/\./g,"-")] = true;
getOrCreateCategory(sf.id,subflowCategories,sf.name);
})
$(".workspace-config-node-category").each(function() {
var id = $(this).attr('id').substring("workspace-config-node-category-".length);
if (!validList[id]) {
$(this).remove();
delete categories[id];
}
})
var globalConfigNodes = [];
var configList = {};
RED.nodes.eachConfig(function(cn) {
if (cn.z) {//} == RED.workspaces.active()) {
configList[cn.z.replace(/\./g,"-")] = configList[cn.z.replace(/\./g,"-")]||[];
configList[cn.z.replace(/\./g,"-")].push(cn);
} else if (!cn.z) {
globalConfigNodes.push(cn);
}
});
for (var id in validList) {
if (validList.hasOwnProperty(id)) {
createConfigNodeList(id,configList[id]||[]);
}
}
createConfigNodeList('global',globalConfigNodes);
}
function init() {
RED.sidebar.addTab({
id: "config",
label: RED._("sidebar.config.label"),
name: RED._("sidebar.config.name"),
content: content,
toolbar: toolbar,
iconClass: "fa fa-cog",
onchange: function() { refreshConfigNodeList(); }
});
RED.actions.add("core:show-config-tab",function() {RED.sidebar.show('config')});
$("#workspace-config-node-collapse-all").on("click", function(e) {
e.preventDefault();
for (var cat in categories) {
if (categories.hasOwnProperty(cat)) {
categories[cat].close();
}
}
});
$("#workspace-config-node-expand-all").on("click", function(e) {
e.preventDefault();
for (var cat in categories) {
if (categories.hasOwnProperty(cat)) {
if (categories[cat].size() > 0) {
categories[cat].open();
}
}
}
});
$('#workspace-config-node-filter-all').on("click",function(e) {
e.preventDefault();
if (showUnusedOnly) {
$(this).addClass('selected');
$('#workspace-config-node-filter-unused').removeClass('selected');
showUnusedOnly = !showUnusedOnly;
refreshConfigNodeList();
}
});
$('#workspace-config-node-filter-unused').on("click",function(e) {
e.preventDefault();
if (!showUnusedOnly) {
$(this).addClass('selected');
$('#workspace-config-node-filter-all').removeClass('selected');
showUnusedOnly = !showUnusedOnly;
refreshConfigNodeList();
}
});
}
function show(id) {
if (typeof id === 'boolean') {
if (id) {
$('#workspace-config-node-filter-unused').click();
} else {
$('#workspace-config-node-filter-all').click();
}
}
refreshConfigNodeList();
if (typeof id === "string") {
$('#workspace-config-node-filter-all').click();
id = id.replace(/\./g,"-");
setTimeout(function() {
var node = $(".palette_node_id_"+id);
var y = node.position().top;
var h = node.height();
var scrollWindow = $(".sidebar-node-config");
var scrollHeight = scrollWindow.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-30)},150);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},150);
}
var flash = 21;
var flashFunc = function() {
if ((flash%2)===0) {
node.removeClass('node_highlighted');
} else {
node.addClass('node_highlighted');
}
flash--;
if (flash >= 0) {
setTimeout(flashFunc,100);
}
}
flashFunc();
},100);
}
RED.sidebar.show("config");
}
return {
init:init,
show:show,
refresh:refreshConfigNodeList
}
})();

View File

@@ -0,0 +1,292 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar.context = (function() {
var content;
var sections;
var localCache = {};
var nodeSection;
// var subflowSection;
var flowSection;
var globalSection;
var currentNode;
var currentFlow;
function init() {
content = $("<div>").css({"position":"relative","height":"100%"});
content.className = "sidebar-context"
// var toolbar = $('<div class="sidebar-header">'+
// '</div>').appendTo(content);
var footerToolbar = $('<div>'+
// '<span class="button-group"><a class="sidebar-footer-button" href="#" data-i18n="[title]node-red:debug.sidebar.openWindow"><i class="fa fa-desktop"></i></a></span> ' +
'</div>');
var stackContainer = $("<div>",{class:"sidebar-context-stack"}).appendTo(content);
sections = RED.stack.create({
container: stackContainer
});
nodeSection = sections.add({
title: RED._("sidebar.context.node"),
collapsible: true,
// onexpand: function() {
// updateNode(currentNode,true);
// }
});
nodeSection.expand();
nodeSection.content.css({height:"100%"});
nodeSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(nodeSection.content);
var table = $('<table class="node-info"></table>').appendTo(nodeSection.content);
nodeSection.table = $('<tbody>').appendTo(table);
var bg = $('<div style="float: right"></div>').appendTo(nodeSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateNode(currentNode, true);
})
// subflowSection = sections.add({
// title: "Subflow",
// collapsible: true
// });
// subflowSection.expand();
// subflowSection.content.css({height:"100%"});
// bg = $('<div style="float: right"></div>').appendTo(subflowSection.header);
// $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
// .appendTo(bg)
// .click(function(evt) {
// evt.stopPropagation();
// evt.preventDefault();
// })
//
// subflowSection.container.hide();
flowSection = sections.add({
title: RED._("sidebar.context.flow"),
collapsible: true
});
flowSection.expand();
flowSection.content.css({height:"100%"});
flowSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(flowSection.content);
var table = $('<table class="node-info"></table>').appendTo(flowSection.content);
flowSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(flowSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateFlow(currentFlow);
})
globalSection = sections.add({
title: RED._("sidebar.context.global"),
collapsible: true
});
globalSection.expand();
globalSection.content.css({height:"100%"});
globalSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(globalSection.content);
var table = $('<table class="node-info"></table>').appendTo(globalSection.content);
globalSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(globalSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateEntry(globalSection,"context/global","global");
})
RED.actions.add("core:show-context-tab",show);
RED.sidebar.addTab({
id: "context",
label: RED._("sidebar.context.label"),
name: RED._("sidebar.context.name"),
iconClass: "fa fa-database",
content: content,
toolbar: footerToolbar,
// pinned: true,
enableOnEdit: false
});
// var toggleLiveButton = $("#sidebar-context-toggle-live");
// toggleLiveButton.click(function(evt) {
// evt.preventDefault();
// if ($(this).hasClass("selected")) {
// $(this).removeClass("selected");
// $(this).find("i").removeClass("fa-pause");
// $(this).find("i").addClass("fa-play");
// } else {
// $(this).addClass("selected");
// $(this).find("i").removeClass("fa-play");
// $(this).find("i").addClass("fa-pause");
// }
// });
// RED.popover.tooltip(toggleLiveButton, function() {
// if (toggleLiveButton.hasClass("selected")) {
// return "Pause live updates"
// } else {
// return "Start live updates"
// }
// });
RED.events.on("view:selection-changed", function(event) {
var selectedNode = event.nodes && event.nodes.length === 1 && event.nodes[0];
updateNode(selectedNode);
})
RED.events.on("workspace:change", function(event) {
updateFlow(RED.nodes.workspace(event.workspace));
})
updateEntry(globalSection,"context/global","global");
}
function updateNode(node,force) {
currentNode = node;
if (force) {
if (node) {
updateEntry(nodeSection,"context/node/"+node.id,node.id);
// if (/^subflow:/.test(node.type)) {
// subflowSection.container.show();
// updateEntry(subflowSection,"context/flow/"+node.id,node.id);
// } else {
// subflowSection.container.hide();
// }
} else {
// subflowSection.container.hide();
updateEntry(nodeSection)
}
} else {
$(nodeSection.table).empty();
if (node) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.refresh"></td></tr>').appendTo(nodeSection.table).i18n();
} else {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.none"></td></tr>').appendTo(nodeSection.table).i18n();
}
nodeSection.timestamp.html("&nbsp;");
}
}
function updateFlow(flow) {
currentFlow = flow;
if (flow) {
updateEntry(flowSection,"context/flow/"+flow.id,flow.id);
} else {
updateEntry(flowSection)
}
}
function refreshEntry(section,baseUrl,id) {
var contextStores = RED.settings.context.stores;
var container = section.table;
$.getJSON(baseUrl, function(data) {
$(container).empty();
var sortedData = {};
for (var store in data) {
if (data.hasOwnProperty(store)) {
for (var key in data[store]) {
if (data[store].hasOwnProperty(key)) {
if (!sortedData.hasOwnProperty(key)) {
sortedData[key] = [];
}
data[store][key].store = store;
sortedData[key].push(data[store][key])
}
}
}
}
var keys = Object.keys(sortedData);
keys.sort();
var l = keys.length;
for (var i = 0; i < l; i++) {
sortedData[keys[i]].forEach(function(v) {
var k = keys[i];
var l2 = sortedData[k].length;
var propRow = $('<tr class="node-info-node-row"><td class="sidebar-context-property"></td><td></td></tr>').appendTo(container);
var obj = $(propRow.children()[0]);
obj.text(k);
var tools = $('<span class="debug-message-tools button-group"></span>').appendTo(obj);
var refreshItem = $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).click(function(e) {
e.preventDefault();
e.stopPropagation();
$.getJSON(baseUrl+"/"+k+"?store="+v.store, function(data) {
$(propRow.children()[1]).empty();
var payload = data.msg;
var format = data.format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: data.format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
})
});
var payload = v.msg;
var format = v.format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: v.format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
if (contextStores.length > 1) {
$("<span>",{class:"sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
}
});
}
if (l === 0) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
}
$(section.timestamp).text(new Date().toLocaleString());
});
}
function updateEntry(section,baseUrl,id) {
var container = section.table;
if (id) {
refreshEntry(section,baseUrl,id);
} else {
$(container).empty();
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.none"></td></tr>').appendTo(container).i18n();
}
}
function show() {
RED.sidebar.show("context");
}
return {
init: init
}
})();

View File

@@ -0,0 +1,447 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content;
var sections;
var nodeSection;
var infoSection;
var tipBox;
var expandedSections = {
"property": false
};
function init() {
content = document.createElement("div");
content.className = "sidebar-node-info"
RED.actions.add("core:show-info-tab",show);
var stackContainer = $("<div>",{class:"sidebar-node-info-stack"}).appendTo(content);
sections = RED.stack.create({
container: stackContainer
}).hide();
nodeSection = sections.add({
title: RED._("sidebar.info.info"),
collapsible: true
});
nodeSection.expand();
infoSection = sections.add({
title: RED._("sidebar.info.nodeHelp"),
collapsible: true
});
infoSection.expand();
infoSection.content.css("padding","6px");
infoSection.container.css("border-bottom","none");
var tipContainer = $('<div class="node-info-tips"></div>').appendTo(content);
tipBox = $('<div class="node-info-tip"></div>').appendTo(tipContainer);
var tipButtons = $('<div class="node-info-tips-buttons"></div>').appendTo(tipContainer);
var tipRefresh = $('<a href="#" class="workspace-footer-button"><i class="fa fa-refresh"></a>').appendTo(tipButtons);
tipRefresh.click(function(e) {
e.preventDefault();
tips.next();
})
var tipClose = $('<a href="#" class="workspace-footer-button"><i class="fa fa-times"></a>').appendTo(tipButtons);
tipClose.click(function(e) {
e.preventDefault();
RED.actions.invoke("core:toggle-show-tips");
RED.notify(RED._("sidebar.info.showTips"));
});
RED.sidebar.addTab({
id: "info",
label: RED._("sidebar.info.label"),
name: RED._("sidebar.info.name"),
iconClass: "fa fa-info",
content: content,
pinned: true,
enableOnEdit: true
});
if (tips.enabled()) {
tips.start();
} else {
tips.stop();
}
}
function show() {
RED.sidebar.show("info");
}
// TODO: DRY - projects.js
function addTargetToExternalLinks(el) {
$(el).find("a").each(function(el) {
var href = $(this).attr('href');
if (/^https?:/.test(href)) {
$(this).attr('target','_blank');
}
});
return el;
}
function refresh(node) {
if (node === undefined) {
refreshSelection();
return;
}
sections.show();
$(nodeSection.content).empty();
$(infoSection.content).empty();
var propRow;
var table = $('<table class="node-info"></table>').appendTo(nodeSection.content);
var tableBody = $('<tbody>').appendTo(table);
var subflowNode;
var subflowUserCount;
var activeProject = RED.projects.getActiveProject();
if (activeProject) {
propRow = $('<tr class="node-info-node-row"><td>Project</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text(activeProject.name||"");
$('<tr class="node-info-property-expand blank"><td colspan="2"></td></tr>').appendTo(tableBody);
$('<button class="editor-button editor-button-small" style="position:absolute;right:2px;"><i class="fa fa-ellipsis-h"></i></button>')
.appendTo(propRow.children()[1])
.click(function(evt) {
evt.preventDefault();
RED.projects.editProject();
});
}
infoSection.container.show();
if (node === null) {
return;
} else if (Array.isArray(node)) {
infoSection.container.hide();
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.selection")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(RED._("sidebar.info.nodes",{count:node.length}))
} else {
var m = /^subflow(:(.+))?$/.exec(node.type);
if (m) {
if (m[2]) {
subflowNode = RED.nodes.subflow(m[2]);
} else {
subflowNode = node;
}
subflowUserCount = 0;
var subflowType = "subflow:"+subflowNode.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
subflowUserCount++;
}
});
}
if (node.type === "tab" || node.type === "subflow") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info."+(node.type==='tab'?'flow':'subflow'))+'</td><td></td></tr>').appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.tabName")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(node.label||node.name||"");
if (node.type === "tab") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
} else if (node.type === "subflow") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = node.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
}
} else {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.type !== "subflow" && node.type !== "unknown" && node.name) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
}
if (!m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text((node.type === "unknown")?node._orig.type:node.type);
if (node.type === "unknown") {
$('<span style="float: right; font-size: 0.8em"><i class="fa fa-warning"></i></span>').prependTo($(propRow.children()[1]))
}
}
if (!m && node.type != "subflow" && node.type != "comment") {
var defaults;
if (node.type === 'unknown') {
defaults = {};
Object.keys(node._orig).forEach(function(k) {
if (k !== 'type') {
defaults[k] = {};
}
})
} else if (node._def) {
defaults = node._def.defaults;
}
if (defaults) {
var count = 0;
for (var n in defaults) {
if (n != "name" && defaults.hasOwnProperty(n)) {
var val = node[n];
var type = typeof val;
count++;
propRow = $('<tr class="node-info-property-row'+(expandedSections.property?"":" hide")+'"><td>'+n+"</td><td></td></tr>").appendTo(tableBody);
if (defaults[n].type) {
var configNode = RED.nodes.node(val);
if (!configNode) {
RED.utils.createObjectElement(undefined).appendTo(propRow.children()[1]);
} else {
var configLabel = RED.utils.getNodeLabel(configNode,val);
var container = propRow.children()[1];
var div = $('<span>',{class:""}).appendTo(container);
var nodeDiv = $('<div>',{class:"palette_node palette_node_small"}).appendTo(div);
var colour = RED.utils.getNodeColor(configNode.type,configNode._def);
var icon_url = RED.utils.getNodeIcon(configNode._def);
nodeDiv.css({'backgroundColor':colour, "cursor":"pointer"});
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
var nodeContainer = $('<span></span>').css({"verticalAlign":"top","marginLeft":"6px"}).text(configLabel).appendTo(container);
nodeDiv.on('dblclick',function() {
RED.editor.editConfig("", configNode.type, configNode.id);
})
}
} else {
RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
}
}
}
if (count > 0) {
$('<tr class="node-info-property-expand blank"><td colspan="2"><a href="#" class=" node-info-property-header'+(expandedSections.property?" expanded":"")+'"><span class="node-info-property-show-more">'+RED._("sidebar.info.showMore")+'</span><span class="node-info-property-show-less">'+RED._("sidebar.info.showLess")+'</span> <i class="fa fa-caret-down"></i></a></td></tr>').appendTo(tableBody);
}
}
}
if (node.type !== 'tab') {
if (m) {
$('<tr class="blank"><th colspan="2">'+RED._("sidebar.info.subflow")+'</th></tr>').appendTo(tableBody);
$('<tr class="node-info-subflow-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>').appendTo(tableBody);
}
}
}
if (m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = subflowNode.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody);
}
var infoText = "";
if (!subflowNode && node.type !== "comment" && node.type !== "tab") {
infoSection.title.text(RED._("sidebar.info.nodeHelp"));
var helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
infoText = helpText;
} else if (node.type === "tab") {
infoSection.title.text(RED._("sidebar.info.flowDesc"));
infoText = marked(node.info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
}
if (subflowNode) {
infoText = infoText + (marked(subflowNode.info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>'));
infoSection.title.text(RED._("sidebar.info.subflowDesc"));
} else if (node._def && node._def.info) {
infoSection.title.text(RED._("sidebar.info.nodeHelp"));
var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info);
// TODO: help
infoText = infoText + marked(textInfo);
}
if (infoText) {
setInfoText(infoText);
}
$(".sidebar-node-info-stack").scrollTop(0);
$(".node-info-property-header").click(function(e) {
e.preventDefault();
expandedSections["property"] = !expandedSections["property"];
$(this).toggleClass("expanded",expandedSections["property"]);
$(".node-info-property-row").toggle(expandedSections["property"]);
});
}
}
function setInfoText(infoText) {
var info = addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(infoSection.content);
info.find(".bidiAware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
var foldingHeader = "H3";
info.find(foldingHeader).wrapInner('<a class="node-info-header expanded" href="#"></a>')
.find("a").prepend('<i class="fa fa-angle-right">').click(function(e) {
e.preventDefault();
var isExpanded = $(this).hasClass('expanded');
var el = $(this).parent().next();
while(el.length === 1 && el[0].nodeName !== foldingHeader) {
el.toggle(!isExpanded);
el = el.next();
}
$(this).toggleClass('expanded',!isExpanded);
})
}
var tips = (function() {
var enabled = true;
var startDelay = 1000;
var cycleDelay = 15000;
var startTimeout;
var refreshTimeout;
var tipCount = -1;
RED.actions.add("core:toggle-show-tips",function(state) {
if (state === undefined) {
RED.userSettings.toggle("view-show-tips");
} else {
enabled = state;
if (enabled) {
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]));
}
tipBox.html(tip).fadeIn(200);
if (startTimeout) {
startTimeout = null;
refreshTimeout = setInterval(cycleTips,cycleDelay);
}
}
function cycleTips() {
tipBox.fadeOut(300,function() {
setTip();
})
}
function startTips() {
$(".sidebar-node-info").addClass('show-tips');
if (enabled) {
if (!startTimeout && !refreshTimeout) {
if (tipCount === -1) {
do {
tipCount++;
} while(RED._("infotips:info.tip"+tipCount)!=="infotips:info.tip"+tipCount);
}
startTimeout = setTimeout(setTip,startDelay);
}
}
}
function stopTips() {
$(".sidebar-node-info").removeClass('show-tips');
clearInterval(refreshTimeout);
clearTimeout(startTimeout);
refreshTimeout = null;
startTimeout = null;
}
function nextTip() {
clearInterval(refreshTimeout);
startTimeout = true;
setTip();
}
return {
start: startTips,
stop: stopTips,
next: nextTip,
enabled: function() { return enabled; }
}
})();
function clear() {
// sections.hide();
refresh(null);
}
function set(html,title) {
// tips.stop();
// sections.show();
refresh(null);
nodeSection.container.hide();
infoSection.title.text(title||RED._("sidebar.info.info"));
setInfoText(html);
$(".sidebar-node-info-stack").scrollTop(0);
}
function refreshSelection(selection) {
if (selection === undefined) {
selection = RED.view.selection();
}
if (selection.nodes) {
if (selection.nodes.length == 1) {
var node = selection.nodes[0];
if (node.type === "subflow" && node.direction) {
refresh(RED.nodes.subflow(node.z));
} else {
refresh(node);
}
} else {
refresh(selection.nodes);
}
} else {
var activeWS = RED.workspaces.active();
var flow = RED.nodes.workspace(activeWS) || RED.nodes.subflow(activeWS);
if (flow) {
refresh(flow);
} else {
var workspace = RED.nodes.workspace(RED.workspaces.active());
if (workspace && workspace.info) {
refresh(workspace);
} else {
refresh(null)
// clear();
}
}
}
}
RED.events.on("view:selection-changed",refreshSelection);
return {
init: init,
show: show,
refresh: refresh,
clear: clear,
set: set
}
})();

View File

@@ -0,0 +1,184 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.touch = RED.touch||{};
RED.touch.radialMenu = (function() {
var touchMenu = null;
var isActive = false;
var isOutside = false;
var activeOption = null;
function createRadial(obj,pos,options) {
isActive = true;
try {
var w = $("body").width();
var h = $("body").height();
touchMenu = d3.select("body").append("div")
.style({
position:"absolute",
top: 0,
left:0,
bottom:0,
right:0,
"z-index": 1000
})
.on('touchstart',function() {
hide();
d3.event.preventDefault();
});
var menu = touchMenu.append("div")
.style({
position: "absolute",
top: (pos[1]-80)+"px",
left:(pos[0]-80)+"px",
"border-radius": "80px",
width: "160px",
height: "160px",
background: "rgba(255,255,255,0.6)",
border: "1px solid #666"
});
var menuOpts = [];
var createMenuOpt = function(x,y,opt) {
opt.el = menu.append("div")
.style({
position: "absolute",
top: (y+80-25)+"px",
left:(x+80-25)+"px",
"border-radius": "20px",
width: "50px",
height: "50px",
background: "#fff",
border: "2px solid #666",
"text-align": "center",
"line-height":"50px"
});
opt.el.html(opt.name);
if (opt.disabled) {
opt.el.style({"border-color":"#ccc",color:"#ccc"});
}
opt.x = x;
opt.y = y;
menuOpts.push(opt);
opt.el.on('touchstart',function() {
opt.el.style("background","#999");
d3.event.preventDefault();
d3.event.stopPropagation();
});
opt.el.on('touchend',function() {
hide();
opt.onselect();
d3.event.preventDefault();
d3.event.stopPropagation();
});
}
var n = options.length;
var dang = Math.max(Math.PI/(n-1),Math.PI/4);
var ang = Math.PI;
for (var i=0;i<n;i++) {
var x = Math.floor(Math.cos(ang)*80);
var y = Math.floor(Math.sin(ang)*80);
if (options[i].name) {
createMenuOpt(x,y,options[i]);
}
ang += dang;
}
var hide = function() {
isActive = false;
activeOption = null;
touchMenu.remove();
touchMenu = null;
}
obj.on('touchend.radial',function() {
obj.on('touchend.radial',null);
obj.on('touchmenu.radial',null);
if (activeOption) {
try {
activeOption.onselect();
} catch(err) {
RED._debug(err);
}
hide();
} else if (isOutside) {
hide();
}
});
obj.on('touchmove.radial',function() {
try {
var touch0 = d3.event.touches.item(0);
var p = [touch0.pageX - pos[0],touch0.pageY-pos[1]];
for (var i=0;i<menuOpts.length;i++) {
var opt = menuOpts[i];
if (!opt.disabled) {
if (p[0]>opt.x-30 && p[0]<opt.x+30 && p[1]>opt.y-30 && p[1]<opt.y+30) {
if (opt !== activeOption) {
opt.el.style("background","#999");
activeOption = opt;
}
} else if (opt === activeOption) {
opt.el.style("background","#fff");
activeOption = null;
} else {
opt.el.style("background","#fff");
}
}
}
if (!activeOption) {
var d = Math.abs((p[0]*p[0])+(p[1]*p[1]));
isOutside = (d > 80*80);
}
} catch(err) {
RED._debug(err);
}
});
} catch(err) {
RED._debug(err);
}
}
return {
show: createRadial,
active: function() {
return isActive;
}
}
})();

View File

@@ -0,0 +1,279 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.tray = (function() {
var stack = [];
var editorStack = $("#editor-stack");
var openingTray = false;
function resize() {
}
function showTray(options) {
var el = $('<div class="editor-tray"></div>');
var header = $('<div class="editor-tray-header"></div>').appendTo(el);
var bodyWrapper = $('<div class="editor-tray-body-wrapper"></div>').appendTo(el);
var body = $('<div class="editor-tray-body"></div>').appendTo(bodyWrapper);
var footer = $('<div class="editor-tray-footer"></div>').appendTo(el);
var resizer = $('<div class="editor-tray-resize-handle"></div>').appendTo(el);
// var growButton = $('<a class="editor-tray-resize-button" style="cursor: w-resize;"><i class="fa fa-angle-left"></i></a>').appendTo(resizer);
// var shrinkButton = $('<a class="editor-tray-resize-button" style="cursor: e-resize;"><i style="margin-left: 1px;" class="fa fa-angle-right"></i></a>').appendTo(resizer);
if (options.title) {
var titles = stack.map(function(e) { return e.options.title });
titles.push(options.title);
var title = '<ul class="editor-tray-breadcrumbs"><li>'+titles.join("</li><li>")+'</li></ul>';
$('<div class="editor-tray-titlebar">'+title+'</div>').appendTo(header);
}
if (options.width === Infinity) {
options.maximized = true;
resizer.addClass('editor-tray-resize-maximised');
}
var buttonBar = $('<div class="editor-tray-toolbar"></div>').appendTo(header);
var primaryButton;
if (options.buttons) {
for (var i=0;i<options.buttons.length;i++) {
var button = options.buttons[i];
var b = $('<button>').button().appendTo(buttonBar);
if (button.id) {
b.attr('id',button.id);
}
if (button.text) {
b.text(button.text);
}
if (button.click) {
b.click((function(action) {
return function(evt) {
if (!$(this).hasClass('disabled')) {
action(evt);
}
};
})(button.click));
}
if (button.class) {
b.addClass(button.class);
if (button.class === "primary") {
primaryButton = button;
}
}
}
}
el.appendTo(editorStack);
var tray = {
tray: el,
header: header,
body: body,
footer: footer,
options: options,
primaryButton: primaryButton
};
stack.push(tray);
if (!options.maximized) {
el.draggable({
handle: resizer,
axis: "x",
start:function(event,ui) {
el.width('auto');
},
drag: function(event,ui) {
var absolutePosition = editorStack.position().left+ui.position.left
if (absolutePosition < 7) {
ui.position.left += 7-absolutePosition;
} else if (ui.position.left > -tray.preferredWidth-1) {
ui.position.left = -Math.min(editorStack.position().left-7,tray.preferredWidth-1);
}
if (tray.options.resize) {
setTimeout(function() {
tray.options.resize({width: -ui.position.left});
},0);
}
tray.width = -ui.position.left;
},
stop:function(event,ui) {
el.width(-ui.position.left);
el.css({left:''});
if (tray.options.resize) {
tray.options.resize({width: -ui.position.left});
}
tray.width = -ui.position.left;
}
});
}
function finishBuild() {
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$(".sidebar-shade").show();
tray.preferredWidth = Math.max(el.width(),500);
if (!options.maximized) {
body.css({"minWidth":tray.preferredWidth-40});
}
if (options.width) {
if (options.width > $("#editor-stack").position().left-8) {
options.width = $("#editor-stack").position().left-8;
}
el.width(options.width);
} else {
el.width(tray.preferredWidth);
}
tray.width = el.width();
if (tray.width > $("#editor-stack").position().left-8) {
tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8);
el.width(tray.width);
}
// tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width));
el.css({
right: -(el.width()+10)+"px",
transition: "right 0.25s ease"
});
$("#workspace").scrollLeft(0);
handleWindowResize();
openingTray = true;
setTimeout(function() {
setTimeout(function() {
if (!options.width) {
el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8));
}
if (options.resize) {
options.resize({width:el.width()});
}
if (options.show) {
options.show();
}
setTimeout(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);
}
if (options.open) {
if (options.open.length === 1) {
options.open(el);
finishBuild();
} else {
options.open(el,finishBuild);
}
} else {
finishBuild();
}
}
function handleWindowResize() {
if (stack.length > 0) {
var tray = stack[stack.length-1];
var trayHeight = tray.tray.height()-tray.header.outerHeight()-tray.footer.outerHeight();
tray.body.height(trayHeight);
if (tray.options.maximized || tray.width > $("#editor-stack").position().left-8) {
tray.width = $("#editor-stack").position().left-8;
tray.tray.width(tray.width);
// tray.body.parent().width(tray.width);
} else if (tray.width < tray.preferredWidth) {
tray.width = Math.min($("#editor-stack").position().left-8,tray.preferredWidth);
tray.tray.width(tray.width);
// tray.body.parent().width(tray.width);
}
if (tray.options.resize) {
tray.options.resize({width:tray.width, height:trayHeight});
}
}
}
return {
init: function init() {
$(window).resize(handleWindowResize);
RED.events.on("sidebar:resize",handleWindowResize);
$("#editor-shade").click(function() {
if (!openingTray) {
var tray = stack[stack.length-1];
if (tray && tray.primaryButton) {
tray.primaryButton.click();
}
}
});
},
show: function show(options) {
if (stack.length > 0 && !options.overlay) {
var oldTray = stack[stack.length-1];
if (options.width === "inherit") {
options.width = oldTray.tray.width();
}
oldTray.tray.css({
right: -(oldTray.tray.width()+10)+"px"
});
setTimeout(function() {
oldTray.tray.detach();
showTray(options);
},250)
} else {
RED.events.emit("editor:open");
showTray(options);
}
},
close: function close(done) {
if (stack.length > 0) {
var tray = stack.pop();
tray.tray.css({
right: -(tray.tray.width()+10)+"px"
});
setTimeout(function() {
if (tray.options.close) {
tray.options.close();
}
tray.tray.remove();
if (stack.length > 0) {
var oldTray = stack[stack.length-1];
if (!oldTray.options.overlay) {
oldTray.tray.appendTo("#editor-stack");
setTimeout(function() {
handleWindowResize();
oldTray.tray.css({right:0});
if (oldTray.options.show) {
oldTray.options.show();
}
},0);
} else {
handleWindowResize();
if (oldTray.options.show) {
oldTray.options.show();
}
}
}
if (done) {
done();
}
if (stack.length === 0) {
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$(".sidebar-shade").hide();
RED.events.emit("editor:close");
RED.view.focus();
}
},250)
}
}
}
})();

View File

@@ -0,0 +1,330 @@
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 cancelCallback;
var typesUsed = {};
function search(val) {
activeFilter = val.toLowerCase();
var visible = searchResults.editableList('filter');
searchResults.editableList('sort');
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);
},
sort: function(A,B) {
if (activeFilter === "") {
return A.i - B.i;
}
var Ai = A.index.indexOf(activeFilter);
var Bi = B.index.indexOf(activeFilter);
if (Ai === -1) {
return 1;
}
if (Bi === -1) {
return -1;
}
if (Ai === Bi) {
return sortTypeLabels(A,B);
}
return Ai-Bi;
},
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 = RED.utils.getNodeColor(object.type,def);
var icon_url = RED.utils.getNodeIcon(def);
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url("+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"}).text(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);
if (cancelCallback) {
cancelCallback();
}
}
}
function show(opts) {
if (!visible) {
RED.keyboard.add("*","escape",function(){
hide();
if (cancelCallback) {
cancelCallback();
}
});
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;
closeCallback = opts.close;
RED.events.emit("type-search:open");
//shade.show();
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
searchResultsDiv.slideDown(300);
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 sortTypeLabels(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;
}
}
function refreshTypeList() {
var i;
searchResults.editableList('empty');
searchInput.searchBox('value','');
selected = -1;
var common = [
'inject','debug','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' && t !== 'tab') {
items.push({type:t,def: def, label:getTypeLabel(t,def)});
}
});
items.sort(sortTypeLabels);
var commonCount = 0;
var item;
var index = 0;
for(i=0;i<common.length;i++) {
var itemDef = RED.nodes.getType(common[i]);
if (itemDef) {
item = {
type: common[i],
common: true,
def: itemDef,
i: index++
};
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,
i: index++
};
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++) {
items[i].i = index++;
searchResults.editableList('addItem', items[i]);
}
setTimeout(function() {
selected = 0;
searchResults.children(":first").addClass('selected');
},100);
}
return {
show: show,
hide: hide
};
})();

View File

@@ -0,0 +1,242 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.userSettings = (function() {
var trayWidth = 700;
var settingsVisible = false;
var panes = [];
function addPane(options) {
panes.push(options);
}
function show(initialTab) {
if (settingsVisible) {
return;
}
if (!RED.user.hasPermission("settings.write")) {
RED.notify(RED._("user.errors.settings"),"error");
return;
}
settingsVisible = true;
var tabContainer;
var trayOptions = {
title: RED._("menu.label.userSettings"),
buttons: [
{
id: "node-dialog-ok",
text: RED._("common.label.close"),
class: "primary",
click: function() {
RED.tray.close();
}
}
],
resize: function(dimensions) {
trayWidth = dimensions.width;
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var settingsContent = $('<div></div>').appendTo(trayBody);
var tabContainer = $('<div></div>',{id:"user-settings-tabs-container"}).appendTo(settingsContent);
$('<ul></ul>',{id:"user-settings-tabs"}).appendTo(tabContainer);
var settingsTabs = RED.tabs.create({
id: "user-settings-tabs",
vertical: true,
onchange: function(tab) {
setTimeout(function() {
$("#user-settings-tabs-content").children().hide();
$("#" + tab.id).show();
if (tab.pane.focus) {
tab.pane.focus();
}
},50);
}
});
var tabContents = $('<div></div>',{id:"user-settings-tabs-content"}).appendTo(settingsContent);
panes.forEach(function(pane) {
settingsTabs.addTab({
id: "user-settings-tab-"+pane.id,
label: pane.title,
pane: pane
});
pane.get().hide().appendTo(tabContents);
});
settingsContent.i18n();
settingsTabs.activateTab("user-settings-tab-"+(initialTab||'view'))
$("#sidebar-shade").show();
},
close: function() {
settingsVisible = false;
panes.forEach(function(pane) {
if (pane.close) {
pane.close();
}
});
$("#sidebar-shade").hide();
},
show: function() {}
}
if (trayWidth !== null) {
trayOptions.width = trayWidth;
}
RED.tray.show(trayOptions);
}
var viewSettings = [
{
title: "menu.label.view.grid",
options: [
{setting:"view-show-grid",oldSetting:"menu-menu-item-view-show-grid",label:"menu.label.view.showGrid",toggle:true,onchange:"core:toggle-show-grid"},
{setting:"view-snap-grid",oldSetting:"menu-menu-item-view-snap-grid",label:"menu.label.view.snapGrid",toggle:true,onchange:"core:toggle-snap-grid"},
{setting:"view-grid-size",label:"menu.label.view.gridSize",type:"number",default: 20, onchange:RED.view.gridSize}
]
},
{
title: "menu.label.nodes",
options: [
{setting:"view-node-status",oldSetting:"menu-menu-item-status",label:"menu.label.displayStatus",default: true, toggle:true,onchange:"core:toggle-status"}
]
},
{
title: "menu.label.other",
options: [
{setting:"view-show-tips",oldSettings:"menu-menu-item-show-tips",label:"menu.label.showTips",toggle:true,default:true,onchange:"core:toggle-show-tips"}
]
}
];
var allSettings = {};
function createViewPane() {
var pane = $('<div id="user-settings-tab-view" class="node-help"></div>');
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.view = currentEditorSettings.view || {};
viewSettings.forEach(function(section) {
$('<h3></h3>').text(RED._(section.title)).appendTo(pane);
section.options.forEach(function(opt) {
var initialState = currentEditorSettings.view[opt.setting];
var row = $('<div class="user-settings-row"></div>').appendTo(pane);
var input;
if (opt.toggle) {
input = $('<label for="user-settings-'+opt.setting+'"><input id="user-settings-'+opt.setting+'" type="checkbox"> '+RED._(opt.label)+'</label>').appendTo(row).find("input");
input.prop('checked',initialState);
} else {
$('<label for="user-settings-'+opt.setting+'">'+RED._(opt.label)+'</label>').appendTo(row);
$('<input id="user-settings-'+opt.setting+'" type="'+(opt.type||"text")+'">').appendTo(row).val(initialState);
}
});
})
return pane;
}
function setSelected(id, value) {
var opt = allSettings[id];
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.view = currentEditorSettings.view || {};
currentEditorSettings.view[opt.setting] = value;
RED.settings.set('editor', currentEditorSettings);
var callback = opt.onchange;
if (typeof callback === 'string') {
callback = RED.actions.get(callback);
}
if (callback) {
callback.call(opt,value);
}
}
function toggle(id) {
var opt = allSettings[id];
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.view = currentEditorSettings.view || {};
setSelected(id,!currentEditorSettings.view[opt.setting]);
}
function init() {
RED.actions.add("core:show-user-settings",show);
RED.actions.add("core:show-help", function() { show('keyboard')});
addPane({
id:'view',
title: RED._("menu.label.view.view"),
get: createViewPane,
close: function() {
viewSettings.forEach(function(section) {
section.options.forEach(function(opt) {
var input = $("#user-settings-"+opt.setting);
if (opt.toggle) {
setSelected(opt.setting,input.prop('checked'));
} else {
setSelected(opt.setting,input.val());
}
});
})
}
})
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.view = currentEditorSettings.view || {};
var editorSettingsChanged = false;
viewSettings.forEach(function(section) {
section.options.forEach(function(opt) {
if (opt.oldSetting) {
var oldValue = RED.settings.get(opt.oldSetting);
if (oldValue !== undefined && oldValue !== null) {
currentEditorSettings.view[opt.setting] = oldValue;
editorSettingsChanged = true;
RED.settings.remove(opt.oldSetting);
}
}
allSettings[opt.setting] = opt;
if (opt.onchange) {
var value = currentEditorSettings.view[opt.setting];
if ((value === null || value === undefined) && opt.hasOwnProperty('default')) {
value = opt.default;
currentEditorSettings.view[opt.setting] = value;
editorSettingsChanged = true;
}
var callback = opt.onchange;
if (typeof callback === 'string') {
callback = RED.actions.get(callback);
}
if (callback) {
callback.call(opt,value);
}
}
});
});
if (editorSettingsChanged) {
RED.settings.set('editor',currentEditorSettings);
}
}
return {
init: init,
toggle: toggle,
show: show,
add: addPane
};
})();

View File

@@ -0,0 +1,894 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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>').text('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>').text('buffer['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('array['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'function') {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('function');
} else if (value.hasOwnProperty('type') && value.type === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(value.data);
} 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 if (typeof value === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(""+value);
} else {
result = $('<span class="debug-message-object-value debug-message-type-other"></span>').text(""+value);
}
return result;
}
function makeExpandable(el,onbuild,ontoggle,expand) {
el.addClass("debug-message-expandable");
el.prop('toggle',function() {
return function(state) {
var parent = el.parent();
if (parent.hasClass('collapsed')) {
if (state) {
if (onbuild && !parent.hasClass('built')) {
onbuild();
parent.addClass('built');
}
parent.removeClass('collapsed');
return true;
}
} else {
if (!state) {
parent.addClass('collapsed');
return true;
}
}
return false;
}
});
el.click(function(e) {
var parent = $(this).parent();
var currentState = !parent.hasClass('collapsed');
if ($(this).prop('toggle')(!currentState)) {
if (ontoggle) {
ontoggle(!currentState);
}
}
// if (parent.hasClass('collapsed')) {
// if (onbuild && !parent.hasClass('built')) {
// onbuild();
// parent.addClass('built');
// }
// if (ontoggle) {
// ontoggle(true);
// }
// parent.removeClass('collapsed');
// } else {
// parent.addClass('collapsed');
// if (ontoggle) {
// ontoggle(false);
// }
// }
e.preventDefault();
});
if (expand) {
el.click();
}
}
var pinnedPaths = {};
var formattedPaths = {};
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey) {
if (!pinnedPaths.hasOwnProperty(sourceId)) {
pinnedPaths[sourceId] = {}
}
var tools = $('<span class="debug-message-tools"></span>').appendTo(obj);
var copyTools = $('<span class="debug-message-tools-copy button-group"></span>').appendTo(tools);
if (!!key) {
var copyPath = $('<button class="editor-button editor-button-small"><i class="fa fa-terminal"></i></button>').appendTo(copyTools).click(function(e) {
e.preventDefault();
e.stopPropagation();
RED.clipboard.copyText(key,copyPath,"clipboard.copyMessagePath");
})
}
var copyPayload = $('<button class="editor-button editor-button-small"><i class="fa fa-clipboard"></i></button>').appendTo(copyTools).click(function(e) {
e.preventDefault();
e.stopPropagation();
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
})
if (strippedKey !== undefined && strippedKey !== '') {
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
var pinPath = $('<button class="editor-button editor-button-small debug-message-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).click(function(e) {
e.preventDefault();
e.stopPropagation();
if (pinnedPaths[sourceId].hasOwnProperty(strippedKey)) {
delete pinnedPaths[sourceId][strippedKey];
$(this).removeClass("selected");
obj.removeClass("debug-message-row-pinned");
} else {
var rootedPath = "$"+(strippedKey[0] === '['?"":".")+strippedKey;
pinnedPaths[sourceId][strippedKey] = normalisePropertyExpression(rootedPath);
$(this).addClass("selected");
obj.addClass("debug-message-row-pinned");
}
}).toggleClass("selected",isPinned);
obj.toggleClass("debug-message-row-pinned",isPinned);
}
}
function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
if (expandPaths && expandPaths.length > 0) {
if (strippedKey === '' && minRange === undefined) {
return true;
}
for (var i=0;i<expandPaths.length;i++) {
var p = expandPaths[i];
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
if (minRange !== undefined && p[strippedKey.length] === "[") {
var subkey = p.substring(strippedKey.length);
var m = (/\[(\d+)\]/.exec(subkey));
if (m) {
var index = parseInt(m[1]);
return minRange<=index && index<=maxRange;
}
} else {
return true;
}
}
}
}
return false;
}
function formatNumber(element,obj,sourceId,path,cycle,initialFormat) {
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path] && formattedPaths[sourceId][path]['number']) || initialFormat || "dec";
if (cycle) {
if (format === 'dec') {
if ((obj.toString().length===13) && (obj<=2147483647000)) {
format = 'dateMS';
} else if ((obj.toString().length===10) && (obj<=2147483647)) {
format = 'dateS';
} else {
format = 'hex'
}
} else if (format === 'dateMS' || format == 'dateS') {
format = 'hex';
} else {
format = 'dec';
}
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['number'] = format;
} else if (initialFormat !== undefined){
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['number'] = format;
}
if (format === 'dec') {
element.text(""+obj);
} else if (format === 'dateMS') {
element.text((new Date(obj)).toISOString());
} else if (format === 'dateS') {
element.text((new Date(obj*1000)).toISOString());
} else if (format === 'hex') {
element.text("0x"+(obj).toString(16));
}
}
function formatBuffer(element,button,sourceId,path,cycle) {
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path] && formattedPaths[sourceId][path]['buffer']) || "raw";
if (cycle) {
if (format === 'raw') {
format = 'string';
} else {
format = 'raw';
}
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['buffer'] = format;
}
if (format === 'raw') {
button.text('raw');
element.removeClass('debug-message-buffer-string').addClass('debug-message-buffer-raw');
} else if (format === 'string') {
button.text('string');
element.addClass('debug-message-buffer-string').removeClass('debug-message-buffer-raw');
}
}
function buildMessageElement(obj,options) {
options = options || {};
var key = options.key;
var typeHint = options.typeHint;
var hideKey = options.hideKey;
var path = options.path;
var sourceId = options.sourceId;
var rootPath = options.rootPath;
var expandPaths = options.expandPaths;
var ontoggle = options.ontoggle;
var exposeApi = options.exposeApi;
var subElements = {};
var i;
var e;
var entryObj;
var expandableHeader;
var header;
var headerHead;
var value;
var strippedKey;
if (path !== undefined && rootPath !== undefined) {
strippedKey = path.substring(rootPath.length+(path[rootPath.length]==="."?1:0));
}
var element = $('<span class="debug-message-element"></span>');
element.collapse = function() {
element.find(".debug-message-expandable").parent().addClass("collapsed");
}
header = $('<span class="debug-message-row"></span>').appendTo(element);
if (sourceId) {
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey);
}
if (!key) {
element.addClass("debug-message-top-level");
if (sourceId) {
var pinned = pinnedPaths[sourceId];
expandPaths = [];
if (pinned) {
for (var pinnedPath in pinned) {
if (pinned.hasOwnProperty(pinnedPath)) {
try {
var res = getMessageProperty({$:obj},pinned[pinnedPath]);
if (res !== undefined) {
expandPaths.push(pinnedPath);
}
} catch(err) {
}
}
}
expandPaths.sort();
}
element.clearPinned = function() {
element.find(".debug-message-row-pinned").removeClass("debug-message-row-pinned");
pinnedPaths[sourceId] = {};
}
}
} else {
if (!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.__enc__ && 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 (obj.__enc__ && obj.type === 'number') {
e = $('<span class="debug-message-type-number debug-message-object-header"></span>').text(obj.data).appendTo(entryObj);
} else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("function").appendTo(entryObj);
} else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("[internal]").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>').text(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);
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
}
e = $('<span class="debug-message-type-string debug-message-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
if (/^#[0-9a-f]{6}$/i.test(obj)) {
$('<span class="debug-message-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
}
} else if (typeof obj === 'number') {
e = $('<span class="debug-message-type-number"></span>').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) {
evt.preventDefault();
formatNumber($(this), obj, sourceId, path, true);
});
}
formatNumber(e,obj,sourceId,path,false,typeHint==='hex'?'hex':undefined);
} 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;
}
if (data.__enc__) {
data = data.data;
}
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');
}
if (key) {
headerHead = $('<span class="debug-message-type-meta"></span>').text(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);
}
if (originalLength > 0) {
makeExpandable(header,function() {
if (!key) {
headerHead = $('<span class="debug-message-type-meta debug-message-object-type-header"></span>').text(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);
var switchFormat = $('<a href="#"></a>').addClass('selected').text('raw').appendTo(bufferOpts).click(function(e) {
e.preventDefault();
e.stopPropagation();
formatBuffer(element,$(this),sourceId,path,true);
});
formatBuffer(element,switchFormat,sourceId,path,false);
}
var row;
if (fullLength <= 10) {
for (i=0;i<fullLength;i++) {
row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(arrayRows);
subElements[path+"["+i+"]"] = buildMessageElement(
data[i],
{
key: ""+i,
typeHint: type==='buffer'?'hex':false,
hideKey: false,
path: path+"["+i+"]",
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi
}
).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);
subElements[path+"["+i+"]"] = buildMessageElement(
data[i],
{
key: ""+i,
typeHint: type==='buffer'?'hex':false,
hideKey: false,
path: path+"["+i+"]",
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi
}
).appendTo(row);
}
}
})(),
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
$('<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);
}
}
},
function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey,expandPaths));
}
} 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>').text('object').appendTo(header);
}
for (i=0;i<keys.length;i++) {
var row = $('<div class="debug-message-object-entry collapsed"></div>').appendTo(element);
var newPath = path;
if (newPath !== undefined) {
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(keys[i])) {
newPath += (newPath.length > 0?".":"")+keys[i];
} else {
newPath += "[\""+keys[i].replace(/"/,"\\\"")+"\"]"
}
}
subElements[newPath] = buildMessageElement(
obj[keys[i]],
{
key: keys[i],
typeHint: false,
hideKey: false,
path: newPath,
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi
}
).appendTo(row);
}
if (keys.length === 0) {
$('<div class="debug-message-object-entry debug-message-type-meta collapsed"></div>').text("empty").appendTo(element);
}
},
function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey,expandPaths));
}
if (key) {
$('<span class="debug-message-type-meta"></span>').text('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);
}
if (exposeApi) {
element.prop('expand', function() { return function(targetPath, state) {
if (path === targetPath) {
if (header.prop('toggle')) {
header.prop('toggle')(state);
}
} else if (subElements[targetPath] && subElements[targetPath].prop('expand') ) {
subElements[targetPath].prop('expand')(targetPath,state);
} else {
for (var p in subElements) {
if (subElements.hasOwnProperty(p)) {
if (targetPath.indexOf(p) === 0) {
if (subElements[p].prop('expand') ) {
subElements[p].prop('expand')(targetPath,state);
}
break;
}
}
}
}
}});
}
return element;
}
function normalisePropertyExpression(str) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
var length = str.length;
if (length === 0) {
throw new Error("Invalid property expression: zero-length");
}
var parts = [];
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) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0) {
throw new Error("Invalid property expression: unexpected . at position 0");
}
if (start != i) {
v = str.substring(start,i);
if (/^\d+$/.test(v)) {
parts.push(parseInt(v));
} else {
parts.push(v);
}
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
}
// Next char is first char of an identifier: a-z 0-9 $ _
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
parts.push(str.substring(start,i));
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
}
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
v = str.substring(start,i);
if (/^\d+$/.test(v)) {
parts.push(parseInt(v));
} else {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
throw new Error("Invalid property expression: unexpected ' ' at position "+i);
}
} 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));
// 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;
}
}
}
if (inBox || inString) {
throw new Error("Invalid property expression: unterminated expression");
}
if (start < length) {
parts.push(str.substring(start));
}
return parts;
}
function validatePropertyExpression(str) {
try {
var parts = normalisePropertyExpression(str);
return true;
} catch(err) {
return false;
}
}
function getMessageProperty(msg,expr) {
var result = null;
var msgPropParts;
if (typeof expr === 'string') {
if (expr.indexOf('msg.')===0) {
expr = expr.substring(4);
}
msgPropParts = normalisePropertyExpression(expr);
} else {
msgPropParts = expr;
}
var m;
msgPropParts.reduce(function(obj, key) {
result = (typeof obj[key] !== "undefined" ? obj[key] : undefined);
if (result === undefined && obj.hasOwnProperty('type') && obj.hasOwnProperty('data')&& obj.hasOwnProperty('length')) {
result = (typeof obj.data[key] !== "undefined" ? obj.data[key] : undefined);
}
return result;
}, msg);
return result;
}
function separateIconPath(icon) {
var result = {module: "", file: ""};
if (icon) {
var index = icon.indexOf('/');
if (index !== -1) {
result.module = icon.slice(0, index);
result.file = icon.slice(index + 1);
} else {
result.file = icon;
}
}
return result;
}
function getDefaultNodeIcon(def,node) {
var icon_url;
if (node && node.type === "subflow") {
icon_url = "node-red/subflow.png";
} else if (typeof def.icon === "function") {
try {
icon_url = def.icon.call(node);
} catch(err) {
console.log("Definition error: "+def.type+".icon",err);
icon_url = "arrow-in.png";
}
} else {
icon_url = def.icon;
}
var iconPath = separateIconPath(icon_url);
if (!iconPath.module) {
if (def.set) {
iconPath.module = def.set.module;
} else {
// Handle subflow instance nodes that don't have def.set
iconPath.module = "node-red";
}
}
return iconPath;
}
function isIconExists(iconPath) {
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
if (iconFileList && iconFileList.indexOf(iconPath.file) !== -1) {
return true;
} else {
return false;
}
}
function getNodeIcon(def,node) {
if (def.category === 'config') {
return RED.settings.apiRootUrl+"icons/node-red/cog.png"
} else if (node && node.type === 'tab') {
return RED.settings.apiRootUrl+"icons/node-red/subflow.png"
} else if (node && node.type === 'unknown') {
return RED.settings.apiRootUrl+"icons/node-red/alert.png"
} else if (node && node.icon) {
var iconPath = separateIconPath(node.icon);
if (isIconExists(iconPath)) {
return RED.settings.apiRootUrl+"icons/" + node.icon;
}
}
var iconPath = getDefaultNodeIcon(def, node);
if (def.category === 'subflows') {
if (!isIconExists(iconPath)) {
return RED.settings.apiRootUrl+"icons/node-red/subflow.png";
}
}
return RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file;
}
function getNodeLabel(node,defaultLabel) {
defaultLabel = defaultLabel||"";
var l;
if (node.type === 'tab') {
l = node.label || defaultLabel
} else {
l = node._def.label;
try {
l = (typeof l === "function" ? l.call(node) : l)||defaultLabel;
} catch(err) {
console.log("Definition error: "+node.type+".label",err);
l = defaultLabel;
}
}
return RED.text.bidi.enforceTextDirectionWithUCC(l);
}
var nodeColorCache = {};
function getNodeColor(type, def) {
var result = def.color;
var paletteTheme = RED.settings.theme('palette.theme') || [];
if (paletteTheme.length > 0) {
if (!nodeColorCache.hasOwnProperty(type)) {
var l = paletteTheme.length;
for (var i=0;i<l;i++ ){
var themeRule = paletteTheme[i];
if (themeRule.hasOwnProperty('category')) {
if (!themeRule.hasOwnProperty('_category')) {
themeRule._category = new RegExp(themeRule.category);
}
if (!themeRule._category.test(def.category)) {
continue;
}
}
if (themeRule.hasOwnProperty('type')) {
if (!themeRule.hasOwnProperty('_type')) {
themeRule._type = new RegExp(themeRule.type);
}
if (!themeRule._type.test(type)) {
continue;
}
}
nodeColorCache[type] = themeRule.color || def.color;
break;
}
}
result = nodeColorCache[type];
}
return result;
}
function addSpinnerOverlay(container,contain) {
var spinner = $('<div class="projects-dialog-spinner "><img src="red/images/spin.svg"/></div>').appendTo(container);
if (contain) {
spinner.addClass('projects-dialog-spinner-contain');
}
return spinner;
}
function decodeObject(payload,format) {
if ((format === 'number') && (payload === "NaN")) {
payload = Number.NaN;
} else if ((format === 'number') && (payload === "Infinity")) {
payload = Infinity;
} else if ((format === 'number') && (payload === "-Infinity")) {
payload = -Infinity;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
payload = (payload.name?payload.name+": ":"")+payload.message;
} 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));
}
}
return payload;
}
function parseContextKey(key) {
var parts = {};
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
if (m) {
parts.store = m[1];
parts.key = m[2];
} else {
parts.key = key;
if (RED.settings.context) {
parts.store = RED.settings.context.default;
}
}
return parts;
}
return {
createObjectElement: buildMessageElement,
getMessageProperty: getMessageProperty,
normalisePropertyExpression: normalisePropertyExpression,
validatePropertyExpression: validatePropertyExpression,
separateIconPath: separateIconPath,
getDefaultNodeIcon: getDefaultNodeIcon,
getNodeIcon: getNodeIcon,
getNodeLabel: getNodeLabel,
getNodeColor: getNodeColor,
addSpinnerOverlay: addSpinnerOverlay,
decodeObject: decodeObject,
parseContextKey: parseContextKey
}
})();

View File

@@ -0,0 +1,168 @@
/**
* 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.view.navigator = (function() {
var nav_scale = 25;
var nav_width = 5000/nav_scale;
var nav_height = 5000/nav_scale;
var navContainer;
var navBox;
var navBorder;
var navVis;
var scrollPos;
var scaleFactor;
var chartSize;
var dimensions;
var isDragging;
var isShowing = false;
function refreshNodes() {
if (!isShowing) {
return;
}
var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id});
navNode.exit().remove();
navNode.enter().insert("rect")
.attr('class','navnode')
.attr("pointer-events", "none");
navNode.each(function(d) {
d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale })
.attr("y",function(d) { return (d.y-d.h/2)/nav_scale })
.attr("width",function(d) { return Math.max(9,d.w/nav_scale) })
.attr("height",function(d) { return Math.max(3,d.h/nav_scale) })
.attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def);})
});
}
function onScroll() {
if (!isDragging) {
resizeNavBorder();
}
}
function resizeNavBorder() {
if (navBorder) {
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
navBorder.attr('x',scrollPos[0]/nav_scale)
.attr('y',scrollPos[1]/nav_scale)
.attr('width',chartSize[0]/nav_scale/scaleFactor)
.attr('height',chartSize[1]/nav_scale/scaleFactor)
}
}
function toggle() {
if (!isShowing) {
isShowing = true;
$("#btn-navigate").addClass("selected");
resizeNavBorder();
refreshNodes();
$("#chart").on("scroll",onScroll);
navContainer.fadeIn(200);
} else {
isShowing = false;
navContainer.fadeOut(100);
$("#chart").off("scroll",onScroll);
$("#btn-navigate").removeClass("selected");
}
}
return {
init: function() {
$(window).resize(resizeNavBorder);
RED.events.on("sidebar:resize",resizeNavBorder);
RED.actions.add("core:toggle-navigator",toggle);
var hideTimeout;
navContainer = $('<div>').css({
"position":"absolute",
"bottom":$("#workspace-footer").height(),
"right":0,
zIndex: 1
}).appendTo("#workspace").hide();
navBox = d3.select(navContainer[0])
.append("svg:svg")
.attr("width", nav_width)
.attr("height", nav_height)
.attr("pointer-events", "all")
.style({
position: "absolute",
bottom: 0,
right:0,
zIndex: 101,
"border-left": "1px solid #ccc",
"border-top": "1px solid #ccc",
background: "rgba(245,245,245,0.5)",
"box-shadow": "-1px 0 3px rgba(0,0,0,0.1)"
});
navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({
fill:"none",
stroke:"none",
pointerEvents:"all"
}).on("mousedown", function() {
// Update these in case they have changed
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor];
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
isDragging = true;
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mousemove", function() {
if (!isDragging) { return }
if (d3.event.buttons === 0) {
isDragging = false;
return;
}
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mouseup", function() {
isDragging = false;
})
navBorder = navBox.append("rect")
.attr("stroke-dasharray","5,5")
.attr("pointer-events", "none")
.style({
stroke: "#999",
strokeWidth: 1,
fill: "white",
});
navVis = navBox.append("svg:g")
$("#btn-navigate").click(function(evt) {
evt.preventDefault();
toggle();
})
},
refresh: refreshNodes,
resize: resizeNavBorder,
toggle: toggle
}
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,377 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.workspaces = (function() {
var activeWorkspace = 0;
var workspaceIndex = 0;
function addWorkspace(ws,skipHistoryEntry) {
if (ws) {
workspace_tabs.addTab(ws);
workspace_tabs.resize();
} else {
var tabId = RED.nodes.id();
do {
workspaceIndex += 1;
} while ($("#workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
ws = {type:"tab",id:tabId,disabled: false,info:"",label:RED._('workspace.defaultName',{number:workspaceIndex})};
RED.nodes.addWorkspace(ws);
workspace_tabs.addTab(ws);
workspace_tabs.activateTab(tabId);
if (!skipHistoryEntry) {
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
}
}
RED.view.focus();
return ws;
}
function deleteWorkspace(ws) {
if (workspaceTabCount === 1) {
return;
}
removeWorkspace(ws);
var historyEvent = RED.nodes.removeWorkspace(ws.id);
historyEvent.t = 'delete';
historyEvent.dirty = RED.nodes.dirty();
historyEvent.workspaces = [ws];
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
}
function showRenameWorkspaceDialog(id) {
var workspace = RED.nodes.workspace(id);
RED.view.state(RED.state.EDITING);
var tabflowEditor;
var trayOptions = {
title: RED._("workspace.editFlow",{name:workspace.label}),
buttons: [
{
id: "node-dialog-delete",
class: 'leftButton'+((workspaceTabCount === 1)?" disabled":""),
text: RED._("common.label.delete"), //'<i class="fa fa-trash"></i>',
click: function() {
deleteWorkspace(workspace);
RED.tray.close();
}
},
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
class: "primary",
text: RED._("common.label.done"),
click: function() {
var label = $( "#node-input-name" ).val();
var changed = false;
var changes = {};
if (workspace.label != label) {
changes.label = workspace.label;
changed = true;
workspace.label = label;
workspace_tabs.renameTab(workspace.id,label);
}
var disabled = $("#node-input-disabled").prop("checked");
if (workspace.disabled !== disabled) {
changes.disabled = workspace.disabled;
changed = true;
workspace.disabled = disabled;
}
var info = tabflowEditor.getValue();
if (workspace.info !== info) {
changes.info = workspace.info;
changed = true;
workspace.info = info;
}
$("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('workspace-disabled',workspace.disabled);
// $("#workspace").toggleClass("workspace-disabled",workspace.disabled);
if (changed) {
var historyEvent = {
t: "edit",
changes:changes,
node: workspace,
dirty: RED.nodes.dirty()
}
workspace.changed = true;
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
var selection = RED.view.selection();
if (!selection.nodes && !selection.links) {
RED.sidebar.info.refresh(workspace);
}
}
RED.tray.close();
}
}
],
resize: function(dimensions) {
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")));
height -= 28;
$(".node-text-editor").css("height",height+"px");
tabflowEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
$('<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name">'+
'</div>').appendTo(dialogForm);
$('<div class="form-row">'+
'<label for="node-input-disabled-btn" data-i18n="editor:workspace.status"></label>'+
'<button id="node-input-disabled-btn" class="editor-button"><i class="fa fa-toggle-on"></i> <span id="node-input-disabled-label"></span></button> '+
'<input type="checkbox" id="node-input-disabled" style="display: none;"/>'+
'</div>').appendTo(dialogForm);
$('<div class="form-row node-text-editor-row">'+
'<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
'<div style="height:250px;" class="node-text-editor" id="node-input-info"></div>'+
'</div>').appendTo(dialogForm);
tabflowEditor = RED.editor.createEditor({
id: 'node-input-info',
mode: 'ace/mode/markdown',
value: ""
});
$('<div class="form-tips" data-i18n="editor:workspace.tip"></div>').appendTo(dialogForm);
dialogForm.find('#node-input-disabled-btn').on("click",function(e) {
var i = $(this).find("i");
if (i.hasClass('fa-toggle-off')) {
i.addClass('fa-toggle-on');
i.removeClass('fa-toggle-off');
$("#node-input-disabled").prop("checked",false);
$("#node-input-disabled-label").text(RED._("editor:workspace.enabled"));
} else {
i.addClass('fa-toggle-off');
i.removeClass('fa-toggle-on');
$("#node-input-disabled").prop("checked",true);
$("#node-input-disabled-label").text(RED._("editor:workspace.disabled"));
}
})
if (workspace.hasOwnProperty("disabled")) {
$("#node-input-disabled").prop("checked",workspace.disabled);
if (workspace.disabled) {
dialogForm.find("#node-input-disabled-btn i").removeClass('fa-toggle-on').addClass('fa-toggle-off');
$("#node-input-disabled-label").text(RED._("editor:workspace.disabled"));
} else {
$("#node-input-disabled-label").text(RED._("editor:workspace.enabled"));
}
} else {
workspace.disabled = false;
$("#node-input-disabled-label").text(RED._("editor:workspace.enabled"));
}
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
$("#node-input-name").val(workspace.label);
RED.text.bidi.prepareInput($("#node-input-name"));
tabflowEditor.getSession().setValue(workspace.info || "", -1);
dialogForm.i18n();
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.info.refresh(workspace);
tabflowEditor.destroy();
}
}
RED.tray.show(trayOptions);
}
var workspace_tabs;
var workspaceTabCount = 0;
function createWorkspaceTabs() {
workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
// $("#workspace").toggleClass("workspace-disabled",tab.disabled);
RED.events.emit("workspace:change",event);
window.location.hash = 'flow/'+tab.id;
RED.sidebar.config.refresh();
RED.view.focus();
},
onclick: function(tab) {
RED.view.focus();
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
if (tab.type === "tab") {
workspaceTabCount++;
}
$('<span class="workspace-disabled-icon"><i class="fa fa-ban"></i> </span>').prependTo("#red-ui-tab-"+(tab.id.replace(".","-"))+" .red-ui-tab-label");
if (tab.disabled) {
$("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('workspace-disabled');
}
RED.menu.setDisabled("menu-item-workspace-delete",workspaceTabCount <= 1);
if (workspaceTabCount === 1) {
showWorkspace();
}
},
onremove: function(tab) {
if (tab.type === "tab") {
workspaceTabCount--;
}
RED.menu.setDisabled("menu-item-workspace-delete",workspaceTabCount <= 1);
if (workspaceTabCount === 0) {
hideWorkspace();
}
},
onreorder: function(oldOrder, newOrder) {
RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
setWorkspaceOrder(newOrder);
},
minimumActiveTabWidth: 150,
scrollable: true,
addButton: function() {
addWorkspace();
}
});
workspaceTabCount = 0;
}
function showWorkspace() {
$("#workspace .red-ui-tabs").show()
$("#chart").show()
$("#workspace-footer").children().show()
}
function hideWorkspace() {
$("#workspace .red-ui-tabs").hide()
$("#chart").hide()
$("#workspace-footer").children().hide()
}
function init() {
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));
});
$(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);
hideWorkspace();
}
function editWorkspace(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
}
function removeWorkspace(ws) {
if (!ws) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
} else {
if (workspace_tabs.contains(ws.id)) {
workspace_tabs.removeTab(ws.id);
}
}
if (ws.id === activeWorkspace) {
activeWorkspace = 0;
}
}
function setWorkspaceOrder(order) {
RED.nodes.setWorkspaceOrder(order.filter(function(id) {
return RED.nodes.workspace(id) !== undefined;
}));
workspace_tabs.order(order);
}
return {
init: init,
add: addWorkspace,
remove: removeWorkspace,
order: setWorkspaceOrder,
edit: editWorkspace,
contains: function(id) {
return workspace_tabs.contains(id);
},
count: function() {
return workspaceTabCount;
},
active: function() {
return activeWorkspace
},
show: function(id) {
if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id);
if (sf) {
addWorkspace({type:"subflow",id:id,icon:"red/images/subflow_tab.png",label:sf.name, closeable: true});
} else {
return;
}
}
workspace_tabs.activateTab(id);
},
refresh: function() {
RED.nodes.eachWorkspace(function(ws) {
workspace_tabs.renameTab(ws.id,ws.label);
})
RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) {
workspace_tabs.renameTab(sf.id,sf.name);
}
});
RED.sidebar.config.refresh();
},
resize: function() {
workspace_tabs.resize();
}
}
})();

View File

@@ -0,0 +1,296 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.user = (function() {
function login(opts,done) {
if (typeof opts == 'function') {
done = opts;
opts = {};
}
var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
'<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
'</div>'+
'</div>');
dialog.dialog({
autoOpen: false,
dialogClass: "ui-dialog-no-close",
modal: true,
closeOnEscape: !!opts.cancelable,
width: 600,
resizable: false,
draggable: false
});
$("#node-dialog-login-fields").empty();
$.ajax({
dataType: "json",
url: "auth/login",
success: function(data) {
var i=0;
if (data.type == "credentials") {
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var row = $("<div/>",{class:"form-row"});
$('<label for="node-dialog-login-'+field.id+'">'+RED._(field.label)+':</label><br/>').appendTo(row);
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
if (i<data.prompts.length-1) {
input.keypress(
(function() {
var r = row;
return function(event) {
if (event.keyCode == 13) {
r.next("div").find("input").focus();
event.preventDefault();
}
}
})()
);
}
row.appendTo("#node-dialog-login-fields");
}
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">'+RED._("common.label.cancel")+'</a>':'')+
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="'+RED._("user.login")+'"></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button();
$("#node-dialog-login-fields").submit(function(event) {
$("#node-dialog-login-submit").button("option","disabled",true);
$("#node-dialog-login-failed").hide();
$(".login-spinner").show();
var body = {
client_id: "node-red-editor",
grant_type: "password",
scope:""
}
for (var i=0;i<data.prompts.length;i++) {
var field = data.prompts[i];
body[field.id] = $("#node-dialog-login-"+field.id).val();
}
$.ajax({
url:"auth/token",
type: "POST",
data: body
}).done(function(data,textStatus,xhr) {
RED.settings.set("auth-tokens",data);
$("#node-dialog-login").dialog('destroy').remove();
if (opts.updateMenu) {
updateUserMenu();
}
done();
}).fail(function(jqXHR,textStatus,errorThrown) {
RED.settings.remove("auth-tokens");
$("#node-dialog-login-failed").show();
}).always(function() {
$("#node-dialog-login-submit").button("option","disabled",false);
$(".login-spinner").hide();
});
event.preventDefault();
});
} else if (data.type == "strategy") {
i = 0;
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
var loginButton = $('<a href="#"></a>',{style: "padding: 10px"}).appendTo(row).click(function() {
document.location = field.url;
});
if (field.image) {
$("<img>",{src:field.image}).appendTo(loginButton);
} else if (field.label) {
var label = $('<span></span>').text(field.label);
if (field.icon) {
$('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton);
label.css({
"verticalAlign":"middle",
"marginLeft":"8px"
});
}
label.appendTo(loginButton);
}
loginButton.button();
}
}
if (opts.cancelable) {
$("#node-dialog-login-cancel").button().click(function( event ) {
$("#node-dialog-login").dialog('destroy').remove();
});
}
var loginImageSrc = data.image || "red/images/node-red-256.png";
$("#node-dialog-login-image").load(function() {
dialog.dialog("open");
}).attr("src",loginImageSrc);
}
});
}
function logout() {
var tokens = RED.settings.get("auth-tokens");
var token = tokens?tokens.access_token:"";
$.ajax({
url: "auth/revoke",
type: "POST",
data: {token:token}
}).done(function(data,textStatus,xhr) {
RED.settings.remove("auth-tokens");
if (data && data.redirect) {
document.location.href = data.redirect;
} else {
document.location.reload(true);
}
}).fail(function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
document.location.reload(true);
} else {
console.log(textStatus);
}
})
}
function updateUserMenu() {
$("#btn-usermenu-submenu li").remove();
if (RED.settings.user.anonymous) {
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-login",
label:RED._("menu.label.login"),
onselect: function() {
RED.user.login({cancelable:true},function() {
RED.settings.load(function() {
RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success");
updateUserMenu();
RED.events.emit("login",RED.settings.user.username);
});
});
}
});
} else {
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-username",
label:"<b>"+RED.settings.user.username+"</b>"
});
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-logout",
label:RED._("menu.label.logout"),
onselect: function() {
RED.user.logout();
}
});
}
}
function init() {
if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
var userMenu = $('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"></a></li>')
.prependTo(".header-toolbar");
if (RED.settings.user.image) {
$('<span class="user-profile"></span>').css({
backgroundImage: "url("+RED.settings.user.image+")",
}).appendTo(userMenu.find("a"));
} else {
$('<i class="fa fa-user"></i>').appendTo(userMenu.find("a"));
}
RED.menu.init({id:"btn-usermenu",
options: []
});
updateUserMenu();
}
}
}
var readRE = /^((.+)\.)?read$/
var writeRE = /^((.+)\.)?write$/
function hasPermission(permission) {
if (permission === "") {
return true;
}
if (!RED.settings.user) {
return true;
}
return checkPermission(RED.settings.user.permissions||"",permission);
}
function checkPermission(userScope,permission) {
if (permission === "") {
return true;
}
var i;
if (Array.isArray(permission)) {
// Multiple permissions requested - check each one
for (i=0;i<permission.length;i++) {
if (!checkPermission(userScope,permission[i])) {
return false;
}
}
// All permissions check out
return true;
}
if (Array.isArray(userScope)) {
if (userScope.length === 0) {
return false;
}
for (i=0;i<userScope.length;i++) {
if (checkPermission(userScope[i],permission)) {
return true;
}
}
return false;
}
if (userScope === "*" || userScope === permission) {
return true;
}
if (userScope === "read" || userScope === "*.read") {
return readRE.test(permission);
} else if (userScope === "write" || userScope === "*.write") {
return writeRE.test(permission);
}
return false;
}
return {
init: init,
login: login,
logout: logout,
hasPermission: hasPermission
}
})();

View File

@@ -0,0 +1,35 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.validators = {
number: function(blankAllowed){return function(v) { return (blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v));}},
regex: function(re){return function(v) { return re.test(v);}},
typedInput: function(ptypeName,isConfig) { return function(v) {
var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
if (ptype === 'json') {
try {
JSON.parse(v);
return true;
} catch(err) {
return false;
}
} else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
return RED.utils.validatePropertyExpression(v);
} else if (ptype === 'num') {
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
}
return true;
}}
};