1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #611 from node-red/themes

Editor Themes
This commit is contained in:
Nick O'Leary 2015-04-13 22:51:52 +01:00
commit 87e537da90
27 changed files with 847 additions and 268 deletions

View File

@ -118,6 +118,24 @@ module.exports = function(grunt) {
"editor/js/ui/touch/radialMenu.js" "editor/js/ui/touch/radialMenu.js"
], ],
dest: "public/red/red.js" dest: "public/red/red.js"
},
vendor: {
files: {
"public/vendor/vendor.js": [
"editor/vendor/jquery/js/jquery-1.11.1.min.js",
"editor/vendor/bootstrap/js/bootstrap.min.js",
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"editor/vendor/marked/marked.min.js",
"editor/vendor/orion/built-editor.min.js",
"editor/vendor/d3/d3.v3.min.js"
],
"public/vendor/vendor.css": [
"editor/vendor/orion/built-editor.css"
// TODO: resolve relative resource paths in
// bootstrap/FA/jquery
]
}
} }
}, },
uglify: { uglify: {
@ -214,7 +232,13 @@ module.exports = function(grunt) {
}, },
{ {
cwd: 'editor/vendor', cwd: 'editor/vendor',
src: '**', src: [
'ace/**',
'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
],
expand: true, expand: true,
dest: 'public/vendor/' dest: 'public/vendor/'
}, },
@ -244,7 +268,8 @@ module.exports = function(grunt) {
'nodes/*.demo', 'nodes/*.demo',
'nodes/core/**', 'nodes/core/**',
'red/**', 'red/**',
'public/**' 'public/**',
'editor/templates/**'
], ],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>') dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}] }]
@ -333,7 +358,7 @@ module.exports = function(grunt) {
grunt.registerTask('build', grunt.registerTask('build',
'Builds editor content', 'Builds editor content',
['clean:build','concat:build','uglify:build','sass:build','copy:build','attachCopyright']); ['clean:build','concat:build','concat:vendor','uglify:build','sass:build','copy:build','attachCopyright']);
grunt.registerTask('dev', grunt.registerTask('dev',
'Developer mode: run node-red, watch for source changes and build/restart', 'Developer mode: run node-red, watch for source changes and build/restart',

View File

@ -23,8 +23,8 @@
<title>Node-RED</title> <title>Node-RED</title>
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen"> <link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen"> <link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" type="text/css" href="vendor/orion/built-editor.css"/> <link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="vendor/font-awesome/css/font-awesome.min.css"/> <link rel="stylesheet" href="vendor/vendor.css">
<link rel="stylesheet" href="red/style.min.css"> <link rel="stylesheet" href="red/style.min.css">
</head> </head>
<body spellcheck="false"> <body spellcheck="false">
@ -168,15 +168,9 @@
</div> </div>
</script> </script>
<script src="vendor/jquery/js/jquery-1.11.1.min.js"></script> <script src="vendor/vendor.js"></script>
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>
<script src="vendor/jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
<script src="vendor/jquery/js/jquery.ui.touch-punch.min.js"></script>
<script src="vendor/marked/marked.min.js"></script>
<script src="vendor/orion/built-editor.min.js"></script>
<script src="vendor/ace/ace.js"></script> <script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script> <script src="vendor/ace/ext-language_tools.js"></script>
<script src="vendor/d3/d3.v3.min.js"></script>
<script src="red/red.min.js"></script> <script src="red/red.min.js"></script>
</body> </body>

View File

@ -138,34 +138,37 @@ var RED = (function() {
function loadEditor() { function loadEditor() {
RED.menu.init({id:"btn-sidemenu", RED.menu.init({id:"btn-sidemenu",
options: [ options: [
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}, {id:"menu-item-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"btn-node-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true}, {id:"menu-item-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
null, null,
{id:"btn-import-menu",label:"Import",options:[ {id:"menu-item-import",label:"Import",options:[
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import}, {id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
{id:"btn-import-library",label:"Library",options:[]} {id:"menu-item-import-library",label:"Library",options:[]}
]}, ]},
{id:"btn-export-menu",label:"Export",disabled:true,options:[ {id:"menu-item-export",label:"Export",disabled:true,options:[
{id:"btn-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export}, {id:"menu-item-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
{id:"btn-export-library",label:"Library",disabled:true,onselect:RED.library.export} {id:"menu-item-export-library",label:"Library",disabled:true,onselect:RED.library.export}
]}, ]},
null, null,
{id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show}, {id:"menu-item-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
null, null,
{id:"btn-subflow-menu",label:"Subflows", options: [ {id:"menu-item-subflow",label:"Subflows", options: [
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.subflow.createSubflow}, {id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow}, {id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
]}, ]},
null, null,
{id:"btn-workspace-menu",label:"Workspaces",options:[ {id:"menu-item-workspace",label:"Workspaces",options:[
{id:"btn-workspace-add",label:"Add",onselect:RED.workspaces.add}, {id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add},
{id:"btn-workspace-edit",label:"Rename",onselect:RED.workspaces.edit}, {id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
{id:"btn-workspace-delete",label:"Delete",onselect:RED.workspaces.remove}, {id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
null null
]}, ]},
null, null,
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp}, {id:"menu-item-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
{id:"btn-help",label:"Node-RED Website", href:"http://nodered.org/docs"} {id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
}
] ]
}); });
@ -178,7 +181,8 @@ var RED = (function() {
RED.workspaces.init(); RED.workspaces.init();
RED.clipboard.init(); RED.clipboard.init();
RED.view.init(); RED.view.init();
RED.deploy.init();
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();}); RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
RED.comms.connect(); RED.comms.connect();
@ -192,7 +196,7 @@ var RED = (function() {
$(function() { $(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) { if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = "Node-RED : "+window.location.hostname; document.title = document.title+" : "+window.location.hostname;
} }
ace.require("ace/ext/language_tools"); ace.require("ace/ext/language_tools");

View File

@ -119,13 +119,30 @@ RED.settings = (function () {
}); });
}; };
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]];
}
return v;
} catch(err) {
return defaultValue;
}
}
return { return {
init: init, init: init,
load: load, load: load,
set: set, set: set,
get: get, get: get,
remove: remove remove: remove,
theme: theme
} }
}) })
(); ();

View File

@ -129,13 +129,13 @@ RED.clipboard = (function() {
init: function() { init: function() {
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("btn-export-menu",true); RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("btn-export-clipboard",true); RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true); RED.menu.setDisabled("menu-item-export-library",true);
} else { } else {
RED.menu.setDisabled("btn-export-menu",false); RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("btn-export-clipboard",false); RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false); RED.menu.setDisabled("menu-item-export-library",false);
} }
}); });
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();}); RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});

