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

Merge pull request #2836 from node-red/theme-plugins

Add support for Theme Plugins
This commit is contained in:
Nick O'Leary 2021-01-27 20:45:59 +00:00 committed by GitHub
commit 575d07e41a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 20 deletions

View File

@ -17,6 +17,10 @@ module.exports = {
}) })
} else { } else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
if (/[^a-z\-\*]/i.test(opts.lang)) {
res.json({});
return;
}
runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) { runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) {
res.send(configs); res.send(configs);
}) })
@ -28,6 +32,10 @@ module.exports = {
lang: req.query.lng, lang: req.query.lng,
req: apiUtils.getRequestLogObject(req) req: apiUtils.getRequestLogObject(req)
} }
if (/[^a-z\-\*]/i.test(opts.lang)) {
res.json({});
return;
}
runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) { runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) {
res.json(result); res.json(result);
}).catch(function(err) { }).catch(function(err) {

View File

@ -76,7 +76,7 @@ module.exports = {
editorApp.get("/icons/:scope/:module/:icon",ui.icon); editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme"); var theme = require("./theme");
theme.init(settings); theme.init(settings, runtimeAPI);
editorApp.use("/theme",theme.app()); editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources); editorApp.use("/",ui.editorResources);

View File

@ -41,6 +41,10 @@ var theme = null;
var themeContext = clone(defaultContext); var themeContext = clone(defaultContext);
var themeSettings = null; var themeSettings = null;
var activeTheme = null;
var activeThemeInitialised = false;
var runtimeAPI;
var themeApp; var themeApp;
function serveFile(app,baseUrl,file) { function serveFile(app,baseUrl,file) {
@ -58,7 +62,7 @@ function serveFile(app,baseUrl,file) {
} }
} }
function serveFilesFromTheme(themeValue, themeApp, directory) { function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
var result = []; var result = [];
if (themeValue) { if (themeValue) {
var array = themeValue; var array = themeValue;
@ -67,7 +71,14 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
} }
for (var i=0;i<array.length;i++) { for (var i=0;i<array.length;i++) {
var url = serveFile(themeApp,directory,array[i]); let fullPath = array[i];
if (baseDirectory) {
fullPath = path.resolve(baseDirectory,array[i]);
if (fullPath.indexOf(baseDirectory) !== 0) {
continue;
}
}
var url = serveFile(themeApp,directory,fullPath);
if (url) { if (url) {
result.push(url); result.push(url);
} }
@ -77,10 +88,12 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
} }
module.exports = { module.exports = {
init: function(settings) { init: function(settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
themeContext = clone(defaultContext); themeContext = clone(defaultContext);
themeSettings = null; themeSettings = null;
theme = settings.editorTheme || {}; theme = settings.editorTheme || {};
activeTheme = theme.theme;
}, },
app: function() { app: function() {
@ -169,7 +182,9 @@ module.exports = {
} }
} }
} }
themeApp.get("/", function(req,res) { 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); res.json(themeContext);
}) })
@ -185,10 +200,38 @@ module.exports = {
themeSettings.projects = theme.projects; themeSettings.projects = theme.projects;
} }
if (theme.theme) {
themeSettings.theme = theme.theme;
}
return themeApp; return themeApp;
}, },
context: function() { 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 || [])
}
if (themePlugin.scripts) {
const scriptFiles = serveFilesFromTheme(
themePlugin.scripts,
themeApp,
"/scripts/",
themePlugin.path
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
}
}
activeThemeInitialised = true;
}
return themeContext; return themeContext;
}, },
settings: function() { settings: function() {

View File

@ -68,8 +68,8 @@ module.exports = {
apiUtils.rejectHandler(req,res,err); apiUtils.rejectHandler(req,res,err);
}) })
}, },
editor: function(req,res) { editor: async function(req,res) {
res.send(Mustache.render(editorTemplate,theme.context())); res.send(Mustache.render(editorTemplate,await theme.context()));
}, },
editorResources: express.static(path.join(editorClientDir,'public')) editorResources: express.static(path.join(editorClientDir,'public'))
}; };

View File

