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 = "
"+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;i"+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;i