From 685aef83c9445ad0c2238da8fc5079f36277fb60 Mon Sep 17 00:00:00 2001 From: Julian Knight <1591850+TotallyInformation@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:20:34 +0100 Subject: [PATCH 01/63] Typos --- nodes/libs/socket.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/libs/socket.js b/nodes/libs/socket.js index 5be86f29..16244d44 100644 --- a/nodes/libs/socket.js +++ b/nodes/libs/socket.js @@ -253,7 +253,7 @@ class UibSockets { return this._isConfigured } - // ? Consider adding isConfigered checks on each method? + // ? Consider adding isConfigured checks on each method? /** Output a msg to the front-end. * @param {object} msg The message to output, include msg._socketId to send to a single client @@ -293,7 +293,6 @@ class UibSockets { log.trace(`[uibuilder:socket.js:sendToFe:${url}] msg sent on to ALL clients. Channel: ${channel}. ${JSON.stringify(msg)}`) ioNs.emit(channel, msg) } - } // ---- End of sendToFe ---- // /** Output a normal msg to the front-end. Can override socketid From b0364b960ac9b746140fe10ed43d6a285f9d23a6 Mon Sep 17 00:00:00 2001 From: Julian Knight <1591850+TotallyInformation@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:20:52 +0100 Subject: [PATCH 02/63] Comments --- nodes/libs/uiblib.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodes/libs/uiblib.js b/nodes/libs/uiblib.js index 9d93e37e..05fa6c05 100644 --- a/nodes/libs/uiblib.js +++ b/nodes/libs/uiblib.js @@ -146,6 +146,7 @@ module.exports = { node.status(node.statusDisplay) }, // ---- End of setNodeStatus ---- // + // TODO Move to fs /** Replace template in front-end instance folder * @param {string} url The uib instance URL * @param {string} template Name of one of the built-in templates including 'blank' and 'external' @@ -177,6 +178,7 @@ module.exports = { if ( extTemplate ) extTemplate = extTemplate.trim() if ( extTemplate === undefined ) throw new Error('extTemplate is undefined') + // TODO Move degit processing to its own function. Don't need the emitter on uib // If template="external" & extTemplate not blank - use degit to load if ( template === 'external' ) { const degit = require('degit') From 34d511cab15b73fc7ad9cdc1ca2f96280822822a Mon Sep 17 00:00:00 2001 From: Julian Knight <1591850+TotallyInformation@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:42:14 +0100 Subject: [PATCH 03/63] Add template switch. Add help. Integrate uib blank template and msg.template. --- docs/nodes/uib-html.md | 25 +++++++++--- nodes/uib-html/customNode.html | 66 ++++++++++++++++++++++++++++++- nodes/uib-html/customNode.js | 44 +++++++-------------- src/editor/uib-html/editor.js | 1 + src/editor/uib-html/help.html | 60 +++++++++++++++++++++++++++- src/editor/uib-html/template.html | 5 +++ 6 files changed, 165 insertions(+), 36 deletions(-) diff --git a/docs/nodes/uib-html.md b/docs/nodes/uib-html.md index 87d54d61..995d49bf 100644 --- a/docs/nodes/uib-html.md +++ b/docs/nodes/uib-html.md @@ -3,15 +3,30 @@ title: uib-html - Hydrate uibuilder msg._ui configuration data to HTML description: > Usage and configuration. created: 2023-02-05 16:31:39 -lastUpdated: 2023-08-28 18:22:08 +lastUpdated: 2023-10-28 15:33:18 --- -Available from uibuilder v6.6.0. +Available since uibuilder v6.6.0. -Initial release has no real configuration. - -Simply send the node a msg containing a `msg._ui` in accordance with the specifications listed here: [Dynamic, configuration-driven UI's (low-code)](Dynamic, configuration-driven UI's (low-code)). +Simply send the node a msg containing a `msg._ui` in accordance with the specifications listed here: [Dynamic, configuration-driven UI's (low-code)](client-docs/config-driven-ui). It will output a new msg containing the HTML in `msg.payload`. `msg._ui` is removed and all other msg properties are passed through. +## Optional template wrapper + +Generally though, the no-code and low-code `_ui` configurations relate to a specific area of the `body` of your HTML page. So the resulting HTML output from this node will be the same. In such cases, you will often want to wrap the output in a template - perhaps one that is a complete web page. By selecting the "Merge HTML Template?" flag, the node will wrap the generated HTML in a template. + +The template can be provided as an HTML string in the `msg.template` property. + +If that property does not exist on the incoming message, the node will use the current UIBUILDER "Blank" template which will give you a fully working UIBUILDER managed web page. In such a case, you can use the `uib-save` node to replace the existing `index.html` page or save as a new page as desired. + +If passing a template, you can use the core Node-RED `template` node to create the text and even include some data from Node-RED as needed. Of course, you could also read the text from an external file or even get it from a remote server. + +## Different uses for the output + +Use with the `uib-save` node is mentioned above. Of course, you can save manually using the core `file-out` node instead. + +However, you can use the output in other ways as well: +* As the HTML returned to an `http-out` core node. +* As the input to a Dashboard `ui-template` node for use with Dashboard. diff --git a/nodes/uib-html/customNode.html b/nodes/uib-html/customNode.html index 4918776f..0d50222c 100644 --- a/nodes/uib-html/customNode.html +++ b/nodes/uib-html/customNode.html @@ -59,6 +59,11 @@ + +
+ + +
@@ -67,9 +72,67 @@ + - + + -
- - -
- -
- - -
- -
- - -
- -
- - -
-
- - -
- - - -
-
- - -
- -
- -
- - -
+ diff --git a/nodes/uib-save/customNode.html b/nodes/uib-save/customNode.html index 67538dc1..b28abd75 100644 --- a/nodes/uib-save/customNode.html +++ b/nodes/uib-save/customNode.html @@ -14,42 +14,12 @@ limitations under the License. --> - + + - diff --git a/nodes/uib-update/customNode.html b/nodes/uib-update/customNode.html index a470e0b3..71c2585b 100644 --- a/nodes/uib-update/customNode.html +++ b/nodes/uib-update/customNode.html @@ -14,42 +14,11 @@ limitations under the License. --> - + + - diff --git a/nodes/uibuilder.html b/nodes/uibuilder.html index 6b6f7cbf..43960463 100644 --- a/nodes/uibuilder.html +++ b/nodes/uibuilder.html @@ -1,35 +1,11 @@ - \ No newline at end of file + `)}.call(this,t)}),void 0!==t.url&&""!==t.url||y(t.urlErrors,Object.keys(t.urlErrors).length<1),L(),window.tiDoTooltips("#ti-edit-panel")}RED.nodes.registerType("uibuilder",{category:e,color:"var(--uib-node-colour)",defaults:{name:{value:""},topic:{value:""},url:{required:!0,validate:function(e){v(this),this.urlErrors={},this.urlDeployedChanged=n[this.id]!==e,this.urlChanged=this.url!==e;var t=Object.values(n).indexOf(e);return this.urlDeployedDup=-1> Copy/Paste >>",this.id,"this.url:",this.url,", value:",e,", this.oldUrl:",this.oldUrl)),void 0===e&&(this.urlErrors.config="Not yet configured, valid URL needed"),void 0===e||""===e?this.urlErrors.none="Must not be empty":(20.. (double-dot)"),-1!==e.indexOf("/")&&(this.urlErrors.fslash="Cannot contain /"),-1!==e.indexOf("\\")&&(this.urlErrors.bslash="Cannot contain \\"),-1!==e.indexOf(" ")&&(this.urlErrors.sp="Cannot contain spaces"),"_"===e.substring(0,1)&&(this.urlErrors.strtU="Cannot start with _ (underscore)"),"."===e.substring(0,1)&&(this.urlErrors.strtDot="Cannot start with . (dot)"),"templates"===e.toLowerCase().substring(0,9)&&(this.urlErrors.templ='Cannot be "templates"'),"uibuilder"===e.toLowerCase()&&(this.urlErrors.uibname='Cannot be "uibuilder" (since v5)')),!0===this.urlDeployedDup&&(this.urlErrors.dup="Cannot be a URL already deployed"),!0===this.urlEditorDup&&(this.urlErrors.dup="Cannot be a URL already in use"),void 0!==e&&""!==e&&(this.folderExists=function(e){if(void 0===e)return!1;let o=!1;return $.ajax({type:"GET",async:!1,dataType:"json",url:"./uibuilder/admin/"+e,data:{cmd:"checkfolder"},success:function(e){o=e},error:function(e,t,i){"Not Found"!==i&&console.error("[uibuilder:queryFolderExists] Error "+t,i),o=!1}}),o}(e),!0===this.folderExists&&!0===this.urlChanged&&(i(">> folder already exists >>",this.url,this.id),RED.notify(`WARNING:

The folder for the chosen URL (${e}) is already exists.
It will be adopted by this node.

`,{type:"warning"})),!1===this.folderExists)&&(this.urlErrors.fldNotExist="URL does not yet exist. You must Deploy first.
If changing a deployed URL, the folder will be renamed on Deploy"),this.isDeployed&&!0===this.deployedUrlChanged&&(i("[uib] >> deployed url changed >> this.url:",this.url,", this.oldUrl:",this.oldUrl,this.id),this.urlErrors.warnChange=`Renaming from ${this.url} to ${e}. MUST redeploy now`,RED.notify(`NOTE:

You are renaming the url from ${this.url} to ${e}.
You MUST redeploy before doing anything else.

`,{type:"warning"})),0RED.settings.uibuilderRedeployNeeded&&(RED.notify(`uibuilder ${this.url}
uibuilder has been updated since you last deployed this instance. Please deploy now.`,{modal:!1,fixed:!1,type:"warning"}),this.mustChange=!0))}},showMsgUib:{value:!1},title:{value:""},descr:{value:""}},credentials:{jwtSecret:{type:"password"}},inputs:1,inputLabels:"Msg to send to front-end",outputs:2,outputLabels:["Data from front-end","Control Msgs from front-end"],icon:"node-blue-inverted.svg",paletteLabel:"uibuilder",label:function(){var e=this.url?`<${this.url}>`:"";return(this.name?this.name+" ":"")+e},oneditprepare:function(){T(this)},oneditsave:function(){i("[uib] >> this >>",this),!0===u.editorLoaded&&(u.editor.destroy(),delete u.editor,u.editorLoaded=!1);let e;(e=""!==$("#node-input-url").val()?$("#node-input-url").val():e)!==this.url?(this.oldUrl=this.url,i(`>> oneditsave URL CHANGED >> New=${$("#node-input-url").val()}, Old=`+this.url)):void 0!==this.oldUrl&&(this.oldUrl=void 0),(this.changed||this.mustChange)&&(this.deployedVersion=RED.settings.uibuilderCurrentVersion)},oneditcancel:function(){!0===u.editorLoaded&&(u.editor.destroy(),delete u.editor,u.editorLoaded=!1)},oneditresize:function(){m();try{$("#node-input-packageList").editableList("height",x())}catch(e){}},oneditdelete:function(){if(v(this),i("[uib] >> deleting >> isDeployed? ",this.isDeployed,void 0!==n[this.id]),this.isDeployed){const e=this,t=RED.notify(`DELETE:

You are deleting a uibuilder instance with url ${this.url}.
Would you like to also delete the folder?
WARNING: This cannot be undone.

