1) Improved autocomplete loading indicator.

2) Avoid too many ajax requests per key press.

refs #4488
This commit is contained in:
Akshay Joshi
2022-07-20 18:02:45 +05:30
parent 4808df5e95
commit 631a08189a

View File

@@ -29,12 +29,13 @@ const useStyles = makeStyles(()=>({
function registerAutocomplete(api, transId, onFailure) { function registerAutocomplete(api, transId, onFailure) {
let timeoutId; let timeoutId;
let loadingEle;
let autoCompleteList = [];
let prevSearch = null;
OrigCodeMirror.registerHelper('hint', 'sql', function (editor) { OrigCodeMirror.registerHelper('hint', 'sql', function (editor) {
var data = [], var data = [],
doc = editor.getDoc(), doc = editor.getDoc(),
cur = doc.getCursor(), cur = doc.getCursor(),
// Get the current cursor position
current_cur = cur.ch,
// function context // function context
ctx = { ctx = {
editor: editor, editor: editor,
@@ -78,21 +79,67 @@ function registerAutocomplete(api, transId, onFailure) {
}; };
data.push(doc.getValue()); data.push(doc.getValue());
// Get the text from start to the current cursor position.
data.push( // This function is used to show the loading element until response comes.
doc.getRange({ const showLoading = (editor)=>{
line: 0, if (editor.getInputField().getAttribute('aria-activedescendant') != null) {
ch: 0, hideLoading();
}, { return;
line: ctx.current_line, }
ch: current_cur,
}) if(!loadingEle) {
); var ownerDocument = editor.getInputField().ownerDocument;
loadingEle = ownerDocument.createElement('div');
loadingEle.className = 'CodeMirror-hints';
var iconEle = ownerDocument.createElement('div');
iconEle.className = 'icon-spinner';
iconEle.style.marginTop = '4px';
iconEle.style.marginLeft = '2px';
var spanEle = ownerDocument.createElement('span');
spanEle.innerText = gettext('Loading...');
spanEle.style.marginLeft = '17px';
iconEle.appendChild(spanEle);
loadingEle.appendChild(iconEle);
ownerDocument.body.appendChild(loadingEle);
}
var pos = editor.cursorCoords(true);
loadingEle.style.left = pos.left + 'px';
loadingEle.style.top = pos.bottom + 'px';
loadingEle.style.height = '25px';
};
// This function is used to hide the loading element.
const hideLoading = ()=>{
loadingEle?.parentNode?.removeChild(loadingEle);
loadingEle = null;
};
return { return {
then: function (cb) { then: function (cb) {
var self_local = this; var self_local = this;
// This function is used to filter the data and call the callback
// function with that filtered data.
function setAutoCompleteData() {
let filterData = autoCompleteList.filter((item)=>{
return item.text.toLowerCase().startsWith(search.toLowerCase());
});
cb({
list: filterData,
from: {
line: self_local.current_line,
ch: start,
},
to: {
line: self_local.current_line,
ch: end,
},
});
}
/* /*
* Below logic find the start and end point * Below logic find the start and end point
* to replace the selected auto complete suggestion. * to replace the selected auto complete suggestion.
@@ -118,28 +165,37 @@ function registerAutocomplete(api, transId, onFailure) {
* started with "." or "`" else auto complete of code mirror * started with "." or "`" else auto complete of code mirror
* will remove the "." when user select any suggestion. * will remove the "." when user select any suggestion.
*/ */
if (search.charAt(0) == '.' || search.charAt(0) == '``') if (search.charAt(0) == '.' || search.charAt(0) == '``') {
start += 1; start += 1;
search = search.slice(1);
}
cb({ // Clear the auto complete list if previous token/search is blank or dot.
list: [{ prevSearch = search;
text: '', if (prevSearch == '' || prevSearch == '.')
render: (elt)=>{ autoCompleteList = [];
var el = document.createElement('span');
el.className = 'sqleditor-hint icon-spinner'; // Get the text from start to the current cursor position.
el.appendChild(document.createTextNode(gettext('Loading...'))); self_local.data.push(
elt.appendChild(el); doc.getRange({
}, line: 0,
}, {text: ''}], ch: 0,
from: { }, {
line: self_local.current_line, line: self_local.current_line,
ch: start, ch: token.start + 1,
}, })
to: { );
line: self_local.current_line,
ch: end, // If search token is not empty and auto complete list have some data
}, // then no need to send the request to the backend to fetch the data.
}); // auto complete the data using already fetched list.
if (search != '' && autoCompleteList.length != 0) {
setAutoCompleteData();
return;
}
//Show loading indicator
showLoading(self_local.editor);
timeoutId && clearTimeout(timeoutId); timeoutId && clearTimeout(timeoutId);
timeoutId = setTimeout(()=> { timeoutId = setTimeout(()=> {
@@ -147,6 +203,7 @@ function registerAutocomplete(api, transId, onFailure) {
// Make ajax call to find the autocomplete data // Make ajax call to find the autocomplete data
api.post(self_local.url, JSON.stringify(self_local.data)) api.post(self_local.url, JSON.stringify(self_local.data))
.then((res) => { .then((res) => {
hideLoading();
var result = []; var result = [];
_.each(res.data.data.result, function (obj, key) { _.each(res.data.data.result, function (obj, key) {
@@ -157,19 +214,11 @@ function registerAutocomplete(api, transId, onFailure) {
}); });
}); });
cb({ autoCompleteList = result;
list: result, setAutoCompleteData();
from: {
line: self_local.current_line,
ch: start,
},
to: {
line: self_local.current_line,
ch: end,
},
});
}) })
.catch((err) => { .catch((err) => {
hideLoading();
onFailure?.(err); onFailure?.(err);
}); });
}, 300); }, 300);