From d9dc171c28633bd124724001244a150393d9343c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 11 Jun 2017 21:19:46 +0100 Subject: [PATCH] Add buffer mode to typedInput --- editor/js/ui/common/panels.js | 2 - editor/js/ui/common/typedInput.js | 14 ++ editor/js/ui/editor.js | 197 +++++++++++++++++++++++++- editor/sass/editor.scss | 19 ++- editor/sass/ui/common/typedInput.scss | 2 +- editor/templates/index.mst | 18 ++- nodes/core/core/20-inject.html | 2 +- nodes/core/logic/15-change.html | 4 +- nodes/core/logic/15-change.js | 4 +- red/api/locales/en-US/editor.json | 6 + red/runtime/util.js | 3 + 11 files changed, 256 insertions(+), 15 deletions(-) diff --git a/editor/js/ui/common/panels.js b/editor/js/ui/common/panels.js index fbe595662..84e1d4c7e 100644 --- a/editor/js/ui/common/panels.js +++ b/editor/js/ui/common/panels.js @@ -39,7 +39,6 @@ RED.panels = (function() { var height = container.height(); startPosition = ui.position.top; panelHeights = [$(children[0]).height(),$(children[1]).height()]; - console.log("START",height,panelHeights,panelHeights[0]+panelHeights[1],height-(panelHeights[0]+panelHeights[1])); }, drag: function(event,ui) { var height = container.height(); @@ -68,7 +67,6 @@ RED.panels = (function() { panelHeights = [topPanelHeight,bottomPanelHeight]; $(children[0]).height(panelHeights[0]); $(children[1]).height(panelHeights[1]); - console.log("SET",height,panelHeights,panelHeights[0]+panelHeights[1],height-(panelHeights[0]+panelHeights[1])); } if (options.resize) { options.resize(panelHeights[0],panelHeights[1]); diff --git a/editor/js/ui/common/typedInput.js b/editor/js/ui/common/typedInput.js index 3b0a56753..e481c6ef4 100644 --- a/editor/js/ui/common/typedInput.js +++ b/editor/js/ui/common/typedInput.js @@ -62,6 +62,20 @@ } }) } + }, + bin: { + value: "bin", + label: "buffer", + icon: "red/images/typedInput/bin.png", + expand: function() { + var that = this; + RED.editor.editBuffer({ + value: this.value(), + complete: function(v) { + that.value(v); + } + }) + } } }; var nlsd = false; diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index 622861ab5..bdeadca43 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -496,6 +496,8 @@ RED.editor = (function() { label = RED._("expressionEditor.title"); } else if (node.type === '_json') { label = RED._("jsonEditor.title"); + } else if (node.type === '_buffer') { + label = RED._("bufferEditor.title"); } else if (node.type === 'subflow') { label = RED._("subflow.editSubflow",{name:node.name}) } else if (node.type.indexOf("subflow:")===0) { @@ -1874,7 +1876,7 @@ RED.editor = (function() { }); legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression); } catch(err) { - testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message})); + testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1); return; } $(".node-input-expression-legacy").toggle(legacyMode); @@ -1888,7 +1890,7 @@ RED.editor = (function() { try { var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData); if (usesContext) { - testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported")); + testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1); return; } @@ -1898,9 +1900,9 @@ RED.editor = (function() { } else { formattedResult = RED._("expressionEditor.noMatch"); } - testResultEditor.setValue(formattedResult); + testResultEditor.setValue(formattedResult,-1); } catch(err) { - testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message})); + testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1); } } @@ -2028,6 +2030,191 @@ RED.editor = (function() { RED.tray.show(trayOptions); } + function stringToUTF8Array(str) { + var data = []; + var i=0, l = str.length; + for (i=0; i> 6)); + data.push(0x80 | (char & 0x3f)); + } else if (char < 0xd800 || char >= 0xe000) { + data.push(0xe0 | (char >> 12)); + data.push(0x80 | ((char>>6) & 0x3f)); + data.push(0x80 | (char & 0x3f)); + } else { + i++; + char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff)); + data.push(0xf0 | (char >>18)); + data.push(0x80 | ((char>>12) & 0x3f)); + data.push(0x80 | ((char>>6) & 0x3f)); + data.push(0x80 | (char & 0x3f)); + } + } + return data; + } + + function editBuffer(options) { + var value = options.value; + var onComplete = options.complete; + var type = "_buffer" + editStack.push({type:type}); + RED.view.state(RED.state.EDITING); + var bufferStringEditor = []; + var bufferBinValue; + + var panels; + + var trayOptions = { + title: getEditStackTitle(), + buttons: [ + { + id: "node-dialog-cancel", + text: RED._("common.label.cancel"), + click: function() { + RED.tray.close(); + } + }, + { + id: "node-dialog-ok", + text: RED._("common.label.done"), + class: "primary", + click: function() { + onComplete(JSON.stringify(bufferBinValue)); + RED.tray.close(); + } + } + ], + resize: function(dimensions) { + if (dimensions) { + editTrayWidthCache[type] = dimensions.width; + } + var height = $("#dialog-form").height(); + if (panels) { + panels.resize(height); + } + }, + open: function(tray) { + var trayBody = tray.find('.editor-tray-body'); + var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor'); + + bufferStringEditor = RED.editor.createEditor({ + id: 'node-input-buffer-str', + value: "", + mode:"ace/mode/text" + }); + bufferStringEditor.getSession().setValue(value||"",-1); + + bufferBinEditor = RED.editor.createEditor({ + id: 'node-input-buffer-bin', + value: "", + mode:"ace/mode/text", + readOnly: true + }); + + var changeTimer; + var buildBuffer = function(data) { + var valid = true; + var isString = typeof data === 'string'; + var binBuffer = []; + if (isString) { + bufferBinValue = stringToUTF8Array(data); + } else { + bufferBinValue = data; + } + var i=0,l=bufferBinValue.length; + var c = 0; + for(i=0;i 255)) { + valid = false; + break; + } + if (i>0) { + if (i%8 === 0) { + if (i%16 === 0) { + binBuffer.push("\n"); + } else { + binBuffer.push(" "); + } + } else { + binBuffer.push(" "); + } + } + binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase()); + } + if (valid) { + $("#node-input-buffer-type-string").toggle(isString); + $("#node-input-buffer-type-array").toggle(!isString); + bufferBinEditor.setValue(binBuffer.join(""),1); + } + return valid; + } + var bufferStringUpdate = function() { + var value = bufferStringEditor.getValue(); + var isValidArray = false; + if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) { + isValidArray = true; + try { + var data = JSON.parse(value); + isValidArray = buildBuffer(data); + } catch(err) { + isValidArray = false; + } + } + if (!isValidArray) { + buildBuffer(value); + } + + } + bufferStringEditor.getSession().on('change', function() { + clearTimeout(changeTimer); + changeTimer = setTimeout(bufferStringUpdate,200); + }); + + bufferStringUpdate(); + + dialogForm.i18n(); + + panels = RED.panels.create({ + id:"node-input-buffer-panels", + resize: function(p1Height,p2Height) { + var p1 = $("#node-input-buffer-panel-str"); + p1Height -= $(p1.children()[0]).outerHeight(true); + var editorRow = $(p1.children()[1]); + p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("#node-input-buffer-str").css("height",(p1Height-5)+"px"); + bufferStringEditor.resize(); + + var p2 = $("#node-input-buffer-panel-bin"); + editorRow = $(p2.children()[0]); + p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("#node-input-buffer-bin").css("height",(p2Height-5)+"px"); + bufferBinEditor.resize(); + } + }); + + $(".node-input-buffer-type").click(function(e) { + e.preventDefault(); + RED.sidebar.info.set(RED._("bufferEditor.modeDesc")); + RED.sidebar.info.show(); + }) + + + }, + close: function() { + editStack.pop(); + }, + show: function() {} + } + if (editTrayWidthCache.hasOwnProperty(type)) { + trayOptions.width = editTrayWidthCache[type]; + } + RED.tray.show(trayOptions); + } + return { init: function() { RED.tray.init(); @@ -2045,6 +2232,7 @@ RED.editor = (function() { editSubflow: showEditSubflowDialog, editExpression: editExpression, editJSON: editJSON, + editBuffer: editBuffer, validateNode: validateNode, updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo @@ -2071,6 +2259,7 @@ RED.editor = (function() { } if (options.readOnly) { editor.setOption('readOnly',options.readOnly); + editor.container.classList.add("ace_read-only"); } if (options.hasOwnProperty('lineNumbers')) { editor.renderer.setOption('showGutter',options.lineNumbers); diff --git a/editor/sass/editor.scss b/editor/sass/editor.scss index 8590095d7..a68eeedb8 100644 --- a/editor/sass/editor.scss +++ b/editor/sass/editor.scss @@ -282,16 +282,21 @@ } } } -.node-input-expression-legacy { +.node-input-expression-legacy, .node-input-buffer-type { + font-size: 0.8em; float: left; cursor: pointer; border: 1px solid white; - padding: 0 5px; + padding: 2px 5px; border-radius: 2px; &:hover { - border-color: $primary-border-color; + border-color: $form-input-border-color; } } +.node-input-buffer-type { + float: none; + text-align: right; +} #clipboard-hidden { position: absolute; @@ -318,3 +323,11 @@ color: #999; } } + +.ace_read-only { + background: #eee !important; + .ace_cursor { + color: transparent !important; + } + +} diff --git a/editor/sass/ui/common/typedInput.scss b/editor/sass/ui/common/typedInput.scss index b4e6cdfc9..e664cd8ec 100644 --- a/editor/sass/ui/common/typedInput.scss +++ b/editor/sass/ui/common/typedInput.scss @@ -35,7 +35,7 @@ } input { width: 100%; - padding: 0 0 0 1px; + padding: 0 0 0 3px; margin:0; height: 32px; border:none; diff --git a/editor/templates/index.mst b/editor/templates/index.mst index cc11bcd0f..ebf26947f 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -206,7 +206,23 @@
- + diff --git a/nodes/core/core/20-inject.html b/nodes/core/core/20-inject.html index c5654f490..fbc180c47 100644 --- a/nodes/core/core/20-inject.html +++ b/nodes/core/core/20-inject.html @@ -256,7 +256,7 @@ If you want every 20 minutes from now - use the "interval" option.

$("#node-input-payload").typedInput({ default: 'str', typeField: $("#node-input-payloadType"), - types:['flow','global','str','num','bool','json','date'] + types:['flow','global','str','num','bool','json','bin','date'] }); $("#inject-time-type-select").change(function() { diff --git a/nodes/core/logic/15-change.html b/nodes/core/logic/15-change.html index f2f5c1edd..c7d941568 100644 --- a/nodes/core/logic/15-change.html +++ b/nodes/core/logic/15-change.html @@ -143,7 +143,7 @@ .appendTo(row2); var propertyValue = $('',{class:"node-input-rule-property-value",type:"text"}) .appendTo(row2) - .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','date','jsonata']}); + .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata']}); var row3_1 = $('
').appendTo(row3); $('
',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) @@ -159,7 +159,7 @@ .appendTo(row3_2); var toValue = $('',{class:"node-input-rule-property-replace-value",type:"text"}) .appendTo(row3_2) - .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']}); + .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin']}); $('
',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(to) diff --git a/nodes/core/logic/15-change.js b/nodes/core/logic/15-change.js index dda8bfb97..a831ac379 100644 --- a/nodes/core/logic/15-change.js +++ b/nodes/core/logic/15-change.js @@ -76,7 +76,7 @@ module.exports = function(RED) { } if (rule.tot === 'num') { rule.to = Number(rule.to); - } else if (rule.tot === 'json') { + } else if (rule.tot === 'json' || rule.tot === 'bin') { try { // check this is parsable JSON JSON.parse(rule.to); @@ -102,6 +102,8 @@ module.exports = function(RED) { var value = rule.to; if (rule.tot === 'json') { value = JSON.parse(rule.to); + } else if (rule.tot === 'bin') { + value = Buffer.from(JSON.parse(rule.to)) } var current; var fromValue; diff --git a/red/api/locales/en-US/editor.json b/red/api/locales/en-US/editor.json index a0788cd28..8ee3be8b2 100644 --- a/red/api/locales/en-US/editor.json +++ b/red/api/locales/en-US/editor.json @@ -419,5 +419,11 @@ "jsonEditor": { "title": "JSON editor", "format": "format JSON" + }, + "bufferEditor": { + "title": "Buffer editor", + "modeString": "Handle as UTF-8 String", + "modeArray": "Handle as JSON array", + "modeDesc":"

Buffer editor

The Buffer type is stored as a JSON array of byte values. The editor will attempt to parse the entered value as a JSON array. If it is not valid JSON, it will be treated as a UTF-8 String and converted to an array of the individual character code points.

For example, a value of Hello World will be converted to the JSON array:

[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

" } } diff --git a/red/runtime/util.js b/red/runtime/util.js index 30bb2e259..08a68d60e 100644 --- a/red/runtime/util.js +++ b/red/runtime/util.js @@ -314,6 +314,9 @@ function evaluateNodeProperty(value, type, node, msg) { return new RegExp(value); } else if (type === 'date') { return Date.now(); + } else if (type === 'bin') { + var data = JSON.parse(value); + return Buffer.from(data); } else if (type === 'msg' && msg) { return getMessageProperty(msg,value); } else if (type === 'flow' && node) {