Add auto-complete to flow/global typedInput types

This commit is contained in:
Nick O'Leary
2023-12-11 17:55:02 +00:00
parent 918943816f
commit b9c1dedab3
4 changed files with 169 additions and 39 deletions

View File

@@ -46,6 +46,12 @@
opacity: 0.3
}).appendTo(container);
this.elementDiv.show();
if (!this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete({
search: contextAutoComplete({ input: that }),
minLength: 0
})
}
}
var mapDeprecatedIcon = function(icon) {
if (/^red\/images\/typedInput\/.+\.png$/.test(icon)) {
@@ -54,26 +60,28 @@
return icon;
}
var autoComplete = function(options) {
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,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 getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,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;
}
const msgAutoComplete = function(options) {
return function(val) {
console.log('msgAutoComplete', val)
var matches = [];
options.forEach(opt => {
const optVal = opt.value;
@@ -101,6 +109,86 @@
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).
var msgCompletions = [
@@ -166,7 +254,7 @@
{ value: "_session", source: ["websocket out","tcp out"] },
]
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,
options:[],
validate:RED.utils.validatePropertyExpression,
@@ -544,7 +632,7 @@
that.element.trigger('paste',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
}
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
var previousType = this.typeMap[this.propertyType];
previousValue = this.input.val();
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
if (previousType && this.typeChanged) {
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }

View File

@@ -2082,6 +2082,7 @@ RED.editor = (function() {
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
getEditStack: function () { return [...editStack] },
buildEditForm: buildEditForm,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties,