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

View File

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

View File

@ -41,6 +41,10 @@ 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) {
@ -58,7 +62,7 @@ function serveFile(app,baseUrl,file) {
}
}
function serveFilesFromTheme(themeValue, themeApp, directory) {
function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
var result = [];
if (themeValue) {
var array = themeValue;
@ -67,7 +71,14 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
}
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) {
result.push(url);
}
@ -77,10 +88,12 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
}
module.exports = {
init: function(settings) {
init: function(settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
themeContext = clone(defaultContext);
themeSettings = null;
theme = settings.editorTheme || {};
activeTheme = theme.theme;
},
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);
})
@ -185,10 +200,38 @@ module.exports = {
themeSettings.projects = theme.projects;
}
if (theme.theme) {
themeSettings.theme = theme.theme;
}
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;
},
settings: function() {

View File

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

View File

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

@ -109,13 +109,19 @@ RED.userSettings = (function() {
function compText(a, b) {
return a.text.localeCompare(b.text);
}
var viewSettings = [
{
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))) }},
]
},{
},
// {
// options: [
// {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
// ]
// },
{
title: "menu.label.view.grid",
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"},

View File

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

View File

@ -9,21 +9,59 @@ var api = module.exports = {
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
* @param {Object} opts
* @param {String} opts.lang - the locale language to return
* @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_nodes
* @memberof @node-red/runtime_plugins
*/
getPluginList: async function(opts) {
runtime.log.audit({event: "plugins.list.get"}, opts.req);
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) {
if (/[^a-z\-]/i.test(opts.lang)) {
throw new Error("Invalid language: "+opts.lang)
return;
}
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
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 {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalogs
* @memberof @node-red/runtime_nodes
* @memberof @node-red/runtime_plugins
*/
getPluginCatalogs: async function(opts) {
var lang = opts.lang;

View File

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