Commit 1fa24546 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Getting close to working implementation.

parent d86abfde
......@@ -415,7 +415,7 @@ eval("/**\n * Copyright 2016-2019 Iker Hurtado, Georg Huhs\n *\n * Licensed unde
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
eval("\n/**\n * Copyright 2016-2019 Iker Hurtado, Georg Huhs\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n\n /*\n This file implements the Search Module of the application.\n It's a container UI component that shows the seach part of the application:\n front search interface and search results page.\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\nlet UserGuidance = __webpack_require__(/*! ../common/UserGuidance.js */ \"./src/common/UserGuidance.js\");\nlet SearchBox = __webpack_require__(/*! ./SearchBox.view.js */ \"./src/search-mod/SearchBox.view.js\");\nlet ElementTable = __webpack_require__(/*! ./ElemenTable.view.js */ \"./src/search-mod/ElemenTable.view.js\");\nlet MaterialList = __webpack_require__(/*! ./MaterialList.view.js */ \"./src/search-mod/MaterialList.view.js\");\nlet MaterialNameBox = __webpack_require__(/*! ./MaterialName.view.js */ \"./src/search-mod/MaterialName.view.js\");\nlet FilterPanel = __webpack_require__(/*! ./FilterPanel.view.js */ \"./src/search-mod/FilterPanel.view.js\");\nlet SwitchComponent = __webpack_require__(/*! ../common/SwitchComponent.js */ \"./src/common/SwitchComponent.js\");\n\n\n\nfunction replaceDashes(s){\n return s.split('-').join('_');\n}\n\nclass NewSearchMod {\n\n constructor() {\n\n this.userGuidance = true; // can enabled/disabled\n\n this.searchFilters = [];\n\n this.element = document.createElement('div');\n this.element.setAttribute(\"id\",'search-module');\n this.element.innerHTML=\n `\n <div class=\"search-filter-side\">\n\n <!-- <div class=\"search-title\">Properties</div>-->\n\n <!-- <div id=\"filter-panel-placeholder\"> </div> -->\n\n </div>\n\n <div class=\"search-main-side\">\n\n <!-- <div class=\"search-title\">Composition</div> -->\n\n <div class=\"search-box-placeholder\" > </div>\n \n <input type=\"checkbox\" id=\"multiples-of-formula\" value=\"\">\n Include multiples of formula\n <br>\n <input type=\"checkbox\" id=\"allow-other-elements\" value=\"\" checked>\n Allow other elements\n <br>\n\n\n <div class=\"add-buttons\" >\n <div class=\"tab-buttons\" style=\"width: 70%; display: inline-block\">\n <button class=\"element-add-btn\" id=\"add-tab-selected\" style=\"margin-left: 50px\">Element</button>\n <button class=\"formula-add-btn\" style=\"padding: 10px 20px;\" >Formula</button>\n <button class=\"name-add-btn\" >Name</button>\n </div>\n <div class=\"bool-buttons\" style=\"width: 28%; display: inline-block\" >\n OR <span id=\"and-or-switch\" ></span> AND\n <button class=\"not-button\">NOT</button>\n <button class=\"open-parentheses\" >(</button>\n <button class=\"close-parentheses\">)</button>\n <!--<input type=\"checkbox\" name=\"and-or\" class=\"not-symbol-btn\" />NOT-->\n </div>\n </div>\n\n\n <div class=\"add-box\">\n <div style=\"width: 70%; display: inline-block;\">\n <div class=\"triangle-container\" style=\"margin-left: 50px;\">\n <div class=\"triangle element-tri\" style=\"visibility: visible\"></div>\n </div>\n <div class=\"triangle-container\" >\n <div class=\"triangle formula-tri\" style=\"visibility: hidden\"></div>\n </div>\n <div class=\"triangle-container\" >\n <div class=\"triangle name-tri\" style=\"visibility: hidden\"></div>\n </div>\n </div>\n\n <div class=\"add-panel\">\n </div>\n </div>\n\n <div class=\"results-panel\"> <!-- style=\"display: none\"-->\n </div>\n\n </div> <!-- search-main-side -->\n `;\n\n this.filterSidePanel = this.element.querySelector('.search-filter-side');\n\n this.searchBox = new SearchBox();\n this.searchBox.setBoolOperator('AND');\n this.element.querySelector('.search-box-placeholder').append(this.searchBox.element);\n this.searchBox.setRemoveElementListener( (element) => {\n this.elementTable.deselectElement(element);\n });\n // When the clear all button is clicked on and \n // when the search query gets blank by removing query items\n this.searchBox.setCleanSearchQueryListener( () => { \n this.addFormulaButton.disabled = false;\n this.addMatNameButton.disabled = false;\n this.addElementButton.disabled = false;\n this.formulaBox.enableInput();\n this.materialNameBox.enableInput();\n this.elementTable.deselectAllElements();\n });\n\n /*\n this.searchBox.setSearchQueryChangeListener( () => {\n this.sendQuery();\n }); */\n\n this.allowOtherElementsCheckbox = this.element.querySelector('#allow-other-elements');\n /*\n this.allowOtherElementsCheckbox.addEventListener( 'change', e => {\n this.sendQuery();\n }); */\n\n /* This button could be out of the search box because its functionality\n is not part of it. It can also be removed eventually, if considered not necessary */\n this.searchButton = this.searchBox.getSearchButtonElement();\n this.searchButton.addEventListener( 'click', e => {\n this.sendQuery();\n });\n\n this.addButtonsBox= this.element.querySelector('.add-buttons');\n this.addElementButton = this.addButtonsBox.querySelector('.element-add-btn');\n this.addFormulaButton = this.addButtonsBox.querySelector('.formula-add-btn');\n this.addMatNameButton = this.addButtonsBox.querySelector('.name-add-btn');\n\n this.addPanel= this.element.querySelector('.add-panel');\n\n let andOrSwitch = new SwitchComponent(util.IMAGE_DIR+'switch');\n this.element.querySelector('#and-or-switch').appendChild(andOrSwitch.element);\n andOrSwitch.setListener( e => {\n this.searchBox.setBoolOperator( e ? 'AND' : 'OR');\n });\n\n this.notButton = this.element.querySelector('.not-button');\n this.notButton.addEventListener( 'click', e => {\n this.searchBox.addNOT();\n });\n\n this.openParenthButton = this.element.querySelector('.open-parentheses');\n this.closeParenthButton = this.element.querySelector('.close-parentheses');\n this.openParenthButton.addEventListener( 'click', e => {\n this.searchBox.addParentheses(true);\n });\n this.closeParenthButton.addEventListener( 'click', e => {\n this.searchBox.addParentheses(false);\n });\n\n this.elementTable= new ElementTable();\n this.elementTable.setClickListener(elementArray => {\n this.searchBox.addElements(elementArray);\n this.addMatNameButton.disabled = true; // Not always necessary but it simplifies the code\n this.addFormulaButton.disabled = true;\n });\n this.elementTable.setDeselectListener(e => this.searchBox.removeElementORFormulaInSearchQuery(e));\n\n\n this.formulaBox = new FormulaBox();\n this.formulaBox.setAddFormulaListener(formula => {\n if (formula.trim() !== ''){\n this.searchBox.addTag(formula, 'F');\n //this.formulaBox.disable(true);\n this.addElementButton.disabled = true;\n this.addMatNameButton.disabled = true;\n // this.materialNameBox.disableInput(); It doesn't seem needed\n }\n });\n\n\n this.materialNameBox = new MaterialNameBox();\n this.materialNameBox.setAddMaterialNameListener( name => {\n if (name.trim() !== ''){\n this.searchBox.addTag(name, 'MN');// this.addTag(name, 'MN');\n this.addElementButton.disabled = true;\n this.addFormulaButton.disabled = true;\n // this.formulaBox.disableInput(); It doesn't seem needed\n this.materialNameBox.disableInput();\n }\n });\n // add autocomplete functionality to the textfield\n// this.materialNameBox.setAutocomplete();\n\n this.filterPanel = new FilterPanel();\n this.filterSidePanel.appendChild(this.filterPanel.element);\n\n/*\n this.filterPanel.setPropsChangeListener( propsMap => {\n console.log('filterPanel.change Event propsMap: ', propsMap);\n this.sendQuery();\n })*/\n\n this.materialList= new MaterialList();\n this.resultsPage = this.element.querySelector('.results-panel');\n this.materialList.attachAndSetEvents(this.resultsPage);\n\n this.currentTab = 'element';\n this.addPanel.appendChild(this.elementTable.element);\n\n this._events();\n }\n\n\n _events() {\n\n /*\n // External event - Search button press\n this.mainButton.addEventListener( \"click\", (e) => {\n //console.log(\"this.labels: \"+JSON.stringify(this.labels));\n\n if (this.searchQuery.lenght === 0){\n util.showUserMsg('No query');\n }else{\n let searchExpressionQuery;\n\n // Search by Material name\n if (this.queryTypes[0] === 'MN'){\n //queryObj.push(this._getESSimpleMatch('material_name', item));\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.must = [];\n rootQueryObj.bool.must.push( this._getESSimpleMatch('material_name', this.searchQuery[0]) );\n this.materialList.initSearch( rootQueryObj );\n\n }else{ // Search by complex search expression\n\n searchExpressionQuery = this.getOptimadeQueryFromSearchQuery(this.searchQuery, this.queryTypes);\n\n this.materialList.initSearch(searchExpressionQuery);//this._addFiltersInSearchQuery( this.filterPanel.getValues(), searchExpressionQuery));\n\n \n if (this.element.querySelector('#allow-other-elements').checked)\n searchExpressionQuery = this._getESQueryFromSearchQuery_otherElements(\n this.searchQuery, this.queryTypes);\n // Regular case: search containing only the elements in the search expression\n else searchExpressionQuery =\n this._getESQueryFromSearchQuery(this.searchQuery, this.queryTypes);\n\n this.materialList.initSearch(\n this._addFiltersInSearchQuery( this.filterPanel.getValues(),\n searchExpressionQuery));\n //util.setBrowserHashPath('search','results');\n\n //TODO: why not identifying by ID???\n this.element.querySelector('.add-box').style.display = 'none';\n \n }\n\n }\n\n });\n*/\n\n \n\n this.addButtonsBox.addEventListener( \"click\", (e) => {\n if (e.target !== e.currentTarget) { // When the event source is a child\n let className = e.target.className;\n let index = className.indexOf('add-btn');\n\n if (index > 0){\n let selectingElement;\n let selectingTab = className.substring(0, index-1);\n if (selectingTab === 'element')\n selectingElement = this.elementTable.element;\n else if (selectingTab === 'name') {\n selectingElement = this.materialNameBox.element;\n // add autocomplete functionality,\n // but load data not before the name tab is activated\n this.materialNameBox.setAutocomplete();\n }\n else if (selectingTab === 'formula')\n selectingElement = this.formulaBox.element;\n\n this.addPanel.replaceChild(selectingElement, this.addPanel.lastChild);\n\n this.element.querySelector('.add-box').style.display = 'block';\n\n // Change the styles of the buttons\n let selEl = this.element.querySelector('.'+this.currentTab+'-add-btn');\n this._setTabSelectedStyles(selEl, false);\n this._setTabSelectedStyles(e.target, true);\n\n // Change the triangle\n this.element.querySelector('.'+this.currentTab+'-tri').style.visibility = 'hidden';\n this.element.querySelector('.'+selectingTab+'-tri').style.visibility = 'visible';\n\n this.currentTab = selectingTab;\n\n/*\n if (this.userGuidance){\n if (selectingTab === 'element'){\n UserGuidance.showIndependentTip(7, false);\n UserGuidance.showIndependentTip(3, true);\n }\n else if (selectingTab === 'props'){\n UserGuidance.showIndependentTip(3, false);\n UserGuidance.showIndependentTip(7, true);\n }else if (selectingTab === 'formula'){\n UserGuidance.showIndependentTip(3, false);\n UserGuidance.showIndependentTip(7, false);\n }\n }\n */\n\n }\n }\n });\n\n\n // gray-out events for the Properties panel and Composition panel \n \n let mainSideElem = this.element.querySelector('.search-main-side');\n\n this.filterSidePanel.addEventListener( \"mouseenter\", event => {\n mainSideElem.style.opacity = 0.3\n });\n this.filterSidePanel.addEventListener( \"mouseleave\", event => {\n mainSideElem.style.opacity = ''\n });\n\n mainSideElem.addEventListener( \"mouseenter\", event => {\n // console.log('filters:', this.filterPanel.getValues())\n this.filterPanel.showSelectedProps(true)\n });\n mainSideElem.addEventListener( \"mouseleave\", event => {\n this.filterPanel.showSelectedProps(false)\n });\n\n }\n\n\n sendQuery(){\n \n //**** The optimade query must be formed from the search box and the properties selected\n const searchBoxOptimadeQuery = this.searchBox.getOptimadeQuery(this.allowOtherElementsCheckbox.checked);\n const propsOptimadeQuery = getOptimadeQueryFromProps(this.filterPanel.getValues());\n\n const sep = (searchBoxOptimadeQuery !== '' && propsOptimadeQuery !== '' ? ' AND ' : '');\n // If one of them is empty, it and sep variable are ''\n this.materialList.initSearch(searchBoxOptimadeQuery+sep+propsOptimadeQuery);\n // this.materialList.invalidateSearch();\n \n function getOptimadeQueryFromProps(propsValuesMap){\n\n let query = '';\n propsValuesMap.forEach( (values, prop) => {\n let subquery = '';\n values.forEach( v => { // values should be and array, sometimes with just one value\n let val = ( v === true ? 'TRUE' : '\"'+v+'\"')\n subquery += (subquery === '' ? '' : ' OR ')+prop+'='+val;\n })\n query += (query === '' ? '' : ' AND ')+`(${subquery})`\n })\n return query\n }\n\n }\n\n\n _getESQueryFromSearchQuery(searchQuery, queryTypes){\n let formulas = [];\n let parFormulas = [];\n let parOperator = null; // operator just before the opening parenthesis\n let openIndex = -1;\n searchQuery.forEach( (item, i) => {\n if (queryTypes[i] === 'F' || queryTypes[i] === 'E'){\n addItem( (openIndex >= 0 ? parFormulas : formulas), i,\n item+(queryTypes[i] === 'E' ? '0' : ''));\n\n }else if ( searchQuery[i] === '(' ){\n if (i-1 >= 0 ) parOperator = searchQuery[i-1];\n openIndex = i;\n }\n else if ( searchQuery[i] === ')' ){\n if (parOperator === null){ // The starting ( was the first symbol of the expression\n formulas = parFormulas;\n }else if (parOperator === 'OR'){ // OR (...\n formulas = formulas.concat(parFormulas);\n }else{ // AND (...\n let rFormulas = [];\n formulas.forEach( formula => {\n parFormulas.forEach( parFormula => rFormulas.push(formula+parFormula) );\n });\n formulas = rFormulas;\n }\n parFormulas = []; // reset the array formulas inside the parentheses\n\n openIndex = -1;\n }\n });\n console.log('_getESQueryFromSearchQuery: ',formulas, parFormulas);\n\n //*********** Get the elastic search expression from the search expression\n // the elements inserted must be sorted for this to work\n // queryObj.bool.must.push(this._getESSimpleMatch('atom_labels_keyword', this._sortElements(elements) )); ///elements.join('')));\n // if ( this.element.querySelector('#multiples-of-formula').checked ){ // reduced search\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.should = [];\n let queryObj = rootQueryObj.bool.should;\n let reduced = this.element.querySelector('#multiples-of-formula').checked;\n\n formulas.forEach( formula => {\n let pFormula;\n let searchByElement = (formula.indexOf('0') >= 0);\n if (searchByElement){ // There are some element in the search expression\n /** TDO**/\n let f = formula;\n if (reduced) f = this._reduceFormula(formula, false);\n pFormula = this._processFormula(f, 'element-string');\n let tempQueryObj = { 'bool' : { 'must' : [] } };\n\n if (pFormula[0].length > 0) // length === 0 No formula, only elements\n tempQueryObj.bool.must.push(\n this._getESOperatorMatch(\n (reduced ? 'formula_reduced_terms' : 'formula_cell_terms'), pFormula[0]));\n tempQueryObj.bool.must.push(\n this._getESSimpleMatch('atom_labels_keyword', pFormula[1]));\n\n queryObj.push(tempQueryObj);\n\n }else{ // Only formulas\n if ( reduced ){ // reduced search\n pFormula = this._reduceFormula(formula, false);\n queryObj.push(this._getESSimpleMatch('formula_reduced_keyword', pFormula));\n }else{\n pFormula = this._processFormula(formula, 'canonical-formula');\n queryObj.push(this._getESSimpleMatch('formula_cell_keyword', pFormula));\n }\n }\n\n });\n\n return rootQueryObj;\n\n function addItem(formulas, i, item){\n if (i === 0 || searchQuery[i-1] === '('){\n formulas.push(item);\n }else{\n if (searchQuery[i-1] === 'OR') formulas.push(item);\n else if (searchQuery[i-1] === 'AND')\n formulas.push(formulas.pop()+item);\n }\n }\n\n } // _getESQueryFromSearchQuery()\n\n\n _getESQueryFromSearchQuery_otherElements(searchQuery, queryTypes){\n // Query structure analysis - looking for parentheses (only one level supported)\n if ( searchQuery.indexOf('(') >= 0){ // Recursion\n\n let openIndex = -1;\n let prodQuery = [];\n let prodTypes = [];\n //let prodQueryIndex = 0;\n for (let i = 0; i < searchQuery.length; i++) {\n if ( searchQuery[i] === '(' ) openIndex = i;\n else if ( searchQuery[i] === ')' ){\n prodQuery.push(this._getESQueryFromSearchQuery_otherElements(\n searchQuery.slice(openIndex+1, i), queryTypes.slice(openIndex+1, i)));\n prodTypes.push('Q');\n openIndex = -1;\n }else if (openIndex < 0){ // outside a parentheses\n prodQuery.push(searchQuery[i]);\n prodTypes.push(queryTypes[i]);\n }\n }\n //console.log('prodQuery', prodQuery, prodTypes);\n return this._getESQueryFromSearchQuery_otherElements(prodQuery, prodTypes);\n\n }else{ // BASE CASE: there is no parentheses\n\n let boolOperator;\n searchQuery.forEach( (item, i) => {\n if (searchQuery[i] === 'AND' || searchQuery[i] === 'OR')\n boolOperator = item;\n });\n\n let rootQueryObj = { 'bool' : {} };\n let queryObj;\n if (boolOperator === 'AND'){\n rootQueryObj.bool.must = [];\n queryObj = rootQueryObj.bool.must;\n }else{ // OR\n rootQueryObj.bool.should = [];\n queryObj = rootQueryObj.bool.should;\n }\n\n searchQuery.forEach( (item, i) => {\n\n if (queryTypes[i] === 'F'){ // Formula case\n let esMatchQuery;\n if ( this.element.querySelector('#multiples-of-formula').checked ){ // reduced search\n esMatchQuery = this._getESOperatorMatch('formula_reduced_terms', this._reduceFormula(item));\n }else\n esMatchQuery = this._getESOperatorMatch('formula_cell_terms', this._processFormula(item, 'tokens'));\n queryObj.push(esMatchQuery);\n\n }else if (queryTypes[i] === 'E'){ // Element case\n queryObj.push(this._getESSimpleMatch('atom_labels_terms', item));\n\n }else if (queryTypes[i] === 'Q'){\n queryObj.push(item);\n }\n });\n\n return rootQueryObj;\n } // else\n } // _getESQueryFromSearchQuery_otherElements()\n\n\n\n\n _addFiltersInSearchQuery(filterMap, searchExpressionQuery){\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.must = [];\n rootQueryObj.bool.must.push( searchExpressionQuery );\n\n\n filterMap.forEach((values/*Array*/, filterName) => {\n\n let filterNameDef = replaceDashes(filterName);\n\n if (filterName === 'mass-density' || filterName === 'band-gap'){\n //***** util.eV2J() apply?\n rootQueryObj.bool.must.push( this._getFieldESRange(filterNameDef, values) );\n\n }else if (filterName === 'band-gap-type'){ // special case\n if ( values !== 'both')\n rootQueryObj.bool.must.push( this._getESSimpleMatch('band_gap_direct',\n ( values === 'direct' ? true : false ) ) );\n\n }else if (filterName.startsWith('has')){ // has- filters\n rootQueryObj.bool.must.push( this._getESSimpleMatch(filterNameDef, values, false) );\n\n }else{ // normal case\n //rootQueryObj.bool.must.push( this._getESOperatorMatch(filterNameDef, values, false) );\n rootQueryObj.bool.must.push( this._getESTermsArray(filterNameDef, values) );\n\n //console.log(this._getESOperatorMatch(filterNameDef, values, false) );\n }\n\n });\n\n\n return rootQueryObj;\n }\n\n\n _getESSimpleMatch(field, value){\n return {\n \"match\": { [field] : value }\n };\n }\n\n _getESTermsArray(field, value){\n return {\n \"terms\": { [field] : value }\n };\n }\n\n\n _getESOperatorMatch(field, elements, and = true){\n let elementsString = '';\n if (elements.length > 0) elementsString = elements.join(' ');\n\n return {\n \"match\": {\n [field]: {\n \"operator\": (and ? \"and\" : 'or'),\n \"query\": elementsString\n }\n }\n };\n }\n\n\n _getFieldESRange(field, valuesString){\n let data = valuesString.split(':');\n console.log('_getFieldESRange data', data);\n\n return {\n \"range\": {\n [field]: { \"gte\" : parseInt(data[0]), \"lte\" : parseInt(data[1]) }\n }\n };\n }\n\n\n\n\n _setTabSelectedStyles(element, value){\n /*\n element.style.fontWeight = (value ? 'bold' : 'normal');\n element.style.color = (value ? '#E56400' : '#777');\n element.style.borderColor = (value ? '#E56400' : '#777');\n */\n element.id = (value ? 'add-tab-selected' : '');\n }\n\n\n/*\n _showSearchBox(){\n if (!this.showingSearchBox){\n this.showingSearchBox = true;\n this.searchLine.style.visibility = 'visible';\n\n if (this.userGuidance) UserGuidance.setFinal();\n }\n }*/\n\n \n\n\n \n\n ///********* DELETE?\n showSearchResults(){\n /*\n this.searchPage.style.display= 'none';\n this.resultsPage.style.display= 'block';\n */\n //if (this.userGuidance) UserGuidance.show(false);\n }\n\n\n showSearchPage(){\n /*\n this.searchPage.style.display= 'block';\n this.resultsPage.style.display= 'none';\n\n if (this.userGuidance){\n setTimeout(() => {\n UserGuidance.init(this.addButtonsBox, this.elementTable.element,\n this.searchBox, this.propertiesBox.tabsElement);\n UserGuidance.show(true, this.currentTab === 'element',\n this.currentTab === 'props');\n }, 400);\n }\n */\n }\n\n\n _sortElements(elements){\n let numbers = [];\n let sortedElements = [];\n elements.forEach( e => numbers.push(util.ELEMENTS.indexOf(e)) );\n numbers.sort( (a, b) => a - b ); // atomic number-1\n numbers.forEach( n => sortedElements.push(util.ELEMENTS[n]) );\n //console.log('_sortElements ',numbers, elString);\n return sortedElements;\n }\n\n\n\n\n _reduceFormula(formula, getTokens = true){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n\n let counter = 0;\n while ( !checkIfReduced(map) ){ // console.log('Reducing', map);\n let div = 1;\n if (isDivisibleBy(map, 2)) div = 2;\n else if (isDivisibleBy(map, 3)) div = 3;\n else if (isDivisibleBy(map, 5)) div = 5;\n else if (isDivisibleBy(map, 7)) div = 7;\n else if (isDivisibleBy(map, 11)) div = 11;\n\n map.forEach( (value, key) => {\n map.set(key, (value/div));\n });\n //console.log('Reducing DIV', map);\n counter++;\n if (counter > 5) break;\n }\n\n function checkIfReduced(formulaMap){\n let min = 100;\n formulaMap.forEach( (value, key) => {\n if (value < min) min = value;\n });\n return min === 1;\n }\n\n function isDivisibleBy(formulaMap, n){\n let div = true;\n formulaMap.forEach( (value, key) => {\n if (value % n !== 0) div = false;\n });\n return div;\n }\n\n let tokens = [];\n let canonicalFormula = '';\n if (getTokens){\n map.forEach( (value, key) => tokens.push(key+value) );\n }else{\n let sortedElements = this._sortElements( Array.from( map.keys() ) );\n sortedElements.forEach( element => {\n canonicalFormula += element+map.get(element);\n //canonicalFormula += element+(map.get(element) === 1 ? '' : map.get(element));\n });\n }\n\n\n console.log('_reduceFormula RETURN: ', map, tokens, canonicalFormula);\n return (getTokens ? tokens : canonicalFormula);\n }\n\n\n _processFormula(formula, type){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n\n if (type === 'tokens'){\n let tokens = [];\n map.forEach( (value, key) => tokens.push(key+value) );\n console.log('_processFormula RETURN: ', map, tokens);\n return tokens;\n }else{\n let sortedElements = this._sortElements( Array.from( map.keys() ) );\n if (type === 'canonical-formula'){\n let formulaString = '';\n sortedElements.forEach( element => {\n formulaString += element+map.get(element);\n });\n console.log('_processFormula RETURN: ', map, formulaString);\n return formulaString;\n }else{ // elements-string\n let elementsString = '';\n let elementsInFormulas = [];\n sortedElements.forEach( element => {\n elementsString += element;\n let val = map.get(element);\n if (val !== 0) elementsInFormulas.push(element+val);\n });\n console.log('_processFormula RETURN: ', map, [elementsInFormulas ,elementsString]);\n return [elementsInFormulas ,elementsString];\n }\n }\n\n }\n\n}\n\n\nclass FormulaBox{\n\n constructor() {\n this.element = document.createElement('div');\n this.element.setAttribute(\"id\",'formula-box');\n this.element.innerHTML=\n `\n <div style=\"padding-bottom: 20px;\">\n<!--\n <input type=\"checkbox\" class=\"allow-other-elements\" value=\"\">\n Allow other elements\n <br>\n\n\n <input type=\"checkbox\" class=\"multiples-of-formula\" value=\"\">\n Include multiples of formula\n <br> -->\n\n <input type=\"text\" class=\"textfield-composition\"\n placeholder=\"Add formula to the search query above\" >\n <button class=\"adding-formula-btn\" disabled>Add to query</button>\n </div>\n `;\n this.formulaTextField = this.element.querySelector('.textfield-composition');\n this.formulaButton = this.element.querySelector('.adding-formula-btn');\n\n this.formulaButton.addEventListener( \"click\", (e) => {\n this.addFormulaListener(this.formulaTextField.value);\n this.formulaTextField.value = '';\n });\n\n this.formulaTextField.addEventListener( 'input', e => {\n //console.log('formulaTextField input: ',this.formulaTextField.value);\n this.formulaButton.disabled = (this.formulaTextField.value === '');\n });\n\n }\n\n\n setAddFormulaListener(listener) {\n this.addFormulaListener= listener;\n }\n\n\n disableInput() {\n this.formulaTextField.disabled = true;\n this.formulaButton.disabled = true;\n }\n\n enableInput() {\n this.formulaTextField.disabled = false;\n this.formulaButton.disabled = false;\n }\n\n/*\n getAllowOtherElements(){\n return this.element.querySelector('.allow-other-elements').checked;\n }\n\n\n getMultiplesOfFormula(){\n return this.element.querySelector('.multiples-of-formula').checked;\n }*/\n\n}\n\n// EXPORTS\nmodule.exports = NewSearchMod;\n\n\n//# sourceURL=webpack:///./src/search-mod/NewSearchMod.js?");
eval("\n/**\n * Copyright 2016-2019 Iker Hurtado, Georg Huhs\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n\n /*\n This file implements the Search Module of the application.\n It's a container UI component that shows the seach part of the application:\n front search interface and search results page.\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\nlet UserGuidance = __webpack_require__(/*! ../common/UserGuidance.js */ \"./src/common/UserGuidance.js\");\nlet SearchBox = __webpack_require__(/*! ./SearchBox.view.js */ \"./src/search-mod/SearchBox.view.js\");\nlet ElementTable = __webpack_require__(/*! ./ElemenTable.view.js */ \"./src/search-mod/ElemenTable.view.js\");\nlet MaterialList = __webpack_require__(/*! ./MaterialList.view.js */ \"./src/search-mod/MaterialList.view.js\");\nlet MaterialNameBox = __webpack_require__(/*! ./MaterialName.view.js */ \"./src/search-mod/MaterialName.view.js\");\nlet FilterPanel = __webpack_require__(/*! ./FilterPanel.view.js */ \"./src/search-mod/FilterPanel.view.js\");\nlet SwitchComponent = __webpack_require__(/*! ../common/SwitchComponent.js */ \"./src/common/SwitchComponent.js\");\n\n\n\nfunction replaceDashes(s){\n return s.split('-').join('_');\n}\n\nclass NewSearchMod {\n\n constructor() {\n\n this.userGuidance = true; // can enabled/disabled\n\n this.searchFilters = [];\n\n this.element = document.createElement('div');\n this.element.setAttribute(\"id\",'search-module');\n this.element.innerHTML=\n `\n <div class=\"search-filter-side\">\n\n <!-- <div class=\"search-title\">Properties</div>-->\n\n <!-- <div id=\"filter-panel-placeholder\"> </div> -->\n\n </div>\n\n <div class=\"search-main-side\">\n\n <!-- <div class=\"search-title\">Composition</div> -->\n\n <div class=\"search-box-placeholder\" > </div>\n \n <input type=\"checkbox\" id=\"multiples-of-formula\" value=\"\">\n Include multiples of formula\n <br>\n <input type=\"checkbox\" id=\"allow-other-elements\" value=\"\" checked>\n Allow other elements\n <br>\n\n\n <div class=\"add-buttons\" >\n <div class=\"tab-buttons\" style=\"width: 70%; display: inline-block\">\n <button class=\"element-add-btn\" id=\"add-tab-selected\" style=\"margin-left: 50px\">Element</button>\n <button class=\"formula-add-btn\" style=\"padding: 10px 20px;\" >Formula</button>\n <button class=\"name-add-btn\" >Name</button>\n </div>\n <div class=\"bool-buttons\" style=\"width: 28%; display: inline-block\" >\n OR <span id=\"and-or-switch\" ></span> AND\n <button class=\"not-button\">NOT</button>\n <button class=\"open-parentheses\" >(</button>\n <button class=\"close-parentheses\">)</button>\n <!--<input type=\"checkbox\" name=\"and-or\" class=\"not-symbol-btn\" />NOT-->\n </div>\n </div>\n\n\n <div class=\"add-box\">\n <div style=\"width: 70%; display: inline-block;\">\n <div class=\"triangle-container\" style=\"margin-left: 50px;\">\n <div class=\"triangle element-tri\" style=\"visibility: visible\"></div>\n </div>\n <div class=\"triangle-container\" >\n <div class=\"triangle formula-tri\" style=\"visibility: hidden\"></div>\n </div>\n <div class=\"triangle-container\" >\n <div class=\"triangle name-tri\" style=\"visibility: hidden\"></div>\n </div>\n </div>\n\n <div class=\"add-panel\">\n </div>\n </div>\n\n <div class=\"results-panel\"> <!-- style=\"display: none\"-->\n </div>\n\n </div> <!-- search-main-side -->\n `;\n\n this.filterSidePanel = this.element.querySelector('.search-filter-side');\n\n this.searchBox = new SearchBox();\n this.searchBox.setBoolOperator('AND');\n this.element.querySelector('.search-box-placeholder').append(this.searchBox.element);\n this.searchBox.setRemoveElementListener( (element) => {\n this.elementTable.deselectElement(element);\n });\n // When the clear all button is clicked on and \n // when the search query gets blank by removing query items\n this.searchBox.setCleanSearchQueryListener( () => { \n this.addFormulaButton.disabled = false;\n this.addMatNameButton.disabled = false;\n this.addElementButton.disabled = false;\n this.formulaBox.enableInput();\n this.materialNameBox.enableInput();\n this.elementTable.deselectAllElements();\n });\n\n /*\n this.searchBox.setSearchQueryChangeListener( () => {\n this.sendQuery();\n }); */\n\n this.allowOtherElementsCheckbox = this.element.querySelector('#allow-other-elements');\n /*\n this.allowOtherElementsCheckbox.addEventListener( 'change', e => {\n this.sendQuery();\n }); */\n\n /* This button could be out of the search box because its functionality\n is not part of it. It can also be removed eventually, if considered not necessary */\n this.searchButton = this.searchBox.getSearchButtonElement();\n this.searchButton.addEventListener( 'click', e => {\n this.sendQuery();\n });\n\n this.addButtonsBox= this.element.querySelector('.add-buttons');\n this.addElementButton = this.addButtonsBox.querySelector('.element-add-btn');\n this.addFormulaButton = this.addButtonsBox.querySelector('.formula-add-btn');\n this.addMatNameButton = this.addButtonsBox.querySelector('.name-add-btn');\n\n this.addPanel= this.element.querySelector('.add-panel');\n\n let andOrSwitch = new SwitchComponent(util.IMAGE_DIR+'switch');\n this.element.querySelector('#and-or-switch').appendChild(andOrSwitch.element);\n andOrSwitch.setListener( e => {\n this.searchBox.setBoolOperator( e ? 'AND' : 'OR');\n });\n\n this.notButton = this.element.querySelector('.not-button');\n this.notButton.addEventListener( 'click', e => {\n this.searchBox.addNOT();\n });\n\n this.openParenthButton = this.element.querySelector('.open-parentheses');\n this.closeParenthButton = this.element.querySelector('.close-parentheses');\n this.openParenthButton.addEventListener( 'click', e => {\n this.searchBox.addParentheses(true);\n });\n this.closeParenthButton.addEventListener( 'click', e => {\n this.searchBox.addParentheses(false);\n });\n\n this.elementTable= new ElementTable();\n this.elementTable.setClickListener(elementArray => {\n this.searchBox.addElements(elementArray);\n this.addMatNameButton.disabled = true; // Not always necessary but it simplifies the code\n this.addFormulaButton.disabled = true;\n });\n this.elementTable.setDeselectListener(e => this.searchBox.removeElementORFormulaInSearchQuery(e));\n\n\n this.formulaBox = new FormulaBox();\n this.formulaBox.setAddFormulaListener(formula => {\n if (formula.trim() !== ''){\n this.searchBox.addTag(formula, 'F');\n //this.formulaBox.disable(true);\n this.addElementButton.disabled = true;\n this.addMatNameButton.disabled = true;\n // this.materialNameBox.disableInput(); It doesn't seem needed\n }\n });\n\n\n this.materialNameBox = new MaterialNameBox();\n this.materialNameBox.setAddMaterialNameListener( name => {\n if (name.trim() !== ''){\n this.searchBox.addTag(name, 'MN');// this.addTag(name, 'MN');\n this.addElementButton.disabled = true;\n this.addFormulaButton.disabled = true;\n // this.formulaBox.disableInput(); It doesn't seem needed\n this.materialNameBox.disableInput();\n }\n });\n // add autocomplete functionality to the textfield\n// this.materialNameBox.setAutocomplete();\n\n this.filterPanel = new FilterPanel();\n this.filterSidePanel.appendChild(this.filterPanel.element);\n\n/*\n this.filterPanel.setPropsChangeListener( propsMap => {\n console.log('filterPanel.change Event propsMap: ', propsMap);\n this.sendQuery();\n })*/\n\n this.materialList= new MaterialList();\n this.resultsPage = this.element.querySelector('.results-panel');\n this.materialList.attachAndSetEvents(this.resultsPage);\n\n this.currentTab = 'element';\n this.addPanel.appendChild(this.elementTable.element);\n\n this._events();\n }\n\n\n _events() {\n\n /*\n // External event - Search button press\n this.mainButton.addEventListener( \"click\", (e) => {\n //console.log(\"this.labels: \"+JSON.stringify(this.labels));\n\n if (this.searchQuery.lenght === 0){\n util.showUserMsg('No query');\n }else{\n let searchExpressionQuery;\n\n // Search by Material name\n if (this.queryTypes[0] === 'MN'){\n //queryObj.push(this._getESSimpleMatch('material_name', item));\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.must = [];\n rootQueryObj.bool.must.push( this._getESSimpleMatch('material_name', this.searchQuery[0]) );\n this.materialList.initSearch( rootQueryObj );\n\n }else{ // Search by complex search expression\n\n searchExpressionQuery = this.getOptimadeQueryFromSearchQuery(this.searchQuery, this.queryTypes);\n\n this.materialList.initSearch(searchExpressionQuery);//this._addFiltersInSearchQuery( this.filterPanel.getValues(), searchExpressionQuery));\n\n \n if (this.element.querySelector('#allow-other-elements').checked)\n searchExpressionQuery = this._getESQueryFromSearchQuery_otherElements(\n this.searchQuery, this.queryTypes);\n // Regular case: search containing only the elements in the search expression\n else searchExpressionQuery =\n this._getESQueryFromSearchQuery(this.searchQuery, this.queryTypes);\n\n this.materialList.initSearch(\n this._addFiltersInSearchQuery( this.filterPanel.getValues(),\n searchExpressionQuery));\n //util.setBrowserHashPath('search','results');\n\n //TODO: why not identifying by ID???\n this.element.querySelector('.add-box').style.display = 'none';\n \n }\n\n }\n\n });\n*/\n\n \n\n this.addButtonsBox.addEventListener( \"click\", (e) => {\n if (e.target !== e.currentTarget) { // When the event source is a child\n let className = e.target.className;\n let index = className.indexOf('add-btn');\n\n if (index > 0){\n let selectingElement;\n let selectingTab = className.substring(0, index-1);\n if (selectingTab === 'element')\n selectingElement = this.elementTable.element;\n else if (selectingTab === 'name') {\n selectingElement = this.materialNameBox.element;\n // add autocomplete functionality,\n // but load data not before the name tab is activated\n this.materialNameBox.setAutocomplete();\n }\n else if (selectingTab === 'formula')\n selectingElement = this.formulaBox.element;\n\n this.addPanel.replaceChild(selectingElement, this.addPanel.lastChild);\n\n this.element.querySelector('.add-box').style.display = 'block';\n\n // Change the styles of the buttons\n let selEl = this.element.querySelector('.'+this.currentTab+'-add-btn');\n this._setTabSelectedStyles(selEl, false);\n this._setTabSelectedStyles(e.target, true);\n\n // Change the triangle\n this.element.querySelector('.'+this.currentTab+'-tri').style.visibility = 'hidden';\n this.element.querySelector('.'+selectingTab+'-tri').style.visibility = 'visible';\n\n this.currentTab = selectingTab;\n\n/*\n if (this.userGuidance){\n if (selectingTab === 'element'){\n UserGuidance.showIndependentTip(7, false);\n UserGuidance.showIndependentTip(3, true);\n }\n else if (selectingTab === 'props'){\n UserGuidance.showIndependentTip(3, false);\n UserGuidance.showIndependentTip(7, true);\n }else if (selectingTab === 'formula'){\n UserGuidance.showIndependentTip(3, false);\n UserGuidance.showIndependentTip(7, false);\n }\n }\n */\n\n }\n }\n });\n\n\n // gray-out events for the Properties panel and Composition panel \n \n let mainSideElem = this.element.querySelector('.search-main-side');\n\n this.filterSidePanel.addEventListener( \"mouseenter\", event => {\n mainSideElem.style.opacity = 0.3\n });\n this.filterSidePanel.addEventListener( \"mouseleave\", event => {\n mainSideElem.style.opacity = ''\n });\n\n mainSideElem.addEventListener( \"mouseenter\", event => {\n // console.log('filters:', this.filterPanel.getValues())\n this.filterPanel.showSelectedProps(true)\n });\n mainSideElem.addEventListener( \"mouseleave\", event => {\n this.filterPanel.showSelectedProps(false)\n });\n\n }\n\n\n sendQuery(){\n \n //**** The optimade query must be formed from the search box and the properties selected\n const searchBoxOptimadeQuery = this.searchBox.getOptimadeQuery2(this.allowOtherElementsCheckbox.checked);\n const propsOptimadeQuery = getOptimadeQueryFromProps(this.filterPanel.getValues());\n\n const sep = (searchBoxOptimadeQuery !== '' && propsOptimadeQuery !== '' ? ' AND ' : '');\n // If one of them is empty, it and sep variable are ''\n this.materialList.initSearch(searchBoxOptimadeQuery+sep+propsOptimadeQuery);\n // this.materialList.invalidateSearch();\n \n function getOptimadeQueryFromProps(propsValuesMap){\n\n let query = '';\n propsValuesMap.forEach( (values, prop) => {\n let subquery = '';\n values.forEach( v => { // values should be and array, sometimes with just one value\n let val = ( v === true ? 'TRUE' : '\"'+v+'\"')\n subquery += (subquery === '' ? '' : ' OR ')+prop+'='+val;\n })\n query += (query === '' ? '' : ' AND ')+`(${subquery})`\n })\n return query\n }\n\n }\n\n\n _getESQueryFromSearchQuery(searchQuery, queryTypes){\n let formulas = [];\n let parFormulas = [];\n let parOperator = null; // operator just before the opening parenthesis\n let openIndex = -1;\n searchQuery.forEach( (item, i) => {\n if (queryTypes[i] === 'F' || queryTypes[i] === 'E'){\n addItem( (openIndex >= 0 ? parFormulas : formulas), i,\n item+(queryTypes[i] === 'E' ? '0' : ''));\n\n }else if ( searchQuery[i] === '(' ){\n if (i-1 >= 0 ) parOperator = searchQuery[i-1];\n openIndex = i;\n }\n else if ( searchQuery[i] === ')' ){\n if (parOperator === null){ // The starting ( was the first symbol of the expression\n formulas = parFormulas;\n }else if (parOperator === 'OR'){ // OR (...\n formulas = formulas.concat(parFormulas);\n }else{ // AND (...\n let rFormulas = [];\n formulas.forEach( formula => {\n parFormulas.forEach( parFormula => rFormulas.push(formula+parFormula) );\n });\n formulas = rFormulas;\n }\n parFormulas = []; // reset the array formulas inside the parentheses\n\n openIndex = -1;\n }\n });\n console.log('_getESQueryFromSearchQuery: ',formulas, parFormulas);\n\n //*********** Get the elastic search expression from the search expression\n // the elements inserted must be sorted for this to work\n // queryObj.bool.must.push(this._getESSimpleMatch('atom_labels_keyword', this._sortElements(elements) )); ///elements.join('')));\n // if ( this.element.querySelector('#multiples-of-formula').checked ){ // reduced search\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.should = [];\n let queryObj = rootQueryObj.bool.should;\n let reduced = this.element.querySelector('#multiples-of-formula').checked;\n\n formulas.forEach( formula => {\n let pFormula;\n let searchByElement = (formula.indexOf('0') >= 0);\n if (searchByElement){ // There are some element in the search expression\n /** TDO**/\n let f = formula;\n if (reduced) f = this._reduceFormula(formula, false);\n pFormula = this._processFormula(f, 'element-string');\n let tempQueryObj = { 'bool' : { 'must' : [] } };\n\n if (pFormula[0].length > 0) // length === 0 No formula, only elements\n tempQueryObj.bool.must.push(\n this._getESOperatorMatch(\n (reduced ? 'formula_reduced_terms' : 'formula_cell_terms'), pFormula[0]));\n tempQueryObj.bool.must.push(\n this._getESSimpleMatch('atom_labels_keyword', pFormula[1]));\n\n queryObj.push(tempQueryObj);\n\n }else{ // Only formulas\n if ( reduced ){ // reduced search\n pFormula = this._reduceFormula(formula, false);\n queryObj.push(this._getESSimpleMatch('formula_reduced_keyword', pFormula));\n }else{\n pFormula = this._processFormula(formula, 'canonical-formula');\n queryObj.push(this._getESSimpleMatch('formula_cell_keyword', pFormula));\n }\n }\n\n });\n\n return rootQueryObj;\n\n function addItem(formulas, i, item){\n if (i === 0 || searchQuery[i-1] === '('){\n formulas.push(item);\n }else{\n if (searchQuery[i-1] === 'OR') formulas.push(item);\n else if (searchQuery[i-1] === 'AND')\n formulas.push(formulas.pop()+item);\n }\n }\n\n } // _getESQueryFromSearchQuery()\n\n\n _getESQueryFromSearchQuery_otherElements(searchQuery, queryTypes){\n // Query structure analysis - looking for parentheses (only one level supported)\n if ( searchQuery.indexOf('(') >= 0){ // Recursion\n\n let openIndex = -1;\n let prodQuery = [];\n let prodTypes = [];\n //let prodQueryIndex = 0;\n for (let i = 0; i < searchQuery.length; i++) {\n if ( searchQuery[i] === '(' ) openIndex = i;\n else if ( searchQuery[i] === ')' ){\n prodQuery.push(this._getESQueryFromSearchQuery_otherElements(\n searchQuery.slice(openIndex+1, i), queryTypes.slice(openIndex+1, i)));\n prodTypes.push('Q');\n openIndex = -1;\n }else if (openIndex < 0){ // outside a parentheses\n prodQuery.push(searchQuery[i]);\n prodTypes.push(queryTypes[i]);\n }\n }\n //console.log('prodQuery', prodQuery, prodTypes);\n return this._getESQueryFromSearchQuery_otherElements(prodQuery, prodTypes);\n\n }else{ // BASE CASE: there is no parentheses\n\n let boolOperator;\n searchQuery.forEach( (item, i) => {\n if (searchQuery[i] === 'AND' || searchQuery[i] === 'OR')\n boolOperator = item;\n });\n\n let rootQueryObj = { 'bool' : {} };\n let queryObj;\n if (boolOperator === 'AND'){\n rootQueryObj.bool.must = [];\n queryObj = rootQueryObj.bool.must;\n }else{ // OR\n rootQueryObj.bool.should = [];\n queryObj = rootQueryObj.bool.should;\n }\n\n searchQuery.forEach( (item, i) => {\n\n if (queryTypes[i] === 'F'){ // Formula case\n let esMatchQuery;\n if ( this.element.querySelector('#multiples-of-formula').checked ){ // reduced search\n esMatchQuery = this._getESOperatorMatch('formula_reduced_terms', this._reduceFormula(item));\n }else\n esMatchQuery = this._getESOperatorMatch('formula_cell_terms', this._processFormula(item, 'tokens'));\n queryObj.push(esMatchQuery);\n\n }else if (queryTypes[i] === 'E'){ // Element case\n queryObj.push(this._getESSimpleMatch('atom_labels_terms', item));\n\n }else if (queryTypes[i] === 'Q'){\n queryObj.push(item);\n }\n });\n\n return rootQueryObj;\n } // else\n } // _getESQueryFromSearchQuery_otherElements()\n\n\n\n\n _addFiltersInSearchQuery(filterMap, searchExpressionQuery){\n let rootQueryObj = { 'bool' : {} };\n rootQueryObj.bool.must = [];\n rootQueryObj.bool.must.push( searchExpressionQuery );\n\n\n filterMap.forEach((values/*Array*/, filterName) => {\n\n let filterNameDef = replaceDashes(filterName);\n\n if (filterName === 'mass-density' || filterName === 'band-gap'){\n //***** util.eV2J() apply?\n rootQueryObj.bool.must.push( this._getFieldESRange(filterNameDef, values) );\n\n }else if (filterName === 'band-gap-type'){ // special case\n if ( values !== 'both')\n rootQueryObj.bool.must.push( this._getESSimpleMatch('band_gap_direct',\n ( values === 'direct' ? true : false ) ) );\n\n }else if (filterName.startsWith('has')){ // has- filters\n rootQueryObj.bool.must.push( this._getESSimpleMatch(filterNameDef, values, false) );\n\n }else{ // normal case\n //rootQueryObj.bool.must.push( this._getESOperatorMatch(filterNameDef, values, false) );\n rootQueryObj.bool.must.push( this._getESTermsArray(filterNameDef, values) );\n\n //console.log(this._getESOperatorMatch(filterNameDef, values, false) );\n }\n\n });\n\n\n return rootQueryObj;\n }\n\n\n _getESSimpleMatch(field, value){\n return {\n \"match\": { [field] : value }\n };\n }\n\n _getESTermsArray(field, value){\n return {\n \"terms\": { [field] : value }\n };\n }\n\n\n _getESOperatorMatch(field, elements, and = true){\n let elementsString = '';\n if (elements.length > 0) elementsString = elements.join(' ');\n\n return {\n \"match\": {\n [field]: {\n \"operator\": (and ? \"and\" : 'or'),\n \"query\": elementsString\n }\n }\n };\n }\n\n\n _getFieldESRange(field, valuesString){\n let data = valuesString.split(':');\n console.log('_getFieldESRange data', data);\n\n return {\n \"range\": {\n [field]: { \"gte\" : parseInt(data[0]), \"lte\" : parseInt(data[1]) }\n }\n };\n }\n\n\n\n\n _setTabSelectedStyles(element, value){\n /*\n element.style.fontWeight = (value ? 'bold' : 'normal');\n element.style.color = (value ? '#E56400' : '#777');\n element.style.borderColor = (value ? '#E56400' : '#777');\n */\n element.id = (value ? 'add-tab-selected' : '');\n }\n\n\n/*\n _showSearchBox(){\n if (!this.showingSearchBox){\n this.showingSearchBox = true;\n this.searchLine.style.visibility = 'visible';\n\n if (this.userGuidance) UserGuidance.setFinal();\n }\n }*/\n\n \n\n\n \n\n ///********* DELETE?\n showSearchResults(){\n /*\n this.searchPage.style.display= 'none';\n this.resultsPage.style.display= 'block';\n */\n //if (this.userGuidance) UserGuidance.show(false);\n }\n\n\n showSearchPage(){\n /*\n this.searchPage.style.display= 'block';\n this.resultsPage.style.display= 'none';\n\n if (this.userGuidance){\n setTimeout(() => {\n UserGuidance.init(this.addButtonsBox, this.elementTable.element,\n this.searchBox, this.propertiesBox.tabsElement);\n UserGuidance.show(true, this.currentTab === 'element',\n this.currentTab === 'props');\n }, 400);\n }\n */\n }\n\n\n _sortElements(elements){\n let numbers = [];\n let sortedElements = [];\n elements.forEach( e => numbers.push(util.ELEMENTS.indexOf(e)) );\n numbers.sort( (a, b) => a - b ); // atomic number-1\n numbers.forEach( n => sortedElements.push(util.ELEMENTS[n]) );\n //console.log('_sortElements ',numbers, elString);\n return sortedElements;\n }\n\n\n\n\n _reduceFormula(formula, getTokens = true){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n\n let counter = 0;\n while ( !checkIfReduced(map) ){ // console.log('Reducing', map);\n let div = 1;\n if (isDivisibleBy(map, 2)) div = 2;\n else if (isDivisibleBy(map, 3)) div = 3;\n else if (isDivisibleBy(map, 5)) div = 5;\n else if (isDivisibleBy(map, 7)) div = 7;\n else if (isDivisibleBy(map, 11)) div = 11;\n\n map.forEach( (value, key) => {\n map.set(key, (value/div));\n });\n //console.log('Reducing DIV', map);\n counter++;\n if (counter > 5) break;\n }\n\n function checkIfReduced(formulaMap){\n let min = 100;\n formulaMap.forEach( (value, key) => {\n if (value < min) min = value;\n });\n return min === 1;\n }\n\n function isDivisibleBy(formulaMap, n){\n let div = true;\n formulaMap.forEach( (value, key) => {\n if (value % n !== 0) div = false;\n });\n return div;\n }\n\n let tokens = [];\n let canonicalFormula = '';\n if (getTokens){\n map.forEach( (value, key) => tokens.push(key+value) );\n }else{\n let sortedElements = this._sortElements( Array.from( map.keys() ) );\n sortedElements.forEach( element => {\n canonicalFormula += element+map.get(element);\n //canonicalFormula += element+(map.get(element) === 1 ? '' : map.get(element));\n });\n }\n\n\n console.log('_reduceFormula RETURN: ', map, tokens, canonicalFormula);\n return (getTokens ? tokens : canonicalFormula);\n }\n\n\n _processFormula(formula, type){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n\n if (type === 'tokens'){\n let tokens = [];\n map.forEach( (value, key) => tokens.push(key+value) );\n console.log('_processFormula RETURN: ', map, tokens);\n return tokens;\n }else{\n let sortedElements = this._sortElements( Array.from( map.keys() ) );\n if (type === 'canonical-formula'){\n let formulaString = '';\n sortedElements.forEach( element => {\n formulaString += element+map.get(element);\n });\n console.log('_processFormula RETURN: ', map, formulaString);\n return formulaString;\n }else{ // elements-string\n let elementsString = '';\n let elementsInFormulas = [];\n sortedElements.forEach( element => {\n elementsString += element;\n let val = map.get(element);\n if (val !== 0) elementsInFormulas.push(element+val);\n });\n console.log('_processFormula RETURN: ', map, [elementsInFormulas ,elementsString]);\n return [elementsInFormulas ,elementsString];\n }\n }\n\n }\n\n}\n\n\nclass FormulaBox{\n\n constructor() {\n this.element = document.createElement('div');\n this.element.setAttribute(\"id\",'formula-box');\n this.element.innerHTML=\n `\n <div style=\"padding-bottom: 20px;\">\n<!--\n <input type=\"checkbox\" class=\"allow-other-elements\" value=\"\">\n Allow other elements\n <br>\n\n\n <input type=\"checkbox\" class=\"multiples-of-formula\" value=\"\">\n Include multiples of formula\n <br> -->\n\n <input type=\"text\" class=\"textfield-composition\"\n placeholder=\"Add formula to the search query above\" >\n <button class=\"adding-formula-btn\" disabled>Add to query</button>\n </div>\n `;\n this.formulaTextField = this.element.querySelector('.textfield-composition');\n this.formulaButton = this.element.querySelector('.adding-formula-btn');\n\n this.formulaButton.addEventListener( \"click\", (e) => {\n this.addFormulaListener(this.formulaTextField.value);\n this.formulaTextField.value = '';\n });\n\n this.formulaTextField.addEventListener( 'input', e => {\n //console.log('formulaTextField input: ',this.formulaTextField.value);\n this.formulaButton.disabled = (this.formulaTextField.value === '');\n });\n\n }\n\n\n setAddFormulaListener(listener) {\n this.addFormulaListener= listener;\n }\n\n\n disableInput() {\n this.formulaTextField.disabled = true;\n this.formulaButton.disabled = true;\n }\n\n enableInput() {\n this.formulaTextField.disabled = false;\n this.formulaButton.disabled = false;\n }\n\n/*\n getAllowOtherElements(){\n return this.element.querySelector('.allow-other-elements').checked;\n }\n\n\n getMultiplesOfFormula(){\n return this.element.querySelector('.multiples-of-formula').checked;\n }*/\n\n}\n\n// EXPORTS\nmodule.exports = NewSearchMod;\n\n\n//# sourceURL=webpack:///./src/search-mod/NewSearchMod.js?");
/***/ }),
......@@ -426,7 +426,7 @@ eval("\n/**\n * Copyright 2016-2019 Iker Hurtado, Georg Huhs\n *\n * Licensed un
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
eval("/**\n * Copyright 2016-2020 Iker Hurtado\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n\n /*\n This is the UI component implementing the search box.\n It represents the search query for the user and enables its edition.\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\n//\n//\n//// local utility functions\nfunction getTagHtml(tag, isFormula){\n return `<span class=\"search-label\" data-el=\"${tag}\" >\n <img src=\"img/tag.svg\" height=\"16px\" class=\"remove-label\"\n style=\"vertical-align: bottom\"/>\n ${isFormula ? util.getSubscriptedFormula(tag) : tag}\n <img src=\"img/cross.svg\" height=\"6px\" class=\"remove-label\"\n style=\"vertical-align: middle; padding: 4px 3px 6px 5px;\" />\n </span>`;\n}\n\n\nclass SearchBox{\n\n constructor() {\n\n this.searchQuery = [];\n this.queryTypes = []; //**** Types associated to query elements\n // Types: element (E), formula (F), symbol (S) and prop names\n this.currentOperator;\n\n this.element = document.createElement('div');\n this.element.className = 'search-box';\n \n this.element.innerHTML = ` \n <div class=\"search-query-wrapper\" >\n <div class=\"search-query-box\" style=\"float: left;\"></div>\n <button class=\"clean-btn\" style=\"float: right;\">Clear all</button>\n </div>\n\n <!-- this button should be out of the search box because its functionality\n is not part of the search box, it's general for the search module\n and it's probably being removed eventually --> \n <button class=\"search-btn\" >Search</button>\n `;\n\n this.cleanButton = this.element.querySelector('.clean-btn');\n this.cleanButton.addEventListener( \"click\", (e) => {\n this.searchQuery = [];\n this.queryTypes = [];\n this.updateSearchQuery();\n this.cleanSearchQueryListener();\n });\n\n this.searchQueryBox = this.element.querySelector('.search-query-box');\n this.searchQueryBox.addEventListener( \"click\", e => {\n let className = e.target.className;\n if (className === 'remove-label'){\n let elSymbol = e.target.parentElement.getAttribute('data-el');\n this.removeElementORFormulaInSearchQuery(elSymbol);\n }\n });\n \n }\n\n /**\n * Given the final query, this function uses boolean algebra to reduce it\n * into a flattened list of sets combined with AND or OR.\n */\n reduceQuery(query, types) {\n\n // For combining elements connected by AND into a list\n function combineANDIn(query, types) {\n const newQuery = []\n const newTypes = []\n for (let i=0; i < query.length;) {\n const q1 = query[i];\n const t1 = types[i];\n const q2 = query[i+1];\n const t2 = types[i+1];\n const q3 = query[i+2];\n const t3 = types[i+2];\n \n if ((t1 === \"E\" || t1 === \"EL\" ) && (t3 === \"E\" || t3 === \"EL\" ) && q2 === \"AND\") {\n let active = true\n let elements = [q1, q3]\n i = i + 3\n while(active) {\n const q4 = query[i];\n const t4 = types[i];\n const q5 = query[i+1];\n const t5 = types[i+1];\n if (q4 === \"AND\" && (t5 === \"E\" || t5 === \"EL\" )) {\n elements.push(q5)\n i += 2\n } else {\n active = false\n newQuery.push(elements.join(\", \"))\n newTypes.push(\"EL\")\n }\n }\n } else {\n newQuery.push(q1)\n newTypes.push(t1)\n i += 1\n }\n }\n return [newQuery, newTypes]\n }\n\n // For removing unnecessary quotes\n function removeParenthesis(query, types) {\n //console.log(query)\n //console.log(types)\n let removed = []\n for (let i=0; i < query.length; ++i) {\n const q1 = query[i];\n const t1 = types[i];\n if (q1 === \"(\") {\n //console.log(\"Start found!\")\n let index = 0;\n for (let j=i+1; j < query.length; ++j) {\n const q2 = query[j];\n const t2 = types[j];\n if (q2 === \"(\") {\n index += 1;\n }\n if (q2 === \")\") {\n //console.log(\"End found!\")\n if (index != 0) {\n index -= 1;\n } else {\n const contents = query.slice(i+1, j)\n //console.log(\"PARENTHESIS VALUES\")\n //console.log(contents)\n //console.log(i+1, j)\n const tp = types.slice(i+1, j)\n //console.log(\"PARENTHESIS TYPES\")\n //console.log(tp)\n let noop = true\n for (let t of tp) {\n if (t === \"S\") {\n noop = false\n break\n }\n }\n if (noop) {\n removed.push(j)\n removed.push(i)\n }\n break\n }\n }\n }\n }\n }\n\n // Remove found parentheses\n removed = removed.sort((a, b) => b - a);\n //console.log(query.join(\" \"));\n //console.log(removed);\n for (let j=0; j < removed.length; ++j) {\n query.splice(removed[j], 1);\n types.splice(removed[j], 1);\n }\n //console.log(query.join(\" \"));\n return [query, types]\n }\n\n // For combining elements connected by AND into a list\n function combineANDOut(query, types) {\n let newQuery = query\n let newTypes = types\n\n let combined = false\n for (let i=0; i < query.length; ++i) {\n\n // Look for AND surrounded by one or more parenthesis\n const q1 = query[i];\n const t1 = types[i];\n const q2 = query[i+1];\n const t2 = types[i+1];\n const q3 = query[i+2];\n const t3 = types[i+2];\n if (q2 === \"AND\" && (q1 === \")\" || q3 === \"(\" )) {\n combined = true\n\n // Get left hand and right hand side expressions\n let lhsQ\n let lhsT\n let rhsQ\n let rhsT\n lhsQ = query.slice(0, i+1)\n lhsT = types.slice(0, i+1)\n if (q1 !== \")\") {\n lhsQ.unshift(\"(\")\n lhsT.unshift(\"P\")\n lhsQ.push(\")\")\n lhsT.push(\"P\")\n }\n rhsQ = query.slice(i+2, query.length)\n rhsT = types.slice(i+2, query.length)\n if (q3 !== \"(\") {\n rhsQ.unshift(\"(\")\n rhsT.unshift(\"P\")\n rhsQ.push(\")\")\n rhsT.push(\"P\")\n }\n\n // Split the rhs into parts separated by OR\n const rhsParts = []\n let prevPos = 1\n for (let j=1; j < rhsQ.length - 1; ++j) {\n const sq1 = rhsQ[j];\n if (sq1 === \"OR\") {\n rhsParts.push([rhsQ.slice(prevPos, j), rhsT.slice(prevPos, j)])\n prevPos = j+1\n } else if (j === rhsQ.length - 2) {\n rhsParts.push([rhsQ.slice(prevPos, j+1), rhsT.slice(prevPos, j+1)])\n }\n }\n\n // Combine lhs with the different parts of rhs\n let comboQ = []\n let comboT = []\n for (let j=0; j < rhsParts.length; ++j) {\n let icomboQ = []\n let icomboT = []\n icomboQ = icomboQ.concat(rhsParts[j][0])\n icomboT = icomboT.concat(rhsParts[j][1])\n icomboQ.push(\"AND\")\n icomboT.push(\"S\")\n icomboQ = icomboQ.concat(lhsQ)\n icomboT = icomboT.concat(lhsT)\n if (j === 0) {\n if (rhsParts.length > 1) {\n comboQ.push(\"(\")\n comboT.push(\"P\")\n }\n comboQ = comboQ.concat(icomboQ)\n comboT = comboT.concat(icomboT)\n if (rhsParts.length > 1) {\n comboQ.push(\")\")\n comboT.push(\"P\")\n }\n } else {\n comboQ.push(\"OR\")\n comboT.push(\"S\")\n comboQ.push(\"(\")\n comboT.push(\"P\")\n comboQ = comboQ.concat(icomboQ)\n comboT = comboT.concat(icomboT)\n comboQ.push(\")\")\n comboT.push(\"P\")\n }\n }\n\n console.log(newQuery.join(\" \"));\n newQuery = comboQ\n newTypes = comboT\n break\n }\n }\n return [newQuery, newTypes, combined]\n }\n \n // Merge\n function merge(query, types) {\n\n // Remove unnecessary parenthesis\n [query, types] = removeParenthesis(query, types);\n\n // Combined all elements connected with AND\n [query, types] = combineANDIn(query, types);\n\n // Combine parenthesis statements\n let combined\n [query, types, combined] = combineANDOut(query, types);\n\n // If a combination was done, run recursively until no combination is\n // done\n if (combined) {\n [query, types] = merge(query, types)\n } else {\n [query, types] = removeParenthesis(query, types);\n [query, types] = combineANDIn(query, types);\n }\n return [query, types]\n }\n [query, types] = merge(query, types);\n console.log(query.join(\" \"));\n }\n\n\n getOptimadeQuery(allowOtherElements){\n\n this.reduceQuery(this.searchQuery, this.queryTypes)\n\n console.log('getOptimadeQuery this.searchQuery', this.searchQuery)\n\n if (this.searchQuery.length === 0 || \n !isQueryWellFormed(this.searchQuery)) return '';\n\n else if (this.searchQuery.length === 1 && this.queryTypes[0] === 'MN'){ // material case\n return `material_name=\"${this.searchQuery[0]}\"`;\n\n }else{ // elements and formulas\n let searchQueryMod = [];\n let parOptimadeQueries = [];\n let openIndex = -1;\n let parCounter = 0;\n this.searchQuery.forEach( (item, i) => {\n\n if ( this.searchQuery[i] === '(' ){\n openIndex = i;\n }\n else if ( this.searchQuery[i] === ')' ){\n searchQueryMod.push('('+parCounter);\n const parSearchQuery = this.searchQuery.slice(openIndex+1, i);\n //console.log('parSearchQuery: ',i, parSearchQuery, getOptimadeFromSearchQuery(allowOtherElements, parSearchQuery));\n parOptimadeQueries.push(getOptimadeFromSearchQuery(allowOtherElements, parSearchQuery))\n openIndex = -1;\n parCounter++;\n\n }else if (openIndex < 0) searchQueryMod.push(item);\n });\n // console.log('FFFFFFFFF searchQueryMod: ', searchQueryMod, parOptimadeQueries, getOptimadeFromSearchQuery(allowOtherElements, searchQueryMod, parOptimadeQueries));\n\n\n //********* Not valid expressions\n\n // return getOptimadeFromSearchQuery(allowOtherElements, this.searchQuery);\n return getOptimadeFromSearchQuery(allowOtherElements, searchQueryMod, parOptimadeQueries);\n } \n\n\n function getOptimadeFromSearchQuery(allowOtherElements, searchQuery, optimadeSubqueries){\n\n let optimadeQuery = '';\n\n // Get the subqueries: parts of the query separated by ORs\n const subqueries = [];\n let currentSubquery = [];\n subqueries.push(currentSubquery);\n\n searchQuery.forEach( (item, i) => {\n\n if (searchQuery[i] === 'OR'){\n currentSubquery = []; // new subquery\n subqueries.push(currentSubquery);\n }else\n currentSubquery.push(item);\n\n });\n console.log(\"getOptimadeQuery subqueries: \", subqueries);\n\n // Subqueries are either elements or formulas separated by ANDs\n subqueries.forEach( (subquery, i) => {\n\n if (i > 0) optimadeQuery += ' OR '\n\n if (allowOtherElements){ // Inclusive search\n \n subquery.forEach( (item, i) => { // For every item in the subquery\n if (item.charAt(0) === '('){\n // console.log('OPPPPP', optimadeSubqueries[+item.charAt(1)])\n optimadeQuery += `(${optimadeSubqueries[+item.charAt(1)]})`;\n\n }else if ( item === 'AND' || item === 'NOT'){\n optimadeQuery += ' '+item+' ';\n\n }else if ( isElement(item) ){\n optimadeQuery += `elements HAS \"${item}\"`\n \n }else{ // Formula\n let formula = new Formula(item);\n optimadeQuery += formula.getOptimadeSubquery(allowOtherElements);// optimadeQuery += ` formula=\"${this.searchQuery[i]}\"`\n }\n });\n\n }else{ // Exclusive search. For now only one formula or elements subqueries supported. Operator NOT doesn't make sense here\n\n const items = [];\n const notItems = [];\n let notItem = false;\n let areElements = false;\n subquery.forEach( (item) => {\n if (isElement(item)) areElements = true;\n if (item === 'NOT'){\n notItem = true;\n }else if (item !== 'AND'){ // item = element or formula\n if (notItem){\n notItem = false;\n if (areElements) notItems.push(`\"${item}\"`);\n else // formula\n notItems.push(new Formula(item).getFragments());\n\n }else{\n if (areElements) items.push(`\"${item}\"`);\n else // formula\n items.push(new Formula(item).getFragments());\n }\n \n } \n }); \n // console.log('Exclusive search: ', items)\n optimadeQuery += (areElements ? 'elements' : 'formula') + ' HAS ONLY '+ items.join(', ');\n\n }\n\n }); \n return optimadeQuery;\n }\n\n\n function isElement(item){\n return util.ELEMENTS.includes(item)\n }\n\n/* NOt used anymore??\n function getOptimadeElementExclusiveANDSubquery(subquery){ \n const elements = []\n subquery.forEach( (item) => {\n if (isElement(item)) elements.push(`\"${item}\"`);\n }); \n return 'elements HAS ONLY '+elements.join(', ');\n }\n \n\n function getOptimadeFormulaExclusiveANDSubquery(subquery){ \n const fragments = [];\n subquery.forEach( (item) => {\n if (!isElement(item) && item !== 'AND' && item !== 'NOT'){// Is formula\n fragments.push(new Formula(item).getFragments());\n }\n }); \n return 'formula HAS ONLY '+fragments.join(', ');\n }\n */\n\n function isQueryWellFormed(searchQuery){\n const openingParIndex = searchQuery.indexOf('(')\n const closingParIndex = searchQuery.indexOf(')')\n return searchQuery[searchQuery.length-1] !== 'NOT' && \n ((openingParIndex < 0) || // there are no paratheses or\n // they are in right position\n (openingParIndex >= 0 && openingParIndex < closingParIndex+1)) \n }\n\n }\n\n\n setBoolOperator(operator){\n this.currentOperator = operator;\n }\n\n\n updateSearchQuery(){\n let html= '';\n for (let i = 0; i < this.searchQuery.length; i++) {\n let type = this.queryTypes[i];\n\n if (type === 'S' || type === 'P')\n html+= `<span class=\"search-query-symbol\" > ${this.searchQuery[i]} </span>`;\n else\n html+= getTagHtml(this.searchQuery[i], ( type === 'F' ? true : false));\n }\n console.log('this.updateSearchQuery: ', this.searchQuery ,this.queryTypes);\n this.searchQueryBox.innerHTML = html;\n if (this.searchQueryChangeListener) this.searchQueryChangeListener();\n }\n\n\n addItemInSearchQuery(item, type){\n this.searchQuery.push(item);\n this.queryTypes.push(type);\n }\n\n\n addTag(tag, type){\n // If the it's an element and is already in the query it's not inserted\n if (type === 'E' && this.searchQuery.indexOf(tag) >= 0) return;\n\n if ( this.searchQuery.length > 0\n && this.searchQuery[this.searchQuery.length-1] !== '('\n && this.searchQuery[this.searchQuery.length-1] !== 'NOT' )\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery(tag, type);\n this.updateSearchQuery();\n }\n\n\n addElements(elementArray){\n let index = elementArray.length;\n while (index--) {\n this.addTag(elementArray[index], 'E');\n }\n return true;\n }\n\n\n addParentheses(isOpen){\n\n if ( this.searchQuery.length > 0 && isOpen)\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery( (isOpen ? '(' : ')'), 'P');\n this.updateSearchQuery();\n }\n\n\n addNOT(){\n\n if ( this.searchQuery.length > 0)\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery( 'NOT', 'S');\n this.updateSearchQuery();\n }\n\n\n removeElementORFormulaInSearchQuery(item){\n //console.log(\" removeElementORFormulaInSearchQuery item: \",item, this.searchQuery.indexOf(item));\n \n // NOT being used let isMaterialName = (this.queryTypes[0] === 'MN');\n \n // spot the item and remove the item and the bool operator(s) related\n let itemIndex = this.searchQuery.indexOf(item);\n if (itemIndex >= 0){\n let i, elementsToRemove;\n /*\n if (this.queryTypes[itemIndex+1] === 'S'){ // bool operator on the right\n console.log('itemIndex', itemIndex, this.searchQuery[itemIndex+1])\n if (this.searchQuery[itemIndex-1] === 'NOT'){\n i = itemIndex-1; elementsToRemove = 3;\n }else{ i = itemIndex; elementsToRemove = 2; }\n\n }else*/\n if (this.queryTypes[itemIndex-1] === 'S'){ // bool operator on the left\n // console.log('itemIndex', itemIndex, this.searchQuery[itemIndex-1])\n if (this.searchQuery[itemIndex-1] === 'NOT'){\n i = itemIndex-2; elementsToRemove = 3;\n }else{ i = itemIndex-1; elementsToRemove = 2; }\n \n }else{ // case: (item)\n i = itemIndex; elementsToRemove = 1;\n }\n \n removeItemsFromSearchQuery(this, i, elementsToRemove); \n }\n\n // Remove the symbols before the first element/formula\n if (this.queryTypes[0] === 'S') { // AND , OR\n let elementsToRemove = 1;\n if (this.queryTypes[1] === 'S') elementsToRemove = 2; // NOT\n removeItemsFromSearchQuery(this, 0, elementsToRemove);\n }\n\n // Travese the array removing the unnecessary parethesis (only tested for one level nested)\n if ( this.searchQuery.indexOf('(') >= 0){ // Recursion\n\n for (let i = 0; i < this.searchQuery.length; i++) { // dangerous: modifing a array being traversed\n if ( this.searchQuery[i] === '(' ){\n if ( this.searchQuery[i+1] === ')'){ // '()' case\n removeItemsFromSearchQuery(this, i, 2);// this.searchQuery.splice(i, 2); this.queryTypes.splice(i, 2);\n }else if (this.searchQuery[i+2] === ')'){ // '(item)' case\n this.searchQuery.splice(i, 3, this.searchQuery[i+1]);\n this.queryTypes.splice(i, 3, this.queryTypes[i+1]);\n }\n }\n }\n }\n\n this.updateSearchQuery();\n\n if (util.ELEMENTS.indexOf(item) >= 0){ // It's an element (being removed)\n this.removeElementListener(item);\n }\n\n if (this.queryTypes.length === 0){ // The search query gets blank\n this.cleanSearchQueryListener();\n }\n\n return true;\n\n\n function removeItemsFromSearchQuery(self, start, delCount){\n self.searchQuery.splice(start, delCount);\n self.queryTypes.splice(start, delCount);\n }\n }\n\n \n setCleanSearchQueryListener(listener){\n this.cleanSearchQueryListener = listener;\n }\n\n\n setRemoveElementListener(listener) {\n this.removeElementListener = listener;\n }\n\n setSearchQueryChangeListener(listener) {\n this.searchQueryChangeListener = listener;\n }\n\n getSearchButtonElement(){\n return this.element.querySelector('.search-btn');\n }\n\n \n}\n\n\nclass Formula{\n\n constructor(formula) {\n\n this.formula = formula;\n this.formulaMap = this._parseFormula(formula);\n console.log('this.formulaMap: ', this.formulaMap);\n\n }\n\n\n _parseFormula(formula){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n return map;\n }\n\n getOptimadeSubquery(allowOtherElements) {\n const fragments = []\n this.formulaMap.forEach( (number, element)=> {\n const fragment = '\"' + element + (number === 1 ? '' : +number) + '\"';\n fragments.push(fragment)\n })\n return 'formula HAS ' + (allowOtherElements ? 'ALL ' : 'ONLY ') + fragments.join(', ')\n }\n\n getFragments() {\n const fragments = []\n this.formulaMap.forEach( (number, element)=> {\n const fragment = '\"' + element + (number === 1 ? '' : +number) + '\"';\n fragments.push(fragment);\n })\n return fragments;\n }\n\n\n/*\n getReducedFormula(getTokens = true){\n let counter = 0;\n while ( !checkIfReduced(this.formulaMap) ){ // console.log('Reducing', this.formulaMap);\n let div = 1;\n if (isDivisibleBy(this.formulaMap, 2)) div = 2;\n else if (isDivisibleBy(this.formulaMap, 3)) div = 3;\n else if (isDivisibleBy(this.formulaMap, 5)) div = 5;\n else if (isDivisibleBy(this.formulaMap, 7)) div = 7;\n else if (isDivisibleBy(this.formulaMap, 11)) div = 11;\n\n this.formulaMap.forEach( (value, key) => {\n this.formulaMap.set(key, (value/div));\n });\n //console.log('Reducing DIV', this.formulaMap);\n counter++;\n if (counter > 5) break;\n }\n\n function checkIfReduced(formulaMap){\n let min = 100;\n formulaMap.forEach( (value, key) => {\n if (value < min) min = value;\n });\n return min === 1;\n }\n\n function isDivisibleBy(formulaMap, n){\n let div = true;\n formulaMap.forEach( (value, key) => {\n if (value % n !== 0) div = false;\n });\n return div;\n }\n\n let tokens = [];\n let canonicalFormula = '';\n if (getTokens){\n this.formulaMap.forEach( (value, key) => tokens.push(key+value) );\n }else{\n let sortedElements = this._sortElements( Array.from( this.formulaMap.keys() ) );\n sortedElements.forEach( element => {\n canonicalFormula += element+this.formulaMap.get(element);\n //canonicalFormula += element+(map.get(element) === 1 ? '' : map.get(element));\n });\n }\n\n console.log('_reduceFormula RETURN: ', this.formulaMap, tokens, canonicalFormula);\n return (getTokens ? tokens : canonicalFormula);\n }\n*/\n\n}\n\n// EXPORTS\nmodule.exports = SearchBox;\n\n\n//# sourceURL=webpack:///./src/search-mod/SearchBox.view.js?");
eval("/**\n * Copyright 2016-2020 Iker Hurtado\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n\n /*\n This is the UI component implementing the search box.\n It represents the search query for the user and enables its edition.\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\n//\n//\n//// local utility functions\nfunction getTagHtml(tag, isFormula){\n return `<span class=\"search-label\" data-el=\"${tag}\" >\n <img src=\"img/tag.svg\" height=\"16px\" class=\"remove-label\"\n style=\"vertical-align: bottom\"/>\n ${isFormula ? util.getSubscriptedFormula(tag) : tag}\n <img src=\"img/cross.svg\" height=\"6px\" class=\"remove-label\"\n style=\"vertical-align: middle; padding: 4px 3px 6px 5px;\" />\n </span>`;\n}\n\n\nclass SearchBox{\n\n constructor() {\n\n this.searchQuery = [];\n this.queryTypes = []; //**** Types associated to query elements\n // Types: element (E), formula (F), symbol (S) and prop names\n this.currentOperator;\n\n this.element = document.createElement('div');\n this.element.className = 'search-box';\n \n this.element.innerHTML = ` \n <div class=\"search-query-wrapper\" >\n <div class=\"search-query-box\" style=\"float: left;\"></div>\n <button class=\"clean-btn\" style=\"float: right;\">Clear all</button>\n </div>\n\n <!-- this button should be out of the search box because its functionality\n is not part of the search box, it's general for the search module\n and it's probably being removed eventually --> \n <button class=\"search-btn\" >Search</button>\n `;\n\n this.cleanButton = this.element.querySelector('.clean-btn');\n this.cleanButton.addEventListener( \"click\", (e) => {\n this.searchQuery = [];\n this.queryTypes = [];\n this.updateSearchQuery();\n this.cleanSearchQueryListener();\n });\n\n this.searchQueryBox = this.element.querySelector('.search-query-box');\n this.searchQueryBox.addEventListener( \"click\", e => {\n let className = e.target.className;\n if (className === 'remove-label'){\n let elSymbol = e.target.parentElement.getAttribute('data-el');\n this.removeElementORFormulaInSearchQuery(elSymbol);\n }\n });\n \n }\n\n getOptimadeQuery2(allowOtherElements) {\n let query = [...this.searchQuery];\n let types = [...this.queryTypes];\n\n // For combining elements connected by AND into a list\n function combineANDIn(query, types) {\n const newQuery = []\n const newTypes = []\n for (let i=0; i < query.length;) {\n const q0 = query[i-1];\n const t0 = types[i-1];\n const q1 = query[i];\n const t1 = types[i];\n const q2 = query[i+1];\n const t2 = types[i+1];\n const q3 = query[i+2];\n const t3 = types[i+2];\n \n if ((t1 === \"E\" || t1 === \"EL\" ) && (t3 === \"E\" || t3 === \"EL\" ) && q2 === \"AND\" && q0 !== \"NOT\") {\n let active = true\n let elements = [q1, q3]\n i = i + 3\n while(active) {\n const q4 = query[i];\n const t4 = types[i];\n const q5 = query[i+1];\n const t5 = types[i+1];\n if (q4 === \"AND\" && (t5 === \"E\" || t5 === \"EL\" )) {\n elements.push(q5)\n i += 2\n } else {\n active = false\n newQuery.push(elements.join(\", \"))\n newTypes.push(\"EL\")\n }\n }\n } else {\n newQuery.push(q1)\n newTypes.push(t1)\n i += 1\n }\n }\n return [newQuery, newTypes]\n }\n\n // For finding parenthesis pairs\n function findPairs(query, types) {\n const pairs = new Map();\n\n // Find list of indices where parenthesis should be removed\n let removed = []\n for (let i=0; i < query.length; ++i) {\n const q1 = query[i];\n const t1 = types[i];\n if (q1 === \"(\") {\n let index = 0;\n for (let j=i+1; j < query.length; ++j) {\n const q2 = query[j];\n const t2 = types[j];\n if (q2 === \"(\") {\n index += 1;\n }\n if (q2 === \")\") {\n if (index != 0) {\n index -= 1;\n } else {\n pairs.set(i, j)\n pairs.set(j, i)\n break\n }\n }\n }\n }\n }\n return pairs;\n }\n\n // For removing unnecessary parenthesis\n function removeParenthesis(query, types) {\n\n // Find list of indices where parenthesis should be removed\n const pairs = findPairs(query, types)\n let removed = []\n for (let i=0; i < query.length; ++i) {\n const q1 = query[i];\n const t1 = types[i];\n if (q1 === \"(\") {\n let p = pairs.get(i)\n\n // Remove outer parentheses\n if (i === 0 && p == query.length-1) {\n removed.push(p)\n removed.push(i)\n // Remove parentheses that do not contain multiple operations\n } else {\n const contents = query.slice(i+1, p)\n const tp = types.slice(i+1, p)\n let noop = true\n for (let t of tp) {\n if (t === \"S\") {\n noop = false\n break\n }\n }\n if (noop) {\n removed.push(p)\n removed.push(i)\n }\n }\n }\n }\n\n // Find repeated quotes and reduce them to single one\n for (let i=0; i < query.length; ++i) {\n const q1 = query[i];\n const t1 = types[i];\n const q2 = query[i+1];\n const t2 = types[i+1];\n if (q1 === \"(\" && q1 === \"(\") {\n let p1 = pairs.get(i)\n let p2 = pairs.get(i+1)\n if (p2 == p1 - 1) {\n removed.push(i)\n removed.push(p1)\n }\n }\n }\n\n // Remove found parentheses\n removed = removed.sort((a, b) => b - a);\n for (let j=0; j < removed.length; ++j) {\n query.splice(removed[j], 1);\n types.splice(removed[j], 1);\n }\n return [query, types]\n }\n\n // Combines AND statements where either one or both of the statements has a\n // parenthesis. These statements are refactored using distributive laws of\n // set theory:\n // A AND (B OR C) == (A AND B) OR (A AND C)\n // in order to see if there are lists of elements that should be joined.\n function combineANDOut(query, types) {\n let newQuery = query\n let newTypes = types\n const pairs = findPairs(query, types)\n\n let combined = false\n for (let i=0; i < query.length; ++i) {\n\n // Look for AND surrounded by one or more parenthesis\n const q1 = query[i];\n const t1 = types[i];\n const q2 = query[i+1];\n const t2 = types[i+1];\n const q3 = query[i+2];\n const t3 = types[i+2];\n if (q2 === \"AND\" && (q1 === \")\" || q3 === \"(\" )) {\n combined = true\n\n // Get left hand and right hand side expressions\n let lhsQ\n let lhsT\n let rhsQ\n let rhsT\n let rhsEnd\n let lhsStart\n if (q1 === \")\") {\n lhsStart = pairs.get(i)\n lhsQ = query.slice(lhsStart, i+1)\n lhsT = types.slice(lhsStart, i+1)\n } else {\n if (query[i-1] === \"NOT\") {\n lhsStart = i-1\n } else {\n lhsStart = i\n }\n lhsQ = query.slice(lhsStart, i+1)\n lhsT = types.slice(lhsStart, i+1)\n lhsQ.unshift(\"(\")\n lhsT.unshift(\"P\")\n lhsQ.push(\")\")\n lhsT.push(\"P\")\n }\n if (q3 === \"(\") {\n rhsEnd = pairs.get(i+2)+1\n rhsQ = query.slice(i+2, rhsEnd)\n rhsT = types.slice(i+2, rhsEnd)\n } else {\n if (query[i+2] === \"NOT\") {\n rhsEnd = i+4\n } else {\n rhsEnd = i+3\n }\n rhsQ = query.slice(i+2, rhsEnd)\n rhsT = types.slice(i+2, rhsEnd)\n rhsQ.unshift(\"(\")\n rhsT.unshift(\"P\")\n rhsQ.push(\")\")\n rhsT.push(\"P\")\n }\n\n // Split the rhs into parts separated by OR\n const rhsParts = []\n let prevPos = 1\n for (let j=1; j < rhsQ.length - 1; ++j) {\n const sq1 = rhsQ[j];\n if (sq1 === \"OR\") {\n rhsParts.push([rhsQ.slice(prevPos, j), rhsT.slice(prevPos, j)])\n prevPos = j+1\n } else if (j === rhsQ.length - 2) {\n rhsParts.push([rhsQ.slice(prevPos, j+1), rhsT.slice(prevPos, j+1)])\n }\n }\n\n // Combine lhs with the different parts of rhs\n let comboQ = []\n let comboT = []\n for (let j=0; j < rhsParts.length; ++j) {\n let icomboQ = []\n let icomboT = []\n icomboQ = icomboQ.concat(rhsParts[j][0])\n icomboT = icomboT.concat(rhsParts[j][1])\n icomboQ.push(\"AND\")\n icomboT.push(\"S\")\n icomboQ = icomboQ.concat(lhsQ)\n icomboT = icomboT.concat(lhsT)\n if (j === 0) {\n if (rhsParts.length > 1) {\n comboQ.push(\"(\")\n comboT.push(\"P\")\n }\n comboQ = comboQ.concat(icomboQ)\n comboT = comboT.concat(icomboT)\n if (rhsParts.length > 1) {\n comboQ.push(\")\")\n comboT.push(\"P\")\n }\n } else {\n comboQ.push(\"OR\")\n comboT.push(\"S\")\n comboQ.push(\"(\")\n comboT.push(\"P\")\n comboQ = comboQ.concat(icomboQ)\n comboT = comboT.concat(icomboT)\n comboQ.push(\")\")\n comboT.push(\"P\")\n }\n }\n comboQ.unshift(\"(\")\n comboT.unshift(\"P\")\n comboQ.push(\")\")\n comboT.push(\"P\")\n newQuery = query.slice(0, lhsStart).concat(comboQ).concat(query.slice(rhsEnd, query.length))\n newTypes = types.slice(0, lhsStart).concat(comboT).concat(types.slice(rhsEnd, query.length))\n break\n }\n }\n return [newQuery, newTypes, combined]\n }\n \n // Merge\n function merge(query, types) {\n\n // Remove unnecessary parenthesis, combine all elements connected with\n // AND, and remove unnecessary parenthesis again.\n [query, types] = removeParenthesis(query, types);\n [query, types] = combineANDIn(query, types);\n [query, types] = removeParenthesis(query, types);\n\n // Combine parenthesis statements\n let combined\n [query, types, combined] = combineANDOut(query, types);\n\n // If a combination was done, run recursively until no combination is\n // done\n if (combined) {\n [query, types] = merge(query, types)\n // After the last step perform on final cleanup\n } else {\n [query, types] = removeParenthesis(query, types);\n [query, types] = combineANDIn(query, types);\n [query, types] = removeParenthesis(query, types);\n }\n return [query, types]\n }\n\n // Add escaped quotes to all elements\n for (let i=0; i < query.length; ++i) {\n let q = query[i];\n let t = types[i];\n if (t === \"E\") {\n query[i] = `\\\"${q}\\\"`\n }\n }\n\n // Add escaped quotes to formulas\n for (let i=0; i < query.length; ++i) {\n let q = query[i];\n let t = types[i];\n if (t === \"F\") {\n const formula = new Formula(q)\n const fragments = []\n formula.formulaMap.forEach( (number, element)=> {\n const fragment = '\"' + element + (number === 1 ? '' : +number) + '\"';\n fragments.push(fragment)\n })\n query[i] = fragments.join(', ')\n }\n }\n\n // Construct the reduced expression\n [query, types] = merge(query, types);\n\n // Convert into optimade syntax\n for (let i=0; i < query.length; ++i) {\n let q = query[i];\n let t = types[i];\n if (t === \"E\" || t === \"EL\") {\n if (allowOtherElements) {\n q = \"elements HAS ALL \" + q\n } else {\n q = \"elements HAS ONLY \" + q\n }\n } else if (t === \"F\") {\n if (allowOtherElements) {\n q = \"formula HAS ALL \" + q\n } else {\n q = \"formula HAS ONLY \" + q\n }\n }\n query[i] = q;\n }\n\n let res = query.join(\" \")\n console.log(\"FINAL OPTIMADE QUERY:\")\n console.log(res)\n return res\n }\n\n\n getOptimadeQuery(allowOtherElements){\n\n console.log('getOptimadeQuery this.searchQuery', this.searchQuery)\n\n if (this.searchQuery.length === 0 || \n !isQueryWellFormed(this.searchQuery)) return '';\n\n else if (this.searchQuery.length === 1 && this.queryTypes[0] === 'MN'){ // material case\n return `material_name=\"${this.searchQuery[0]}\"`;\n\n }else{ // elements and formulas\n let searchQueryMod = [];\n let parOptimadeQueries = [];\n let openIndex = -1;\n let parCounter = 0;\n this.searchQuery.forEach( (item, i) => {\n\n if ( this.searchQuery[i] === '(' ){\n openIndex = i;\n }\n else if ( this.searchQuery[i] === ')' ){\n searchQueryMod.push('('+parCounter);\n const parSearchQuery = this.searchQuery.slice(openIndex+1, i);\n //console.log('parSearchQuery: ',i, parSearchQuery, getOptimadeFromSearchQuery(allowOtherElements, parSearchQuery));\n parOptimadeQueries.push(getOptimadeFromSearchQuery(allowOtherElements, parSearchQuery))\n openIndex = -1;\n parCounter++;\n\n }else if (openIndex < 0) searchQueryMod.push(item);\n });\n // console.log('FFFFFFFFF searchQueryMod: ', searchQueryMod, parOptimadeQueries, getOptimadeFromSearchQuery(allowOtherElements, searchQueryMod, parOptimadeQueries));\n\n\n //********* Not valid expressions\n\n // return getOptimadeFromSearchQuery(allowOtherElements, this.searchQuery);\n return getOptimadeFromSearchQuery(allowOtherElements, searchQueryMod, parOptimadeQueries);\n } \n\n\n function getOptimadeFromSearchQuery(allowOtherElements, searchQuery, optimadeSubqueries){\n\n let optimadeQuery = '';\n\n // Get the subqueries: parts of the query separated by ORs\n const subqueries = [];\n let currentSubquery = [];\n subqueries.push(currentSubquery);\n\n searchQuery.forEach( (item, i) => {\n\n if (searchQuery[i] === 'OR'){\n currentSubquery = []; // new subquery\n subqueries.push(currentSubquery);\n }else\n currentSubquery.push(item);\n\n });\n console.log(\"getOptimadeQuery subqueries: \", subqueries);\n\n // Subqueries are either elements or formulas separated by ANDs\n subqueries.forEach( (subquery, i) => {\n\n if (i > 0) optimadeQuery += ' OR '\n\n if (allowOtherElements){ // Inclusive search\n \n subquery.forEach( (item, i) => { // For every item in the subquery\n if (item.charAt(0) === '('){\n // console.log('OPPPPP', optimadeSubqueries[+item.charAt(1)])\n optimadeQuery += `(${optimadeSubqueries[+item.charAt(1)]})`;\n\n }else if ( item === 'AND' || item === 'NOT'){\n optimadeQuery += ' '+item+' ';\n\n }else if ( isElement(item) ){\n optimadeQuery += `elements HAS \"${item}\"`\n \n }else{ // Formula\n let formula = new Formula(item);\n optimadeQuery += formula.getOptimadeSubquery(allowOtherElements);// optimadeQuery += ` formula=\"${this.searchQuery[i]}\"`\n }\n });\n\n }else{ // Exclusive search. For now only one formula or elements subqueries supported. Operator NOT doesn't make sense here\n\n const items = [];\n const notItems = [];\n let notItem = false;\n let areElements = false;\n subquery.forEach( (item) => {\n if (isElement(item)) areElements = true;\n if (item === 'NOT'){\n notItem = true;\n }else if (item !== 'AND'){ // item = element or formula\n if (notItem){\n notItem = false;\n if (areElements) notItems.push(`\"${item}\"`);\n else // formula\n notItems.push(new Formula(item).getFragments());\n\n }else{\n if (areElements) items.push(`\"${item}\"`);\n else // formula\n items.push(new Formula(item).getFragments());\n }\n \n } \n }); \n // console.log('Exclusive search: ', items)\n optimadeQuery += (areElements ? 'elements' : 'formula') + ' HAS ONLY '+ items.join(', ');\n\n }\n\n }); \n return optimadeQuery;\n }\n\n\n function isElement(item){\n return util.ELEMENTS.includes(item)\n }\n\n/* NOt used anymore??\n function getOptimadeElementExclusiveANDSubquery(subquery){ \n const elements = []\n subquery.forEach( (item) => {\n if (isElement(item)) elements.push(`\"${item}\"`);\n }); \n return 'elements HAS ONLY '+elements.join(', ');\n }\n \n\n function getOptimadeFormulaExclusiveANDSubquery(subquery){ \n const fragments = [];\n subquery.forEach( (item) => {\n if (!isElement(item) && item !== 'AND' && item !== 'NOT'){// Is formula\n fragments.push(new Formula(item).getFragments());\n }\n }); \n return 'formula HAS ONLY '+fragments.join(', ');\n }\n */\n\n function isQueryWellFormed(searchQuery){\n const openingParIndex = searchQuery.indexOf('(')\n const closingParIndex = searchQuery.indexOf(')')\n return searchQuery[searchQuery.length-1] !== 'NOT' && \n ((openingParIndex < 0) || // there are no paratheses or\n // they are in right position\n (openingParIndex >= 0 && openingParIndex < closingParIndex+1)) \n }\n\n }\n\n\n setBoolOperator(operator){\n this.currentOperator = operator;\n }\n\n\n updateSearchQuery(){\n let html= '';\n for (let i = 0; i < this.searchQuery.length; i++) {\n let type = this.queryTypes[i];\n\n if (type === 'S' || type === 'P')\n html+= `<span class=\"search-query-symbol\" > ${this.searchQuery[i]} </span>`;\n else\n html+= getTagHtml(this.searchQuery[i], ( type === 'F' ? true : false));\n }\n console.log('this.updateSearchQuery: ', this.searchQuery ,this.queryTypes);\n this.searchQueryBox.innerHTML = html;\n if (this.searchQueryChangeListener) this.searchQueryChangeListener();\n }\n\n\n addItemInSearchQuery(item, type){\n this.searchQuery.push(item);\n this.queryTypes.push(type);\n }\n\n\n addTag(tag, type){\n // If the it's an element and is already in the query it's not inserted\n if (type === 'E' && this.searchQuery.indexOf(tag) >= 0) return;\n\n if ( this.searchQuery.length > 0\n && this.searchQuery[this.searchQuery.length-1] !== '('\n && this.searchQuery[this.searchQuery.length-1] !== 'NOT' )\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery(tag, type);\n this.updateSearchQuery();\n }\n\n\n addElements(elementArray){\n let index = elementArray.length;\n while (index--) {\n this.addTag(elementArray[index], 'E');\n }\n return true;\n }\n\n\n addParentheses(isOpen){\n\n if ( this.searchQuery.length > 0 && isOpen)\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery( (isOpen ? '(' : ')'), 'P');\n this.updateSearchQuery();\n }\n\n\n addNOT(){\n\n if ( this.searchQuery.length > 0)\n this.addItemInSearchQuery(this.currentOperator, 'S');\n this.addItemInSearchQuery( 'NOT', 'S');\n this.updateSearchQuery();\n }\n\n\n removeElementORFormulaInSearchQuery(item){\n //console.log(\" removeElementORFormulaInSearchQuery item: \",item, this.searchQuery.indexOf(item));\n \n // NOT being used let isMaterialName = (this.queryTypes[0] === 'MN');\n \n // spot the item and remove the item and the bool operator(s) related\n let itemIndex = this.searchQuery.indexOf(item);\n if (itemIndex >= 0){\n let i, elementsToRemove;\n /*\n if (this.queryTypes[itemIndex+1] === 'S'){ // bool operator on the right\n console.log('itemIndex', itemIndex, this.searchQuery[itemIndex+1])\n if (this.searchQuery[itemIndex-1] === 'NOT'){\n i = itemIndex-1; elementsToRemove = 3;\n }else{ i = itemIndex; elementsToRemove = 2; }\n\n }else*/\n if (this.queryTypes[itemIndex-1] === 'S'){ // bool operator on the left\n // console.log('itemIndex', itemIndex, this.searchQuery[itemIndex-1])\n if (this.searchQuery[itemIndex-1] === 'NOT'){\n i = itemIndex-2; elementsToRemove = 3;\n }else{ i = itemIndex-1; elementsToRemove = 2; }\n \n }else{ // case: (item)\n i = itemIndex; elementsToRemove = 1;\n }\n \n removeItemsFromSearchQuery(this, i, elementsToRemove); \n }\n\n // Remove the symbols before the first element/formula\n if (this.queryTypes[0] === 'S') { // AND , OR\n let elementsToRemove = 1;\n if (this.queryTypes[1] === 'S') elementsToRemove = 2; // NOT\n removeItemsFromSearchQuery(this, 0, elementsToRemove);\n }\n\n // Travese the array removing the unnecessary parethesis (only tested for one level nested)\n if ( this.searchQuery.indexOf('(') >= 0){ // Recursion\n\n for (let i = 0; i < this.searchQuery.length; i++) { // dangerous: modifing a array being traversed\n if ( this.searchQuery[i] === '(' ){\n if ( this.searchQuery[i+1] === ')'){ // '()' case\n removeItemsFromSearchQuery(this, i, 2);// this.searchQuery.splice(i, 2); this.queryTypes.splice(i, 2);\n }else if (this.searchQuery[i+2] === ')'){ // '(item)' case\n this.searchQuery.splice(i, 3, this.searchQuery[i+1]);\n this.queryTypes.splice(i, 3, this.queryTypes[i+1]);\n }\n }\n }\n }\n\n this.updateSearchQuery();\n\n if (util.ELEMENTS.indexOf(item) >= 0){ // It's an element (being removed)\n this.removeElementListener(item);\n }\n\n if (this.queryTypes.length === 0){ // The search query gets blank\n this.cleanSearchQueryListener();\n }\n\n return true;\n\n\n function removeItemsFromSearchQuery(self, start, delCount){\n self.searchQuery.splice(start, delCount);\n self.queryTypes.splice(start, delCount);\n }\n }\n\n \n setCleanSearchQueryListener(listener){\n this.cleanSearchQueryListener = listener;\n }\n\n\n setRemoveElementListener(listener) {\n this.removeElementListener = listener;\n }\n\n setSearchQueryChangeListener(listener) {\n this.searchQueryChangeListener = listener;\n }\n\n getSearchButtonElement(){\n return this.element.querySelector('.search-btn');\n }\n\n \n}\n\n\nclass Formula{\n\n constructor(formula) {\n\n this.formula = formula;\n this.formulaMap = this._parseFormula(formula);\n console.log('this.formulaMap: ', this.formulaMap);\n\n }\n\n\n _parseFormula(formula){\n let index = 0;\n let map = new Map();\n let key;\n while ( index < formula.length ){\n let el2 = formula.substring(index, index+2);\n let el1 = formula.substring(index, index+1);\n\n if (util.ELEMENTS.indexOf(el2) >= 0){\n map.set(el2, 1); // 1 default value\n index += 2;\n key = el2;\n //console.log('eleemnt 2chars', key);\n }else if (util.ELEMENTS.indexOf(el1) >= 0){\n map.set(el1, 1); // 1 default value\n index++;\n key = el1;\n //console.log('eleemnt 1chars', key);\n }else{ // It's a number\n let num = parseInt(el2);\n if (num >= 10) index += 2; // 2 figures number\n else index++;// 1 figure number\n //console.log('number ', num, key);\n map.set(key, num);\n }\n // console.log('FINAL LOOP', map, index);\n }\n return map;\n }\n\n getOptimadeSubquery(allowOtherElements) {\n const fragments = []\n this.formulaMap.forEach( (number, element)=> {\n const fragment = '\"' + element + (number === 1 ? '' : +number) + '\"';\n fragments.push(fragment)\n })\n return 'formula HAS ' + (allowOtherElements ? 'ALL ' : 'ONLY ') + fragments.join(', ')\n }\n\n getFragments() {\n const fragments = []\n this.formulaMap.forEach( (number, element)=> {\n const fragment = '\"' + element + (number === 1 ? '' : +number) + '\"';\n fragments.push(fragment);\n })\n return fragments;\n }\n\n\n/*\n getReducedFormula(getTokens = true){\n let counter = 0;\n while ( !checkIfReduced(this.formulaMap) ){ // console.log('Reducing', this.formulaMap);\n let div = 1;\n if (isDivisibleBy(this.formulaMap, 2)) div = 2;\n else if (isDivisibleBy(this.formulaMap, 3)) div = 3;\n else if (isDivisibleBy(this.formulaMap, 5)) div = 5;\n else if (isDivisibleBy(this.formulaMap, 7)) div = 7;\n else if (isDivisibleBy(this.formulaMap, 11)) div = 11;\n\n this.formulaMap.forEach( (value, key) => {\n this.formulaMap.set(key, (value/div));\n });\n //console.log('Reducing DIV', this.formulaMap);\n counter++;\n if (counter > 5) break;\n }\n\n function checkIfReduced(formulaMap){\n let min = 100;\n formulaMap.forEach( (value, key) => {\n if (value < min) min = value;\n });\n return min === 1;\n }\n\n function isDivisibleBy(formulaMap, n){\n let div = true;\n formulaMap.forEach( (value, key) => {\n if (value % n !== 0) div = false;\n });\n return div;\n }\n\n let tokens = [];\n let canonicalFormula = '';\n if (getTokens){\n this.formulaMap.forEach( (value, key) => tokens.push(key+value) );\n }else{\n let sortedElements = this._sortElements( Array.from( this.formulaMap.keys() ) );\n sortedElements.forEach( element => {\n canonicalFormula += element+this.formulaMap.get(element);\n //canonicalFormula += element+(map.get(element) === 1 ? '' : map.get(element));\n });\n }\n\n console.log('_reduceFormula RETURN: ', this.formulaMap, tokens, canonicalFormula);\n return (getTokens ? tokens : canonicalFormula);\n }\n*/\n\n}\n\n// EXPORTS\nmodule.exports = SearchBox;\n\n\n//# sourceURL=webpack:///./src/search-mod/SearchBox.view.js?");
/***/ })
......
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/prod/rae/beta/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/",
......
......@@ -362,7 +362,7 @@ class NewSearchMod {
sendQuery(){
//**** The optimade query must be formed from the search box and the properties selected
const searchBoxOptimadeQuery = this.searchBox.getOptimadeQuery(this.allowOtherElementsCheckbox.checked);
const searchBoxOptimadeQuery = this.searchBox.getOptimadeQuery2(this.allowOtherElementsCheckbox.checked);
const propsOptimadeQuery = getOptimadeQueryFromProps(this.filterPanel.getValues());
const sep = (searchBoxOptimadeQuery !== '' && propsOptimadeQuery !== '' ? ' AND ' : '');
......
......@@ -81,17 +81,17 @@ class SearchBox{
}
/**
* Given the final query, this function uses boolean algebra to reduce it
* into a flattened list of sets combined with AND or OR.
*/
reduceQuery(query, types) {
getOptimadeQuery2(allowOtherElements) {
let query = [...this.searchQuery];
let types = [...this.queryTypes];
// For combining elements connected by AND into a list
function combineANDIn(query, types) {
const newQuery = []
const newTypes = []
for (let i=0; i < query.length;) {
const q0 = query[i-1];
const t0 = types[i-1];
const q1 = query[i];
const t1 = types[i];
const q2 = query[i+1];
......@@ -99,7 +99,7 @@ class SearchBox{
const q3 = query[i+2];
const t3 = types[i+2];
if ((t1 === "E" || t1 === "EL" ) && (t3 === "E" || t3 === "EL" ) && q2 === "AND") {
if ((t1 === "E" || t1 === "EL" ) && (t3 === "E" || t3 === "EL" ) && q2 === "AND" && q0 !== "NOT") {
let active = true
let elements = [q1, q3]
i = i + 3
......@@ -126,16 +126,16 @@ class SearchBox{
return [newQuery, newTypes]
}
// For removing unnecessary quotes
function removeParenthesis(query, types) {
//console.log(query)
//console.log(types)
// For finding parenthesis pairs
function findPairs(query, types) {
const pairs = new Map();
// Find list of indices where parenthesis should be removed
let removed = []
for (let i=0; i < query.length; ++i) {
const q1 = query[i];
const t1 = types[i];
if (q1 === "(") {
//console.log("Start found!")
let index = 0;
for (let j=i+1; j < query.length; ++j) {
const q2 = query[j];
......@@ -144,51 +144,89 @@ class SearchBox{
index += 1;
}
if (q2 === ")") {
//console.log("End found!")
if (index != 0) {
index -= 1;
} else {
const contents = query.slice(i+1, j)
//console.log("PARENTHESIS VALUES")
//console.log(contents)
//console.log(i+1, j)
const tp = types.slice(i+1, j)
//console.log("PARENTHESIS TYPES")
//console.log(tp)
let noop = true
for (let t of tp) {
if (t === "S") {
noop = false
break
}
}
if (noop) {
removed.push(j)
removed.push(i)
}
pairs.set(i, j)
pairs.set(j, i)
break
}
}
}
}
}
return pairs;
}
// For removing unnecessary parenthesis
function removeParenthesis(query, types) {
// Find list of indices where parenthesis should be removed
const pairs = findPairs(query, types)
let removed = []
for (let i=0; i < query.length; ++i) {
const q1 = query[i];
const t1 = types[i];
if (q1 === "(") {
let p = pairs.get(i)
// Remove outer parentheses
if (i === 0 && p == query.length-1) {
removed.push(p)
removed.push(i)
// Remove parentheses that do not contain multiple operations
} else {
const contents = query.slice(i+1, p)
const tp = types.slice(i+1, p)
let noop = true
for (let t of tp) {
if (t === "S") {
noop = false
break
}
}
if (noop) {
removed.push(p)
removed.push(i)
}
}
}
}
// Find repeated quotes and reduce them to single one
for (let i=0; i < query.length; ++i) {
const q1 = query[i];
const t1 = types[i];
const q2 = query[i+1];
const t2 = types[i+1];
if (q1 === "(" && q1 === "(") {
let p1 = pairs.get(i)
let p2 = pairs.get(i+1)
if (p2 == p1 - 1) {
removed.push(i)
removed.push(p1)
}
}
}
// Remove found parentheses
removed = removed.sort((a, b) => b - a);
//console.log(query.join(" "));
//console.log(removed);
for (let j=0; j < removed.length; ++j) {
query.splice(removed[j], 1);
types.splice(removed[j], 1);
}
//console.log(query.join(" "));
return [query, types]
}
// For combining elements connected by AND into a list
// Combines AND statements where either one or both of the statements has a
// parenthesis. These statements are refactored using distributive laws of
// set theory:
// A AND (B OR C) == (A AND B) OR (A AND C)
// in order to see if there are lists of elements that should be joined.
function combineANDOut(query, types) {
let newQuery = query
let newTypes = types
const pairs = findPairs(query, types)
let combined = false
for (let i=0; i < query.length; ++i) {
......@@ -208,17 +246,37 @@ class SearchBox{
let lhsT
let rhsQ
let rhsT
lhsQ = query.slice(0, i+1)
lhsT = types.slice(0, i+1)
if (q1 !== ")") {
let rhsEnd
let lhsStart
if (q1 === ")") {
lhsStart = pairs.get(i)
lhsQ = query.slice(lhsStart, i+1)
lhsT = types.slice(lhsStart, i+1)
} else {
if (query[i-1] === "NOT") {
lhsStart = i-1
} else {
lhsStart = i
}
lhsQ = query.slice(lhsStart, i+1)
lhsT = types.slice(lhsStart, i+1)
lhsQ.unshift("(")
lhsT.unshift("P")
lhsQ.push(")")
lhsT.push("P")
}
rhsQ = query.slice(i+2, query.length)
rhsT = types.slice(i+2, query.length)
if (q3 !== "(") {
if (q3 === "(") {
rhsEnd = pairs.get(i+2)+1
rhsQ = query.slice(i+2, rhsEnd)
rhsT = types.slice(i+2, rhsEnd)
} else {
if (query[i+2] === "NOT") {
rhsEnd = i+4
} else {
rhsEnd = i+3
}
rhsQ = query.slice(i+2, rhsEnd)
rhsT = types.slice(i+2, rhsEnd)
rhsQ.unshift("(")
rhsT.unshift("P")
rhsQ.push(")")
......@@ -272,8 +330,12 @@ class SearchBox{
comboT.push("P")
}
}
newQuery = comboQ
newTypes = comboT
comboQ.unshift("(")
comboT.unshift("P")
comboQ.push(")")
comboT.push("P")
newQuery = query.slice(0, lhsStart).concat(comboQ).concat(query.slice(rhsEnd, query.length))
newTypes = types.slice(0, lhsStart).concat(comboT).concat(types.slice(rhsEnd, query.length))
break
}
}
......@@ -283,11 +345,11 @@ class SearchBox{
// Merge
function merge(query, types) {
// Remove unnecessary parenthesis
// Remove unnecessary parenthesis, combine all elements connected with
// AND, and remove unnecessary parenthesis again.
[query, types] = removeParenthesis(query, types);
// Combined all elements connected with AND
[query, types] = combineANDIn(query, types);
[query, types] = removeParenthesis(query, types);
// Combine parenthesis statements
let combined
......@@ -297,21 +359,71 @@ class SearchBox{
// done
if (combined) {
[query, types] = merge(query, types)
// After the last step perform on final cleanup
} else {
[query, types] = removeParenthesis(query, types);
[query, types] = combineANDIn(query, types);
[query, types] = removeParenthesis(query, types);
}
return [query, types]
}
// Add escaped quotes to all elements
for (let i=0; i < query.length; ++i) {
let q = query[i];
let t = types[i];
if (t === "E") {
query[i] = `\"${q}\"`
}
}
// Add escaped quotes to formulas
for (let i=0; i < query.length; ++i) {
let q = query[i];
let t = types[i];
if (t === "F") {
const formula = new Formula(q)
const fragments = []
formula.formulaMap.forEach( (number, element)=> {
const fragment = '"' + element + (number === 1 ? '' : +number) + '"';
fragments.push(fragment)
})
query[i] = fragments.join(', ')
}
}
// Construct the reduced expression
[query, types] = merge(query, types);
console.log(query.join(" "));
// Convert into optimade syntax
for (let i=0; i < query.length; ++i) {
let q = query[i];
let t = types[i];
if (t === "E" || t === "EL") {
if (allowOtherElements) {
q = "elements HAS ALL " + q
} else {
q = "elements HAS ONLY " + q
}
} else if (t === "F") {
if (allowOtherElements) {
q = "formula HAS ALL " + q
} else {
q = "formula HAS ONLY " + q
}
}
query[i] = q;
}
let res = query.join(" ")
console.log("FINAL OPTIMADE QUERY:")
console.log(res)
return res
}
getOptimadeQuery(allowOtherElements){
this.reduceQuery(this.searchQuery, this.queryTypes)
console.log('getOptimadeQuery this.searchQuery', this.searchQuery)
if (this.searchQuery.length === 0 ||
......
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