diff --git a/Gruntfile.js b/Gruntfile.js index f88d61af0..1c01aa618 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -150,6 +150,7 @@ module.exports = function(grunt) { "editor/js/ui/tab-config.js", "editor/js/ui/palette-editor.js", "editor/js/ui/editor.js", + "editor/js/ui/editors/*.js", "editor/js/ui/tray.js", "editor/js/ui/clipboard.js", "editor/js/ui/library.js", diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index e767283bc..500415e48 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -1818,658 +1818,22 @@ RED.editor = (function() { RED.tray.show(trayOptions); } - - var expressionTestCache = {}; - - function editExpression(options) { - var expressionTestCacheId = "_"; - if (editStack.length > 0) { - expressionTestCacheId = editStack[editStack.length-1].id; - } - - var value = options.value; - var onComplete = options.complete; - var type = "_expression" - editStack.push({type:type}); - RED.view.state(RED.state.EDITING); - var expressionEditor; - var testDataEditor; - var testResultEditor - var panels; - - var trayOptions = { - title: getEditStackTitle(), - width: "inherit", - 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() { - $("#node-input-expression-help").html(""); - onComplete(expressionEditor.getValue()); - 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'); - trayBody.addClass("node-input-expression-editor") - var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form','_expression','editor'); - var funcSelect = $("#node-input-expression-func"); - Object.keys(jsonata.functions).forEach(function(f) { - funcSelect.append($("").val(f).text(f)); - }) - funcSelect.change(function(e) { - var f = $(this).val(); - var args = RED._('jsonata:'+f+".args",{defaultValue:''}); - var title = "
"+f+"("+args+")
"; - var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''})); - $("#node-input-expression-help").html(title+"

"+body+"

"); - - }) - expressionEditor = RED.editor.createEditor({ - id: 'node-input-expression', - value: "", - mode:"ace/mode/jsonata", - options: { - enableBasicAutocompletion:true, - enableSnippets:true, - enableLiveAutocompletion: true - } - }); - var currentToken = null; - var currentTokenPos = -1; - var currentFunctionMarker = null; - - expressionEditor.getSession().setValue(value||"",-1); - expressionEditor.on("changeSelection", function() { - var c = expressionEditor.getCursorPosition(); - var token = expressionEditor.getSession().getTokenAt(c.row,c.column); - if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) { - currentToken = token; - var r,p; - var scopedFunction = null; - if (token && token.type === 'keyword') { - r = c.row; - scopedFunction = token; - } else { - var depth = 0; - var next = false; - if (token) { - if (token.type === 'paren.rparen') { - // If this is a block of parens ')))', set - // depth to offset against the cursor position - // within the block - currentTokenPos = c.column; - depth = c.column - (token.start + token.value.length); - } - r = c.row; - p = token.index; - } else { - r = c.row-1; - p = -1; - } - while ( scopedFunction === null && r > -1) { - var rowTokens = expressionEditor.getSession().getTokens(r); - if (p === -1) { - p = rowTokens.length-1; - } - while (p > -1) { - var type = rowTokens[p].type; - if (next) { - if (type === 'keyword') { - scopedFunction = rowTokens[p]; - // console.log("HIT",scopedFunction); - break; - } - next = false; - } - if (type === 'paren.lparen') { - depth-=rowTokens[p].value.length; - } else if (type === 'paren.rparen') { - depth+=rowTokens[p].value.length; - } - if (depth < 0) { - next = true; - depth = 0; - } - // console.log(r,p,depth,next,rowTokens[p]); - p--; - } - if (!scopedFunction) { - r--; - } - } - } - expressionEditor.session.removeMarker(currentFunctionMarker); - if (scopedFunction) { - //console.log(token,.map(function(t) { return t.type})); - funcSelect.val(scopedFunction.value).change(); - } - } - }); - - dialogForm.i18n(); - $("#node-input-expression-func-insert").click(function(e) { - e.preventDefault(); - var pos = expressionEditor.getCursorPosition(); - var f = funcSelect.val(); - var snippet = jsonata.getFunctionSnippet(f); - expressionEditor.insertSnippet(snippet); - expressionEditor.focus(); - }); - $("#node-input-expression-reformat").click(function(evt) { - evt.preventDefault(); - var v = expressionEditor.getValue()||""; - try { - v = jsonata.format(v); - } catch(err) { - // TODO: do an optimistic auto-format - } - expressionEditor.getSession().setValue(v||"",-1); - }); - - var tabs = RED.tabs.create({ - element: $("#node-input-expression-tabs"), - onchange:function(tab) { - $(".node-input-expression-tab-content").hide(); - tab.content.show(); - trayOptions.resize(); - } - }) - - tabs.addTab({ - id: 'expression-help', - label: RED._('expressionEditor.functionReference'), - content: $("#node-input-expression-tab-help") - }); - tabs.addTab({ - id: 'expression-tests', - label: RED._('expressionEditor.test'), - content: $("#node-input-expression-tab-test") - }); - testDataEditor = RED.editor.createEditor({ - id: 'node-input-expression-test-data', - value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}', - mode:"ace/mode/json", - lineNumbers: false - }); - var changeTimer; - $(".node-input-expression-legacy").click(function(e) { - e.preventDefault(); - RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc")); - RED.sidebar.info.show(); - }) - var testExpression = function() { - var value = testDataEditor.getValue(); - var parsedData; - var currentExpression = expressionEditor.getValue(); - var expr; - var usesContext = false; - var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression); - $(".node-input-expression-legacy").toggle(legacyMode); - try { - expr = jsonata(currentExpression); - expr.assign('flowContext',function(val) { - usesContext = true; - return null; - }); - expr.assign('globalContext',function(val) { - usesContext = true; - return null; - }); - } catch(err) { - testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1); - return; - } - try { - parsedData = JSON.parse(value); - } catch(err) { - testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()})) - return; - } - - try { - var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData); - if (usesContext) { - testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1); - return; - } - - var formattedResult; - if (result !== undefined) { - formattedResult = JSON.stringify(result,null,4); - } else { - formattedResult = RED._("expressionEditor.noMatch"); - } - testResultEditor.setValue(formattedResult,-1); - } catch(err) { - testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1); - } - } - - testDataEditor.getSession().on('change', function() { - clearTimeout(changeTimer); - changeTimer = setTimeout(testExpression,200); - expressionTestCache[expressionTestCacheId] = testDataEditor.getValue(); - }); - expressionEditor.getSession().on('change', function() { - clearTimeout(changeTimer); - changeTimer = setTimeout(testExpression,200); - }); - - testResultEditor = RED.editor.createEditor({ - id: 'node-input-expression-test-result', - value: "", - mode:"ace/mode/json", - lineNumbers: false, - readOnly: true - }); - panels = RED.panels.create({ - id:"node-input-expression-panels", - resize: function(p1Height,p2Height) { - var p1 = $("#node-input-expression-panel-expr"); - p1Height -= $(p1.children()[0]).outerHeight(true); - var editorRow = $(p1.children()[1]); - p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("#node-input-expression").css("height",(p1Height-5)+"px"); - expressionEditor.resize(); - - var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child"); - p2Height -= p2.outerHeight(true) + 20; - $(".node-input-expression-tab-content").height(p2Height); - $("#node-input-expression-test-data").css("height",(p2Height-5)+"px"); - testDataEditor.resize(); - $("#node-input-expression-test-result").css("height",(p2Height-5)+"px"); - testResultEditor.resize(); - } - }); - - $("#node-input-example-reformat").click(function(evt) { - evt.preventDefault(); - var v = testDataEditor.getValue()||""; - try { - v = JSON.stringify(JSON.parse(v),null,4); - } catch(err) { - // TODO: do an optimistic auto-format - } - testDataEditor.getSession().setValue(v||"",-1); - }); - - testExpression(); - }, - close: function() { - editStack.pop(); - expressionEditor.destroy(); - testDataEditor.destroy(); - }, - show: function() {} - } - RED.tray.show(trayOptions); - } - - - function editJSON(options) { - var value = options.value; - var onComplete = options.complete; - var type = "_json" - editStack.push({type:type}); - RED.view.state(RED.state.EDITING); - var expressionEditor; - var changeTimer; - - var checkValid = function() { - var v = expressionEditor.getValue(); - try { - JSON.parse(v); - $("#node-dialog-ok").removeClass('disabled'); - return true; - } catch(err) { - $("#node-dialog-ok").addClass('disabled'); - return false; + function showTypeEditor(type, options) { + if (RED.editor.types.hasOwnProperty(type)) { + if (editStack.length > 0) { + options.parent = editStack[editStack.length-1].id; } - } - var trayOptions = { - title: options.title || getEditStackTitle(), - width: "inherit", - 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() { - if (options.requireValid && !checkValid()) { - return; - } - onComplete(expressionEditor.getValue()); - RED.tray.close(); - } - } - ], - resize: function(dimensions) { - editTrayWidthCache[type] = dimensions.width; - - var rows = $("#dialog-form>div:not(.node-text-editor-row)"); - var editorRow = $("#dialog-form>div.node-text-editor-row"); - var height = $("#dialog-form").height(); - for (var i=0;idiv:not(.node-text-editor-row)"); - var editorRow = $("#dialog-form>div.node-text-editor-row"); - var height = $("#dialog-form").height(); - for (var 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)); } + RED.editor.types[type].show(options); + } else { + console.log("Unknown type editor:",type); } - 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(), - width: "inherit", - 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(); - bufferStringEditor.destroy(); - bufferBinEditor.destroy(); - }, - show: function() {} - } - RED.tray.show(trayOptions); - } return { init: function() { @@ -2482,14 +1846,22 @@ RED.editor = (function() { $("#node-dialog-cancel").click(); $("#node-config-dialog-cancel").click(); }); + + for (var type in RED.editor.types) { + if (RED.editor.types.hasOwnProperty(type)) { + RED.editor.types[type].init(); + } + } }, + types: {}, edit: showEditDialog, editConfig: showEditConfigNodeDialog, editSubflow: showEditSubflowDialog, - editExpression: editExpression, - editJSON: editJSON, - editMarkdown: editMarkdown, - editBuffer: editBuffer, + editExpression: function(options) { showTypeEditor("_expression", options) }, + editJSON: function(options) { showTypeEditor("_json", options) }, + editMarkdown: function(options) { showTypeEditor("_markdown", options) }, + editBuffer: function(options) { showTypeEditor("_buffer", options) }, + buildEditForm: buildEditForm, validateNode: validateNode, updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo diff --git a/editor/js/ui/editors/buffer.js b/editor/js/ui/editors/buffer.js new file mode 100644 index 000000000..9e3cbcacd --- /dev/null +++ b/editor/js/ui/editors/buffer.js @@ -0,0 +1,209 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.editor.types._buffer = (function() { + + + var template = ''; + + 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; + } + + + return { + init: function() { + $(template).appendTo(document.body); + }, + show: function(options) { + var value = options.value; + var onComplete = options.complete; + var type = "_buffer" + RED.view.state(RED.state.EDITING); + var bufferStringEditor = []; + var bufferBinValue; + + var panels; + + var trayOptions = { + title: options.title, + width: "inherit", + 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) { + var height = $("#dialog-form").height(); + if (panels) { + panels.resize(height); + } + }, + open: function(tray) { + var trayBody = tray.find('.editor-tray-body'); + var dialogForm = RED.editor.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() { + if (options.onclose) { + options.onclose(); + } + bufferStringEditor.destroy(); + bufferBinEditor.destroy(); + }, + show: function() {} + } + RED.tray.show(trayOptions); + } + } +})(); diff --git a/editor/js/ui/editors/expression.js b/editor/js/ui/editors/expression.js new file mode 100644 index 000000000..1a7760a20 --- /dev/null +++ b/editor/js/ui/editors/expression.js @@ -0,0 +1,325 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.editor.types._expression = (function() { + + + var template = ''; + var expressionTestCache = {}; + + return { + init: function() { + $(template).appendTo(document.body); + }, + show: function(options) { + var expressionTestCacheId = options.parent||"_"; + var value = options.value; + var onComplete = options.complete; + var type = "_expression" + RED.view.state(RED.state.EDITING); + var expressionEditor; + var testDataEditor; + var testResultEditor + var panels; + + var trayOptions = { + title: options.title, + width: "inherit", + 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() { + $("#node-input-expression-help").html(""); + onComplete(expressionEditor.getValue()); + RED.tray.close(); + } + } + ], + resize: function(dimensions) { + var height = $("#dialog-form").height(); + if (panels) { + panels.resize(height); + } + + }, + open: function(tray) { + var trayBody = tray.find('.editor-tray-body'); + trayBody.addClass("node-input-expression-editor") + var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form','_expression','editor'); + var funcSelect = $("#node-input-expression-func"); + Object.keys(jsonata.functions).forEach(function(f) { + funcSelect.append($("").val(f).text(f)); + }) + funcSelect.change(function(e) { + var f = $(this).val(); + var args = RED._('jsonata:'+f+".args",{defaultValue:''}); + var title = "
"+f+"("+args+")
"; + var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''})); + $("#node-input-expression-help").html(title+"

"+body+"

"); + + }) + expressionEditor = RED.editor.createEditor({ + id: 'node-input-expression', + value: "", + mode:"ace/mode/jsonata", + options: { + enableBasicAutocompletion:true, + enableSnippets:true, + enableLiveAutocompletion: true + } + }); + var currentToken = null; + var currentTokenPos = -1; + var currentFunctionMarker = null; + + expressionEditor.getSession().setValue(value||"",-1); + expressionEditor.on("changeSelection", function() { + var c = expressionEditor.getCursorPosition(); + var token = expressionEditor.getSession().getTokenAt(c.row,c.column); + if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) { + currentToken = token; + var r,p; + var scopedFunction = null; + if (token && token.type === 'keyword') { + r = c.row; + scopedFunction = token; + } else { + var depth = 0; + var next = false; + if (token) { + if (token.type === 'paren.rparen') { + // If this is a block of parens ')))', set + // depth to offset against the cursor position + // within the block + currentTokenPos = c.column; + depth = c.column - (token.start + token.value.length); + } + r = c.row; + p = token.index; + } else { + r = c.row-1; + p = -1; + } + while ( scopedFunction === null && r > -1) { + var rowTokens = expressionEditor.getSession().getTokens(r); + if (p === -1) { + p = rowTokens.length-1; + } + while (p > -1) { + var type = rowTokens[p].type; + if (next) { + if (type === 'keyword') { + scopedFunction = rowTokens[p]; + // console.log("HIT",scopedFunction); + break; + } + next = false; + } + if (type === 'paren.lparen') { + depth-=rowTokens[p].value.length; + } else if (type === 'paren.rparen') { + depth+=rowTokens[p].value.length; + } + if (depth < 0) { + next = true; + depth = 0; + } + // console.log(r,p,depth,next,rowTokens[p]); + p--; + } + if (!scopedFunction) { + r--; + } + } + } + expressionEditor.session.removeMarker(currentFunctionMarker); + if (scopedFunction) { + //console.log(token,.map(function(t) { return t.type})); + funcSelect.val(scopedFunction.value).change(); + } + } + }); + + dialogForm.i18n(); + $("#node-input-expression-func-insert").click(function(e) { + e.preventDefault(); + var pos = expressionEditor.getCursorPosition(); + var f = funcSelect.val(); + var snippet = jsonata.getFunctionSnippet(f); + expressionEditor.insertSnippet(snippet); + expressionEditor.focus(); + }); + $("#node-input-expression-reformat").click(function(evt) { + evt.preventDefault(); + var v = expressionEditor.getValue()||""; + try { + v = jsonata.format(v); + } catch(err) { + // TODO: do an optimistic auto-format + } + expressionEditor.getSession().setValue(v||"",-1); + }); + + var tabs = RED.tabs.create({ + element: $("#node-input-expression-tabs"), + onchange:function(tab) { + $(".node-input-expression-tab-content").hide(); + tab.content.show(); + trayOptions.resize(); + } + }) + + tabs.addTab({ + id: 'expression-help', + label: RED._('expressionEditor.functionReference'), + content: $("#node-input-expression-tab-help") + }); + tabs.addTab({ + id: 'expression-tests', + label: RED._('expressionEditor.test'), + content: $("#node-input-expression-tab-test") + }); + testDataEditor = RED.editor.createEditor({ + id: 'node-input-expression-test-data', + value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}', + mode:"ace/mode/json", + lineNumbers: false + }); + var changeTimer; + $(".node-input-expression-legacy").click(function(e) { + e.preventDefault(); + RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc")); + RED.sidebar.info.show(); + }) + var testExpression = function() { + var value = testDataEditor.getValue(); + var parsedData; + var currentExpression = expressionEditor.getValue(); + var expr; + var usesContext = false; + var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression); + $(".node-input-expression-legacy").toggle(legacyMode); + try { + expr = jsonata(currentExpression); + expr.assign('flowContext',function(val) { + usesContext = true; + return null; + }); + expr.assign('globalContext',function(val) { + usesContext = true; + return null; + }); + } catch(err) { + testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1); + return; + } + try { + parsedData = JSON.parse(value); + } catch(err) { + testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()})) + return; + } + + try { + var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData); + if (usesContext) { + testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1); + return; + } + + var formattedResult; + if (result !== undefined) { + formattedResult = JSON.stringify(result,null,4); + } else { + formattedResult = RED._("expressionEditor.noMatch"); + } + testResultEditor.setValue(formattedResult,-1); + } catch(err) { + testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1); + } + } + + testDataEditor.getSession().on('change', function() { + clearTimeout(changeTimer); + changeTimer = setTimeout(testExpression,200); + expressionTestCache[expressionTestCacheId] = testDataEditor.getValue(); + }); + expressionEditor.getSession().on('change', function() { + clearTimeout(changeTimer); + changeTimer = setTimeout(testExpression,200); + }); + + testResultEditor = RED.editor.createEditor({ + id: 'node-input-expression-test-result', + value: "", + mode:"ace/mode/json", + lineNumbers: false, + readOnly: true + }); + panels = RED.panels.create({ + id:"node-input-expression-panels", + resize: function(p1Height,p2Height) { + var p1 = $("#node-input-expression-panel-expr"); + p1Height -= $(p1.children()[0]).outerHeight(true); + var editorRow = $(p1.children()[1]); + p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("#node-input-expression").css("height",(p1Height-5)+"px"); + expressionEditor.resize(); + + var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child"); + p2Height -= p2.outerHeight(true) + 20; + $(".node-input-expression-tab-content").height(p2Height); + $("#node-input-expression-test-data").css("height",(p2Height-5)+"px"); + testDataEditor.resize(); + $("#node-input-expression-test-result").css("height",(p2Height-5)+"px"); + testResultEditor.resize(); + } + }); + + $("#node-input-example-reformat").click(function(evt) { + evt.preventDefault(); + var v = testDataEditor.getValue()||""; + try { + v = JSON.stringify(JSON.parse(v),null,4); + } catch(err) { + // TODO: do an optimistic auto-format + } + testDataEditor.getSession().setValue(v||"",-1); + }); + + testExpression(); + }, + close: function() { + if (options.onclose) { + options.onclose(); + } + expressionEditor.destroy(); + testDataEditor.destroy(); + }, + show: function() {} + } + RED.tray.show(trayOptions); + } + } +})(); diff --git a/editor/js/ui/editors/json.js b/editor/js/ui/editors/json.js new file mode 100644 index 000000000..6dd0bdbcc --- /dev/null +++ b/editor/js/ui/editors/json.js @@ -0,0 +1,118 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.editor.types._json = (function() { + + + var template = ''; + + return { + init: function() { + $(template).appendTo(document.body); + }, + show: function(options) { + var value = options.value; + var onComplete = options.complete; + var type = "_json" + RED.view.state(RED.state.EDITING); + var expressionEditor; + var changeTimer; + + var checkValid = function() { + var v = expressionEditor.getValue(); + try { + JSON.parse(v); + $("#node-dialog-ok").removeClass('disabled'); + return true; + } catch(err) { + $("#node-dialog-ok").addClass('disabled'); + return false; + } + } + var trayOptions = { + title: options.title, + width: "inherit", + 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() { + if (options.requireValid && !checkValid()) { + return; + } + onComplete(expressionEditor.getValue()); + RED.tray.close(); + } + } + ], + resize: function(dimensions) { + var rows = $("#dialog-form>div:not(.node-text-editor-row)"); + var editorRow = $("#dialog-form>div.node-text-editor-row"); + var height = $("#dialog-form").height(); + for (var i=0;idiv:not(.node-text-editor-row)"); + var editorRow = $("#dialog-form>div.node-text-editor-row"); + var height = $("#dialog-form").height(); + for (var i=0;i

-
-
-
-
-
-
- -
-
-
- - -
-
- - -
-
-
- -
-
-
- -
-
-
-
    -
    -
    -
    -
    -
    -
    -
    - - - - - - - -