View File

@ -29,12 +29,43 @@ RED.deploy = (function() {
$("#btn-deploy img").attr("src",deploymentTypes[type].img); $("#btn-deploy img").attr("src",deploymentTypes[type].img);
} }
function init() {
var deployButton = $('<li><span class="deploy-button-group button-group">'+ /**
'<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+ * options:
'<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>'+ * type: "default" - Button with drop-down options - no further customisation available
'</span></li>').prependTo(".header-toolbar"); * 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="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>Deploy</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:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
]
});
} else if (type == "simple") {
var label = options.label || "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="#">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span></a>'+
'</span></li>').prependTo(".header-toolbar");
}
$('#btn-deploy').click(function() { save(); }); $('#btn-deploy').click(function() { save(); });
@ -61,14 +92,6 @@ RED.deploy = (function() {
] ]
}); });
RED.menu.init({id:"btn-deploy-options",
options: [
{id:"btn-deploy-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"btn-deploy-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"btn-deploy-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
]
});
RED.nodes.on('change',function(state) { RED.nodes.on('change',function(state) {
if (state.dirty) { if (state.dirty) {
window.onbeforeunload = function() { window.onbeforeunload = function() {
@ -114,8 +137,8 @@ RED.deploy = (function() {
} }
var nns = RED.nodes.createCompleteNodeSet(); var nns = RED.nodes.createCompleteNodeSet();
$("#btn-icn-deploy").removeClass('fa-download'); $("#btn-deploy-icon").removeClass('fa-download');
$("#btn-icn-deploy").addClass('spinner'); $("#btn-deploy-icon").addClass('spinner');
RED.nodes.dirty(false); RED.nodes.dirty(false);
$.ajax({ $.ajax({
@ -153,8 +176,8 @@ RED.deploy = (function() {
RED.notify("<strong>Error</strong>: no response from server","error"); RED.notify("<strong>Error</strong>: no response from server","error");
} }
}).always(function() { }).always(function() {
$("#btn-icn-deploy").removeClass('spinner'); $("#btn-deploy-icon").removeClass('spinner');
$("#btn-icn-deploy").addClass('fa-download'); $("#btn-deploy-icon").addClass('fa-download');
}); });
} }
} }

View File

@ -786,7 +786,7 @@ RED.editor = (function() {
changes['name'] = editing_node.name; changes['name'] = editing_node.name;
editing_node.name = newName; editing_node.name = newName;
changed = true; changed = true;
$("#btn-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName); $("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
} }
RED.palette.refresh(); RED.palette.refresh();

View File

@ -59,46 +59,53 @@ RED.keyboard = (function() {
} }
var dialog = $('<div id="keyboard-help-dialog" class="hide">'+ var dialog = null;
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
'</table>'+
'</div>'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
'</table>'+
'</div>'+
'</div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: "800",
title:"Keyboard shortcuts",
resizable: false,
open: function() {
RED.keyboard.disable();
},
close: function() {
RED.keyboard.enable();
}
});
function showKeyboardHelp() { function showKeyboardHelp() {
if (!RED.settings.theme("menu.menu-item-keyboard-shortcuts",true)) {
return;
}
if (!dialog) {
dialog = $('<div id="keyboard-help-dialog" class="hide">'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
'</table>'+
'</div>'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
'</table>'+
'</div>'+
'</div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: "800",
title:"Keyboard shortcuts",
resizable: false,
open: function() {
RED.keyboard.disable();
},
close: function() {
RED.keyboard.enable();
}
});
}
dialog.dialog("open"); dialog.dialog("open");
} }

View File

@ -25,7 +25,7 @@ RED.library = (function() {
var li; var li;
var a; var a;
var ul = document.createElement("ul"); var ul = document.createElement("ul");
ul.id = "btn-import-library-submenu"; ul.id = "menu-item-import-library-submenu";
ul.className = "dropdown-menu"; ul.className = "dropdown-menu";
if (data.d) { if (data.d) {
for (i in data.d) { for (i in data.d) {
@ -63,7 +63,7 @@ RED.library = (function() {
}; };
var menu = buildMenu(data,""); var menu = buildMenu(data,"");
//TODO: need an api in RED.menu for this //TODO: need an api in RED.menu for this
$("#btn-import-library-submenu").replaceWith(menu); $("#menu-item-import-library-submenu").replaceWith(menu);
}); });
} }
@ -392,17 +392,19 @@ RED.library = (function() {
init: function() { init: function() {
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("btn-export-menu",true); RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("btn-export-clipboard",true); RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true); RED.menu.setDisabled("menu-item-export-library",true);
} else { } else {
RED.menu.setDisabled("btn-export-menu",false); RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("btn-export-clipboard",false); RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false); RED.menu.setDisabled("menu-item-export-library",false);
} }
}); });
loadFlowLibrary(); if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
}, },
create: createUI, create: createUI,
loadFlowLibrary: loadFlowLibrary, loadFlowLibrary: loadFlowLibrary,

View File

@ -23,6 +23,13 @@ RED.menu = (function() {
function createMenuItem(opt) { function createMenuItem(opt) {
var item; var item;
if (opt !== null && opt.id) {
var themeSetting = RED.settings.theme("menu."+opt.id);
if (themeSetting === false) {
return null;
}
}
function setState() { function setState() {
var savedStateActive = isSavedStateActive(opt.id); var savedStateActive = isSavedStateActive(opt.id);
if (savedStateActive) { if (savedStateActive) {
@ -113,7 +120,10 @@ RED.menu = (function() {
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item); var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
for (var i=0;i<opt.options.length;i++) { for (var i=0;i<opt.options.length;i++) {
createMenuItem(opt.options[i]).appendTo(submenu); var li = createMenuItem(opt.options[i]);
if (li) {
li.appendTo(submenu);
}
} }
} }
if (opt.disabled) { if (opt.disabled) {
@ -148,9 +158,16 @@ RED.menu = (function() {
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button); var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
var lastAddedSeparator = false;
for (var i=0;i<options.options.length;i++) { for (var i=0;i<options.options.length;i++) {
var opt = options.options[i]; var opt = options.options[i];
createMenuItem(opt).appendTo(topMenu); if (opt !== null || !lastAddedSeparator) {
var li = createMenuItem(opt);
if (li) {
li.appendTo(topMenu);
lastAddedSeparator = (opt === null);
}
}
} }
} }
@ -176,7 +193,7 @@ RED.menu = (function() {
} else { } else {
$("#"+id).removeClass("active"); $("#"+id).removeClass("active");
} }
if (opt.onselect) { if (opt && opt.onselect) {
opt.onselect.call(opt,state); opt.onselect.call(opt,state);
} }
setSavedState(id, state); setSavedState(id, state);
@ -198,17 +215,20 @@ RED.menu = (function() {
} }
function setAction(id,action) { function setAction(id,action) {
menuItems[id].onselect = action; var opt = menuItems[id];
$("#"+id).click(function() { if (opt) {
if ($(this).parent().hasClass("disabled")) { opt.onselect = action;
return; $("#"+id).click(function() {
} if ($(this).parent().hasClass("disabled")) {
if (menuItems[id].toggle) { return;
setSelected(id,!isSelected(id)); }
} else { if (menuItems[id].toggle) {
menuItems[id].onselect.call(menuItems[id]); setSelected(id,!isSelected(id));
} } else {
}); menuItems[id].onselect.call(menuItems[id]);
}
});
}
} }
return { return {

View File

@ -51,14 +51,14 @@ RED.sidebar = (function() {
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2; sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
if (!RED.menu.isSelected("btn-sidebar")) { if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true; sidebarSeparator.opening = true;
var newChartRight = 15; var newChartRight = 15;
$("#sidebar").addClass("closing"); $("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight); $("#workspace").css("right",newChartRight);
$("#chart-zoom-controls").css("right",newChartRight+20); $("#chart-zoom-controls").css("right",newChartRight+20);
$("#sidebar").width(0); $("#sidebar").width(0);
RED.menu.setSelected("btn-sidebar",true); RED.menu.setSelected("menu-item-sidebar",true);
eventHandler.emit("resize"); eventHandler.emit("resize");
} }
sidebarSeparator.width = $("#sidebar").width(); sidebarSeparator.width = $("#sidebar").width();
@ -104,7 +104,7 @@ RED.sidebar = (function() {
stop:function(event,ui) { stop:function(event,ui) {
if (sidebarSeparator.closing) { if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing"); $("#sidebar").removeClass("closing");
RED.menu.setSelected("btn-sidebar",false); RED.menu.setSelected("menu-item-sidebar",false);
if ($("#sidebar").width() < 180) { if ($("#sidebar").width() < 180) {
$("#sidebar").width(180); $("#sidebar").width(180);
$("#workspace").css("right",208); $("#workspace").css("right",208);
@ -138,7 +138,7 @@ RED.sidebar = (function() {
} }
function init () { function init () {
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();}); RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("menu-item-sidebar",!RED.menu.isSelected("menu-item-sidebar"));d3.event.preventDefault();});
showSidebar(); showSidebar();
RED.sidebar.info.show(); RED.sidebar.info.show();
} }

View File

@ -177,9 +177,9 @@ RED.subflow = (function() {
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("btn-convert-subflow",true); RED.menu.setDisabled("menu-item-subflow-convert",true);
} else { } else {
RED.menu.setDisabled("btn-convert-subflow",false); RED.menu.setDisabled("menu-item-subflow-convert",false);
} }
}); });

View File

@ -259,8 +259,8 @@ RED.view = (function() {
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0); $("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
} }
RED.menu.setDisabled("btn-workspace-edit", activeSubflow); RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
RED.menu.setDisabled("btn-workspace-delete",RED.workspaces.count() == 1 || activeSubflow); RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
if (workspaceScrollPositions[event.workspace]) { if (workspaceScrollPositions[event.workspace]) {
chart.scrollLeft(workspaceScrollPositions[event.workspace].left); chart.scrollLeft(workspaceScrollPositions[event.workspace].left);

View File

@ -102,18 +102,18 @@ RED.workspaces = (function() {
} }
}, },
onadd: function(tab) { onadd: function(tab) {
RED.menu.addItem("btn-workspace-menu",{ RED.menu.addItem("menu-item-workspace",{
id:"btn-workspace-menu-"+tab.id.replace(".","-"), id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label, label:tab.label,
onselect:function() { onselect:function() {
workspace_tabs.activateTab(tab.id); workspace_tabs.activateTab(tab.id);
} }
}); });
RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
}, },
onremove: function(tab) { onremove: function(tab) {
RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1); RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-")); RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
} }
}); });
@ -141,7 +141,7 @@ RED.workspaces = (function() {
if (workspace.label != label) { if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label); workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true); RED.nodes.dirty(true);
$("#btn-workspace-menu-"+workspace.id.replace(".","-")).text(label); $("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu // TODO: update entry in menu
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
@ -195,7 +195,7 @@ RED.workspaces = (function() {
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()}); $('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
RED.sidebar.on("resize",workspace_tabs.resize); RED.sidebar.on("resize",workspace_tabs.resize);
RED.menu.setAction('btn-workspace-delete',function() { RED.menu.setAction('menu-item-workspace-delete',function() {
deleteWorkspace(RED.nodes.workspace(activeWorkspace)); deleteWorkspace(RED.nodes.workspace(activeWorkspace));
}); });
} }

View File

@ -22,7 +22,7 @@ RED.user = (function() {
} }
var dialog = $('<div id="node-dialog-login" class="hide">'+ var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img src="red/images/node-red-256.png"/></div>'+ '<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;">'+ '<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>'+ '<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
'</div>'+ '</div>'+
@ -45,6 +45,13 @@ RED.user = (function() {
success: function(data) { success: function(data) {
if (data.type == "credentials") { if (data.type == "credentials") {
var i=0; var i=0;
if (data.image) {
$("#node-dialog-login-image").attr("src",data.image);
} else {
$("#node-dialog-login-image").attr("src","red/images/node-red-256.png");
}
for (;i<data.prompts.length;i++) { for (;i<data.prompts.length;i++) {
var field = data.prompts[i]; var field = data.prompts[i];
var row = $("<div/>",{class:"form-row"}); var row = $("<div/>",{class:"form-row"});
@ -110,10 +117,10 @@ RED.user = (function() {
} }
function updateUserMenu() { function updateUserMenu() {
$("#btn-usermenu-submenu li").remove(); $("#usermenu-submenu li").remove();
if (RED.settings.user.anonymous) { if (RED.settings.user.anonymous) {
RED.menu.addItem("btn-usermenu",{ RED.menu.addItem("btn-usermenu",{
id:"btn-login", id:"usermenu-item-login",
label:"Login", label:"Login",
onselect: function() { onselect: function() {
RED.user.login({cancelable:true},function() { RED.user.login({cancelable:true},function() {
@ -126,11 +133,11 @@ RED.user = (function() {
}); });
} else { } else {
RED.menu.addItem("btn-usermenu",{ RED.menu.addItem("btn-usermenu",{
id:"btn-username", id:"usermenu-item-username",
label:"<b>"+RED.settings.user.username+"</b>" label:"<b>"+RED.settings.user.username+"</b>"
}); });
RED.menu.addItem("btn-usermenu",{ RED.menu.addItem("btn-usermenu",{
id:"btn-logout", id:"usermenu-item-logout",
label:"Logout", label:"Logout",
onselect: function() { onselect: function() {
RED.user.logout(); RED.user.logout();
@ -144,13 +151,16 @@ RED.user = (function() {
function init() { function init() {
if (RED.settings.user) { if (RED.settings.user) {
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>') if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
.prependTo(".header-toolbar");
RED.menu.init({id:"btn-usermenu", $('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
options: [] .prependTo(".header-toolbar");
});
updateUserMenu(); RED.menu.init({id:"btn-usermenu",
options: []
});
updateUserMenu();
}
} }
} }

View File

@ -14,6 +14,20 @@
* limitations under the License. * limitations under the License.
**/ **/
$activeButton: #121212;
$deployButton: #8C101C;
$deployButtonHover: #6E0A1E;
$deployButtonActive: #4C0A17;
$deployDisabledButton: #444;
$deployDisabledButtonHover: #555;
$deployDisabledButtonActive: #444;
$headerMenuBackground: #121212;
$headerMenuItemHover: #323232;
$headerMenuItemDivider: #464646;
#header { #header {
position: absolute; position: absolute;
top: 0; top: 0;
@ -34,29 +48,29 @@ span.logo {
font-size: 30px; font-size: 30px;
line-height: 30px; line-height: 30px;
text-decoration: none; text-decoration: none;
span {
vertical-align: middle;
font-size: 16px !important;
}
img {
height: 18px;
}
} }
span.logo span {
vertical-align: middle; .header-toolbar {
font-size: 16px !important;
}
span.logo img {
height: 18px;
}
#header ul.header-toolbar {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
float: right; float: right;
}
#header ul.header-toolbar > li { > li {
padding: 0; display: inline-block;
margin: 0; padding: 0;
position: relative; margin: 0;
} position: relative;
#header ul.header-toolbar > li { }
display: inline-block;
} }
.button { .button {
@ -68,7 +82,7 @@ span.logo img {
text-align: center; text-align: center;
line-height: 40px; line-height: 40px;
display: inline-block; display: inline-block;
font-size: 14px; font-size: 20px;
padding: 0px 12px; padding: 0px 12px;
text-decoration: none; text-decoration: none;
color: #C7C7C7; color: #C7C7C7;
@ -76,110 +90,96 @@ span.logo img {
vertical-align: middle; vertical-align: middle;
border-left: 2px solid #000; border-left: 2px solid #000;
border-right: 2px solid #000; border-right: 2px solid #000;
}
#header .button:not(.disabled):hover {
border-color: #323232;
}
#btn-deploy { &:hover {
background: #8C101C; /*#d24741;*/ border-color: $headerMenuItemHover;
color: #eee !important; }
}
#btn-deploy + a {
background: #8C101C; /*#BA403B;*/
color: #eee;
}
#btn-deploy + a:hover {
background: #6E0A1E; /*#AD3C38;*/
color: #eee;
}
#btn-deploy + a:active {
background: #4C0A17; /*#aa1f19;*/
color: #ccc;
}
span.deploy-button-group.open > #btn-deploy + a {
background: #121212 !important;
}
#btn-deploy:not(.disabled):hover {
background: #6E0A1E; /*#ca3f39;*/
}
#btn-deploy:not(.disabled):active {
background: #4C0A17 /*#aa1f19*/ !important;
}
#btn-deploy:not(.disabled):active {
color: #ccc !important;
}
#btn-deploy.disabled {
cursor: default;
background: #444;
color: #999 !important;
}
#btn-deploy.disabled + a {
background: #444;
color: #ddd;
}
#btn-deploy.disabled + a:hover {
background: #555;
color: #ddd;
}
#btn-deploy.disabled + a:active {
background: #444;
color: #ddd;
}
span.deploy-button-group.open > #btn-deploy.disabled + a {
background: #121212 !important;
}
#btn-deploy img {
margin-right: 8px;
}
#btn-deploy.disabled img {
opacity: 0.3;
} }
.button-group { .button-group {
display: inline-block; display: inline-block;
margin: auto 15px; margin: auto 15px;
vertical-align: middle; vertical-align: middle;
background: #555;
clear: both; clear: both;
} }
.button-group > a { .button-group > a {
display: inline-block;
float: left; float: left;
line-height: 22px; line-height: 22px;
font-size: 14px; font-size: 14px;
text-decoration: none; text-decoration: none;
display: inline-block;
padding: 4px 8px; padding: 4px 8px;
color: #ccc;
margin: 0; margin: 0;
} }
.button-group > a:last-child {
.deploy-button {
background: $deployButton;
color: #eee !important;
&:hover {
background: $deployButtonHover;
}
&:active {
background: $deployButtonActive;
color: #ccc !important;
}
} }
#btn-deploy {
padding: 4px 12px;
&.disabled {
cursor: default;
background: $deployDisabledButton;
color: #999 !important;
img {
opacity: 0.3;
}
&+ #btn-deploy-options {
background: $deployDisabledButton;
color: #ddd;
}
&+ #btn-deploy-options:hover {
background: $deployDisabledButtonHover;
}
&+ #btn-deploy-options:active {
background: $deployDisabledButton;
}
}
img {
margin-right: 8px;
}
}
.deploy-button-group.open {
#btn-deploy-options {
background: $activeButton !important;
}
}
#header .button { #header .button {
font-size: 20px !important; &:active, &.active {
} background: $activeButton;
#header .button:active, #header .button.active { }
background: #121212; &:focus {
} outline: none;
#header .button:focus { }
outline: none;
} }
#header li.open .button { #header li.open .button {
background: #121212; background: $activeButton;
border-color: #121212; border-color: $activeButton;
} }
#header ul.dropdown-menu { #header ul.dropdown-menu {
background: #121212; background: $headerMenuBackground;
width: 250px !important; width: 250px !important;
margin-top: 0; margin-top: 0;
} }
@ -219,12 +219,12 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
#header ul.dropdown-menu > li:hover > a, #header ul.dropdown-menu > li:hover > a,
#header ul.dropdown-menu > li:focus > a { #header ul.dropdown-menu > li:focus > a {
background: #323232 !important; background: $headerMenuItemHover !important;
} }
#header ul.dropdown-menu li.divider { #header ul.dropdown-menu li.divider {
background: #464646; background: $headerMenuItemDivider;
border-bottom-color: #323232; border-bottom-color: $headerMenuItemHover;
} }
#header ul.dropdown-menu li.disabled a { #header ul.dropdown-menu li.disabled a {
color: #666; color: #666;

