From e7c7621b16847133ca3c0210ed18dc23d4157031 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Sun, 10 Dec 2023 09:40:49 +0100 Subject: [PATCH] Add autoComplete for `flow` and `global` types --- .../src/js/ui/common/typedInput.js | 178 +++++++++++++++--- 1 file changed, 151 insertions(+), 27 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 9aa27c710..90aa73e2d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -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($('').text(match.pre)); } + if (match.match) { els.push($('', { style: "font-weight: bold; color: var(--red-ui-text-color-link);" }).text(match.match)); } + if (match.post) { els.push($('').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 = $("