From 1f6328bf4e9a4f12e914816782008376f6c4b3bd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 26 Jan 2021 13:35:40 +0000 Subject: [PATCH] Add initial support for ThemePlugins --- .../@node-red/editor-api/lib/admin/plugins.js | 8 +++ .../@node-red/editor-api/lib/editor/index.js | 2 +- .../@node-red/editor-api/lib/editor/theme.js | 55 +++++++++++++++++-- .../@node-red/editor-api/lib/editor/ui.js | 4 +- .../@node-red/editor-client/src/js/red.js | 13 ++++- .../editor-client/src/js/ui/userSettings.js | 10 +++- .../@node-red/registry/lib/plugins.js | 3 + .../@node-red/runtime/lib/api/plugins.js | 44 ++++++++++++++- .../editor-api/lib/editor/theme_spec.js | 8 +-- 9 files changed, 127 insertions(+), 20 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js index 15428f86f..3d49a7e8a 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js @@ -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) { diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index 71876eaa6..2e8333f3f 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -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); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 17dbbafea..52f7974ec 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -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 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() { diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js index 24b819fec..a4812a668 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js @@ -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')) }; diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index a2bcb0e7d..0b33855fe 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -681,9 +681,12 @@ var RED = (function() { $('').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(); + }); }) } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js index 07ab12180..2bdae4b13 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js @@ -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"}, diff --git a/packages/node_modules/@node-red/registry/lib/plugins.js b/packages/node_modules/@node-red/registry/lib/plugins.js index c2bb2298d..82b230011 100644 --- a/packages/node_modules/@node-red/registry/lib/plugins.js +++ b/packages/node_modules/@node-red/registry/lib/plugins.js @@ -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] || []; diff --git a/packages/node_modules/@node-red/runtime/lib/api/plugins.js b/packages/node_modules/@node-red/runtime/lib/api/plugins.js index ee35d445a..076638640 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/plugins.js +++ b/packages/node_modules/@node-red/runtime/lib/api/plugins.js @@ -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} - 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} - 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} - 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} - 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} - the message catalogs - * @memberof @node-red/runtime_nodes + * @memberof @node-red/runtime_plugins */ getPluginCatalogs: async function(opts) { var lang = opts.lang; diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 5c8123d61..2fa43e01a 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -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");