Commit 196bffa4 authored by Iker Hurtado's avatar Iker Hurtado
Browse files

Rewrite the SearchBox component making the search query representation semantic

parent 5fe5eec5
Pipeline #93973 skipped with stage
window.nomadEnv = {
//apiRoot: "https://nomad-lab.eu/dev/nomad/enc-search/api/encyclopedia/",
//apiRoot: "https://nomad-lab.eu/dev/rae/enc-search/api/encyclopedia/",
apiRoot: "https://nomad-lab.eu/dev/rae/enc-search/api/encyclopedia/",
//apiRoot: "https://nomad-lab.eu/prod/rae/beta/api/encyclopedia/",
//apiRoot: "https://nomad-lab.eu/prod/rae/api/encyclopedia/",
apiRoot: "http://localhost:8000/fairdi/nomad/latest/api/encyclopedia/",
//apiRoot: "http://localhost:8000/fairdi/nomad/latest/api/encyclopedia/",
keycloakBase: 'https://nomad-lab.eu/fairdi/keycloak/auth/',
keycloakRealm: 'fairdi_nomad_test',
keycloakClientId: 'nomad_gui_dev'
......
......@@ -206,7 +206,7 @@ class NewSearchMod {
this.formulaBox = new FormulaBox();
this.formulaBox.setAddFormulaListener(formula => {
if (formula.trim() !== ''){
this.searchBox.addTag(formula, 'F');
this.searchBox.addTag(formula, 'formula');
//this.formulaBox.disable(true);
this.addElementButton.disabled = true;
this.addMatNameButton.disabled = true;
......@@ -217,7 +217,7 @@ class NewSearchMod {
this.materialNameBox = new MaterialNameBox();
this.materialNameBox.setAddMaterialNameListener( name => {
if (name.trim() !== ''){
this.searchBox.addTag(name, 'MN');// this.addTag(name, 'MN');
this.searchBox.addTag(name, 'material');// this.addTag(name, 'MN');
this.addElementButton.disabled = true;
this.addFormulaButton.disabled = true;
// this.formulaBox.disableInput(); It doesn't seem needed
......
......@@ -44,10 +44,21 @@ class SearchBox{
constructor() {
this.searchQuery = [];
this.queryTypes = []; //**** Types associated to query elements
//this.searchQuery = [];
// this.queryTypes = []; //**** Types associated to query elements
// Types: element (E), formula (F), symbol (S) and prop names
this.currentOperator;
//
//
this.currentConnector;
this.notItem; // It signals next item is preceding for a NOT
this.inSubquery; // the current query pointer is in a subquery, paretheses open
this.rootQuery; // Root query representation. Root item object (type="query")
this._resetState();
/* Two type of object representing the expresions:
- item (node): {type: 'element'|'formula'|'query', value: "ElementName"|"Formula"| Another (sub)query structure, not: true|false }
- boolean connector: { type: 'connector', value: 'AND'|'OR'}
*/
this.element = document.createElement('div');
this.element.className = 'search-box';
......@@ -66,9 +77,13 @@ class SearchBox{
this.cleanButton = this.element.querySelector('.clean-btn');
this.cleanButton.addEventListener( "click", (e) => {
/*
this.searchQuery = [];
this.queryTypes = [];
this.updateSearchQuery();
this.updateSearchQuery();*/
// init search query
this._resetState();
this.renderQuery();
this.cleanSearchQueryListener();
});
......@@ -83,7 +98,75 @@ class SearchBox{
}
_resetState(){
this.notItem = false;
this.inSubquery = false;
this.rootQuery = { type: 'query', value: [], not: false }; // no prop open indicates that is the first
}
getOldQueryFormat(){
const oldFormat = [];
this.rootQuery.value.forEach( item => {
oldFormat.push(...getItemInOldFormat(item));
})
const searchQuery = [];
const queryTypes = [];
oldFormat.forEach( item => {
searchQuery.push(item.split(':')[0]);
queryTypes.push(item.split(':')[1]);
})
console.log('OLD F:', oldFormat, searchQuery, queryTypes);
return [searchQuery, queryTypes]
function getItemInOldFormat(item){
const oldFormat = [];
if (item.not){
oldFormat.push('NOT:S');
}
if (item.type === 'query'){
oldFormat.push('(:S');
item.value.forEach( i => {
oldFormat.push(...getItemInOldFormat(i));
})
oldFormat.push('):S');
}else{ // types: element, formula, material and connector
let itemValue = item.value+':';
if (item.type === 'element') itemValue += 'E';
else if (item.type === 'formula') itemValue += 'F';
else if (item.type === 'material') itemValue += 'MN';
else itemValue += 'S'; // connector
oldFormat.push(itemValue);
}
return oldFormat;
}
}
getOptimadeQuery2(allowOtherElements) {
// Empty query
if (this.rootQuery.length === 0) {
return '';
}
// Material name query. Does not allow combinations.
if (this.rootQuery.length === 1 && this.rootQuery.value[0].type === 'material') { // material case
return `material_name="${this.rootQuery.value[0].value}"`;
}
const [searchQuery, queryTypes] = this.getOldQueryFormat();
// Translate element/formula queries into OptimadeSyntax
const translator = new OptimadeTranslator();
return translator.translate(searchQuery, queryTypes, allowOtherElements);
/*
// Empty query
if (this.searchQuery.length === 0) {
return '';
......@@ -94,9 +177,11 @@ class SearchBox{
return `material_name="${this.searchQuery[0]}"`;
}
// Translate element/formula queries into OptimadeSyntax
const translator = new OptimadeTranslator();
return translator.translate(this.searchQuery, this.queryTypes, allowOtherElements);
*/
}
......@@ -223,26 +308,6 @@ class SearchBox{
return util.ELEMENTS.includes(item)
}
/* NOt used anymore??
function getOptimadeElementExclusiveANDSubquery(subquery){
const elements = []
subquery.forEach( (item) => {
if (isElement(item)) elements.push(`"${item}"`);
});
return 'elements HAS ONLY '+elements.join(', ');
}
function getOptimadeFormulaExclusiveANDSubquery(subquery){
const fragments = [];
subquery.forEach( (item) => {
if (!isElement(item) && item !== 'AND' && item !== 'NOT'){// Is formula
fragments.push(new Formula(item).getFragments());
}
});
return 'formula HAS ONLY '+fragments.join(', ');
}
*/
function isQueryWellFormed(searchQuery){
const openingParIndex = searchQuery.indexOf('(')
......@@ -257,73 +322,120 @@ class SearchBox{
setBoolOperator(operator){
this.currentOperator = operator;
this.currentConnector = operator;
}
updateSearchQuery(){
let html= '';
for (let i = 0; i < this.searchQuery.length; i++) {
let type = this.queryTypes[i];
renderQuery(){
if (type === 'S' || type === 'P')
html+= `<span class="search-query-symbol" > ${this.searchQuery[i]} </span>`;
else
html+= getTagHtml(this.searchQuery[i], ( type === 'F' ? true : false));
}
console.log('this.updateSearchQuery: ', this.searchQuery ,this.queryTypes);
this.searchQueryBox.innerHTML = html;
this.searchQueryBox.innerHTML = getQueryStructHTML(this.rootQuery);
if (this.searchQueryChangeListener) this.searchQueryChangeListener();
function getQueryStructHTML(queryObj){
if (queryObj.type === 'connector')
return getSymbolHTML(queryObj.value);
else if (!queryObj.type && queryObj.not) // NOT item wrapper, the item is not defined yet
return getSymbolHTML(' NOT');
else if (queryObj.type === 'element' || queryObj.type === 'formula' || queryObj.type === 'material') // element and formula
return (queryObj.not ? getSymbolHTML(' NOT') : '')+
getTagHtml(queryObj.value, (queryObj.type === 'formula' ? true : false));
else{ // the object represents a query
let html= '';
queryObj.value.forEach( subqueryObj => {
html += getQueryStructHTML(subqueryObj);
});
//console.log('getQueryStructHTML', queryObj, queryObj.value[queryObj.length-1])
const notHTML = (queryObj.not ? getSymbolHTML(' NOT') : '')
const isOpen = queryObj.open;
return notHTML+(isOpen === undefined ? html :
getSymbolHTML(' (')+html+( isOpen ? '' : getSymbolHTML(') ')));
}
}
function getSymbolHTML(s){
return `<span class="search-query-symbol">${s}</span>`;
}
}
addItemInSearchQuery(item, type){
this.searchQuery.push(item);
this.queryTypes.push(type);
// Add an item (with a preceding connector if needed) to the query
// The item could be an NOT wrapper for a tag or subquery
_addItem(item){ // It can be a tag item or a NOT (wrapping an item, added later)
const queryContent = this.rootQuery.value;
const tempQueryContent = this.inSubquery ?
queryContent[queryContent.length-1].value : queryContent;
// If the item is not at the beginning of the query (or subquery) and
if ( tempQueryContent.length > 0 &&
// the last item is not a connector (AND|OR)
tempQueryContent[tempQueryContent.length-1].type !== 'connector'){
// Add a connector (before the item)
tempQueryContent.push( {type: 'connector', value: this.currentConnector} );
}
if (item === 'NOT') // Add an item wrapper with the NOT prop to true
tempQueryContent.push({ not: true }); // type and value props are undefined
else if (item === 'SUBQUERY')
queryContent.push({ type: 'query', value: [], open: true, not: false});
else{ // Regular tags: element|formula|material
const [type, tag] = item.split(':');
tempQueryContent.push({type: type, value: tag, not: false});
}
console.log('ROOT QUERY ', this.rootQuery)
this.renderQuery();
}
addTag(tag, type){
// If the it's an element and is already in the query it's not inserted
if (type === 'E' && this.searchQuery.indexOf(tag) >= 0) return;
// Public method - type = element|formula|material
addTag(tag, type){ // add an element, formula or material
if ( this.searchQuery.length > 0
&& this.searchQuery[this.searchQuery.length-1] !== '('
&& this.searchQuery[this.searchQuery.length-1] !== 'NOT' )
this.addItemInSearchQuery(this.currentOperator, 'S');
this.addItemInSearchQuery(tag, type);
this.updateSearchQuery();
if (this.notItem){ // The tag to add is preceded for a NOT, the item object is already created
const queryContent = this.rootQuery.value;
const tempQueryContent = this.inSubquery ?
queryContent[queryContent.length-1].value : queryContent;
tempQueryContent[tempQueryContent.length-1].type = type;
tempQueryContent[tempQueryContent.length-1].value = tag;
this.notItem = false;
this.renderQuery();
}else
this._addItem(type+':'+tag);
}
addElements(elementArray){
let index = elementArray.length;
while (index--) {
this.addTag(elementArray[index], 'E');
this.addTag(elementArray[index], 'element');
}
return true;
}
addParentheses(isOpen){
if ( isOpen && this.searchQuery.length > 0
&& this.searchQuery[this.searchQuery.length-1] !== 'NOT'){
this.addItemInSearchQuery(this.currentOperator, 'S');
const queryContent = this.rootQuery.value; // ** Not checking if the it's inside a subquery
if (isOpen){
if (this.notItem){
queryContent[queryContent.length-1].type = 'query';
queryContent[queryContent.length-1].value = [];
this.notItem = false;
}else
this._addItem('SUBQUERY');
}
this.addItemInSearchQuery( (isOpen ? '(' : ')'), 'P');
this.updateSearchQuery();
queryContent[queryContent.length-1].open = isOpen;
this.renderQuery();
this.inSubquery = isOpen;
}
addNOT(){
if ( this.searchQuery.length > 0
&& this.searchQuery[this.searchQuery.length-1] !== '('){
this.addItemInSearchQuery(this.currentOperator, 'S');
}
this.addItemInSearchQuery( 'NOT', 'S');
this.updateSearchQuery();
this._addItem('NOT');
this.notItem = true; // The next item will be in a not expression
}
......@@ -333,69 +445,43 @@ class SearchBox{
// NOT being used let isMaterialName = (this.queryTypes[0] === 'MN');
// spot the item and remove the item and the bool operator(s) related
let itemIndex = this.searchQuery.indexOf(item);
if (itemIndex >= 0){
let i, elementsToRemove;
/*
if (this.queryTypes[itemIndex+1] === 'S'){ // bool operator on the right
console.log('itemIndex', itemIndex, this.searchQuery[itemIndex+1])
if (this.searchQuery[itemIndex-1] === 'NOT'){
i = itemIndex-1; elementsToRemove = 3;
}else{ i = itemIndex; elementsToRemove = 2; }
}else*/
if (this.queryTypes[itemIndex-1] === 'S'){ // bool operator on the left
// console.log('itemIndex', itemIndex, this.searchQuery[itemIndex-1])
if (this.searchQuery[itemIndex-1] === 'NOT'){
i = itemIndex-2; elementsToRemove = 3;
}else{ i = itemIndex-1; elementsToRemove = 2; }
}else{ // case: (item)
i = itemIndex; elementsToRemove = 1;
}
removeItemsFromSearchQuery(this, i, elementsToRemove);
}
// Remove the symbols before the first element/formula
if (this.queryTypes[0] === 'S') { // AND , OR
let elementsToRemove = 1;
if (this.queryTypes[1] === 'S') elementsToRemove = 2; // NOT
removeItemsFromSearchQuery(this, 0, elementsToRemove);
}
// Travese the array removing the unnecessary parethesis (only tested for one level nested)
if ( this.searchQuery.indexOf('(') >= 0){ // Recursion
for (let i = 0; i < this.searchQuery.length; i++) { // dangerous: modifing a array being traversed
if ( this.searchQuery[i] === '(' ){
if ( this.searchQuery[i+1] === ')'){ // '()' case
removeItemsFromSearchQuery(this, i, 2);// this.searchQuery.splice(i, 2); this.queryTypes.splice(i, 2);
}else if (this.searchQuery[i+2] === ')'){ // '(item)' case
this.searchQuery.splice(i, 3, this.searchQuery[i+1]);
this.queryTypes.splice(i, 3, this.queryTypes[i+1]);
removeItem(this.rootQuery, item);
function removeItem(queryObj, item){
const queryContent = queryObj.value;
for (let i = 0; i < queryContent.length; i++) {
if (queryContent[i].value === item) {
if (queryContent[i+1] && queryContent[i+1].type === 'connector'){
queryContent.splice(i, 2);
}else
queryContent.splice(i, 1);
break;
}else if (queryContent[i].type === 'query'){
removeItem(queryContent[i], item);
if (queryContent[i].value.length == 0){
if (queryContent[i+1] && queryContent[i+1].type === 'connector'){
queryContent.splice(i, 2);
}else
queryContent.splice(i, 1);
}
}
}
}
this.updateSearchQuery();
this.renderQuery();
console.log('ROOT QUERY REMOVING ', this.rootQuery)
if (util.ELEMENTS.indexOf(item) >= 0){ // It's an element (being removed)
this.removeElementListener(item);
}
if (this.queryTypes.length === 0){ // The search query gets blank
if (this.rootQuery.value.length === 0){ // The search query gets blank
this.cleanSearchQueryListener();
}
return true;
function removeItemsFromSearchQuery(self, start, delCount){
self.searchQuery.splice(start, delCount);
self.queryTypes.splice(start, delCount);
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment