mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Add auto-complete to flow/global typedInput types
This commit is contained in:
parent
918943816f
commit
b9c1dedab3
@ -33,6 +33,9 @@ module.exports = {
|
|||||||
store: req.query['store'],
|
store: req.query['store'],
|
||||||
req: apiUtils.getRequestLogObject(req)
|
req: apiUtils.getRequestLogObject(req)
|
||||||
}
|
}
|
||||||
|
if (req.query['keysOnly'] !== undefined) {
|
||||||
|
opts.keysOnly = true
|
||||||
|
}
|
||||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
|
@ -46,6 +46,12 @@
|
|||||||
opacity: 0.3
|
opacity: 0.3
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
|
if (!this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete({
|
||||||
|
search: contextAutoComplete({ input: that }),
|
||||||
|
minLength: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var mapDeprecatedIcon = function(icon) {
|
var mapDeprecatedIcon = function(icon) {
|
||||||
if (/^red\/images\/typedInput\/.+\.png$/.test(icon)) {
|
if (/^red\/images\/typedInput\/.+\.png$/.test(icon)) {
|
||||||
@ -54,26 +60,28 @@
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
var autoComplete = function(options) {
|
function getMatch(value, searchValue) {
|
||||||
function getMatch(value, searchValue) {
|
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
const len = idx > -1 ? searchValue.length : 0;
|
||||||
const len = idx > -1 ? searchValue.length : 0;
|
return {
|
||||||
return {
|
index: idx,
|
||||||
index: idx,
|
found: idx > -1,
|
||||||
found: idx > -1,
|
pre: value.substring(0,idx),
|
||||||
pre: value.substring(0,idx),
|
match: value.substring(idx,idx+len),
|
||||||
match: value.substring(idx,idx+len),
|
post: value.substring(idx+len),
|
||||||
post: value.substring(idx+len),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function generateSpans(match) {
|
|
||||||
const els = [];
|
|
||||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
|
||||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
|
||||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
|
||||||
return els;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function generateSpans(match) {
|
||||||
|
const els = [];
|
||||||
|
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||||
|
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||||
|
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||||
|
return els;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgAutoComplete = function(options) {
|
||||||
return function(val) {
|
return function(val) {
|
||||||
|
console.log('msgAutoComplete', val)
|
||||||
var matches = [];
|
var matches = [];
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
const optVal = opt.value;
|
const optVal = opt.value;
|
||||||
@ -101,6 +109,86 @@
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const contextAutoComplete = function(options) {
|
||||||
|
const cache = {}
|
||||||
|
const knownKeys = {}
|
||||||
|
|
||||||
|
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
||||||
|
knownKeys[store] = knownKeys[store] || new Set()
|
||||||
|
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
|
||||||
|
if (cache[url]) {
|
||||||
|
console.log('CACHED', url)
|
||||||
|
done()
|
||||||
|
} else {
|
||||||
|
console.log('GET', url)
|
||||||
|
$.getJSON(url, function(data) {
|
||||||
|
cache[url] = true
|
||||||
|
const keys = data[store] || []
|
||||||
|
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
||||||
|
keys.forEach(key => {
|
||||||
|
knownKeys[store].add(keyPrefix + key)
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getContextKeys = function(key, done) {
|
||||||
|
const keyParts = key.split('.')
|
||||||
|
const partialKey = keyParts.pop()
|
||||||
|
let scope = options.input.propertyType
|
||||||
|
if (scope === 'flow') {
|
||||||
|
// Get the flow id of the node we're editing
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
if (editStack.length === 0) {
|
||||||
|
done([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editingNode = editStack.pop()
|
||||||
|
if (editingNode.z) {
|
||||||
|
scope = `${scope}/${editingNode.z}`
|
||||||
|
} else {
|
||||||
|
done([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const store = options.input.optionValue
|
||||||
|
const searchKey = keyParts.join('.')
|
||||||
|
|
||||||
|
getContextKeysFromRuntime(scope, store, searchKey, function() {
|
||||||
|
if (knownKeys[store].has(key)) {
|
||||||
|
getContextKeysFromRuntime(scope, store, key, function() {
|
||||||
|
done(knownKeys[store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
done(knownKeys[store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(val, done) {
|
||||||
|
getContextKeys(val, function (keys) {
|
||||||
|
console.log(keys)
|
||||||
|
const matches = []
|
||||||
|
keys.forEach(v => {
|
||||||
|
let optVal = v
|
||||||
|
const valMatch = getMatch(optVal, val);
|
||||||
|
if (valMatch.found) {
|
||||||
|
const element = $('<div>',{style: "display: flex"});
|
||||||
|
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
||||||
|
valEl.append(generateSpans(valMatch))
|
||||||
|
valEl.appendTo(element)
|
||||||
|
matches.push({
|
||||||
|
value: optVal,
|
||||||
|
label: element,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
done(matches)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
||||||
var msgCompletions = [
|
var msgCompletions = [
|
||||||
@ -166,7 +254,7 @@
|
|||||||
{ value: "_session", source: ["websocket out","tcp out"] },
|
{ value: "_session", source: ["websocket out","tcp out"] },
|
||||||
]
|
]
|
||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)},
|
||||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||||
options:[],
|
options:[],
|
||||||
validate:RED.utils.validatePropertyExpression,
|
validate:RED.utils.validatePropertyExpression,
|
||||||
@ -544,7 +632,7 @@
|
|||||||
that.element.trigger('paste',evt);
|
that.element.trigger('paste',evt);
|
||||||
});
|
});
|
||||||
this.input.on('keydown', function(evt) {
|
this.input.on('keydown', function(evt) {
|
||||||
if (that.typeMap[that.propertyType].autoComplete) {
|
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
||||||
@ -967,6 +1055,9 @@
|
|||||||
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
||||||
var previousType = this.typeMap[this.propertyType];
|
var previousType = this.typeMap[this.propertyType];
|
||||||
previousValue = this.input.val();
|
previousValue = this.input.val();
|
||||||
|
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete("destroy");
|
||||||
|
}
|
||||||
|
|
||||||
if (previousType && this.typeChanged) {
|
if (previousType && this.typeChanged) {
|
||||||
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
||||||
|
@ -2082,6 +2082,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
||||||
|
getEditStack: function () { return [...editStack] },
|
||||||
buildEditForm: buildEditForm,
|
buildEditForm: buildEditForm,
|
||||||
validateNode: validateNode,
|
validateNode: validateNode,
|
||||||
updateNodeProperties: updateNodeProperties,
|
updateNodeProperties: updateNodeProperties,
|
||||||
|
@ -68,6 +68,7 @@ var api = module.exports = {
|
|||||||
* @param {String} opts.store - the context store
|
* @param {String} opts.store - the context store
|
||||||
* @param {String} opts.key - the context key
|
* @param {String} opts.key - the context key
|
||||||
* @param {Object} opts.req - the request to log (optional)
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @param {Boolean} opts.keysOnly - whether to return keys only
|
||||||
* @return {Promise} - the node information
|
* @return {Promise} - the node information
|
||||||
* @memberof @node-red/runtime_context
|
* @memberof @node-red/runtime_context
|
||||||
*/
|
*/
|
||||||
@ -100,8 +101,16 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
if (key) {
|
if (key) {
|
||||||
|
console.log('GET KEY', key)
|
||||||
store = store || availableStores.default;
|
store = store || availableStores.default;
|
||||||
ctx.get(key,store,function(err, v) {
|
ctx.get(key,store,function(err, v) {
|
||||||
|
if (opts.keysOnly) {
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
resolve({ [store]: Object.keys(v) })
|
||||||
|
} else {
|
||||||
|
resolve({ [store]: [] })
|
||||||
|
}
|
||||||
|
}
|
||||||
var encoded = util.encodeObject({msg:v});
|
var encoded = util.encodeObject({msg:v});
|
||||||
if (store !== availableStores.default) {
|
if (store !== availableStores.default) {
|
||||||
encoded.store = store;
|
encoded.store = store;
|
||||||
@ -118,32 +127,58 @@ var api = module.exports = {
|
|||||||
stores = [store];
|
stores = [store];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var result = {};
|
var result = {};
|
||||||
var c = stores.length;
|
var c = stores.length;
|
||||||
var errorReported = false;
|
var errorReported = false;
|
||||||
stores.forEach(function(store) {
|
stores.forEach(function(store) {
|
||||||
exportContextStore(scope,ctx,store,result,function(err) {
|
if (opts.keysOnly) {
|
||||||
if (err) {
|
ctx.keys(store,function(err, keys) {
|
||||||
// TODO: proper error reporting
|
if (err) {
|
||||||
if (!errorReported) {
|
// TODO: proper error reporting
|
||||||
errorReported = true;
|
if (!errorReported) {
|
||||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
|
errorReported = true;
|
||||||
var err = new Error();
|
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
|
||||||
err.code = "unexpected_error";
|
var err = new Error();
|
||||||
err.status = 400;
|
err.code = "unexpected_error";
|
||||||
return reject(err);
|
err.status = 400;
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
result[store] = keys
|
||||||
|
c--;
|
||||||
|
if (c === 0) {
|
||||||
|
if (!errorReported) {
|
||||||
|
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
exportContextStore(scope,ctx,store,result,function(err) {
|
||||||
|
if (err) {
|
||||||
|
// TODO: proper error reporting
|
||||||
|
if (!errorReported) {
|
||||||
|
errorReported = true;
|
||||||
|
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
|
||||||
|
var err = new Error();
|
||||||
|
err.code = "unexpected_error";
|
||||||
|
err.status = 400;
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
c--;
|
|
||||||
if (c === 0) {
|
|
||||||
if (!errorReported) {
|
|
||||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
|
|
||||||
resolve(result);
|
|
||||||
}
|
}
|
||||||
}
|
c--;
|
||||||
});
|
if (c === 0) {
|
||||||
|
if (!errorReported) {
|
||||||
|
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user