@ -681,9 +681,12 @@ var RED = (function() {
$('<span>').html(theme.header.title).appendTo(logo); $('<span>').html(theme.header.title).appendTo(logo);
} }
} }
if (theme.themes) {
knownThemes = theme.themes;
}
}); });
} }
var knownThemes = null;
var initialised = false; var initialised = false;
function init(options) { function init(options) {
@ -703,7 +706,13 @@ var RED = (function() {
buildEditor(options); buildEditor(options);
RED.i18n.init(options, function() { RED.i18n.init(options, function() {
RED.settings.init(options, loadEditor); RED.settings.init(options, function() {
if (knownThemes) {
RED.settings.editorTheme = RED.settings.editorTheme || {};
RED.settings.editorTheme.themes = knownThemes;
}
loadEditor();
});
}) })
} }

View File

@ -115,7 +115,13 @@ RED.userSettings = (function() {
options: [ options: [
{setting:"editor-language",local: true, label:"menu.label.view.language",options:function(done){ done([{val:'',text:RED._('menu.label.view.browserDefault')}].concat(RED.settings.theme("languages").map(localeToName).sort(compText))) }}, {setting:"editor-language",local: true, label:"menu.label.view.language",options:function(done){ done([{val:'',text:RED._('menu.label.view.browserDefault')}].concat(RED.settings.theme("languages").map(localeToName).sort(compText))) }},
] ]
},{ },
// {
// options: [
// {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
// ]
// },
{
title: "menu.label.view.grid", title: "menu.label.view.grid",
options: [ options: [
{setting:"view-show-grid",oldSetting:"menu-menu-item-view-show-grid",label:"menu.label.view.showGrid", default: true, toggle:true,onchange:"core:toggle-show-grid"}, {setting:"view-show-grid",oldSetting:"menu-menu-item-view-show-grid",label:"menu.label.view.showGrid", default: true, toggle:true,onchange:"core:toggle-show-grid"},

View File

@ -24,6 +24,9 @@ function registerPlugin(nodeSetId,id,definition) {
pluginToId[id] = nodeSetId; pluginToId[id] = nodeSetId;
plugins[id] = definition; plugins[id] = definition;
var module = registry.getModule(moduleId); var module = registry.getModule(moduleId);
definition.path = module.path;
module.plugins[pluginId].plugins.push(definition); module.plugins[pluginId].plugins.push(definition);
if (definition.type) { if (definition.type) {
pluginsByType[definition.type] = pluginsByType[definition.type] || []; pluginsByType[definition.type] = pluginsByType[definition.type] || [];

View File

@ -9,21 +9,59 @@ var api = module.exports = {
runtime = _runtime; runtime = _runtime;
}, },
/**
* Gets a plugin definition from the registry
* @param {Object} opts
* @param {String} opts.id - the id of the plugin to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<PluginDefinition>} - the plugin definition
* @memberof @node-red/runtime_plugins
*/
getPlugin: async function(opts) {
return runtime.plugins.getPlugin(opts.id);
},
/**
* Gets all plugin definitions of a given type
* @param {Object} opts
* @param {String} opts.type - the type of the plugins to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Array>} - the plugin definitions
* @memberof @node-red/runtime_plugins
*/
getPluginsByType: async function(opts) {
return runtime.plugins.getPluginsByType(opts.type);
},
/** /**
* Gets the editor content for an individual plugin * Gets the editor content for an individual plugin
* @param {Object} opts * @param {String} opts.lang - the locale language to return
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional) * @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information * @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_plugins
*/ */
getPluginList: async function(opts) { getPluginList: async function(opts) {
runtime.log.audit({event: "plugins.list.get"}, opts.req); runtime.log.audit({event: "plugins.list.get"}, opts.req);
return runtime.plugins.getPluginList(); return runtime.plugins.getPluginList();
}, },
/**
* Gets the editor content for all registered plugins
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_plugins
*/
getPluginConfigs: async function(opts) { getPluginConfigs: async function(opts) {
if (/[^a-z\-]/i.test(opts.lang)) {
throw new Error("Invalid language: "+opts.lang)
return;
}
runtime.log.audit({event: "plugins.configs.get"}, opts.req); runtime.log.audit({event: "plugins.configs.get"}, opts.req);
return runtime.plugins.getPluginConfigs(opts.lang); return runtime.plugins.getPluginConfigs(opts.lang);
}, },
@ -34,7 +72,7 @@ var api = module.exports = {
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US) * @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
* @param {Object} opts.req - the request to log (optional) * @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalogs * @return {Promise<Object>} - the message catalogs
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_plugins
*/ */
getPluginCatalogs: async function(opts) { getPluginCatalogs: async function(opts) {
var lang = opts.lang; var lang = opts.lang;

View File

@ -33,11 +33,11 @@ describe("api/editor/theme", function () {
theme.init({settings: {}}); theme.init({settings: {}});
fs.statSync.restore(); fs.statSync.restore();
}); });
it("applies the default theme", function () { it("applies the default theme", async function () {
var result = theme.init({}); var result = theme.init({});
should.not.exist(result); should.not.exist(result);
var context = theme.context(); var context = await theme.context();
context.should.have.a.property("page"); context.should.have.a.property("page");
context.page.should.have.a.property("title", "Node-RED"); context.page.should.have.a.property("title", "Node-RED");
context.page.should.have.a.property("favicon", "favicon.ico"); context.page.should.have.a.property("favicon", "favicon.ico");
@ -52,7 +52,7 @@ describe("api/editor/theme", function () {
should.not.exist(theme.settings()); should.not.exist(theme.settings());
}); });
it("picks up custom theme", function () { it("picks up custom theme", async function () {
theme.init({ theme.init({
editorTheme: { editorTheme: {
page: { page: {
@ -104,7 +104,7 @@ describe("api/editor/theme", function () {
theme.app(); theme.app();
var context = theme.context(); var context = await theme.context();
context.should.have.a.property("page"); context.should.have.a.property("page");
context.page.should.have.a.property("title", "Test Page Title"); context.page.should.have.a.property("title", "Test Page Title");
context.page.should.have.a.property("favicon", "theme/favicon/favicon"); context.page.should.have.a.property("favicon", "theme/favicon/favicon");