`,{modal:!0,fixed:!0,type:"warning",buttons:[{text:"Yes",click:function(){$.ajax({type:"PUT",dataType:"json",url:"./uibuilder/admin/"+e.url,data:{cmd:"deleteondelete"}}).fail(function(e,t,i){console.error("[uibuilder:oneditdelete:PUT] Error "+t,i),RED.notify("uibuilder: Request url folder delete - error.
Folder will not be deleted, please delete manually.
"+i,{type:"error"})}),t.close(),RED.notify(`The folder ${e.url} will be deleted when you redeploy.`,{type:"compact"})}},{text:"NO",class:"primary",click:function(){t.close()}}]})}}})}(); \ No newline at end of file diff --git a/src/editor/uib-cache/editor.js b/src/editor/uib-cache/editor.js index 5135087b..82a51b23 100644 --- a/src/editor/uib-cache/editor.js +++ b/src/editor/uib-cache/editor.js @@ -5,14 +5,16 @@ (function () { 'use strict' + const mylog = window['uibuilder'].log + /** Module name must match this nodes html file @constant {string} moduleName */ const moduleName = 'uib-cache' /** Node's label @constant {string} paletteCategory */ const nodeLabel = moduleName /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' /** Populate the store dropdown */ function populateUseStoreDropdown() { @@ -110,6 +112,7 @@ } }) + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // RED.nodes.registerType(moduleName, { @@ -141,7 +144,5 @@ }, oneditprepare: function() { onEditPrepare(this) }, - }) // ---- End of registerType() ---- // - }()) diff --git a/src/editor/uib-cache/main.html b/src/editor/uib-cache/main.html index bd5afac3..0548ca62 100644 --- a/src/editor/uib-cache/main.html +++ b/src/editor/uib-cache/main.html @@ -14,6 +14,9 @@ limitations under the License. --> + + + diff --git a/src/editor/uib-cache/panel.html b/src/editor/uib-cache/panel.html index 73143706..36b35e8d 100644 --- a/src/editor/uib-cache/panel.html +++ b/src/editor/uib-cache/panel.html @@ -1,54 +1,55 @@ -
- - msg. -
-
- - -
-
- - -
-
- - -
-
- Number of msgs to cache per topic.
- Messages are dropped off front of queue once limit is reached.
- Use 0 for unlimited but take care not to run out of memory. -
+
+
+ + msg. +
+
+ + +
+
+ + +
+
+ + +
+
+ Number of msgs to cache per topic.
+ Messages are dropped off front of queue once limit is reached.
+ Use 0 for unlimited but take care not to run out of memory. +
- -
- - -
- - - -
+ +
+ + +
+ + + +
-
- Select a context store to use if you have more than one set up.
- Use persistent store if you want cache to survive a system reset.
- If store changed, old one must be manually deleted. -
+
+ Select a context store to use if you have more than one set up.
+ Use persistent store if you want cache to survive a system reset.
+ If store changed, old one must be manually deleted. +
-
- Select which type of store to use. The default is "Node".
- Node = This node's store, only accessible to this node.
- Flow/Global = Accessible to other nodes. Use with caution. -
+
+ Select which type of store to use. The default is "Node".
+ Node = This node's store, only accessible to this node.
+ Flow/Global = Accessible to other nodes. Use with caution. +
-
-
- - +
+
+ + +
- diff --git a/src/editor/uib-element/editor.js b/src/editor/uib-element/editor.js index 21f1dc29..7cce8acc 100644 --- a/src/editor/uib-element/editor.js +++ b/src/editor/uib-element/editor.js @@ -4,14 +4,16 @@ ;(function () { 'use strict' + const mylog = window['uibuilder'].log + /** Module name must match this nodes html file @constant {string} moduleName */ const moduleName = 'uib-element' /** Node's label @constant {string} paletteCategory */ const nodeLabel = moduleName /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' /** Element Types definitions */ const elTypes = { @@ -254,25 +256,6 @@ // Standard width for typed input fields const tiWidth = '68.5%' - /** Add jQuery UI formatted tooltips */ - function doTooltips() { - // Select our page elements - $('#uib-el').tooltip({ - items: 'img[alt], [aria-label], [title]', - track: true, - content: function() { - const element = $( this ) - if ( element.is( '[title]' ) ) { - return element.attr( 'title' ) - } else if ( element.is( '[aria-label]' ) ) { - return element.attr( 'aria-label' ) - } else if ( element.is( 'img[alt]' ) ) { - return element.attr( 'alt' ) - } else return '' - }, - }) - } - /** Prep for edit * @param {*} node A node instance as seen from the Node-RED Editor */ @@ -368,7 +351,7 @@ // } ) // Make position of aria-labels dynamic to cursor - // $('#uib-el *[aria-label]').on('mousemove', function(event) { + // $('#ti-edit-panel *[aria-label]').on('mousemove', function(event) { // document.documentElement.style.setProperty('--x', event.pageX ) // document.documentElement.style.setProperty('--y', event.pageY ) // document.documentElement.style.setProperty('--moveX', event.originalEvent.movementX ) @@ -447,8 +430,7 @@ label: 'Element Config' }) - doTooltips() - + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // /** Prep for save diff --git a/src/editor/uib-element/help.html b/src/editor/uib-element/help.html index eaae22b7..df78604e 100644 --- a/src/editor/uib-element/help.html +++ b/src/editor/uib-element/help.html @@ -4,7 +4,7 @@ Outputs msg._ui standard configuration data.

- Please try out the examples in the import library. + Please try out the examples in the import library. Documentation.

Inputs

diff --git a/src/editor/uib-element/main.html b/src/editor/uib-element/main.html index d180b725..4d43992b 100644 --- a/src/editor/uib-element/main.html +++ b/src/editor/uib-element/main.html @@ -14,15 +14,14 @@ limitations under the License. --> + + + - diff --git a/src/editor/uib-element/template.html b/src/editor/uib-element/template.html index 13cb0760..ce188db0 100644 --- a/src/editor/uib-element/template.html +++ b/src/editor/uib-element/template.html @@ -1,4 +1,4 @@ -
+
    diff --git a/src/editor/uib-html/editor.js b/src/editor/uib-html/editor.js index 17f84a1f..97fff06d 100644 --- a/src/editor/uib-html/editor.js +++ b/src/editor/uib-html/editor.js @@ -4,20 +4,22 @@ (function () { 'use strict' + // const mylog = window['uibuilder'].log + /** Module name must match this nodes html file @constant {string} moduleName */ const moduleName = 'uib-html' /** Node's label @constant {string} paletteCategory */ const nodeLabel = moduleName /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' /** Prep for edit * @param {*} node A node instance as seen from the Node-RED Editor */ function onEditPrepare(node) { - + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // // @ts-ignore diff --git a/src/editor/uib-html/help.html b/src/editor/uib-html/help.html index 7d0d897f..31d20a43 100644 --- a/src/editor/uib-html/help.html +++ b/src/editor/uib-html/help.html @@ -1,6 +1,6 @@

    - Converts uibuilder low-code UI description data into HTML. Send a msg._ui object to the node, + Converts UIBUILDER low-code UI description data into HTML. Send a msg._ui object to the node, the output will be HTML in msg.payload. Details.

    diff --git a/src/editor/uib-html/main.html b/src/editor/uib-html/main.html index def6a3e5..cb783f27 100644 --- a/src/editor/uib-html/main.html +++ b/src/editor/uib-html/main.html @@ -14,41 +14,10 @@ limitations under the License. --> - + + - diff --git a/src/editor/uib-html/template.html b/src/editor/uib-html/template.html index ce2e7d75..0cd0bf24 100644 --- a/src/editor/uib-html/template.html +++ b/src/editor/uib-html/template.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/editor/uib-list/main.html b/src/editor/uib-list/main.html index ca1571a7..7ccaee70 100644 --- a/src/editor/uib-list/main.html +++ b/src/editor/uib-list/main.html @@ -14,6 +14,9 @@ limitations under the License. --> + + + diff --git a/src/editor/uib-list/template.html b/src/editor/uib-list/template.html index de458694..0c355406 100644 --- a/src/editor/uib-list/template.html +++ b/src/editor/uib-list/template.html @@ -1,83 +1,87 @@ -
    - THIS NODE IS NOW DEPRECATED.
    PLEASE SWITCH TO USING THE uib-element node instead. -
    +
    + +
    + THIS NODE IS NOW DEPRECATED.
    PLEASE SWITCH TO USING THE uib-element node instead. +
    -
    - NOTE: This node only works if you are using the new ES Module version of the uibuilder front-end - client library. It does not work with the previous uibuilderfe library. -
    +
    + NOTE: This node only works if you are using the new ES Module version of the uibuilder front-end + client library. It does not work with the previous uibuilderfe library. +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    -
    - - -
    - - - -
    -
    - - -
    +
    + + +
    +
    + + +
    + + + +
    +
    + + +
    -
    +
    -
    - - -
    +
    + + +
    + +
    +

    + With no msg.mode set, sending another message will + automatically remove and replace the list. +

    + +

    + To remove an existing list, set msg.mode to "remove". + This will also clear the cache. +

    +

    + If node status is "Data Registered", when a new client connects to uibuilder, + it will immediately be sent a copy of the data so that all connected clients + have the same display. +

    +

    + If you need to do more complex tasks such as update specific entries in the list, + set the node to just output the configuration and then use the documentation to + tweak it accordingly. +

    +
    -
    -

    - With no msg.mode set, sending another message will - automatically remove and replace the list. -

    - -

    - To remove an existing list, set msg.mode to "remove". - This will also clear the cache. -

    -

    - If node status is "Data Registered", when a new client connects to uibuilder, - it will immediately be sent a copy of the data so that all connected clients - have the same display. -

    -

    - If you need to do more complex tasks such as update specific entries in the list, - set the node to just output the configuration and then use the documentation to - tweak it accordingly. -

    diff --git a/src/editor/uib-save/editor.js b/src/editor/uib-save/editor.js index dfbc61e7..d56f633a 100644 --- a/src/editor/uib-save/editor.js +++ b/src/editor/uib-save/editor.js @@ -9,10 +9,11 @@ /** Node's label @constant {string} paletteCategory */ const nodeLabel = moduleName /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' + // TODO Change to use window.uibuilder /** Copy of all deployed uibuilder node instances */ let uibInstances = null @@ -85,6 +86,8 @@ $(`#node-input-url option[value="${node.url}"]`).prop('selected', true) $('#node-input-url').val(node.url) } + + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // // @ts-ignore diff --git a/src/editor/uib-save/help.html b/src/editor/uib-save/help.html index 351c29f8..e083c2d5 100644 --- a/src/editor/uib-save/help.html +++ b/src/editor/uib-save/help.html @@ -2,6 +2,9 @@

    Save a file into a uibuilder instance folder.

    +

    + Documentation. +

    Inputs

    diff --git a/src/editor/uib-save/main.html b/src/editor/uib-save/main.html index 80184536..bbfc2486 100644 --- a/src/editor/uib-save/main.html +++ b/src/editor/uib-save/main.html @@ -14,41 +14,10 @@ limitations under the License. --> - + + - diff --git a/src/editor/uib-save/template.html b/src/editor/uib-save/template.html index 2ea3a9c5..952e337f 100644 --- a/src/editor/uib-save/template.html +++ b/src/editor/uib-save/template.html @@ -1,4 +1,5 @@ -
    +
    +
    - - -
    +
    -
    - - -
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    -
    - -
    diff --git a/src/editor/uib-tag/editor.js b/src/editor/uib-tag/editor.js index afde7ce3..79df44a7 100644 --- a/src/editor/uib-tag/editor.js +++ b/src/editor/uib-tag/editor.js @@ -4,14 +4,16 @@ (function () { 'use strict' + // const mylog = window['uibuilder'].log + /** Module name must match this nodes html file @constant {string} moduleName */ const moduleName = 'uib-tag' /** Node's label @constant {string} paletteCategory */ const nodeLabel = moduleName /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' const inputTypes = [ 'msg', 'flow', 'global', @@ -89,6 +91,7 @@ typeField: $('#node-input-attribsSourceType') }) + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // /** Validate a typed input as a string @@ -165,7 +168,5 @@ /** Prepares the Editor panel */ oneditprepare: function () { onEditPrepare(this) }, - }) // ---- End of registerType() ---- // - }()) diff --git a/src/editor/uib-tag/help.html b/src/editor/uib-tag/help.html index 4e6c8c8c..17eae591 100644 --- a/src/editor/uib-tag/help.html +++ b/src/editor/uib-tag/help.html @@ -4,7 +4,7 @@ Requires the "new" front-end client library (either the IIFE or ESM version).

    - Please try out the example in the import library. + Please try out the example in the import library. Documentation.

    Inputs

    diff --git a/src/editor/uib-tag/main.html b/src/editor/uib-tag/main.html index 504bb5c0..c331847f 100644 --- a/src/editor/uib-tag/main.html +++ b/src/editor/uib-tag/main.html @@ -14,39 +14,8 @@ limitations under the License. --> - + + - diff --git a/src/editor/uib-update/template.html b/src/editor/uib-update/template.html index cdefd3ee..f5395f10 100644 --- a/src/editor/uib-update/template.html +++ b/src/editor/uib-update/template.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/editor/uibuilder/editor.js b/src/editor/uibuilder/editor.js index b6e03ee7..8da9b3d3 100644 --- a/src/editor/uibuilder/editor.js +++ b/src/editor/uibuilder/editor.js @@ -2,14 +2,9 @@ /* eslint-disable sonarjs/no-duplicate-string, es/no-object-values, strict */ // Isolate this code -(function () { // eslint-disable-line sonarjs/cognitive-complexity +;(function () { // eslint-disable-line sonarjs/cognitive-complexity 'use strict' - const localHost = ['localhost', '127.0.0.1', '::1', ''].includes(window.location.hostname) || window.location.hostname.endsWith('.localhost') - const uibDebug = localHost - const mylog = uibDebug ? console.log : function() {} - mylog('[uibuilder] DEBUG ON (because running on localhost)') - //#region ------------------- Pollyfills --------------------- // if (!Object.entries) { @@ -37,28 +32,31 @@ //#region --------- "global" variables for the panel --------- // + // NOTE: window.uibuilder is added - see `resources` folder + + const localHost = window['uibuilder'].localHost + const uibDebug = window['uibuilder'].debug + const mylog = window['uibuilder'].log + /** Module name must match this nodes html file @constant {string} moduleName */ const moduleName = 'uibuilder' /** Node's label @constant {string} paletteCategory */ const nodeLabel = 'uibuilder' /** Node's palette category @constant {string} paletteCategory */ - const paletteCategory = 'uibuilder' + const paletteCategory = window['uibuilder'].paletteCategory /** Node's background color @constant {string} paletteColor */ - const paletteColor = '#E6E0F8' - /** Default session length (in seconds) if security is active @type {Number} */ - // const defaultSessionLength = 432000 - /** Default JWT secret if security is active - to ensure it isn't blank @type {String} */ - // const defaultJwtSecret = 'Replace This With A Real Secret' - /** Default template name */ - const defaultTemplate = 'blank' + const paletteColor = 'var(--uib-node-colour)' // '#E6E0F8' + /** Track which urls have been used - required to handle copy/paste and import * as these can contain duplicate urls before deployment. */ - const editorInstances = {} + const editorInstances = window['uibuilder'].urlsByNodeId + /** Default template name */ + const defaultTemplate = 'blank' /** List of installed packages - rebuilt when editor is opened, updates by library mgr */ - let packages = [] - /** List of the instances in use by id [{node_id: url}], updated in validateUrl() */ + let packages = window['uibuilder'].packages + /** List of the deployed instances in use by id [{node_id: url}], updated in validateUrl() */ let uibuilderInstances = RED.settings.uibuilderInstances /** placeholder for ACE editor vars - so that they survive close/reopen admin config ui @@ -80,41 +78,41 @@ //#endregion ------------------------------------------------- // - //#region --------- Node-RED Event Handlers --------- // + //#region --------- /Node-RED Event Handlers/ --------- // // These are registered once for the node type - /** Track which urls have been used - required to handle copy/paste and import - * as these can contain duplicate urls before deployment. - */ - RED.events.on('nodes:add', function(node) { - if ( node.type === 'uibuilder') { - // Keep a list of uib nodes in the editor - // may be different to the deployed list - editorInstances[node.id] = node.url - // -- IF uibuilderInstances <> editorInstances THEN there are undeployed instances. -- - } - }) - RED.events.on('nodes:change', function(node) { - if ( node.type === 'uibuilder') { - mylog('nodes:change:', node) - editorInstances[node.id] = node.url - } - }) - RED.events.on('nodes:remove', function(node) { - if ( node.type === 'uibuilder') { - mylog('>> nodes:remove >>', node) - delete editorInstances[node.id] - } - }) - // RED.events.on('deploy', function() { - // console.log('Deployed') + // /** Track which urls have been used - required to handle copy/paste and import + // * as these can contain duplicate urls before deployment. + // */ + // RED.events.on('nodes:add', function(node) { + // if ( node.type === 'uibuilder') { + // // Keep a list of uib nodes in the editor + // // may be different to the deployed list + // editorInstances[node.id] = node.url + // // -- IF uibuilderInstances <> editorInstances THEN there are undeployed instances. -- + // } // }) - // RED.events.on('workspace:dirty', function(data) { - // console.log('Workspace dirty:', data) + // RED.events.on('nodes:change', function(node) { + // if ( node.type === 'uibuilder') { + // mylog('nodes:change:', node) + // editorInstances[node.id] = node.url + // } // }) - // RED.events.on('runtime-state', function(event) { - // console.log('>> Runtime State >>', event) + // RED.events.on('nodes:remove', function(node) { + // if ( node.type === 'uibuilder') { + // mylog('>> nodes:remove >>', node) + // delete editorInstances[node.id] + // } // }) + // // RED.events.on('deploy', function() { + // // console.log('Deployed') + // // }) + // // RED.events.on('workspace:dirty', function(data) { + // // console.log('Workspace dirty:', data) + // // }) + // // RED.events.on('runtime-state', function(event) { + // // console.log('>> Runtime State >>', event) + // // }) //#endregion --------- Node-RED Event Handlers --------- // @@ -140,9 +138,9 @@ const npmOutput = data.result[0] if ( data.success === true) { - packages = data.result[1] + packages = window['uibuilder'].packages = data.result[1] - console.log('[uibuilder:doPkgUpd:get] PACKAGE INSTALLED. ', packageName, node.url, '\n\n', npmOutput, '\n ') + console.log('[uibuilder:doPkgUpd:get] PACKAGE INSTALLED. ', packageName, node.url, '\n\n', npmOutput, '\n ', packages[packageName]) RED.notify(`Successful update of npm package ${packageName}`, 'success') // reset and populate the list @@ -291,16 +289,15 @@ const npmOutput = data.result[0] if ( data.success === true) { - packages = data.result[1] + packages = window['uibuilder'].packages = data.result[1] - console.log('[uibuilder:addPackageRow:get] PACKAGE INSTALLED. ', packageName, node.url, '\n\n', npmOutput, '\n ') + console.log('[uibuilder:addPackageRow:get] PACKAGE INSTALLED. ', packageName, node.url, '\n\n', npmOutput, '\n ', packages[packageName]) RED.notify(`Successful installation of npm package ${packageName} for ${node.url}`, 'success') // reset and populate the list $('#node-input-packageList').editableList('empty') // @ts-ignore $('#node-input-packageList').editableList('addItems', Object.keys(packages)) - } else { console.log('[uibuilder:addPackageRow:get] ERROR ON INSTALLATION OF PACKAGE ', packageName, node.url, '\n\n', npmOutput, '\n ' ) RED.notify(`FAILED installation of npm package ${packageName} for ${node.url}`, 'error') @@ -372,29 +369,6 @@ return null } // ---- End of removePackageRow ---- // - /** Get list of installed packages via v2 API - save to master list */ - function packageList() { - - $.ajax({ - - dataType: 'json', - method: 'get', - url: 'uibuilder/uibvendorpackages', - async: false, - // data: { url: node.url}, - - success: function(vendorPaths) { - packages = vendorPaths - }, - - error: function(err) { - console.log('ERROR', err) - }, - - }) - - } // --- End of packageList --- // - //#endregion ==== Package Management Functions ==== // //#region ==== File Management Functions ==== // @@ -1441,34 +1415,6 @@ //#endregion ==== Template Management Functions ==== // - /** If debug, dump out key information to console */ - function dumpUibSettings() { - if (!uibDebug) return - - mylog('[uibuilder] Settings:\n', - // The server's NODE_ENV environment var (e.g. PRODUCTION or DEVELOPMENT) - '\nNodeEnv: ', RED.settings.uibuilderNodeEnv, - // Current version of uibuilder - '\nCurrentVersion: ', RED.settings.uibuilderCurrentVersion, - // Should the editor tell the user that a redeploy is needed (based on uib versions) - '\nRedeployNeeded: ', RED.settings.uibuilderRedeployNeeded, - // uibRoot folder - '\nRootFolder: ', RED.settings.uibuilderRootFolder, - - // Available templates and details - '\n\nTemplates: ', RED.settings.uibuilderTemplates, - // Custom server details - '\n\nCustomServer: ', RED.settings.uibuilderCustomServer, - - // List of the deployed uib instances [{node_id: url}] - `\n\nInstances (${Object.keys(RED.settings.uibuilderInstances).length}): `, RED.settings.uibuilderInstances, - - `\n\neditorInstances (${Object.keys(editorInstances).length}): `, editorInstances, - - `\n\npackages (${Object.keys(packages).length}): `, packages - ) - } - /** Set initial hidden & checkbox states (called from onEditPrepare) * @param {object} node A reference to the panel's `this` object */ @@ -2005,8 +1951,6 @@ getFolders() - packageList() - // console.log('>> ONEDITPREPARE: NODE >>', node) // Show uibuilder version @@ -2049,8 +1993,7 @@ fileEditor() - dumpUibSettings() - + window['tiDoTooltips']('#ti-edit-panel') // Do this at the end } // ---- End of oneditprepare ---- // //#endregion ------------------------------------------------- // diff --git a/src/editor/uibuilder/editor.min.js b/src/editor/uibuilder/editor.min.js index fc663300..c1c059a5 100644 --- a/src/editor/uibuilder/editor.min.js +++ b/src/editor/uibuilder/editor.min.js @@ -1,11 +1,11 @@ -!function(){"use strict";const r=["localhost","127.0.0.1","::1",""].includes(window.location.hostname)||window.location.hostname.endsWith(".localhost"),e=r,l=e?console.log:function(){};if(l("[uibuilder] DEBUG ON (because running on localhost)"),Object.entries||(Object.entries=function(e){var t=Object.keys(e);let i=t.length;for(var o=new Array(i);i--;)o[i]=[t[i],e[t[i]]];return o}),!Object.values){const O=Function.bind.call(Function.call,Array.prototype.reduce),F=Reflect.ownKeys,S=Function.bind.call(Function.call,Array.prototype.concat),j=Function.bind.call(Function.call,Object.prototype.propertyIsEnumerable);Object.values=function(i){return O(F(i),(e,t)=>S(e,"string"==typeof t&&j(i,t)?[i[t]]:[]),[])}}const d="blank",a={};let s=[],i=RED.settings.uibuilderInstances;const u={format:"html",folder:"src",fname:"index.html",fullscreen:!1};let t=[];function c(o){console.log(">>>> do update",o.data.pkgName);const n=o.data.pkgName,i=o.data.node;RED.notify("Installing npm package "+n),$.get(`uibuilder/uibnpmmanage?cmd=update&package=${n}&url=`+i.url,function(e){var t=e.result[0];!0===e.success?(s=e.result[1],console.log("[uibuilder:doPkgUpd:get] PACKAGE INSTALLED. ",n,i.url,"\n\n",t,"\n "),RED.notify("Successful update of npm package "+n,"success"),$("#node-input-packageList").editableList("empty"),$("#node-input-packageList").editableList("addItems",Object.keys(s))):(console.log("[uibuilder:doPkgUpd:get] ERROR ON INSTALLATION OF PACKAGE ",n,i.url,"\n\n",t,"\n "),RED.notify("FAILED update of npm package "+n,"error")),$("i.spinner").hide()}).fail(function(e,t,i){return console.error("[uibuilder:doPkgUpd:get] Error "+t,i),RED.notify("FAILED update of npm package "+n,"error"),$("i.spinner").hide(),"addPackageRow failed"}),$.ajax({type:"PUT",dataType:"json",url:"./uibuilder/admin/"+o.data.node.url,data:{cmd:"updatepackage",pkgName:o.data.pkgName}}).done(function(e,t,i){}).fail(function(e,t,i){console.error("[uibuilder:doPkgUpd:PUT] Error "+t,i),RED.notify(`uibuilder: Package update for '${o.data.pkgName}' failed.
    `+i,{type:"error"})}),$("#upd_"+o.data.pkgName).hide().next().text("XXX")}function o(o){return""===o||"string"!=typeof o?"No package":(RED.notify("Starting removal of npm package "+o),$("i.spinner").show(),$.get("uibuilder/uibnpmmanage?cmd=remove&package="+o,function(e){!0===e.success?(console.log("[uibuilder:removePackageRow:get] PACKAGE REMOVED. ",o),RED.notify("Successfully uninstalled npm package "+o,"success"),s[o]&&delete s[o]):(console.log("[uibuilder:removePackageRow:get] ERROR ON PACKAGE REMOVAL ",e.result),RED.notify("FAILED to uninstall npm package "+o,"error"),$("#node-input-packageList").editableList("addItem",o)),$("i.spinner").hide()}).fail(function(e,t,i){return console.error("[uibuilder:removePackageRow:get] Error "+t,i),RED.notify("FAILED to uninstall npm package "+o,"error"),$("#node-input-packageList").editableList("addItem",o),$("i.spinner").hide(),"removePackageRow failed"}),null)}function p(e){let t="text";e=e.split(".");if(""===e[0]&&e.shift(),1",{value:t,text:t}))}),e[s]||(s=e.src?"src":"root"),$("#node-input-folder").val(s),u.folder=s,localStorage.setItem("uibuilder."+a+".folder",s),e[s]);$.each(l,function(e,t){$("#node-input-filename").append($("

    Node Settings

    @@ -112,11 +126,25 @@

    Node Settings

    Sets the top-most (root) folder that can be written to. +
    Use pageName boolean
    +
    + If set, either msg._uib.pageName or msg._ui.pageName will be used + instead of the file name. The uibuilder node's current served folder will be used for the folder. +
    +
    + This allows easy overwriting of a uibuilder page's file simply by issuing an htmlSend command + to the front-end and sending the resulting message to this node. See the included examples. +
    +
    Folder string
    The name of an existing or new folder inside the chosen uibuilder instance root.
    + Defaults to src. +
    +
    + Folder name is ALWAYS relative to the node's instance root folder. ".." cannot be included in the path to prevent escaping from the instance root.
    @@ -137,7 +165,10 @@

    Node Settings

    Neither "/" or "\" can be included in the file name. The name must be valid for the OS you are using.
    - msg.fname overrides this as long as this is left blank. + msg.fname will be used if this is left blank. +
    +
    + This may contain prefixed sub-folders but cannot use .. to prevent folder traversal.
    Create Folder? boolean
    @@ -154,6 +185,9 @@

    Node Settings

    Encoding string
    +
    + Not usable. Future enhancement. +
    The optional string encoding to be used when the input data is a string. It defaults to Node.js's utf8. @@ -161,6 +195,9 @@

    Node Settings

    Mode integer
    +
    + Not usable. Future enhancement. +
    The optional file output mode. Defaults to 0o666 See Node.js documentation for what can be provided here. diff --git a/nodes/uib-save/customNode.js b/nodes/uib-save/customNode.js index 6c5053c3..a22d897c 100644 --- a/nodes/uib-save/customNode.js +++ b/nodes/uib-save/customNode.js @@ -60,27 +60,47 @@ const mod = { */ async function inputMsgHandler(msg, send, done) { // eslint-disable-line no-unused-vars - // const RED = mod.RED + const RED = mod.RED + let statusColor = 'blue' - // TODO check if msg.payload exists + // TODO Update node doc // TODO msg/config overrides - // TODO Check the _ui pagename property if fname not set - to allow auto-updates to pages - - // TODO Make the folder name in Editor default to `src` - - // If msg.fname or msg.folder provided, override the static setting but only if the static setting is blank + // TODO Check the _uib/_ui pageName property if fname not set - to allow auto-updates to pages - // Call uibuilder shared library to save file (optional sub-folder creation and client reload) - try { - await uibFs.writeInstanceFile(this.url, this.folder, this.fname, msg.payload, this.createFolder, this.reload) - this.counters.success++ - this.statusDisplay = { fill: 'green', shape: 'dot', text: `Saved: ${this.counters.success}, Failed: ${this.counters.fail}` } - } catch (err) { + if (!msg.payload) { this.counters.fail++ - this.statusDisplay = { fill: 'red', shape: 'dot', text: `Saved: ${this.counters.success}, Failed: ${this.counters.fail}` } - this.error(`🛑${err.message}`, err) + statusColor = 'red' + this.error('🛑 msg.payload not present or empty. File not saved.') + } else { + let folder = this.folder + let fname = this.fname + + // If "Use pageName" + if (this.usePageName === true && ( (msg._uib && msg._uib.pageName) || (msg._ui && msg._ui.pageName) )) { + fname = msg._uib ? msg._uib.pageName : msg._ui.pageName + const srcNode = RED.nodes.getNode(this.uibId) + folder = srcNode.sourceFolder + } else { + this.warn('Use pageName requested but neither msg._uib nor msg._ui exists') + } + + // If msg.fname or msg.folder provided, override the static setting but only if the static setting is blank + if (!folder && msg.folder) folder = msg.folder + if (!fname && msg.fname) fname = msg.fname + + // Call uibuilder shared library to save file (optional sub-folder creation and client reload) + try { + await uibFs.writeInstanceFile(this.url, folder, fname, msg.payload, this.createFolder, this.reload) + this.counters.success++ + statusColor = 'green' + } catch (err) { + this.counters.fail++ + statusColor = 'red' + this.error(`🛑 ${err.message}`, err) + } } + this.statusDisplay = { fill: statusColor, shape: 'dot', text: `Saved: ${this.counters.success}, Failed: ${this.counters.fail}` } setNodeStatus( this ) // We are done @@ -108,6 +128,7 @@ function nodeInstance(config) { this.fname = config.fname ?? '' this.createFolder = config.createFolder ?? false this.reload = config.reload ?? false + this.usePageName = config.usePageName ?? false this.encoding = config.encoding ?? 'utf8' this.mode = config.mode ?? 0o666 this.uibId = config.uibId ?? '' diff --git a/resources/uib-save.js b/resources/uib-save.js index 7ca40a03..dcc27214 100644 --- a/resources/uib-save.js +++ b/resources/uib-save.js @@ -36,6 +36,7 @@ */ function onEditPrepare(node) { // initial checkbox states + $('#node-input-usePageName').prop('checked', node.usePageName) $('#node-input-createFolder').prop('checked', node.createFolder) $('#node-input-reload').prop('checked', node.reload) @@ -73,6 +74,27 @@ $('#node-input-url').val(node.url) } + // If "Use pageName" is set, disable the folder and file fields. + $('#node-input-usePageName').on('change', function() { + if ($(this).prop('checked') === true) { + $('#node-input-folder').attr('disabled', true) + $('#folder').css('color', 'var(--red-ui-tab-text-color-disabled-active)') + $('#node-input-folder').css('background-color', 'var(--red-ui-form-text-color-disabled)') + + $('#node-input-fname').attr('disabled', true) + $('#fname').css('color', 'var(--red-ui-tab-text-color-disabled-active)') + $('#node-input-fname').css('background-color', 'var(--red-ui-form-text-color-disabled)') + } else { + $('#node-input-folder').attr('disabled', false) + $('#folder').removeAttr('style') + $('#node-input-folder').removeAttr('style') + + $('#node-input-fname').attr('disabled', false) + $('#fname').removeAttr('style') + $('#node-input-fname').removeAttr('style') + } + }) + uibuilder.doTooltips('#ti-edit-panel') // Do this at the end } // ----- end of onEditPrepare() ----- // @@ -85,6 +107,7 @@ fname: { value: '', }, createFolder: { value: false, }, reload: { value: false, }, + usePageName: { value: false, }, encoding: { value: 'utf8' }, mode: { value: 0o666 }, name: { value: '' }, diff --git a/src/editor/uib-save/help.html b/src/editor/uib-save/help.html index e083c2d5..a7510cdb 100644 --- a/src/editor/uib-save/help.html +++ b/src/editor/uib-save/help.html @@ -4,6 +4,7 @@

    Documentation. + This node has examples in the Node-RED Import Examples library.

    Inputs

    @@ -18,11 +19,18 @@

    Inputs

    If supplied and the folder setting is blank, will be used as the output folder.
    +
    + Folder name is ALWAYS relative to the node's instance root folder. + Folder traversal outside of that is not permitted. +
    fname string
    If supplied and the file name setting is blank, will be used as the output file name.
    +
    + This may contain prefixed sub-folders but cannot use .. to prevent folder traversal. +

    Node Settings

    @@ -36,11 +44,25 @@

    Node Settings

    Sets the top-most (root) folder that can be written to. +
    Use pageName boolean
    +
    + If set, either msg._uib.pageName or msg._ui.pageName will be used + instead of the file name. The uibuilder node's current served folder will be used for the folder. +
    +
    + This allows easy overwriting of a uibuilder page's file simply by issuing an htmlSend command + to the front-end and sending the resulting message to this node. See the included examples. +
    +
    Folder string
    The name of an existing or new folder inside the chosen uibuilder instance root.
    + Defaults to src. +
    +
    + Folder name is ALWAYS relative to the node's instance root folder. ".." cannot be included in the path to prevent escaping from the instance root.
    @@ -61,7 +83,10 @@

    Node Settings

    Neither "/" or "\" can be included in the file name. The name must be valid for the OS you are using.
    - msg.fname overrides this as long as this is left blank. + msg.fname will be used if this is left blank. +
    +
    + This may contain prefixed sub-folders but cannot use .. to prevent folder traversal.
    Create Folder? boolean
    @@ -78,6 +103,9 @@

    Node Settings

    Encoding string
    +
    + Not usable. Future enhancement. +
    The optional string encoding to be used when the input data is a string. It defaults to Node.js's utf8. @@ -85,6 +113,9 @@

    Node Settings

    Mode integer
    +
    + Not usable. Future enhancement. +
    The optional file output mode. Defaults to 0o666 See Node.js documentation for what can be provided here. diff --git a/src/editor/uib-save/template.html b/src/editor/uib-save/template.html index 952e337f..24425dfd 100644 --- a/src/editor/uib-save/template.html +++ b/src/editor/uib-save/template.html @@ -8,26 +8,32 @@

    -
    +
    + + +
    +
    -
    +
    -
    - -