View File

@ -17,7 +17,9 @@
#palette { #palette {
position: absolute; position: absolute;
top: 5px; left:10px; bottom: 10px; top: 5px;
bottom: 10px;
left:10px;
background: #f3f3f3; background: #f3f3f3;
width: 170px; width: 170px;
text-align: center; text-align: center;
@ -29,13 +31,12 @@
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
left:0;
right: 0; right: 0;
bottom: 35px; bottom: 35px;
left:0;
padding: 5px; padding: 5px;
overflow-y: auto; overflow-y: auto;
box-sizing:border-box; box-sizing:border-box;
-moz-box-sizing: border-box;
} }
.palette-spinner { .palette-spinner {
padding-top: 40px; padding-top: 40px;
@ -53,7 +54,6 @@
padding: 3px; padding: 3px;
border-top: 1px solid #999; border-top: 1px solid #999;
box-sizing:border-box; box-sizing:border-box;
-moz-box-sizing: border-box;
} }
#palette-search i.fa-search { #palette-search i.fa-search {
position: absolute; position: absolute;
@ -82,7 +82,6 @@
margin: 0px; margin: 0px;
height: 30px; height: 30px;
box-sizing:border-box; box-sizing:border-box;
-moz-box-sizing: border-box;
} }
#palette-search input:focus { #palette-search input:focus {

View File

@ -17,12 +17,13 @@
#sidebar { #sidebar {
width: 305px;
position: absolute; position: absolute;
right: 10px; top: 5px; bottom:10px; top: 5px;
right: 10px;
bottom: 10px;
width: 305px;
background: #fff; background: #fff;
box-sizing: border-box; box-sizing: border-box;
-moz-box-sizing: border-box;
@include component-border; @include component-border;
} }
@ -33,17 +34,22 @@
} }
#sidebar-content { #sidebar-content {
position: absolute;
top: 30px;
right: 0;
bottom: 1px;
left: 0px;
font-size: 1.2em; font-size: 1.2em;
overflow-y: auto; overflow-y: auto;
position: absolute;
top: 30px; left: 0px; right: 0; bottom: 1px;
} }
#sidebar-separator { #sidebar-separator {
position: absolute;
top: 5px;
right: 316px;
bottom:10px;
width: 15px; width: 15px;
background: url(images/grip.png) no-repeat 50% 50%; background: url(images/grip.png) no-repeat 50% 50%;
position: absolute;
right: 316px; top: 5px; bottom:10px;
cursor: col-resize; cursor: col-resize;
} }

View File

@ -14,10 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
#workspace {
margin-left: 160px;
overflow: hidden;
}
#chart { #chart {
overflow: auto; overflow: auto;
@ -32,12 +28,16 @@
outline: none; outline: none;
} }
#workspace { #workspace {
position: absolute; position: absolute;
margin: 0; margin: 0;
top:5px; left:190px; bottom: 10px; right: 330px; top:5px;
left:190px;
bottom: 10px;
right: 330px;
overflow: hidden;
} }
#chart-zoom-controls { #chart-zoom-controls {
position: absolute; position: absolute;
bottom:30px; right: 350px; bottom:30px; right: 350px;

