370 lines
13 KiB
JavaScript
370 lines
13 KiB
JavaScript
/**
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
|
|
var util = require("util");
|
|
var path = require("path");
|
|
var fs = require("fs");
|
|
var clone = require("clone");
|
|
const apiUtil = require("../util")
|
|
|
|
var defaultContext = {
|
|
page: {
|
|
title: "Node-RED",
|
|
favicon: "favicon.ico",
|
|
tabicon: {
|
|
icon: "red/images/node-red-icon-black.svg",
|
|
colour: "#8f0000"
|
|
}
|
|
},
|
|
header: {
|
|
title: "Node-RED",
|
|
image: "red/images/node-red.svg"
|
|
},
|
|
asset: {
|
|
red: "red/red.min.js",
|
|
main: "red/main.min.js",
|
|
vendorMonaco: ""
|
|
}
|
|
};
|
|
var settings;
|
|
|
|
var theme = null;
|
|
var themeContext = clone(defaultContext);
|
|
var themeSettings = null;
|
|
|
|
var activeTheme = null;
|
|
var activeThemeInitialised = false;
|
|
|
|
var runtimeAPI;
|
|
var themeApp;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
|
var result = [];
|
|
if (themeValue) {
|
|
var array = themeValue;
|
|
if (!Array.isArray(array)) {
|
|
array = [array];
|
|
}
|
|
|
|
for (var i=0;i<array.length;i++) {
|
|
let fullPath = array[i];
|
|
if (baseDirectory) {
|
|
fullPath = path.resolve(baseDirectory,array[i]);
|
|
if (fullPath.indexOf(path.resolve(baseDirectory)) !== 0) {
|
|
continue;
|
|
}
|
|
}
|
|
var url = serveFile(themeApp,directory,fullPath);
|
|
if (url) {
|
|
result.push(url);
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
module.exports = {
|
|
init: function(_settings, _runtimeAPI) {
|
|
settings = _settings;
|
|
runtimeAPI = _runtimeAPI;
|
|
themeContext = clone(defaultContext);
|
|
if (process.env.NODE_ENV == "development") {
|
|
themeContext.asset.red = "red/red.js";
|
|
themeContext.asset.main = "red/main.js";
|
|
}
|
|
themeSettings = null;
|
|
theme = settings.editorTheme || {};
|
|
themeContext.asset.vendorMonaco = "vendor/monaco/monaco-bootstrap.js"
|
|
if (theme.codeEditor && theme.codeEditor.lib === 'ace') {
|
|
themeContext.asset.vendorMonaco = ''
|
|
}
|
|
activeTheme = theme.theme;
|
|
},
|
|
|
|
app: function() {
|
|
var i;
|
|
var url;
|
|
themeSettings = {};
|
|
|
|
themeApp = apiUtil.createExpressApp(settings);
|
|
|
|
const defaultServerSettings = {
|
|
"x-powered-by": false
|
|
}
|
|
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
|
for (const eOption in serverSettings) {
|
|
themeApp.set(eOption, serverSettings[eOption]);
|
|
}
|
|
|
|
if (theme.page) {
|
|
|
|
themeContext.page.css = serveFilesFromTheme(
|
|
theme.page.css,
|
|
themeApp,
|
|
"/css/")
|
|
themeContext.page.scripts = serveFilesFromTheme(
|
|
theme.page.scripts,
|
|
themeApp,
|
|
"/scripts/")
|
|
|
|
if (theme.page.favicon) {
|
|
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
|
|
if (url) {
|
|
themeContext.page.favicon = url;
|
|
}
|
|
}
|
|
|
|
if (theme.page.tabicon) {
|
|
let icon = theme.page.tabicon.icon || theme.page.tabicon
|
|
url = serveFile(themeApp,"/tabicon/", icon)
|
|
if (url) {
|
|
themeContext.page.tabicon.icon = url;
|
|
}
|
|
if (theme.page.tabicon.colour) {
|
|
themeContext.page.tabicon.colour = theme.page.tabicon.colour
|
|
}
|
|
}
|
|
|
|
themeContext.page.title = theme.page.title || themeContext.page.title;
|
|
|
|
// Store the resolved urls to these resources so nodes (such as Debug)
|
|
// can access them
|
|
theme.page._ = {
|
|
css: themeContext.page.css,
|
|
scripts: themeContext.page.scripts,
|
|
favicon: themeContext.page.favicon
|
|
}
|
|
}
|
|
|
|
if (theme.header) {
|
|
|
|
themeContext.header.title = theme.header.title || themeContext.header.title;
|
|
|
|
if (theme.header.hasOwnProperty("url")) {
|
|
themeContext.header.url = theme.header.url;
|
|
}
|
|
|
|
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) {
|
|
let themeContextLogin = {}
|
|
let hasLoginTheme = false
|
|
if (theme.login.image) {
|
|
url = serveFile(themeApp,"/login/",theme.login.image);
|
|
if (url) {
|
|
themeContextLogin.image = url
|
|
hasLoginTheme = true
|
|
}
|
|
}
|
|
if (theme.login.message) {
|
|
themeContextLogin.message = theme.login.message
|
|
hasLoginTheme = true
|
|
}
|
|
if (theme.login.button) {
|
|
themeContextLogin.button = theme.login.button
|
|
hasLoginTheme = true
|
|
}
|
|
if (hasLoginTheme) {
|
|
themeContext.login = themeContextLogin
|
|
}
|
|
}
|
|
themeApp.get("/", async function(req,res) {
|
|
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
|
themeContext.themes = themePluginList.map(theme => theme.id);
|
|
res.json(themeContext);
|
|
})
|
|
|
|
if (theme.hasOwnProperty("menu")) {
|
|
themeSettings.menu = theme.menu;
|
|
}
|
|
|
|
if (theme.hasOwnProperty("palette")) {
|
|
themeSettings.palette = theme.palette;
|
|
}
|
|
|
|
if (theme.hasOwnProperty("projects")) {
|
|
themeSettings.projects = theme.projects;
|
|
}
|
|
|
|
if (theme.hasOwnProperty("multiplayer")) {
|
|
themeSettings.multiplayer = theme.multiplayer;
|
|
}
|
|
|
|
if (theme.hasOwnProperty("keymap")) {
|
|
themeSettings.keymap = theme.keymap;
|
|
}
|
|
|
|
if (theme.theme) {
|
|
themeSettings.theme = theme.theme;
|
|
}
|
|
|
|
if (theme.hasOwnProperty("tours")) {
|
|
themeSettings.tours = theme.tours;
|
|
}
|
|
|
|
return themeApp;
|
|
},
|
|
context: async function() {
|
|
if (activeTheme && !activeThemeInitialised) {
|
|
const themePlugin = await runtimeAPI.plugins.getPlugin({
|
|
id:activeTheme
|
|
});
|
|
if (themePlugin) {
|
|
if (themePlugin.css) {
|
|
const cssFiles = serveFilesFromTheme(
|
|
themePlugin.css,
|
|
themeApp,
|
|
"/css/",
|
|
themePlugin.path
|
|
);
|
|
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
|
|
theme.page = theme.page || {_:{}}
|
|
theme.page._.css = cssFiles.concat(theme.page._.css || [])
|
|
}
|
|
if (themePlugin.scripts) {
|
|
const scriptFiles = serveFilesFromTheme(
|
|
themePlugin.scripts,
|
|
themeApp,
|
|
"/scripts/",
|
|
themePlugin.path
|
|
)
|
|
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
|
|
theme.page = theme.page || {_:{}}
|
|
theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
|
|
}
|
|
// check and load page settings from theme
|
|
if (themePlugin.page) {
|
|
if (themePlugin.page.favicon && !theme.page.favicon) {
|
|
const result = serveFilesFromTheme(
|
|
[themePlugin.page.favicon],
|
|
themeApp,
|
|
"/",
|
|
themePlugin.path
|
|
)
|
|
if(result && result.length > 0) {
|
|
// update themeContext page favicon
|
|
themeContext.page.favicon = result[0]
|
|
theme.page = theme.page || {_:{}}
|
|
theme.page._.favicon = result[0]
|
|
}
|
|
}
|
|
if (themePlugin.page.tabicon && themePlugin.page.tabicon.icon && !theme.page.tabicon) {
|
|
const result = serveFilesFromTheme(
|
|
[themePlugin.page.tabicon.icon],
|
|
themeApp,
|
|
"/page/",
|
|
themePlugin.path
|
|
)
|
|
if(result && result.length > 0) {
|
|
// update themeContext page tabicon
|
|
themeContext.page.tabicon.icon = result[0]
|
|
themeContext.page.tabicon.colour = themeContext.page.tabicon.colour || themeContext.page.tabicon.colour
|
|
theme.page = theme.page || {_:{}}
|
|
theme.page._.tabicon = theme.page._.tabicon || {}
|
|
theme.page._.tabicon.icon = themeContext.page.tabicon.icon
|
|
theme.page._.tabicon.colour = themeContext.page.tabicon.colour
|
|
}
|
|
}
|
|
// if the plugin has a title AND the users settings.js does NOT
|
|
if (themePlugin.page.title && !theme.page.title) {
|
|
themeContext.page.title = themePlugin.page.title || themeContext.page.title
|
|
}
|
|
}
|
|
// check and load header settings from theme
|
|
if (themePlugin.header) {
|
|
if (themePlugin.header.image && !theme.header.image) {
|
|
const result = serveFilesFromTheme(
|
|
[themePlugin.header.image],
|
|
themeApp,
|
|
"/header/",
|
|
themePlugin.path
|
|
)
|
|
if(result && result.length > 0) {
|
|
// update themeContext header image
|
|
themeContext.header.image = result[0]
|
|
}
|
|
}
|
|
// if the plugin has a title AND the users settings.js does NOT have a title
|
|
if (themePlugin.header.title && !theme.header.title) {
|
|
themeContext.header.title = themePlugin.header.title || themeContext.header.title
|
|
}
|
|
// if the plugin has a header url AND the users settings.js does NOT
|
|
if (themePlugin.header.url && !theme.header.url) {
|
|
themeContext.header.url = themePlugin.header.url || themeContext.header.url
|
|
}
|
|
}
|
|
theme.codeEditor = theme.codeEditor || {}
|
|
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
|
|
|
|
theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid)
|
|
}
|
|
activeThemeInitialised = true;
|
|
}
|
|
return themeContext;
|
|
},
|
|
settings: function() {
|
|
return themeSettings;
|
|
},
|
|
serveFile: function(baseUrl,file) {
|
|
return serveFile(themeApp,baseUrl,file);
|
|
}
|
|
}
|