From 8a076c01ab54d7cab5b144d58ce944e58fd647bf Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 17 Dec 2020 14:35:01 +0000 Subject: [PATCH] Support for library source plugins --- .../editor-client/src/js/ui/clipboard.js | 196 +++++++++--------- .../editor-client/src/js/ui/library.js | 99 +++++---- .../editor-client/src/sass/library.scss | 9 +- .../@node-red/runtime/lib/api/settings.js | 2 +- .../@node-red/runtime/lib/index.js | 2 +- .../@node-red/runtime/lib/library/index.js | 84 +++++++- .../plugin/test-plugin/library-filestore.html | 11 + .../plugin/test-plugin/library-filestore.js | 32 +++ .../resources/plugin/test-plugin/package.json | 3 +- 9 files changed, 283 insertions(+), 155 deletions(-) create mode 100644 test/resources/plugin/test-plugin/library-filestore.html create mode 100644 test/resources/plugin/test-plugin/library-filestore.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index bb82ecea3..eb3b7b6de 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -26,7 +26,8 @@ RED.clipboard = (function() { var currentPopoverError; var activeTab; var libraryBrowser; - var examplesBrowser; + + var activeLibraries = {}; var pendingImportConfig; @@ -93,7 +94,7 @@ RED.clipboard = (function() { $( this ).dialog( "close" ); } else { var flowToExport = $("#red-ui-clipboard-dialog-export-text").val(); - var selectedPath = libraryBrowser.getSelected(); + var selectedPath = activeLibraries[activeTab].getSelected(); if (!selectedPath.children) { selectedPath = selectedPath.parent; } @@ -159,12 +160,7 @@ RED.clipboard = (function() { if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") { importNodes($("#red-ui-clipboard-dialog-import-text").val(),addNewFlow); } else { - var selectedPath; - if (activeTab === "red-ui-clipboard-dialog-import-tab-library") { - selectedPath = libraryBrowser.getSelected(); - } else { - selectedPath = examplesBrowser.getSelected(); - } + var selectedPath = activeLibraries[activeTab].getSelected(); if (selectedPath.path) { $.get('library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path, function(data) { importNodes(data,addNewFlow); @@ -254,11 +250,8 @@ RED.clipboard = (function() { ''+ ''+ ''+ - '
'+ - '
'+ - '
'+ - ''+ - '
'+ + '
'+ + ''+ '
'+ '
'+ '' @@ -280,8 +273,6 @@ RED.clipboard = (function() { ''+ ''+ ''+ - '
'+ - '
'+ ''+ ''+ '
'+ @@ -414,7 +405,7 @@ RED.clipboard = (function() { } },100); } else { - var file = libraryBrowser.getSelected(); + var file = activeLibraries[activeTab].getSelected(); if (file && file.label && !file.children) { $("#red-ui-clipboard-dialog-ok").button("enable"); } else { @@ -446,7 +437,7 @@ RED.clipboard = (function() { if (tab.id === "red-ui-clipboard-dialog-import-tab-clipboard") { $("#red-ui-clipboard-dialog-import-text").trigger("focus"); } else { - libraryBrowser.focus(); + activeLibraries[tab.id].focus(); } validateImport(); } @@ -455,54 +446,43 @@ RED.clipboard = (function() { id: "red-ui-clipboard-dialog-import-tab-clipboard", label: RED._("clipboard.clipboard") }); - tabs.addTab({ - id: "red-ui-clipboard-dialog-import-tab-library", - label: RED._("library.library") - }); - tabs.addTab({ - id: "red-ui-clipboard-dialog-import-tab-examples", - label: RED._("library.types.examples") - }); + + var libraries = RED.settings.libraries || []; + libraries.forEach(function(lib) { + var tabId = "red-ui-clipboard-dialog-import-tab-library-"+lib.id + tabs.addTab({ + id: tabId, + label: RED._(lib.label||lib.id) + }) + + var content = $('
') + .attr("id",tabId) + .hide() + .appendTo("#red-ui-clipboard-dialog-import-tabs-content"); + + var browser = RED.library.createBrowser({ + container: content, + onselect: function(file) { + if (file && file.label && !file.children) { + $("#red-ui-clipboard-dialog-ok").button("enable"); + } else { + $("#red-ui-clipboard-dialog-ok").button("disable"); + } + }, + onconfirm: function(item) { + if (item && item.label && !item.children) { + $("#red-ui-clipboard-dialog-ok").trigger("click"); + } + } + }) + loadFlowLibrary(browser,lib); + activeLibraries[tabId] = browser; + }) $("#red-ui-clipboard-dialog-tab-library-name").on("keyup", validateExportFilename); $("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)}); $("#red-ui-clipboard-dialog-export").button("enable"); - libraryBrowser = RED.library.createBrowser({ - container: $("#red-ui-clipboard-dialog-import-tab-library"), - onselect: function(file) { - if (file && file.label && !file.children) { - $("#red-ui-clipboard-dialog-ok").button("enable"); - } else { - $("#red-ui-clipboard-dialog-ok").button("disable"); - } - }, - onconfirm: function(item) { - if (item && item.label && !item.children) { - $("#red-ui-clipboard-dialog-ok").trigger("click"); - } - } - }) - loadFlowLibrary(libraryBrowser,"local",RED._("library.types.local")); - - examplesBrowser = RED.library.createBrowser({ - container: $("#red-ui-clipboard-dialog-import-tab-examples"), - onselect: function(file) { - if (file && file.label && !file.children) { - $("#red-ui-clipboard-dialog-ok").button("enable"); - } else { - $("#red-ui-clipboard-dialog-ok").button("disable"); - } - }, - onconfirm: function(item) { - if (item && item.label && !item.children) { - $("#red-ui-clipboard-dialog-ok").trigger("click"); - } - } - }) - loadFlowLibrary(examplesBrowser,"_examples_",RED._("library.types.examples")); - - dialogContainer.i18n(); $("#red-ui-clipboard-dialog-ok").show(); @@ -582,10 +562,12 @@ RED.clipboard = (function() { if (tab.id === "red-ui-clipboard-dialog-export-tab-clipboard") { $("#red-ui-clipboard-dialog-export").button("option","label", RED._("clipboard.export.copy")) $("#red-ui-clipboard-dialog-download").show(); + $("#red-ui-clipboard-dialog-export-tab-library-filename").hide(); } else { $("#red-ui-clipboard-dialog-export").button("option","label", RED._("clipboard.export.export")) $("#red-ui-clipboard-dialog-download").hide(); - libraryBrowser.focus(); + $("#red-ui-clipboard-dialog-export-tab-library-filename").show(); + activeLibraries[activeTab].focus(); } } @@ -594,26 +576,45 @@ RED.clipboard = (function() { id: "red-ui-clipboard-dialog-export-tab-clipboard", label: RED._("clipboard.clipboard") }); - tabs.addTab({ - id: "red-ui-clipboard-dialog-export-tab-library", - label: RED._("library.library") - }); + + + var libraries = RED.settings.libraries || []; + + libraries.forEach(function(lib) { + if (lib.readOnly) { + return + } + var tabId = "red-ui-clipboard-dialog-export-tab-library-"+lib.id + tabs.addTab({ + id: tabId, + label: RED._(lib.label||lib.id) + }) + + var content = $('
') + .attr("id",tabId) + .hide() + .insertBefore("#red-ui-clipboard-dialog-export-tab-library-filename"); + + var browser = RED.library.createBrowser({ + container: content, + folderTools: true, + onselect: function(file) { + if (file && file.label && !file.children) { + $("#red-ui-clipboard-dialog-tab-library-name").val(file.label); + } + }, + }) + loadFlowLibrary(browser,lib); + activeLibraries[tabId] = browser; + }) + + + $("#red-ui-clipboard-dialog-tab-library-name").on("keyup", validateExportFilename); $("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)}); $("#red-ui-clipboard-dialog-export").button("enable"); - libraryBrowser = RED.library.createBrowser({ - container: $("#red-ui-clipboard-dialog-export-tab-library-browser"), - folderTools: true, - onselect: function(file) { - if (file && file.label && !file.children) { - $("#red-ui-clipboard-dialog-tab-library-name").val(file.label); - } - } - }) - loadFlowLibrary(libraryBrowser,"local",RED._("library.types.local")); - var clipboardTabs = RED.tabs.create({ id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs", onchange: function(tab) { @@ -852,35 +853,28 @@ RED.clipboard = (function() { $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").treeList('data',treeData); } - function loadFlowLibrary(browser,library,label) { - // if (includeExamples) { - // listing.push({ - // library: "_examples_", - // type: "flows", - // icon: 'fa fa-hdd-o', - // label: RED._("library.types.examples"), - // path: "", - // children: function(done,item) { - // RED.library.loadLibraryFolder("_examples_","flows","",function(children) { - // item.children = children; - // done(children); - // }) - // } - // }) - // } + function loadFlowLibrary(browser,library) { browser.data([{ - library: library, + library: library.id, type: "flows", - icon: 'fa fa-hdd-o', - label: label, + icon: library.icon || 'fa fa-hdd-o', + label: RED._(library.label||library.id), path: "", expanded: true, - children: function(done, item) { - RED.library.loadLibraryFolder(library,"flows","",function(children) { - item.children = children; - done(children); - }) - } + children: [{ + library: library.id, + type: "flows", + icon: 'fa fa-cube', + label: "flows", + path: "", + expanded: true, + children: function(done, item) { + RED.library.loadLibraryFolder(library.id,"flows","",function(children) { + item.children = children; + done(children); + }) + } + }] }], true); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index 54d73c022..3872a38ea 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -216,31 +216,7 @@ RED.library = (function() { { id:'node-input-'+options.type+'-menu-open-library', label: RED._("library.openLibrary"), onselect: function() { - activeLibrary = options; - loadLibraryFolder("local",options.url, "", function(items) { - var listing = [{ - library: "local", - type: options.url, - icon: 'fa fa-hdd-o', - label: RED._("library.types.local"), - path: "", - expanded: true, - writable: false, - children: [{ - library: "local", - type: options.url, - icon: 'fa fa-cube', - label: options.type, - path: "", - expanded: true, - children: items - }] - }] - loadLibraryBrowser.data(listing); - setTimeout(function() { - loadLibraryBrowser.select(listing[0].children[0]); - },200); - }); + libraryEditor = ace.edit('red-ui-library-dialog-load-preview-text',{ useWorker: false }); @@ -256,6 +232,43 @@ RED.library = (function() { libraryEditor.renderer.$cursorLayer.element.style.opacity=0; libraryEditor.$blockScrolling = Infinity; + activeLibrary = options; + var listing = []; + var libraries = RED.settings.libraries || []; + libraries.forEach(function(lib) { + if (lib.types && lib.types.indexOf(options.url) === -1) { + return; + } + listing.push({ + library: lib.id, + type: options.url, + icon: lib.icon || 'fa fa-hdd-o', + label: RED._(lib.label||lib.id), + path: "", + expanded: true, + writable: false, + children: [{ + library: lib.id, + type: options.url, + icon: 'fa fa-cube', + label: options.type, + path: "", + expanded: false, + children: function(done, item) { + loadLibraryFolder(lib.id, options.url, "", function(children) { + item.children = children; + done(children); + }) + } + }] + }) + }); + loadLibraryBrowser.data(listing); + setTimeout(function() { + loadLibraryBrowser.select(listing[0].children[0]); + },200); + + var dialogHeight = 400; var winHeight = $(window).height(); if (winHeight < 570) { @@ -278,30 +291,40 @@ RED.library = (function() { } $("#red-ui-library-dialog-save-filename").attr("value",filename+"."+(options.ext||"txt")); - loadLibraryFolder("local",options.url, "", function(items) { - var listing = [{ - library: "local", + var listing = []; + var libraries = RED.settings.libraries || []; + libraries.forEach(function(lib) { + if (lib.types && lib.types.indexOf(options.url) === -1) { + return; + } + listing.push({ + library: lib.id, type: options.url, - icon: 'fa fa-hdd-o', - label: RED._("library.types.local"), + icon: lib.icon || 'fa fa-hdd-o', + label: RED._(lib.label||lib.id), path: "", expanded: true, writable: false, children: [{ - library: "local", + library: lib.id, type: options.url, icon: 'fa fa-cube', label: options.type, path: "", - expanded: true, - children: items + expanded: false, + children: function(done, item) { + loadLibraryFolder(lib.id, options.url, "", function(children) { + item.children = children; + done(children); + }) + } }] - }] - saveLibraryBrowser.data(listing); - setTimeout(function() { - saveLibraryBrowser.select(listing[0].children[0]); - },200); + }) }); + saveLibraryBrowser.data(listing); + setTimeout(function() { + saveLibraryBrowser.select(listing[0].children[0]); + },200); var dialogHeight = 400; var winHeight = $(window).height(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss index 89109f357..3ddf44a95 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss @@ -93,10 +93,9 @@ border:1px solid $primary-border-color; } -.red-ui-clipboard-dialog-tab-library { - .form-row { - margin-left: 10px; - } +#red-ui-clipboard-dialog-export-tab-library-filename { + height: auto !important; + margin-left: 10px; } #red-ui-clipboard-dialog { @@ -110,7 +109,7 @@ #red-ui-clipboard-dialog-tab-library-name { width: calc(100% - 120px); } -#red-ui-clipboard-dialog-export-tab-library-browser { +.red-ui-clipboard-dialog-tabs-content>div.red-ui-clipboard-dialog-export-tab-library-browser { height: calc(100% - 60px); margin-bottom: 13px; border-bottom: 1px solid $primary-border-color; diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index d96ec5b58..07d09e379 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -81,7 +81,7 @@ var api = module.exports = { if (!runtime.settings.disableEditor) { safeSettings.context = runtime.nodes.listContextStores(); - + safeSettings.libraries = runtime.library.getLibraries(); if (util.isArray(runtime.settings.paletteCategories)) { safeSettings.paletteCategories = runtime.settings.paletteCategories; } diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 0be1c6511..42e053baf 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -74,7 +74,6 @@ function init(userSettings,httpServer,_adminApi) { adminApi = _adminApi; } redNodes.init(runtime); - library.init(runtime); externalAPI.init(runtime); } @@ -104,6 +103,7 @@ function start() { return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json") .then(function() { return storage.init(runtime)}) .then(function() { return settings.load(storage)}) + .then(function() { return library.init(runtime)}) .then(function() { if (log.metric()) { diff --git a/packages/node_modules/@node-red/runtime/lib/library/index.js b/packages/node_modules/@node-red/runtime/lib/library/index.js index 8a4e6518e..293745f2d 100644 --- a/packages/node_modules/@node-red/runtime/lib/library/index.js +++ b/packages/node_modules/@node-red/runtime/lib/library/index.js @@ -15,21 +15,54 @@ **/ -var knownTypes = {}; +const {events} = require("@node-red/util") +const knownTypes = {}; +const libraries = {}; +const libraryPlugins = {}; -var libraries = {}; +// Libraries defined in the settings file. Their configurations +// cannot be modified in the editor. +let runtimeLibraries = []; +// Libraries defined by the user in the editor. +let userLibraries = []; function init(runtime) { - knownTypes = { - 'flows': 'node-red' - }; - libraries["_examples_"] = require("./examples"); - libraries["_examples_"].init(runtime); + events.on("registry:plugin-added", function(id) { + const plugin = runtime.plugins.getPlugin(id); + if (plugin.type === "node-red-library-source") { + libraryPlugins[plugin.id] = plugin; + + runtimeLibraries.forEach(library => { + if (library.type === id) { + library.local = false; + libraries[library.id] = new plugin.class(library) + if (libraries[library.id].init) { + libraries[library.id].init(); + } + } + }) + + + } + }) + + knownTypes.flows = 'node-red'; + + libraries["examples"] = require("./examples"); + libraries["examples"].init(runtime); libraries["local"] = require("./local"); libraries["local"].init(runtime); + try { + runtimeLibraries = runtime.settings.editorTheme.library.sources; + } catch(err) { + runtimeLibraries = []; + } + // userLibraries = runtime.settings.get("library") + + } function registerType(id,type) { @@ -56,7 +89,7 @@ function saveEntry(library,type,path,meta,body) { throw new Error(`Unknown library type '${type}'`); } if (libraries.hasOwnProperty(library)) { - if (libraries[library].hasOwnProperty("saveEntry")) { + if (libraries[library].saveEntry) { return libraries[library].saveEntry(type,path,meta,body); } else { throw new Error(`Library '${library}' is read-only`); @@ -66,8 +99,43 @@ function saveEntry(library,type,path,meta,body) { } } +function getLibraries() { + const libraryList = [ + { + id: "local", + label: "editor:library.types.local", + user: false, + icon: "fa fa-bath" + }, + { + id: "examples", + label: "editor:library.types.examples", + user: false, + readOnly: true, + types: ['flows'] + } + ]; + + for (let id in libraries) { + if (libraries.hasOwnProperty(id)) { + if (id !== 'local' && id !== 'examples') { + libraryList.push({ + id: id, + label: libraries[id].name || id, + user: false, + icon: libraries[id].icon + }) + } + } + } + + return libraryList; + +} + module.exports = { init: init, + getLibraries: getLibraries, register: registerType, getEntry: getEntry, saveEntry: saveEntry diff --git a/test/resources/plugin/test-plugin/library-filestore.html b/test/resources/plugin/test-plugin/library-filestore.html new file mode 100644 index 000000000..b4f1c0946 --- /dev/null +++ b/test/resources/plugin/test-plugin/library-filestore.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/test/resources/plugin/test-plugin/library-filestore.js b/test/resources/plugin/test-plugin/library-filestore.js new file mode 100644 index 000000000..61c8d9803 --- /dev/null +++ b/test/resources/plugin/test-plugin/library-filestore.js @@ -0,0 +1,32 @@ + +module.exports = function(RED) { + + class FileStorePlugin { + constructor(config) { + this.id = config.id; + this.name = config.name; + this.config = config; + this.icon = config.icon; + + console.log("FileStorePlugin",config) + } + async init() { + console.log("FileStorePlugin.init") + + } + async getEntry(type,path) { + console.log("FileStorePlugin.getLibraryEntry",type,path) + return [] + } + async saveEntry(type,path,meta,body) { + console.log("FileStorePlugin.saveLibraryEntry",type,path) + + } + } + + + RED.plugins.registerPlugin("node-red-library-filestore", { + type: "node-red-library-source", + class: FileStorePlugin + }) +} \ No newline at end of file diff --git a/test/resources/plugin/test-plugin/package.json b/test/resources/plugin/test-plugin/package.json index 1077042de..65487c656 100644 --- a/test/resources/plugin/test-plugin/package.json +++ b/test/resources/plugin/test-plugin/package.json @@ -6,7 +6,8 @@ "plugins": { "test": "test.js", "test-editor-plugin": "test-editor-plugin.html", - "test-runtime-plugin": "test-runtime-plugin.js" + "test-runtime-plugin": "test-runtime-plugin.js", + "library-filestore": "library-filestore.js" } } }