Add autoComplete for flow and global types

This commit is contained in:
GogoVega 2023-12-10 09:40:49 +01:00
parent 33cf34f7c7
commit e7c7621b16
No known key found for this signature in database
GPG Key ID: E1E048B63AC5AC2B

View File

@ -54,35 +54,157 @@
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),
const getMatch = function (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),
};
};
const generateSpans = function (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;
};
/**
* Autocomplete for flow and global types
* @param {"flow"|"global"} type Either `flow` or `global`
* @returns void
*/
const contextAutoComplete = function (type) {
const currentSearch = {
completeKey: "",
searchKey: "",
store: "",
options: [],
};
function getContext(contextUrl) {
return new Promise((resolve, _reject) => {
$.getJSON(`context/${contextUrl}`)
.done((data) => {
try {
const decoded = Object.entries(data).reduce((acc, [store, value]) => {
acc[store] = Object.entries(value).reduce((acc, [k, v]) => {
acc[k] = RED.utils.decodeObject(v.msg, v.format);
return acc;
}, {});
return acc;
}, {});
resolve(decoded);
} catch (error) {
console.error("Failed to load context:", error);
resolve({});
}
})
.fail(() => resolve({}));
});
}
function getContextOptions(contextUrl, store, keyParts) {
return new Promise(async (resolve, _reject) => {
const data = await getContext(contextUrl);
if (!(store in data)) {
return [];
}
const options = keyParts.reduce((options, key, i) => {
if (typeof options === "object" && key in options) {
options = options[key];
} else {
options = {};
}
return options;
}, data[store]);
resolve(Object.keys(options || {}));
});
}
function updateSearch(value, contextUrl) {
return new Promise(async (resolve, _reject) => {
const { key, store } = RED.utils.parseContextKey(value);
const keyParts = key.split(".");
const searchKey = keyParts.pop();
const completeKey = keyParts.join(".");
// "" or "foo."
const valueEnd = /^.?$|\.$/.test(value);
const completeKeyDiff = completeKey !== currentSearch.completeKey;
const storeDiff = store !== currentSearch.store;
if (valueEnd || completeKeyDiff || storeDiff) {
currentSearch.completeKey = completeKey;
currentSearch.store = store;
currentSearch.options = await getContextOptions(contextUrl, store, keyParts);
}
currentSearch.searchKey = searchKey;
resolve();
});
}
return function (value, done) {
let contextUrl;
if (type === "global") {
contextUrl = "global";
} else if (type === "flow") {
contextUrl = `flow/${RED.workspaces.active()}`;
} else {
return;
}
updateSearch(value, contextUrl).then(() => {
const { completeKey, options, searchKey } = currentSearch;
const matches = options
.reduce((opts, optKey) => {
const keyMatch = getMatch(optKey, searchKey);
if (keyMatch.found) {
const element = $("<div>", { style: "display: flex" });
$("<div/>", { style: "font-family: var(--red-ui-monospace-font); white-space: nowrap; overflow: hidden; flex-grow: 1" })
.append(generateSpans(keyMatch))
.appendTo(element);
opts.push({
value: `${completeKey}${completeKey && "."}${optKey}`,
label: element,
i: keyMatch.index,
});
}
return opts;
}, [])
.sort(function (a, b) { return a.i - b.i });
done(matches);
}).catch();
}
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;
}
return function(val) {
}
const autoComplete = function (options) {
return function (val) {
var matches = [];
options.forEach(opt => {
const optVal = opt.value;
const optSrc = (opt.source||[]).join(",");
const optSrc = (opt.source || []).join(",");
const valMatch = getMatch(optVal, val);
const srcMatch = getMatch(optSrc, val);
if (valMatch.found || srcMatch.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"});
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);
if (optSrc) {
@ -97,7 +219,7 @@
});
}
})
matches.sort(function(A,B){return A.i-B.i})
matches.sort(function (A, B) { return A.i - B.i })
return matches;
}
}
@ -168,18 +290,20 @@
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
//options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete("flow"),
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
//options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete("global"),
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
@ -1184,7 +1308,7 @@
this.elementDiv.show();
if (opt.autoComplete) {
this.input.autoComplete({
search: opt.autoComplete,
search: (v, d) => opt.autoComplete(this.value(), d),
minLength: 0
})
}