182
editor/templates/index.mst Normal file
View File

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<!--
Copyright 2013, 2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<title>{{ page.title }}</title>
<link rel="icon" type="image/png" href="{{ page.favicon }}">
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="vendor/vendor.css">
<link rel="stylesheet" href="red/style.min.css">
{{#page.css}}
<link rel="stylesheet" href="{{.}}">
{{/page.css}}
</head>
<body spellcheck="false">
<div id="header">
<span class="logo">{{#header.image}}<img src="{{.}}">{{/header.image}} <span>{{ header.title }}</span></span>
<ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
<ul>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-container" class="palette-scroll">
</div>
<div id="palette-search">
<i class="fa fa-search"></i><input id="palette-search-input" type="text" placeholder="filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
</div>
</div><!-- /palette -->
<div id="workspace">
<ul id="workspace-tabs"></ul>
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div>
<div id="workspace-toolbar">
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a>
</div>
</div>
<div id="chart-zoom-controls">
<div class="btn-group">
<a class="btn btn-mini" id="btn-zoom-out" href="#"><i class="fa fa-search-minus"></i></a>
<a class="btn btn-mini" id="btn-zoom-zero" href="#"><i class="fa fa-dot-circle-o"></i></a>
<a class="btn btn-mini" id="btn-zoom-in" href="#"><i class="fa fa-search-plus"></i></a>
</div>
</div>
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
</div>
<div id="sidebar-separator"></div>
</div>
<div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label>Name</label><input type="text" id="subflow-input-name">
</div>
</form>
<div class="form-tips" id="subflow-dialog-user-count"></div>
</div>
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: center; padding-top: 30px;">
Some of the nodes are not properly configured. Are you sure you want to deploy?
</div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: center; padding-top: 10px;">
The workspace contains some unknown node types:
<ul style="width: 300px; margin: auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
Are you sure you want to deploy?
</div>
</form>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
</div>
</form>
</div>
<div id="node-dialog-library-save" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label>
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder">
</div>
<div class="form-row">
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename">
</div>
</form>
</div>
<div id="node-dialog-library-lookup" class="hide">
<form class="form-horizontal">
<div class="form-row">
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
<li class="active"><a href="#">Library</a></li>
</ul>
</div>
<div class="form-row">
<div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;">
<div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div>
</div>
<div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;">
<div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div>
</div>
</div>
</form>
</div>
<div id="node-dialog-rename-workspace" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> Name:</label>
<input type="text" id="node-input-workspace-name">
</div>
</form>
</div>
<div id="node-dialog-delete-workspace" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
</div>
</form>
</div>
<script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row">
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
</div>
</script>
<script src="vendor/vendor.js"></script>
<script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script>
<script src="red/red.min.js"></script>
</body>
</html>

View File

@ -22,6 +22,8 @@ var Tokens = require("./tokens");
var Users = require("./users"); var Users = require("./users");
var permissions = require("./permissions"); var permissions = require("./permissions");
var theme = require("../theme");
var settings = null; var settings = null;
var log = require("../../log"); var log = require("../../log");
@ -80,6 +82,9 @@ function login(req,res) {
"type":"credentials", "type":"credentials",
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}] "prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
} }
if (theme.context().login && theme.context().login.image) {
response.image = theme.context().login.image;
}
} }
res.json(response); res.json(response);
} }

