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"
],
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: {
@ -214,7 +232,13 @@ module.exports = function(grunt) {
},
{
cwd: 'editor/vendor',
src: '**',
src: [
'ace/**',
'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
],
expand: true,
dest: 'public/vendor/'
},
@ -244,7 +268,8 @@ module.exports = function(grunt) {
'nodes/*.demo',
'nodes/core/**',
'red/**',
'public/**'
'public/**',
'editor/templates/**'
],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}]
@ -333,7 +358,7 @@ module.exports = function(grunt) {
grunt.registerTask('build',
'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',
'Developer mode: run node-red, watch for source changes and build/restart',

View File

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

View File

@ -138,34 +138,37 @@ var RED = (function() {
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"btn-node-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
{id:"menu-item-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"menu-item-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"btn-import-menu",label:"Import",options:[
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
{id:"btn-import-library",label:"Library",options:[]}
{id:"menu-item-import",label:"Import",options:[
{id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:"Library",options:[]}
]},
{id:"btn-export-menu",label:"Export",disabled:true,options:[
{id:"btn-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
{id:"btn-export-library",label:"Library",disabled:true,onselect:RED.library.export}
{id:"menu-item-export",label:"Export",disabled:true,options:[
{id:"menu-item-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:"Library",disabled:true,onselect:RED.library.export}
]},
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,
{id:"btn-subflow-menu",label:"Subflows", options: [
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
{id:"menu-item-subflow",label:"Subflows", options: [
{id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"btn-workspace-menu",label:"Workspaces",options:[
{id:"btn-workspace-add",label:"Add",onselect:RED.workspaces.add},
{id:"btn-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
{id:"btn-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
{id:"menu-item-workspace",label:"Workspaces",options:[
{id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
null
]},
null,
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
{id:"btn-help",label:"Node-RED Website", href:"http://nodered.org/docs"}
{id:"menu-item-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
{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.clipboard.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.comms.connect();
@ -192,7 +196,7 @@ var RED = (function() {
$(function() {
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");

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 {
init: init,
load: load,
set: set,
get: get,
remove: remove
remove: remove,
theme: theme
}
})
();

View File

@ -129,13 +129,13 @@ RED.clipboard = (function() {
init: function() {
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("btn-export-menu",true);
RED.menu.setDisabled("btn-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true);
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("btn-export-menu",false);
RED.menu.setDisabled("btn-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false);
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
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);
}
function init() {
/**
* 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";
var deployButton = $('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar");
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(); });
@ -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) {
if (state.dirty) {
window.onbeforeunload = function() {
@ -114,8 +137,8 @@ RED.deploy = (function() {
}
var nns = RED.nodes.createCompleteNodeSet();
$("#btn-icn-deploy").removeClass('fa-download');
$("#btn-icn-deploy").addClass('spinner');
$("#btn-deploy-icon").removeClass('fa-download');
$("#btn-deploy-icon").addClass('spinner');
RED.nodes.dirty(false);
$.ajax({
@ -153,8 +176,8 @@ RED.deploy = (function() {
RED.notify("<strong>Error</strong>: no response from server","error");
}
}).always(function() {
$("#btn-icn-deploy").removeClass('spinner');
$("#btn-icn-deploy").addClass('fa-download');
$("#btn-deploy-icon").removeClass('spinner');
$("#btn-deploy-icon").addClass('fa-download');
});
}
}

View File

@ -786,7 +786,7 @@ RED.editor = (function() {
changes['name'] = editing_node.name;
editing_node.name = newName;
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();

View File

@ -59,46 +59,53 @@ RED.keyboard = (function() {
}
var dialog = $('<div id="keyboard-help-dialog" class="hide">'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#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();
}
});
var dialog = null;
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");
}

View File

@ -25,7 +25,7 @@ RED.library = (function() {
var li;
var a;
var ul = document.createElement("ul");
ul.id = "btn-import-library-submenu";
ul.id = "menu-item-import-library-submenu";
ul.className = "dropdown-menu";
if (data.d) {
for (i in data.d) {
@ -63,7 +63,7 @@ RED.library = (function() {
};
var menu = buildMenu(data,"");
//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() {
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("btn-export-menu",true);
RED.menu.setDisabled("btn-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true);
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("btn-export-menu",false);
RED.menu.setDisabled("btn-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false);
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
loadFlowLibrary();
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
},
create: createUI,
loadFlowLibrary: loadFlowLibrary,

View File

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

View File

@ -51,14 +51,14 @@ RED.sidebar = (function() {
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;
var newChartRight = 15;
$("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight);
$("#chart-zoom-controls").css("right",newChartRight+20);
$("#sidebar").width(0);
RED.menu.setSelected("btn-sidebar",true);
RED.menu.setSelected("menu-item-sidebar",true);
eventHandler.emit("resize");
}
sidebarSeparator.width = $("#sidebar").width();
@ -104,7 +104,7 @@ RED.sidebar = (function() {
stop:function(event,ui) {
if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing");
RED.menu.setSelected("btn-sidebar",false);
RED.menu.setSelected("menu-item-sidebar",false);
if ($("#sidebar").width() < 180) {
$("#sidebar").width(180);
$("#workspace").css("right",208);
@ -138,7 +138,7 @@ RED.sidebar = (function() {
}
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();
RED.sidebar.info.show();
}

View File

@ -177,9 +177,9 @@ RED.subflow = (function() {
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("btn-convert-subflow",true);
RED.menu.setDisabled("menu-item-subflow-convert",true);
} 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);
}
RED.menu.setDisabled("btn-workspace-edit", activeSubflow);
RED.menu.setDisabled("btn-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
if (workspaceScrollPositions[event.workspace]) {
chart.scrollLeft(workspaceScrollPositions[event.workspace].left);

View File

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

View File

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

View File

@ -14,6 +14,20 @@
* 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 {
position: absolute;
top: 0;
@ -34,29 +48,29 @@ span.logo {
font-size: 30px;
line-height: 30px;
text-decoration: none;
span {
vertical-align: middle;
font-size: 16px !important;
}
img {
height: 18px;
}
}
span.logo span {
vertical-align: middle;
font-size: 16px !important;
}
span.logo img {
height: 18px;
}
#header ul.header-toolbar {
.header-toolbar {
padding: 0;
margin: 0;
list-style: none;
float: right;
}
#header ul.header-toolbar > li {
padding: 0;
margin: 0;
position: relative;
}
#header ul.header-toolbar > li {
display: inline-block;
> li {
display: inline-block;
padding: 0;
margin: 0;
position: relative;
}
}
.button {
@ -68,7 +82,7 @@ span.logo img {
text-align: center;
line-height: 40px;
display: inline-block;
font-size: 14px;
font-size: 20px;
padding: 0px 12px;
text-decoration: none;
color: #C7C7C7;
@ -76,110 +90,96 @@ span.logo img {
vertical-align: middle;
border-left: 2px solid #000;
border-right: 2px solid #000;
}
#header .button:not(.disabled):hover {
border-color: #323232;
}
#btn-deploy {
background: #8C101C; /*#d24741;*/
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;
&:hover {
border-color: $headerMenuItemHover;
}
}
.button-group {
display: inline-block;
margin: auto 15px;
vertical-align: middle;
background: #555;
clear: both;
}
.button-group > a {
display: inline-block;
float: left;
line-height: 22px;
font-size: 14px;
text-decoration: none;
display: inline-block;
padding: 4px 8px;
color: #ccc;
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 {
font-size: 20px !important;
}
#header .button:active, #header .button.active {
background: #121212;
}
#header .button:focus {
outline: none;
&:active, &.active {
background: $activeButton;
}
&:focus {
outline: none;
}
}
#header li.open .button {
background: #121212;
border-color: #121212;
background: $activeButton;
border-color: $activeButton;
}
#header ul.dropdown-menu {
background: #121212;
background: $headerMenuBackground;
width: 250px !important;
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:focus > a {
background: #323232 !important;
background: $headerMenuItemHover !important;
}
#header ul.dropdown-menu li.divider {
background: #464646;
border-bottom-color: #323232;
background: $headerMenuItemDivider;
border-bottom-color: $headerMenuItemHover;
}
#header ul.dropdown-menu li.disabled a {
color: #666;

View File

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

View File

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

View File

@ -14,10 +14,6 @@
* limitations under the License.
**/
#workspace {
margin-left: 160px;
overflow: hidden;
}
#chart {
overflow: auto;
@ -32,12 +28,16 @@
outline: none;
}
#workspace {
position: absolute;
margin: 0;
top:5px; left:190px; bottom: 10px; right: 330px;
top:5px;
left:190px;
bottom: 10px;
right: 330px;
overflow: hidden;
}
#chart-zoom-controls {
position: absolute;
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 permissions = require("./permissions");
var theme = require("../theme");
var settings = null;
var log = require("../../log");
@ -80,6 +82,9 @@ function login(req,res) {
"type":"credentials",
"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);
}

View File

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

View File

@ -14,6 +14,7 @@
* limitations under the License.
**/
var settings = require('../settings');
var theme = require("./theme");
var util = require('util');
@ -23,7 +24,12 @@ module.exports = {
httpNodeRoot: settings.httpNodeRoot,
version: settings.version,
user: req.user
};
}
var themeSettings = theme.settings();
if (themeSettings) {
safeSettings.editorTheme = themeSettings;
}
if (util.isArray(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 path = require("path");
var theme = require("./theme");
var Mustache = require("mustache");
var events = require("../events");
var settings = require("../settings");
var settings;
var icon_paths = [path.resolve(__dirname + '/../../public/icons')];
var iconCache = {};
@ -29,7 +33,16 @@ events.on("node-icon-dir",function(dir) {
icon_paths.push(path.resolve(dir));
});
var templateDir = path.resolve(__dirname+"/../../editor/templates");
var editorTemplate;
module.exports = {
init: function(_settings) {
settings = _settings;
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
Mustache.parse(editorTemplate);
},
ensureSlash: function(req,res,next) {
var parts = req.originalUrl.split("?");
if (parts[0].slice(-1) != "/") {
@ -56,7 +69,7 @@ module.exports = {
}
},
editor: function(req,res) {
res.sendfile(path.resolve(__dirname + '/../../public/index.html'));
res.send(Mustache.render(editorTemplate,theme.context()));
},
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");
* 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 info = require("../../../red/api/info");
var theme = require("../../../red/api/theme");
describe("info api", function() {
describe("settings handler", function() {
before(function() {
@ -34,12 +36,16 @@ describe("info api", function() {
paletteCategories :["red","blue","green"]
}
settings.init(userSettings);
sinon.stub(theme,"settings",function() { return { test: 456 };});
app = express();
app.get("/settings",info.settings);
});
after(function() {
settings.reset();
theme.settings.restore();
});
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("version","testVersion");
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);
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");
});
});