View File

@ -24,6 +24,7 @@ var nodes = require("./nodes");
var flows = require("./flows"); var flows = require("./flows");
var library = require("./library"); var library = require("./library");
var info = require("./info"); var info = require("./info");
var theme = require("./theme");
var auth = require("./auth"); var auth = require("./auth");
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
@ -41,9 +42,13 @@ function init(adminApp,storage) {
// Editor // Editor
if (!settings.disableEditor) { if (!settings.disableEditor) {
ui.init(settings);
var editorApp = express(); var editorApp = express();
editorApp.get("/",ui.ensureSlash,ui.editor); editorApp.get("/",ui.ensureSlash,ui.editor);
editorApp.get("/icons/:icon",ui.icon); editorApp.get("/icons/:icon",ui.icon);
if (settings.editorTheme) {
editorApp.use("/theme",theme.init(settings));
}
editorApp.use("/",ui.editorResources); editorApp.use("/",ui.editorResources);
adminApp.use(editorApp); adminApp.use(editorApp);
} }

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
**/ **/
var settings = require('../settings'); var settings = require('../settings');
var theme = require("./theme");
var util = require('util'); var util = require('util');
@ -23,7 +24,12 @@ module.exports = {
httpNodeRoot: settings.httpNodeRoot, httpNodeRoot: settings.httpNodeRoot,
version: settings.version, version: settings.version,
user: req.user user: req.user
}; }
var themeSettings = theme.settings();
if (themeSettings) {
safeSettings.editorTheme = themeSettings;
}
if (util.isArray(settings.paletteCategories)) { if (util.isArray(settings.paletteCategories)) {
safeSettings.paletteCategories = settings.paletteCategories; safeSettings.paletteCategories = settings.paletteCategories;

151
red/api/theme.js Normal file
View File

@ -0,0 +1,151 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var util = require("util");
var path = require("path");
var fs = require("fs");
var clone = require("clone");
var defaultContext = {
page: {
title: "Node-RED",
favicon: "favicon.ico"
},
header: {
title: "Node-RED",
image: "red/images/node-red.png"
}
};
var themeContext = clone(defaultContext);
var themeSettings = null;
function serveFile(app,baseUrl,file) {
try {
var stats = fs.statSync(file);
var url = baseUrl+path.basename(file);
//console.log(url,"->",file);
app.get(url,function(req, res) {
res.sendfile(file);
});
return "theme"+url;
} catch(err) {
//TODO: log filenotfound
return null;
}
}
module.exports = {
init: function(settings) {
var i;
var url;
themeContext = clone(defaultContext);
themeSettings = null;
if (settings.editorTheme) {
var theme = settings.editorTheme;
themeSettings = {};
var themeApp = express();
if (theme.page) {
if (theme.page.css) {
var styles = theme.page.css;
if (!util.isArray(styles)) {
styles = [styles];
}
themeContext.page.css = [];
for (i=0;i<styles.length;i++) {
url = serveFile(themeApp,"/css/",styles[i]);
if (url) {
themeContext.page.css.push(url);
}
}
}
if (theme.page.favicon) {
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
if (url) {
themeContext.page.favicon = url;
}
}
themeContext.page.title = theme.page.title || themeContext.page.title;
}
if (theme.header) {
themeContext.header.title = theme.header.title || themeContext.header.title;
if (theme.header.hasOwnProperty("image")) {
if (theme.header.image) {
url = serveFile(themeApp,"/header/",theme.header.image);
if (url) {
themeContext.header.image = url;
}
} else {
themeContext.header.image = null;
}
}
}
if (theme.deployButton) {
if (theme.deployButton.type == "simple") {
themeSettings.deployButton = {
type: "simple"
}
if (theme.deployButton.label) {
themeSettings.deployButton.label = theme.deployButton.label;
}
if (theme.deployButton.icon) {
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
if (url) {
themeSettings.deployButton.icon = url;
}
}
}
}
if (theme.hasOwnProperty("userMenu")) {
themeSettings.userMenu = theme.userMenu;
}
if (theme.login) {
if (theme.login.image) {
url = serveFile(themeApp,"/login/",theme.login.image);
if (url) {
themeContext.login = {
image: url
}
}
}
}
if (theme.hasOwnProperty("menu")) {
themeSettings.menu = theme.menu;
}
return themeApp;
}
},
context: function() {
return themeContext;
},
settings: function() {
return themeSettings;
}
}

View File

@ -17,8 +17,12 @@ var express = require('express');
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var theme = require("./theme");
var Mustache = require("mustache");
var events = require("../events"); var events = require("../events");
var settings = require("../settings"); var settings;
var icon_paths = [path.resolve(__dirname + '/../../public/icons')]; var icon_paths = [path.resolve(__dirname + '/../../public/icons')];
var iconCache = {}; var iconCache = {};
@ -29,7 +33,16 @@ events.on("node-icon-dir",function(dir) {
icon_paths.push(path.resolve(dir)); icon_paths.push(path.resolve(dir));
}); });
var templateDir = path.resolve(__dirname+"/../../editor/templates");
var editorTemplate;
module.exports = { module.exports = {
init: function(_settings) {
settings = _settings;
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
Mustache.parse(editorTemplate);
},
ensureSlash: function(req,res,next) { ensureSlash: function(req,res,next) {
var parts = req.originalUrl.split("?"); var parts = req.originalUrl.split("?");
if (parts[0].slice(-1) != "/") { if (parts[0].slice(-1) != "/") {
@ -56,7 +69,7 @@ module.exports = {
} }
}, },
editor: function(req,res) { editor: function(req,res) {
res.sendfile(path.resolve(__dirname + '/../../public/index.html')); res.send(Mustache.render(editorTemplate,theme.context()));
}, },
editorResources: express.static(__dirname + '/../../public') editorResources: express.static(__dirname + '/../../public')
}; };

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,6 +24,8 @@ var app = express();
var settings = require("../../../red/settings"); var settings = require("../../../red/settings");
var info = require("../../../red/api/info"); var info = require("../../../red/api/info");
var theme = require("../../../red/api/theme");
describe("info api", function() { describe("info api", function() {
describe("settings handler", function() { describe("settings handler", function() {
before(function() { before(function() {
@ -34,12 +36,16 @@ describe("info api", function() {
paletteCategories :["red","blue","green"] paletteCategories :["red","blue","green"]
} }
settings.init(userSettings); settings.init(userSettings);
sinon.stub(theme,"settings",function() { return { test: 456 };});
app = express(); app = express();
app.get("/settings",info.settings); app.get("/settings",info.settings);
}); });
after(function() { after(function() {
settings.reset(); settings.reset();
theme.settings.restore();
}); });
it('returns the filtered settings', function(done) { it('returns the filtered settings', function(done) {
@ -53,6 +59,7 @@ describe("info api", function() {
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot"); res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion"); res.body.should.have.property("version","testVersion");
res.body.should.have.property("paletteCategories",["red","blue","green"]); res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme",{test:456});
res.body.should.not.have.property("foo",123); res.body.should.not.have.property("foo",123);
done(); done();
}); });

103
test/red/api/theme_spec.js Normal file
View File

@ -0,0 +1,103 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var sinon = require('sinon');
var when = require('when');
var fs = require("fs");
var app = express();
var settings = require("../../../red/settings");
var theme = require("../../../red/api/theme");
describe("theme handler", function() {
beforeEach(function() {
sinon.stub(fs,"statSync",function() { return true; });
});
afterEach(function() {
theme.init({});
fs.statSync.restore();
});
it("applies the default theme", function() {
var result = theme.init({});
should.not.exist(result);
var context = theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title","Node-RED");
context.page.should.have.a.property("favicon","favicon.ico");
context.should.have.a.property("header");
context.header.should.have.a.property("title","Node-RED");
context.header.should.have.a.property("image","red/images/node-red.png");
should.not.exist(theme.settings());
});
it("picks up custom theme", function() {
var result = theme.init({
editorTheme: {
page: {
title: "Test Page Title",
favicon: "/absolute/path/to/theme/icon",
css: "/absolute/path/to/custom/css/file"
},
header: {
title: "Test Header Title",
image: "/absolute/path/to/header/image" // or null to remove image
},
deployButton: {
type:"simple",
label:"Save",
icon: "/absolute/path/to/deploy/button/image" // or null to remove image
},
menu: { // Hide unwanted menu items by id. see editor/js/main.js:loadEditor for complete list
"menu-item-import-library": false,
"menu-item-export-library": false,
"menu-item-keyboard-shortcuts": false,
"menu-item-help": {
label: "Alternative Help Link Text",
url: "http://example.com"
}
},
userMenu: false, // Hide the user-menu even if adminAuth is enabled
login: {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image
}
}
});
should.exist(result);
var context = theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title","Test Page Title");
context.should.have.a.property("header");
context.header.should.have.a.property("title","Test Header Title");
var settings = theme.settings();
settings.should.have.a.property("deployButton");
settings.should.have.a.property("userMenu");
settings.should.have.a.property("menu");
});
});