Commit c401f678 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Refactored the invalid syntax visuals, added possibility to collapse the...

Refactored the invalid syntax visuals, added possibility to collapse the currently shown tab, restyled the seach filter selected with AutoCompleteMultiselectTextfield, disabled panel fading based on hover, made the filter panel on the left update the search automatically, now the initial view loads all results by default.
parent 196bffa4
Pipeline #97356 skipped with stage
......@@ -136,7 +136,7 @@ eval("/**\n * Copyright 2016-2018 Iker Hurtado\n *\n * Licensed under the Apache
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
eval("\n/**\n * Copyright 2016-2018 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 file is the application entry point.\n It defines some app level components (Breadcrumb) and\n initializes several more (app level events, app routing, authentication)\n */\n\n\n\n\nlet util = __webpack_require__(/*! ./common/util.js */ \"./src/common/util.js\");\nlet LoadingPopup = __webpack_require__(/*! ./common/LoadingPopup.js */ \"./src/common/LoadingPopup.js\");\nlet FlaggingFormPopup = __webpack_require__(/*! ./common/FlaggingFormPopup.js */ \"./src/common/FlaggingFormPopup.js\");\nlet PubSub = __webpack_require__(/*! ./common/PubSub.js */ \"./src/common/PubSub.js\");\nlet Router = __webpack_require__(/*! ./common/Router.js */ \"./src/common/Router.js\");\nlet MaterialMod = __webpack_require__(/*! ./material-mod/MaterialMod.js */ \"./src/material-mod/MaterialMod.js\");\nlet SearchModule = __webpack_require__(/*! ./search-mod/NewSearchMod.js */ \"./src/search-mod/NewSearchMod.js\");//require('./search-mod/SearchMod.js');\nlet UserGuidance = __webpack_require__(/*! ./common/UserGuidance.js */ \"./src/common/UserGuidance.js\");\nlet DataStore = __webpack_require__(/*! ./material-mod/DataStore.js */ \"./src/material-mod/DataStore.js\");\n\n\n// main DOM elements\nlet contentElement = document.getElementById('content');\nlet titleElement = document.querySelector('title');\nwindow.allowNewLoadPopup = true;\n\nvar getUrl = window.location;\nvar guiRoot = getUrl.protocol + \"//\" + getUrl.host + \"/\" + getUrl.pathname;\n\n// As of 0.8.3 nomad-FAIR is using KeyCloak 7.0.0, which does\n// not support the \"silentCheckSsoRedirectUri\" option. This option enables a\n// silent login check that does not enforce reloads. In order to do such silent\n// login, the Javascript adapter for KeyCloak 8.0.0 is used instead. This is\n// against the best practice of directly downloading the Javascript adapter\n// from the authentication server (window.nomadEnv.keycloakBase +\n// \"js/keycloak.min.js\"), but is in this case acceptable as it result is a much\n// smoother user experience.\nPubSub.subscribe('authenticated', data => {\n let hashPath = document.location.hash.substring(2);\n if (hashPath.lastIndexOf('/') === (hashPath.length-1))\n hashPath = hashPath.substring(0,hashPath.length-1);\n if (hashPath.indexOf('/') > 0){\n let command = hashPath.split('/')[0];\n if (command === \"material\") {\n flaggingTab.style.visibility = 'visible';\n }\n }\n});\nvar keycloak = new Keycloak({\n url: window.nomadEnv.keycloakBase,\n realm: window.nomadEnv.keycloakRealm,\n clientId: window.nomadEnv.keycloakClientId\n});\nwindow.keycloak = keycloak;\nlet loginButton = document.querySelector('#login-button');\nlet logoutButton = document.querySelector('#logout-button');\nlet userName = document.querySelector('#user-name');\nkeycloak.init({\n onLoad: \"check-sso\",\n silentCheckSsoRedirectUri: `${guiRoot}silent-check-sso.html`,\n promiseType: \"native\",\n}).then((authenticated) => {\n if (authenticated) {\n keycloak.loadUserProfile()\n .then(function(profile) {\n userName.textContent = `${profile.firstName} ${profile.lastName}`;\n loginButton.style.display = 'none';\n logoutButton.style.display = 'inline';\n PubSub.publish('authenticated');\n }).catch(function() {\n console.log('Failed to load user profile.');\n });\n //util.setAuthRequestHeader(data.user, data.token.data);\n } else {\n loginButton.style.display = 'inline';\n logoutButton.style.display = 'none';\n userName.textContent = \"Guest\";\n }\n});\nloginButton.onclick = () => {\n keycloak.login({redirectUri: `${guiRoot}#/search`})\n .catch(() => {console.log(\"Authentication error.\");});\n};\nlogoutButton.onclick = () => {\n keycloak.logout();\n};\n\n\n/********* User flagging side tab ****************/\n\n/* This side vertical tab is hidden initially\n but it has to be set up when the app starts */\nlet flaggingTab = document.getElementById('calc-flagging-tab');\nflaggingTab.style.top = (window.innerHeight/2)+'px';\nflaggingTab.addEventListener('click', e => {\n FlaggingFormPopup.show(MaterialModule.getCurrentPageStatus());\n});\n\n\n/*********** App Breadcrumb component definition ***************/\n\nclass Breadcrumb {\n\n constructor() {\n\n this.element = document.querySelector('#breadcrumb-placeholder');\n this.element.innerHTML = `\n <span class=\"goto-page Search\">Search</span>\n <span class=\"goto-page Results\">&nbsp; > &nbsp; <span>Results</span></span>\n <span class=\"goto-page Overview\">&nbsp; > &nbsp; <span>Overview</span></span>\n <span class=\"Details\">\n &nbsp; > &nbsp;\n <select class=\"details-dropdown\" >\n <option value=\"structure\">Structure</option>\n <option value=\"electronicstruct\">Electronic structure</option>\n <option value=\"methodology\">Methodology</option>\n <option value=\"thermalprops\">Thermal Properties</option>\n <!-- elasticconst-->\n </select>\n </span>\n `;\n this.resultsSel = this.element.querySelector('.Results');\n this.overviewSel = this.element.querySelector('.Overview');\n this.detailsSel = this.element.querySelector('.Details');\n this.detailsDropDown = this.element.querySelector('.details-dropdown');\n\n // Events\n this.element.querySelector('.Search').addEventListener( \"click\", e => {\n util.setBrowserHashPath('search');\n });\n this.resultsSel.addEventListener( \"click\", e => {\n util.setBrowserHashPath('search/results');\n });\n\n this.overviewSel.addEventListener('click', () => {\n util.setBrowserHashPath('material', util.materialId);\n });\n\n this.detailsDropDown.addEventListener('change', e => {\n util.setBrowserHashPath('material',\n DataStore.getMaterialData().material_id+'/'+e.target.value);\n });\n\n let self = this;\n function adjustDropdownOptions() {\n let esOption = self.detailsDropDown.querySelector('option[value=\"electronicstruct\"]');\n if (!DataStore.hasElecStructureData()) self.detailsDropDown.removeChild(esOption);\n\n let thOption = self.detailsDropDown.querySelector('option[value=\"thermalprops\"]');\n if (!DataStore.hasThermalData()) self.detailsDropDown.removeChild(thOption);\n // Remove because we want it's executed once\n self.detailsDropDown.removeEventListener('focus', adjustDropdownOptions);\n }\n\n this.detailsDropDown.addEventListener('focus', adjustDropdownOptions);\n }\n\n\n setState(appModule, param){\n let resultsSetLabel = this.resultsSel.querySelector('span');\n resultsSetLabel.style.fontWeight = 'normal';\n let overviewSelLabel = this.overviewSel.querySelector('span');\n overviewSelLabel.style.fontWeight = 'normal';\n\n if (appModule === 'search'){\n this.overviewSel.style.display = 'none';\n this.detailsSel.style.display = 'none';\n\n if (param === 'results'){\n this.resultsSel.style.display = 'inline';\n this.resultsSel.querySelector('span').style.fontWeight = 'bold';\n this.element.style.visibility = 'visible';\n }else\n this.element.style.visibility = 'hidden';\n\n }else if (appModule === 'material'){\n this.element.style.visibility = 'visible';\n this.resultsSel.style.display = (util.searchResults ? 'inline' : 'none');\n this.overviewSel.style.display = 'inline';\n\n if (param === undefined){ // Overview page\n this.detailsSel.style.display = 'none';\n overviewSelLabel.style.fontWeight = 'bold';\n }else{ // Details page\n this.detailsSel.style.display = 'inline';\n this.detailsDropDown.value = param;\n }\n }\n } // setState\n\n} // class Breadcrumb\n\n\n/***************************\n App setup\n***************************/\n\nlet breadcrumb = new Breadcrumb();\n\nlet searchMod;\nlet MaterialModule;\nlet materialModDOM;\nlet currentModule; // current module DOM being shown\n\n\nfunction showModuleDOM(module){\n if (currentModule) contentElement.removeChild(currentModule);\n currentModule = module;\n contentElement.appendChild(currentModule);\n}\n\n\n/****** App level events setup ********/\n\nPubSub.subscribe('show-material', data => {\n console.log('Handling event show-material: ' + data.material_id + ' view: '+data.view);\n\n //titleElement.innerHTML = 'NOMAD Encyclopedia - Material '+data.id;\n breadcrumb.setState('material', data.view);\n\n if (typeof materialModDOM === 'undefined'){\n MaterialModule = new MaterialMod();\n materialModDOM = MaterialModule.element;\n }\n showModuleDOM(materialModDOM);\n MaterialModule.setMaterialView(data);\n\n // In case the app comes from the search module through the url (back button)\n UserGuidance.show(false);\n\n // When a logged user comes to a material page, the error reporting tab is shown.\n if (keycloak.authenticated) {\n flaggingTab.style.visibility = 'visible';\n }\n});\n\nPubSub.subscribe('show-search', search => {\n console.log('Handling event show-search: '+search);\n\n // When a logged user comes to a material page, the error reporting tab is shown.\n flaggingTab.style.visibility = 'hidden';\n\n titleElement.innerHTML = 'NOMAD Encyclopedia - Search';\n breadcrumb.setState('search', search);\n\n if (search === undefined) {\n //LoadingPopup.reset();\n searchMod.showSearchPage();\n } else if (search === 'results') {\n searchMod.showResultsPage();\n }\n\n showModuleDOM(searchMod.element);\n});\n\n\n/****** App routing config ******/\nRouter.add('search', search => PubSub.publish('show-search', search));\nRouter.add('material', (matId, view) => PubSub.publish('show-material', {'material_id': matId, 'view': view}));\n\n/****** init ******/\nsearchMod = new SearchModule();\nif (document.location.hash === '') document.location += \"#/search\";\nRouter.route();\n\n/********* User authentication ***********/\n\nlet userNameElement = document.querySelector('#user-name');\n\nfunction getCookie(name) {\n let value = \"; \" + document.cookie;\n let parts = value.split(\"; \" + name + \"=\");\n if (parts.length === 2) return parts.pop().split(\";\").shift();\n}\n\nfunction parseCookie(userData) {\n return userData.substring(1, userData.length-1).replace(/\\\\054/g,',').replace(/\\\\/g,'');\n}\n\nlet userInfoCookie = getCookie('user_info');\n\nif (userInfoCookie !== undefined) {\n let userInfoData = JSON.parse(parseCookie(userInfoCookie));\n //console.log('userInfoData: ', userInfoData);\n setAppAuthenticated(userInfoData);\n}\n\n\n//# sourceURL=webpack:///./src/main.js?");
eval("\n/**\n * Copyright 2016-2018 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 file is the application entry point.\n It defines some app level components (Breadcrumb) and\n initializes several more (app level events, app routing, authentication)\n */\n\n\n\n\nlet util = __webpack_require__(/*! ./common/util.js */ \"./src/common/util.js\");\nlet LoadingPopup = __webpack_require__(/*! ./common/LoadingPopup.js */ \"./src/common/LoadingPopup.js\");\nlet FlaggingFormPopup = __webpack_require__(/*! ./common/FlaggingFormPopup.js */ \"./src/common/FlaggingFormPopup.js\");\nlet PubSub = __webpack_require__(/*! ./common/PubSub.js */ \"./src/common/PubSub.js\");\nlet Router = __webpack_require__(/*! ./common/Router.js */ \"./src/common/Router.js\");\nlet MaterialMod = __webpack_require__(/*! ./material-mod/MaterialMod.js */ \"./src/material-mod/MaterialMod.js\");\nlet SearchModule = __webpack_require__(/*! ./search-mod/NewSearchMod.js */ \"./src/search-mod/NewSearchMod.js\");//require('./search-mod/SearchMod.js');\nlet UserGuidance = __webpack_require__(/*! ./common/UserGuidance.js */ \"./src/common/UserGuidance.js\");\nlet DataStore = __webpack_require__(/*! ./material-mod/DataStore.js */ \"./src/material-mod/DataStore.js\");\n\n\n// main DOM elements\nlet contentElement = document.getElementById('content');\nlet titleElement = document.querySelector('title');\nwindow.allowNewLoadPopup = true;\n\nvar getUrl = window.location;\nvar guiRoot = getUrl.protocol + \"//\" + getUrl.host + \"/\" + getUrl.pathname;\n\n// As of 0.8.3 nomad-FAIR is using KeyCloak 7.0.0, which does\n// not support the \"silentCheckSsoRedirectUri\" option. This option enables a\n// silent login check that does not enforce reloads. In order to do such silent\n// login, the Javascript adapter for KeyCloak 8.0.0 is used instead. This is\n// against the best practice of directly downloading the Javascript adapter\n// from the authentication server (window.nomadEnv.keycloakBase +\n// \"js/keycloak.min.js\"), but is in this case acceptable as it result is a much\n// smoother user experience.\nPubSub.subscribe('authenticated', data => {\n let hashPath = document.location.hash.substring(2);\n if (hashPath.lastIndexOf('/') === (hashPath.length-1))\n hashPath = hashPath.substring(0,hashPath.length-1);\n if (hashPath.indexOf('/') > 0){\n let command = hashPath.split('/')[0];\n if (command === \"material\") {\n flaggingTab.style.visibility = 'visible';\n }\n }\n});\nvar keycloak = new Keycloak({\n url: window.nomadEnv.keycloakBase,\n realm: window.nomadEnv.keycloakRealm,\n clientId: window.nomadEnv.keycloakClientId\n});\nwindow.keycloak = keycloak;\nlet loginButton = document.querySelector('#login-button');\nlet logoutButton = document.querySelector('#logout-button');\nlet userName = document.querySelector('#user-name');\nkeycloak.init({\n onLoad: \"check-sso\",\n silentCheckSsoRedirectUri: `${guiRoot}silent-check-sso.html`,\n promiseType: \"native\",\n}).then((authenticated) => {\n if (authenticated) {\n keycloak.loadUserProfile()\n .then(function(profile) {\n userName.textContent = `${profile.firstName} ${profile.lastName}`;\n loginButton.style.display = 'none';\n logoutButton.style.display = 'inline';\n PubSub.publish('authenticated');\n }).catch(function() {\n console.log('Failed to load user profile.');\n });\n //util.setAuthRequestHeader(data.user, data.token.data);\n } else {\n loginButton.style.display = 'inline';\n logoutButton.style.display = 'none';\n userName.textContent = \"Guest\";\n }\n});\nloginButton.onclick = () => {\n keycloak.login({redirectUri: `${guiRoot}#/search`})\n .catch(() => {console.log(\"Authentication error.\");});\n};\nlogoutButton.onclick = () => {\n keycloak.logout();\n};\n\n\n/********* User flagging side tab ****************/\n\n/* This side vertical tab is hidden initially\n but it has to be set up when the app starts */\nlet flaggingTab = document.getElementById('calc-flagging-tab');\nflaggingTab.style.top = (window.innerHeight/2)+'px';\nflaggingTab.addEventListener('click', e => {\n FlaggingFormPopup.show(MaterialModule.getCurrentPageStatus());\n});\n\n\n/*********** App Breadcrumb component definition ***************/\n\nclass Breadcrumb {\n\n constructor() {\n\n this.element = document.querySelector('#breadcrumb-placeholder');\n this.element.innerHTML = `\n <span class=\"goto-page Search\">Search</span>\n <span class=\"goto-page Results\">&nbsp; > &nbsp; <span>Results</span></span>\n <span class=\"goto-page Overview\">&nbsp; > &nbsp; <span>Overview</span></span>\n <span class=\"Details\">\n &nbsp; > &nbsp;\n <select class=\"details-dropdown\" >\n <option value=\"structure\">Structure</option>\n <option value=\"electronicstruct\">Electronic structure</option>\n <option value=\"methodology\">Methodology</option>\n <option value=\"thermalprops\">Thermal Properties</option>\n <!-- elasticconst-->\n </select>\n </span>\n `;\n this.resultsSel = this.element.querySelector('.Results');\n this.overviewSel = this.element.querySelector('.Overview');\n this.detailsSel = this.element.querySelector('.Details');\n this.detailsDropDown = this.element.querySelector('.details-dropdown');\n\n // Events\n this.element.querySelector('.Search').addEventListener( \"click\", e => {\n util.setBrowserHashPath('search');\n });\n this.resultsSel.addEventListener( \"click\", e => {\n util.setBrowserHashPath('search/results');\n });\n\n this.overviewSel.addEventListener('click', () => {\n util.setBrowserHashPath('material', util.materialId);\n });\n\n this.detailsDropDown.addEventListener('change', e => {\n util.setBrowserHashPath('material',\n DataStore.getMaterialData().material_id+'/'+e.target.value);\n });\n\n let self = this;\n function adjustDropdownOptions() {\n let esOption = self.detailsDropDown.querySelector('option[value=\"electronicstruct\"]');\n if (!DataStore.hasElecStructureData()) self.detailsDropDown.removeChild(esOption);\n\n let thOption = self.detailsDropDown.querySelector('option[value=\"thermalprops\"]');\n if (!DataStore.hasThermalData()) self.detailsDropDown.removeChild(thOption);\n // Remove because we want it's executed once\n self.detailsDropDown.removeEventListener('focus', adjustDropdownOptions);\n }\n\n this.detailsDropDown.addEventListener('focus', adjustDropdownOptions);\n }\n\n\n setState(appModule, param){\n let resultsSetLabel = this.resultsSel.querySelector('span');\n resultsSetLabel.style.fontWeight = 'normal';\n let overviewSelLabel = this.overviewSel.querySelector('span');\n overviewSelLabel.style.fontWeight = 'normal';\n\n if (appModule === 'search'){\n this.overviewSel.style.display = 'none';\n this.detailsSel.style.display = 'none';\n\n if (param === 'results'){\n this.resultsSel.style.display = 'inline';\n this.resultsSel.querySelector('span').style.fontWeight = 'bold';\n this.element.style.visibility = 'visible';\n }else\n this.element.style.visibility = 'hidden';\n\n }else if (appModule === 'material'){\n this.element.style.visibility = 'visible';\n this.resultsSel.style.display = (util.searchResults ? 'inline' : 'none');\n this.overviewSel.style.display = 'inline';\n\n if (param === undefined){ // Overview page\n this.detailsSel.style.display = 'none';\n overviewSelLabel.style.fontWeight = 'bold';\n }else{ // Details page\n this.detailsSel.style.display = 'inline';\n this.detailsDropDown.value = param;\n }\n }\n } // setState\n\n} // class Breadcrumb\n\n\n/***************************\n App setup\n***************************/\n\nlet breadcrumb = new Breadcrumb();\n\nlet searchMod;\nlet MaterialModule;\nlet materialModDOM;\nlet currentModule; // current module DOM being shown\n\n\nfunction showModuleDOM(module){\n if (currentModule) contentElement.removeChild(currentModule);\n currentModule = module;\n contentElement.appendChild(currentModule);\n}\n\n\n/****** App level events setup ********/\n\nPubSub.subscribe('show-material', data => {\n console.log('Handling event show-material: ' + data.material_id + ' view: '+data.view);\n\n //titleElement.innerHTML = 'NOMAD Encyclopedia - Material '+data.id;\n breadcrumb.setState('material', data.view);\n\n if (typeof materialModDOM === 'undefined'){\n MaterialModule = new MaterialMod();\n materialModDOM = MaterialModule.element;\n }\n showModuleDOM(materialModDOM);\n MaterialModule.setMaterialView(data);\n\n // In case the app comes from the search module through the url (back button)\n UserGuidance.show(false);\n\n // When a logged user comes to a material page, the error reporting tab is shown.\n if (keycloak.authenticated) {\n flaggingTab.style.visibility = 'visible';\n }\n});\n\nPubSub.subscribe('show-search', search => {\n console.log('Handling event show-search: '+search);\n\n // When a logged user comes to a material page, the error reporting tab is shown.\n flaggingTab.style.visibility = 'hidden';\n\n titleElement.innerHTML = 'NOMAD Encyclopedia - Search';\n breadcrumb.setState('search', search);\n showModuleDOM(searchMod.element);\n\n searchMod.sendQuery();\n});\n\n\n/****** App routing config ******/\nRouter.add('search', search => PubSub.publish('show-search', search));\nRouter.add('material', (matId, view) => PubSub.publish('show-material', {'material_id': matId, 'view': view}));\n\n/****** init ******/\nsearchMod = new SearchModule();\nif (document.location.hash === '') document.location += \"#/search\";\nRouter.route();\n\n/********* User authentication ***********/\n\nlet userNameElement = document.querySelector('#user-name');\n\nfunction getCookie(name) {\n let value = \"; \" + document.cookie;\n let parts = value.split(\"; \" + name + \"=\");\n if (parts.length === 2) return parts.pop().split(\";\").shift();\n}\n\nfunction parseCookie(userData) {\n return userData.substring(1, userData.length-1).replace(/\\\\054/g,',').replace(/\\\\/g,'');\n}\n\nlet userInfoCookie = getCookie('user_info');\n\nif (userInfoCookie !== undefined) {\n let userInfoData = JSON.parse(parseCookie(userInfoCookie));\n //console.log('userInfoData: ', userInfoData);\n setAppAuthenticated(userInfoData);\n}\n\n\n//# sourceURL=webpack:///./src/main.js?");
/***/ }),
......@@ -371,7 +371,7 @@ eval("\n/**\n * Copyright 2016-2018 Iker Hurtado\n *\n * Licensed under the Apac
/***/ ((module) => {
"use strict";
eval("/**\n * Copyright 2019-2019 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 Basic class for extending a text field with autocomplete functionality\n */\n\n\n\n\n\nclass AutocompleteMultiselectTextfield {\n\n constructor(id = \"\", placeholder = '', allowEmptyInput = true) {\n this.id = id;\n this.element = document.createElement('div');\n //this.element.style.display = 'inline';\n this.element.className = `AutocompleteMultiselectTextfield ${id}-autocomplete-multiselect-textfield`;\n\n this.element.innerHTML = `\n <input type=\"text\" placeholder=\"${placeholder}\" /> \n <div class=\"AutocompleteMultiselectTextfield-selected-box\"></div> \n <div class=\"AutocompleteMultiselectTextfield-dropdown ${this.id}-autocomplete-multiselect-dropdown\"></div> \n `;\n\n this.input = this.element.querySelector('input');\n this.selectedItemsBox = this.element.querySelector('.AutocompleteMultiselectTextfield-selected-box');\n this.listContainer = this.element.querySelector('.AutocompleteMultiselectTextfield-dropdown');\n \n this.selectListener;\n\n // state\n this.valueList; // List of autocomplete (possible) values\n this.selectedValues = new Set();\n this.allowEmptyInput = allowEmptyInput;\n\n // event management\n \n // When clicking on the textfield the dropdown shows up \n this.input.addEventListener(\"click\", (e) => {\n this._processInput();\n e.stopPropagation();\n });\n\n // the dropdown list is updated as the user writes on the textfield\n this.input.addEventListener(\"input\", (e) => {\n this._processInput();\n });\n\n // The dropdown is hiden when the mouse leaves the component area\n this.element.addEventListener(\"mouseleave\", e => {\n this._cleanList();\n });\n\n // Handle the item removal on the selected items box\n this.selectedItemsBox.addEventListener(\"click\", e => {\n let itemLabel = event.target.closest('span'); \n this._removeSelectedValue(itemLabel.dataset.value);\n //itemLabel.dispatchEvent(new Event('change'));\n });\n\n // Handle the item selection on the dropdown\n this.listContainer.addEventListener(\"click\", e => {\n let listItem = event.target.closest('div'); \n this._toggleItem(listItem);\n });\n\n\n this.listContainer.addEventListener(\"mouseover\", e => {\n let listItem = event.target.closest('div'); \n this._setActiveListItem(listItem);\n });\n\n }\n\n\n getValues(){\n return Array.from(this.selectedValues);\n }\n\n\n/*resetValue(){\n this.input.value = '';\n }*/\n\n\n disable(bool){\n this.input.disabled = bool;\n }\n\n\n setAutocompleteList(valueList){\n this.valueList = valueList;\n }\n\n\n setSelectListener(listener) {\n this.selectListener = listener;\n }\n\n\n _processInput() {\n const currentInput = this.input.value;\n // close any already open lists of autocompleted values\n this._cleanList();//this._closeAllLists();\n\n // in case of an empty input field\n if (!this.allowEmptyInput && !currentInput) {\n return false;\n }\n\n // for each autocomplete value\n let counter = 0;\n const matchingValues = this.valueList.filter( value => {\n const matching = value.toUpperCase().includes(currentInput.toUpperCase());\n if (matching) counter++;\n return counter <= 15 && matching;\n });\n //console.log('matchingValues', matchingValues)\n\n this.listContainer.innerHTML = '';\n\n matchingValues.forEach( value => {\n const listItem = generateListItem(value, currentInput, this.selectedValues.has(value));\n this.listContainer.append(listItem);\n // check if a valid option was completely entered. if so, set the focus to the element corresponding to the input\n if (value.toUpperCase() === currentInput.toUpperCase()) \n this._setActiveListItem(listItem);\n\n function generateListItem(value, inputText, present) {\n const listItem = document.createElement(\"div\");\n let innerHTML = `<input type=\"checkbox\" data-value=\"${value}\" ${present ? 'checked' : ''}>`;\n if (inputText){\n const pos = value.toUpperCase().indexOf(inputText.toUpperCase()); // console.log('pos', pos)\n innerHTML += \n `${value.substring(0, pos)}<strong>${value.substring(pos, pos+inputText.length)}</strong>${value.substring(pos + inputText.length)}`;\n }else \n innerHTML += value;\n listItem.innerHTML = innerHTML;\n return listItem;\n }\n\n });\n\n }\n\n\n _removeSelectedValue(value) {\n\n this.selectedValues.delete(value);\n this.selectedItemsBox.querySelector('span[data-value=\"'+value+'\"]').remove();\n // check if the dropdown is unfolded and uncheck the checkbox if so\n const itemCheckbox = this.listContainer.querySelector('input[data-value=\"'+value+'\"]');\n if (itemCheckbox) itemCheckbox.checked = false;\n\n // Emit a value change event\n this.element.dispatchEvent(new Event(\"change\", {bubbles: true}));\n }\n\n\n _toggleItem(listItem) {\n const value = listItem.textContent;\n const present = this.selectedValues.has(value);\n\n listItem.querySelector('input').checked = !present;\n\n if (present){\n this.selectedValues.delete(value);\n this.selectedItemsBox.querySelector('span[data-value=\"'+value+'\"]').remove();\n }else{\n this.selectedValues.add(value);\n this.selectedItemsBox.append(createSelectedItemLabel(value));\n } \n\n if (this.selectListener) this.selectListener(value);\n\n // Emit a value change event\n this.element.dispatchEvent(new Event(\"change\", {bubbles: true}));\n\n\n function createSelectedItemLabel(value){\n const label = document.createElement('span');\n label.className = 'selectedItemLabel';\n label.dataset.value = value;\n label.innerHTML = `${value} ❌`;\n return label;\n }\n }\n\n\n _setActiveListItem(element){\n const currentActiveItem = this.listContainer.querySelector('.autocomplete-active');\n if (currentActiveItem) currentActiveItem.classList.remove('autocomplete-active');\n element.classList.add('autocomplete-active');\n }\n\n/* Not being used for now\n\n _getActiveListItem(){\n return this.listContainer.querySelector('.autocomplete-active');\n }\n\n _clickOnActiveItem() {\n const activeItem = this.listContainer.querySelector('.autocomplete-active');\n if (activeItem) activeItem.click();\n }*/\n\n\n _cleanList() {\n this.listContainer.innerHTML = '';\n }\n \n}\n\n\n// EXPORTS\nmodule.exports = AutocompleteMultiselectTextfield;\n\n\n//# sourceURL=webpack:///./src/search-mod/AutocompleteMultiselectTextfield.js?");
eval("/**\n * Copyright 2019-2019 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 Basic class for extending a text field with autocomplete functionality\n */\n\n\n\n\n\nclass AutocompleteMultiselectTextfield {\n\n constructor(id = \"\", placeholder = '', allowEmptyInput = true) {\n this.id = id;\n this.element = document.createElement('div');\n //this.element.style.display = 'inline';\n this.element.className = `AutocompleteMultiselectTextfield ${id}-autocomplete-multiselect-textfield`;\n\n this.element.innerHTML = `\n <input type=\"text\" placeholder=\"${placeholder}\" /> \n <div class=\"AutocompleteMultiselectTextfield-dropdown ${this.id}-autocomplete-multiselect-dropdown\"></div> \n <div class=\"AutocompleteMultiselectTextfield-selected-box\"></div> \n `;\n\n this.input = this.element.querySelector('input');\n this.selectedItemsBox = this.element.querySelector('.AutocompleteMultiselectTextfield-selected-box');\n this.listContainer = this.element.querySelector('.AutocompleteMultiselectTextfield-dropdown');\n \n this.selectListener;\n\n // state\n this.valueList; // List of autocomplete (possible) values\n this.selectedValues = new Set();\n this.allowEmptyInput = allowEmptyInput;\n\n // event management\n \n // When clicking on the textfield the dropdown shows up \n this.input.addEventListener(\"click\", (e) => {\n this._processInput();\n e.stopPropagation();\n });\n\n // the dropdown list is updated as the user writes on the textfield\n this.input.addEventListener(\"input\", (e) => {\n this._processInput();\n });\n\n // The dropdown is hiden when the mouse leaves the component area\n this.element.addEventListener(\"mouseleave\", e => {\n this._cleanList();\n });\n\n // Handle the item removal on the selected items box\n this.selectedItemsBox.addEventListener(\"click\", e => {\n let itemLabel = event.target.closest('span'); \n this._removeSelectedValue(itemLabel.dataset.value);\n //itemLabel.dispatchEvent(new Event('change'));\n });\n\n // Handle the item selection on the dropdown\n this.listContainer.addEventListener(\"click\", e => {\n let listItem = event.target.closest('div'); \n this._toggleItem(listItem);\n });\n\n\n this.listContainer.addEventListener(\"mouseover\", e => {\n let listItem = event.target.closest('div'); \n this._setActiveListItem(listItem);\n });\n\n }\n\n\n getValues(){\n return Array.from(this.selectedValues);\n }\n\n\n/*resetValue(){\n this.input.value = '';\n }*/\n\n\n disable(bool){\n this.input.disabled = bool;\n }\n\n\n setAutocompleteList(valueList){\n this.valueList = valueList;\n }\n\n\n setSelectListener(listener) {\n this.selectListener = listener;\n }\n\n\n _processInput() {\n const currentInput = this.input.value;\n // close any already open lists of autocompleted values\n this._cleanList();//this._closeAllLists();\n\n // in case of an empty input field\n if (!this.allowEmptyInput && !currentInput) {\n return false;\n }\n\n // for each autocomplete value\n let counter = 0;\n const matchingValues = this.valueList.filter( value => {\n const matching = value.toUpperCase().includes(currentInput.toUpperCase());\n if (matching) counter++;\n return counter <= 15 && matching;\n });\n //console.log('matchingValues', matchingValues)\n\n this.listContainer.innerHTML = '';\n\n matchingValues.forEach( value => {\n const listItem = generateListItem(value, currentInput, this.selectedValues.has(value));\n this.listContainer.append(listItem);\n // check if a valid option was completely entered. if so, set the focus to the element corresponding to the input\n if (value.toUpperCase() === currentInput.toUpperCase()) \n this._setActiveListItem(listItem);\n\n function generateListItem(value, inputText, present) {\n const listItem = document.createElement(\"div\");\n let innerHTML = `<input class=\"value-checkbox\" type=\"checkbox\" data-value=\"${value}\" ${present ? 'checked' : ''}>`;\n if (inputText){\n const pos = value.toUpperCase().indexOf(inputText.toUpperCase()); // console.log('pos', pos)\n innerHTML += \n `${value.substring(0, pos)}<strong>${value.substring(pos, pos+inputText.length)}</strong>${value.substring(pos + inputText.length)}`;\n }else \n innerHTML += value;\n listItem.innerHTML = innerHTML;\n return listItem;\n }\n\n });\n\n }\n\n\n _removeSelectedValue(value) {\n\n this.selectedValues.delete(value);\n this.selectedItemsBox.querySelector('span[data-value=\"'+value+'\"]').remove();\n // check if the dropdown is unfolded and uncheck the checkbox if so\n const itemCheckbox = this.listContainer.querySelector('input[data-value=\"'+value+'\"]');\n if (itemCheckbox) itemCheckbox.checked = false;\n\n // Emit a value change event\n this.element.dispatchEvent(new Event(\"change\", {bubbles: true}));\n }\n\n\n _toggleItem(listItem) {\n const value = listItem.textContent;\n const present = this.selectedValues.has(value);\n\n listItem.querySelector('input').checked = !present;\n\n if (present){\n this.selectedValues.delete(value);\n this.selectedItemsBox.querySelector('span[data-value=\"'+value+'\"]').remove();\n }else{\n this.selectedValues.add(value);\n this.selectedItemsBox.append(createSelectedItemLabel(value));\n } \n\n if (this.selectListener) this.selectListener(value);\n\n // Emit a value change event\n this.element.dispatchEvent(new Event(\"change\", {bubbles: true}));\n\n\n function createSelectedItemLabel(value){\n const label = document.createElement('span');\n label.className = 'selectedItemLabel';\n label.dataset.value = value;\n const labelText = document.createElement('div');\n labelText.textContent = `${value}`;\n const labelImage = document.createElement('img');\n labelImage.src = 'img/cross-maroon.svg'\n labelImage.width = 10\n labelImage.height = 10\n label.appendChild(labelText);\n label.appendChild(labelImage);\n return label;\n }\n }\n\n\n _setActiveListItem(element){\n const currentActiveItem = this.listContainer.querySelector('.autocomplete-active');\n if (currentActiveItem) currentActiveItem.classList.remove('autocomplete-active');\n element.classList.add('autocomplete-active');\n }\n\n/* Not being used for now\n\n _getActiveListItem(){\n return this.listContainer.querySelector('.autocomplete-active');\n }\n\n _clickOnActiveItem() {\n const activeItem = this.listContainer.querySelector('.autocomplete-active');\n if (activeItem) activeItem.click();\n }*/\n\n\n _cleanList() {\n this.listContainer.innerHTML = '';\n }\n \n}\n\n\n// EXPORTS\nmodule.exports = AutocompleteMultiselectTextfield;\n\n\n//# sourceURL=webpack:///./src/search-mod/AutocompleteMultiselectTextfield.js?");
/***/ }),
......@@ -425,7 +425,7 @@ eval("let util = __webpack_require__(/*! ../common/util.js */ \"./src/common/uti
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
eval("\n/**\n * Copyright 2016-2018 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 component implements the list of materials found in the search\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\nlet InfoSys = __webpack_require__(/*! ../common/InfoSys.js */ \"./src/common/InfoSys.js\");\nlet LoadingPopup = __webpack_require__(/*! ../common/LoadingPopup.js */ \"./src/common/LoadingPopup.js\");\n\n//const RESULTS_PER_PAGE = 20;\n\n\nclass MaterialList {\n\n constructor(){\n this.element = document.createElement('div');\n this.element.className = 'MaterialList';\n\n // state \n this.visible = false;\n this.noResults = true;\n this.matMap = new Map();\n //this.currentSystemType = 'bulk';\n this.optimadeQuery = null;\n \n this.noResultsBox = document.createElement('div');\n this.noResultsBox.style = 'text-align: center; font-weight: bold'\n this.noResultsBox.innerHTML = 'NO RESULTS FOUND';\n this.element.append(this.noResultsBox);\n\n this.matListWrapper = document.createElement('div');\n this.element.append(this.matListWrapper);\n this.pagControl = new PaginationControl();\n this.matListWrapper.append(this.pagControl.element);\n this.pagControl.setPrevPageListener( page => {\n this._search(page);\n });\n this.pagControl.setNextPageListener( page => {\n this._search(page);\n })\n\n this.matListContainer = new MatListContainer();\n this.matListWrapper.append(this.matListContainer.element);\n }\n\n\n attachAndSetEvents(element){\n element.appendChild(this.element);\n this._render();\n }\n\n\n invalidateSearch(){\n this.visible = false;\n this._render();\n }\n\n\n initSearch(optimadeQuery){\n\n this.optimadeQuery = optimadeQuery;\n this._search();\n }\n\n _search(page){\n //this.resultsContainer.style.visibility = 'hidden';\n this.matMap.clear();\n\n LoadingPopup.show();\n\n let reqJson = {\n query: this.optimadeQuery,\n search_by: {}\n };\n if (page) reqJson.search_by = { page: page}\n\n // Add the restricted option from the checkbox\n let restrictedEl = document.getElementById('restricted-search');\n reqJson.search_by.restricted = (restrictedEl.checked ? '1' : '0');\n console.log('SEARCHING: ', reqJson );\n\n fetch(util.getSearchURL(), {\n method: 'POST',\n headers: {'Content-Type': 'application/json;charset=utf-8'},\n body: JSON.stringify(reqJson)\n })\n .then( resp => resp.json() )\n .then( result => {\n console.log('GETTING: ', result);\n\n // Update state\n this.noResults = (result.results.length === 0); \n this._setMatList(result.results);\n this.pagControl.set(result.pages);\n \n this.visible = true;\n this._render();\n\n /* Go to the material page straightaway - Not wanted fro now\n if (result.results.length === 1){\n const onlyMat = this.matMap.values().next().value[0];\n util.setBrowserHashPath('material', onlyMat.material_id);\n } */\n \n LoadingPopup.hide();\n })\n .catch(error => {\n document.querySelector('.user-msg-box').innerHTML = 'Query expression NOT valid';\n });\n \n \n/*\n oReq.addEventListener(\"error\", e => { // Not valid query\n console.log('Search ERROR - Not valid query ');\n this.total_results= 0;\n this.setData([]);\n this._updateUI();\n this.resultsContainer.style.visibility = 'visible';\n LoadingPopup.hide();\n });\n*/\n }\n\n\n _render(){\n this.element.style.display = this.visible ? '' : 'none';\n\n if (this.visible){\n this.noResultsBox.style.display = this.noResults ? '' : 'none';\n this.matListWrapper.style.display = this.noResults ? 'none' : '';\n this.matListContainer.updateList(this.matMap);\n document.querySelector('.user-msg-box').innerHTML = this.noResults ? 'No search results' : 'See result list below';\n }else\n document.querySelector('.user-msg-box').innerHTML = '';\n\n \n }\n\n\n _setMatList(matList){\n\n if (matList.length > 0){\n matList.forEach( mat => {\n\n if (this.matMap.has(mat.formula_reduced)){\n let matArray = this.matMap.get(mat.formula_reduced);\n matArray.push(mat);\n }else\n this.matMap.set(mat.formula_reduced, [mat]);\n });\n\n }else this.matMap.clear(); // Right query - results not found\n }\n\n}\n\n\n\nclass PaginationControl{\n\n constructor(){\n this.element = document.createElement('div');\n //this.element.className = 'pag-header';\n this.element.innerHTML = `\n <div class=\"results-total\" >Results</div>\n\n <div class=\"pag-header\" >\n <span class=\"prevButton\">\n <img src=\"img/prev.svg\" style=\"display: inline;\" width=\"7px\"/> &nbsp; prev\n </span> &nbsp;&nbsp;\n <span class=\"page\"> X </span> &nbsp;&nbsp;\n <span class=\"nextButton\"> next &nbsp;\n <img src=\"img/next.svg\" width=\"7px\" />\n </span>\n </div>\n `;\n\n this.titleBox = this.element.querySelector('.results-total');\n\n this.prevButton = this.element.querySelector('.prevButton');\n this.pageElement = this.element.querySelector('.page'); \n this.nextButton = this.element.querySelector('.nextButton');\n\n this.prevButton.addEventListener('click', e => {\n if (this.pagesData.page === 1) return;\n this.prevPageListener(this.pagesData.page-1);\n });\n\n this.nextButton.addEventListener('click', e => {\n console.log('nextButton')\n if (this.pagesData.page === this.pagesData.pages) return;\n this.nextPageListener(this.pagesData.page+1);\n });\n\n this.pagesData;\n\n }\n\n\n set(pagesData){\n this.pagesData = pagesData;\n this.titleBox.innerHTML= 'Results (total: '+pagesData.total+')';\n this.pageElement.innerHTML= 'page '+pagesData.page+' / '+pagesData.pages;\n }\n\n\n setPrevPageListener(listener){\n this.prevPageListener = listener;\n }\n\n\n setNextPageListener(listener){\n this.nextPageListener = listener;\n }\n}\n\n\n\nclass MatListContainer{\n\n constructor(){\n this.element = document.createElement('div');\n this.element.className = 'mat-list-container';\n this.element.innerHTML = `\n <table>\n <thead> <tr>\n <th style=\"width: 24%;\"></th>\n <th style=\"width: 16%;\">\n <span info-sys-data=\"space-group\">Space group</span>\n </th>\n <th style=\"width: 20%;\">\n <span >Space gr. int. symbol</span>\n </th>\n\n <th style=\"width: 22%;\">\n <span info-sys-data=\"structure-type\">Structure type</span>\n </th>\n <th style=\"width: 18%;\">Nº calculations</th>\n </tr> </thead>\n\n <tbody> </tbody> \n </table>\n `;\n\n this.tbody = this.element.querySelector('tbody');\n\n this.tbody.addEventListener('click', e => {\n\n let materialRow = event.target.closest('tr.mat-row');\n\n if (materialRow) \n util.setBrowserHashPath('material', materialRow.getAttribute('data-mat-id'));\n\n e.stopPropagation();\n });\n\n }\n\n\n updateList(matMap){\n //console.log('matMap', matMap);\n if (matMap.size === 0){ this.tbody.innerHTML = ''; return }\n\n let html = ''\n matMap.forEach( (mats, formula) => {\n\n let rFormula = util.getSubscriptedFormula(formula);\n html+= '<tr> <td class=\"formula\" colspan=\"5\"><b>'+rFormula+'</b>';\n if ( mats.length > 1)\n html += '<span style=\"font-size: 0.86em;\"> ('+mats.length+' structures)</span>';\n html += '</td></tr>';\n\n mats.forEach( mat => {\n let label = (mat.material_name ? mat.material_name : rFormula);\n //console.log(\"MATERIAL \", mat.formula, mat.material_name, rFormula, label);\n html +=\n `<tr class=\"mat-row\" data-mat-id=\"${mat.material_id}\">\n <td > ${label} [${mat.formula}] </td>\n <td style=\"text-align:center\" >\n ${mat.space_group_number ? mat.space_group_number : '' }\n </td>\n <td>\n ${mat.space_group_international_short_symbol ? \n mat.space_group_international_short_symbol : '' }\n </td>\n\n <td> ${mat.structure_type ? mat.structure_type : '' } </td>\n <td style=\"text-align:center\" > ${mat.n_calculations ? mat.n_calculations : ''} </td>\n </tr>`;\n });\n });\n\n this.tbody.innerHTML = html;\n\n InfoSys.addToInfoSystem(this.element);\n }\n\n\n}\n\n\nmodule.exports = MaterialList;\n\n\n//# sourceURL=webpack:///./src/search-mod/MaterialList.view.js?");
eval("\n/**\n * Copyright 2016-2018 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 component implements the list of materials found in the search\n */\n\n\n\nlet util = __webpack_require__(/*! ../common/util.js */ \"./src/common/util.js\");\nlet InfoSys = __webpack_require__(/*! ../common/InfoSys.js */ \"./src/common/InfoSys.js\");\nlet LoadingPopup = __webpack_require__(/*! ../common/LoadingPopup.js */ \"./src/common/LoadingPopup.js\");\n\n//const RESULTS_PER_PAGE = 20;\n\n\nclass MaterialList {\n\n constructor(){\n this.element = document.createElement('div');\n this.element.className = 'MaterialList';\n\n // state \n this.visible = false;\n this.noResults = true;\n this.matMap = new Map();\n //this.currentSystemType = 'bulk';\n this.optimadeQuery = null;\n \n this.noResultsBox = document.createElement('div');\n this.noResultsBox.style = 'text-align: center; font-weight: bold'\n this.noResultsBox.innerHTML = 'NO RESULTS FOUND';\n this.element.append(this.noResultsBox);\n\n this.matListWrapper = document.createElement('div');\n this.element.append(this.matListWrapper);\n this.pagControl = new PaginationControl();\n this.matListWrapper.append(this.pagControl.element);\n this.pagControl.setPrevPageListener( page => {\n this._search(page);\n });\n this.pagControl.setNextPageListener( page => {\n this._search(page);\n })\n\n this.matListContainer = new MatListContainer();\n this.matListWrapper.append(this.matListContainer.element);\n }\n\n\n attachAndSetEvents(element){\n element.appendChild(this.element);\n this._render();\n }\n\n\n invalidateSearch(){\n this.visible = false;\n this._render();\n }\n\n\n initSearch(optimadeQuery){\n\n this.optimadeQuery = optimadeQuery;\n this._search();\n }\n\n _search(page){\n //this.resultsContainer.style.visibility = 'hidden';\n this.matMap.clear();\n\n LoadingPopup.show();\n\n let reqJson = {\n query: this.optimadeQuery,\n search_by: {}\n };\n if (page) reqJson.search_by = { page: page}\n\n // Add the restricted option from the checkbox\n let restrictedEl = document.getElementById('restricted-search');\n reqJson.search_by.restricted = (restrictedEl.checked ? '1' : '0');\n console.log('SEARCHING: ', reqJson );\n\n document.querySelector('#syntax-error').style.visibility = 'hidden';\n fetch(util.getSearchURL(), {\n method: 'POST',\n headers: {'Content-Type': 'application/json;charset=utf-8'},\n body: JSON.stringify(reqJson)\n })\n .then( resp => resp.json() )\n .then( result => {\n console.log('GETTING: ', result);\n\n // Update state\n this.noResults = (result.results.length === 0); \n this._setMatList(result.results);\n this.pagControl.set(result.pages);\n \n this.visible = true;\n this._render();\n })\n .catch(error => {\n document.querySelector('#syntax-error').style.visibility = 'visible';\n })\n .finally(() => {\n LoadingPopup.hide();\n });\n \n \n/*\n oReq.addEventListener(\"error\", e => { // Not valid query\n console.log('Search ERROR - Not valid query ');\n this.total_results= 0;\n this.setData([]);\n this._updateUI();\n this.resultsContainer.style.visibility = 'visible';\n LoadingPopup.hide();\n });\n*/\n }\n\n\n _render(){\n this.element.style.display = this.visible ? '' : 'none';\n if (this.visible) {\n this.noResultsBox.style.display = this.noResults ? '' : 'none';\n this.matListWrapper.style.display = this.noResults ? 'none' : '';\n this.matListContainer.updateList(this.matMap);\n //document.querySelector('.user-msg-box').innerHTML = this.noResults ? 'No search results' : 'See result list below';\n } else {\n //document.querySelector('.user-msg-box').innerHTML = '';\n }\n }\n\n\n _setMatList(matList){\n\n if (matList.length > 0){\n matList.forEach( mat => {\n\n if (this.matMap.has(mat.formula_reduced)){\n let matArray = this.matMap.get(mat.formula_reduced);\n matArray.push(mat);\n }else\n this.matMap.set(mat.formula_reduced, [mat]);\n });\n\n }else this.matMap.clear(); // Right query - results not found\n }\n\n}\n\n\n\nclass PaginationControl{\n\n constructor(){\n this.element = document.createElement('div');\n //this.element.className = 'pag-header';\n this.element.innerHTML = `\n <div class=\"results-total\" >Results</div>\n\n <div class=\"pag-header\" >\n <span class=\"prevButton\">\n <img src=\"img/prev.svg\" style=\"display: inline;\" width=\"7px\"/> &nbsp; prev\n </span> &nbsp;&nbsp;\n <span class=\"page\"> X </span> &nbsp;&nbsp;\n <span class=\"nextButton\"> next &nbsp;\n <img src=\"img/next.svg\" width=\"7px\" />\n </span>\n </div>\n `;\n\n this.titleBox = this.element.querySelector('.results-total');\n\n this.prevButton = this.element.querySelector('.prevButton');\n this.pageElement = this.element.querySelector('.page'); \n this.nextButton = this.element.querySelector('.nextButton');\n\n this.prevButton.addEventListener('click', e => {\n if (this.pagesData.page === 1) return;\n this.prevPageListener(this.pagesData.page-1);\n });\n\n this.nextButton.addEventListener('click', e => {\n console.log('nextButton')\n if (this.pagesData.page === this.pagesData.pages) return;\n this.nextPageListener(this.pagesData.page+1);\n });\n\n this.pagesData;\n\n }\n\n\n set(pagesData){\n this.pagesData = pagesData;\n this.titleBox.innerHTML= 'Results (total: '+pagesData.total+')';\n this.pageElement.innerHTML= 'page '+pagesData.page+' / '+pagesData.pages;\n }\n\n\n setPrevPageListener(listener){\n this.prevPageListener = listener;\n }\n\n\n setNextPageListener(listener){\n this.nextPageListener = listener;\n }\n}\n\n\n\nclass MatListContainer{\n\n constructor(){\n this.element = document.createElement('div');\n this.element.className = 'mat-list-container';\n this.element.innerHTML = `\n <table>\n <thead> <tr>\n <th style=\"width: 24%;\"></th>\n <th style=\"width: 16%;\">\n <span info-sys-data=\"space-group\">Space group</span>\n </th>\n <th style=\"width: 20%;\">\n <span >Space gr. int. symbol</span>\n </th>\n\n <th style=\"width: 22%;\">\n <span info-sys-data=\"structure-type\">Structure type</span>\n </th>\n <th style=\"width: 18%;\">Nº calculations</th>\n </tr> </thead>\n\n <tbody> </tbody> \n </table>\n `;\n\n this.tbody = this.element.querySelector('tbody');\n\n this.tbody.addEventListener('click', e => {\n\n let materialRow = event.target.closest('tr.mat-row');\n\n if (materialRow) \n util.setBrowserHashPath('material', materialRow.getAttribute('data-mat-id'));\n\n e.stopPropagation();\n });\n\n }\n\n\n updateList(matMap){\n //console.log('matMap', matMap);\n if (matMap.size === 0){ this.tbody.innerHTML = ''; return }\n\n let html = ''\n matMap.forEach( (mats, formula) => {\n\n let rFormula = util.getSubscriptedFormula(formula);\n html+= '<tr> <td class=\"formula\" colspan=\"5\"><b>'+rFormula+'</b>';\n if ( mats.length > 1)\n html += '<span style=\"font-size: 0.86em;\"> ('+mats.length+' structures)</span>';\n html += '</td></tr>';\n\n mats.forEach( mat => {\n let label = (mat.material_name ? mat.material_name : rFormula);\n //console.log(\"MATERIAL \", mat.formula, mat.material_name, rFormula, label);\n html +=\n `<tr class=\"mat-row\" data-mat-id=\"${mat.material_id}\">\n <td > ${label} [${mat.formula}] </td>\n <td style=\"text-align:center\" >\n ${mat.space_group_number ? mat.space_group_number : '' }\n </td>\n <td>\n ${mat.space_group_international_short_symbol ? \n mat.space_group_international_short_symbol : '' }\n </td>\n\n <td> ${mat.structure_type ? mat.structure_type : '' } </td>\n <td style=\"text-align:center\" > ${mat.n_calculations ? mat.n_calculations : ''} </td>\n </tr>`;\n });\n });\n\n this.tbody.innerHTML = html;\n\n InfoSys.addToInfoSystem(this.element);\n }\n\n\n}\n\n\nmodule.exports = MaterialList;\n\n\n//# sourceURL=webpack:///./src/search-mod/MaterialList.view.js?");
/***/ }),
......@@ -447,7 +447,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\nconst REACTIVE_SEARCH = false;\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 <!--<div style=\"padding: 5px 0; \">\n <input type=\"checkbox\" id=\"allow-other-elements\" value=\"\" checked>Allow other elements\n </div>-->\n <div style=\"display: flex\">\n <div class=\"perm-tooltip search-option\" style=\"margin-right: 10px\">\n <input id=\"allow-other-elements\" name=\"allow-other-elements\" type=\"checkbox\" checked>\n <label for=\"allow-other-elements\" class=\"perm-tooltip\">Allow other elements</label>\n <span class=\"tooltiptext\">If selected, the returned materials may also contain other elements.</span>\n </div>\n <div class=\"perm-tooltip search-option\">\n <input id=\"restricted-search\" name=\"restricted-search\" type=\"checkbox\" >\n <label for=\"restricted-search\" class=\"perm-tooltip\">Restrict to individual calculations</label>\n <span class=\"tooltiptext\">If selected, the query will return materials that have individual calculations matching all your search criteria.</span>\n </div>\n </div>\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 REACTIVE_SEARCH ? this.sendQuery() : this.materialList.invalidateSearch();\n }); \n\n this.allowOtherElementsCheckbox = this.element.querySelector('#allow-other-elements');\n this.allowOtherElementsCheckbox.addEventListener( 'change', e => {\n REACTIVE_SEARCH ? this.sendQuery() : this.materialList.invalidateSearch();\n }); \n this.restrictedCheckbox = this.element.querySelector('#restricted-search');\n this.restrictedCheckbox.addEventListener( 'change', e => {\n REACTIVE_SEARCH ? this.sendQuery() : this.materialList.invalidateSearch();\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 => {\n 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 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\n this.filterPanel = new FilterPanel();\n this.filterSidePanel.appendChild(this.filterPanel.element);\n\n this.filterPanel.setPropsChangeListener( propsMap => {\n REACTIVE_SEARCH ? this.sendQuery() : this.materialList.invalidateSearch();\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 // Disable/enable bool operation buttons\n this.element.querySelectorAll('.bool-buttons button').forEach( button => {\n button.disabled = (selectingTab === 'name');\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 searchBoxOptimadeQuery = this.searchBox.getOptimadeQuery(this.allowOtherElementsCheckbox.checked);\n\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.className = 'FormulaBox';// this.element.setAttribute(\"id\",'formula-box');\n this.element.innerHTML=\n `\n <input type=\"text\" placeholder=\"Add formula to the search query above\">\n <button class=\"adding-formula-btn\" disabled> Add to query </button>\n `;\n this.formulaTextField = this.element.querySelector('input');\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\nconst REACTIVE_SEARCH_RIGHT = false; // Whether the right panel uses reactive search\nconst REACTIVE_SEARCH_LEFT = true; // Whether the left panel uses reactive search\nconst INVALIDATE_RESULTS = false; // Whether the search results are invalidated upon changing search criteria\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 this.searchFilters = [];\n this.isVisible = true;\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 <!--<div style=\"padding: 5px 0; \">\n <input type=\"checkbox\" id=\"allow-other-elements\" value=\"\" checked>Allow other elements\n </div>-->\n <div style=\"display: flex\">\n <div class=\"perm-tooltip search-option\" style=\"margin-right: 10px\">\n <input id=\"allow-other-elements\" name=\"allow-other-elements\" type=\"checkbox\" checked>\n <label for=\"allow-other-elements\" class=\"perm-tooltip\">Allow other elements</label>\n <span class=\"tooltiptext\">If selected, the returned materials may also contain other elements.</span>\n </div>\n <div class=\"perm-tooltip search-option\">\n <input id=\"restricted-search\" name=\"restricted-search\" type=\"checkbox\" >\n <label for=\"restricted-search\" class=\"perm-tooltip\">Restrict to individual calculations</label>\n <span class=\"tooltiptext\">If selected, the query will return materials that have individual calculations matching all your search criteria.</span>\n </div>\n </div>\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\">Element<img class=\"search-fold-icon\" src=\"img/unfolded.png\"></button><button class=\"formula-add-btn\">Formula<img class=\"search-fold-icon\" src=\"img/unfolded.png\"></button><button class=\"name-add-btn\">Name<img class=\"search-fold-icon\" src=\"img/unfolded.png\"></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 <div class=\"add-box\">\n <div class=\"add-panel\">\n </div>\n </div>\n <div class=\"results-panel\"> <!-- style=\"display: none\"-->\n </div>\n </div> <!-- search-main-side -->\n `;\n\n this.filterSidePanel = this.element.querySelector('.search-filter-side');\n this.addBox = this.element.querySelector('.add-box');\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 REACTIVE_SEARCH_RIGHT ? this.sendQuery() : null;\n //REACTIVE_SEARCH_RIGHT ? this.sendQuery() : INVALIDATE_RESULTS && this.materialList.invalidateSearch();\n }); \n this.allowOtherElementsCheckbox = this.element.querySelector('#allow-other-elements');\n this.allowOtherElementsCheckbox.addEventListener( 'change', e => {\n REACTIVE_SEARCH_RIGHT ? this.sendQuery() : null;\n //REACTIVE_SEARCH_RIGHT ? this.sendQuery() : this.materialList.invalidateSearch();\n }); \n this.restrictedCheckbox = this.element.querySelector('#restricted-search');\n this.restrictedCheckbox.addEventListener( 'change', e => {\n REACTIVE_SEARCH_RIGHT ? this.sendQuery() : null;\n //REACTIVE_SEARCH_RIGHT ? this.sendQuery() : this.materialList.invalidateSearch();\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 this.addBox.style.display = 'none';\n this.isVisible = false;\n const icon = this.currentTabElement.querySelector('.search-fold-icon')\n icon.src = \"img/folded.png\";\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 this.currentTabElement = this.addElementButton;\n this.currentTab = 'element';\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 => {\n 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, 'formula');\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 this.materialNameBox = new MaterialNameBox();\n this.materialNameBox.setAddMaterialNameListener( name => {\n if (name.trim() !== ''){\n this.searchBox.addTag(name, 'material');// 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\n this.filterPanel = new FilterPanel();\n this.filterSidePanel.appendChild(this.filterPanel.element);\n\n this.filterPanel.setPropsChangeListener( propsMap => {\n REACTIVE_SEARCH_LEFT ? this.sendQuery() : null;\n //REACTIVE_SEARCH_LEFT ? this.sendQuery() : this.materialList.invalidateSearch();\n })\n\n this.materialList= new MaterialList();\n this.resultsPage = this.element.querySelector('.results-panel');\n this.materialList.attachAndSetEvents(this.resultsPage);\n\n this.addPanel.appendChild(this.elementTable.element);\n\n this._events();\n }\n\n\n _events() {\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 console.log(\"CLICK!\");\n\n // Hide or show the current tab\n const icon = e.target.querySelector('.search-fold-icon')\n if (this.currentTab == selectingTab) {\n if (this.isVisible) {\n this.addBox.style.display = 'none';\n icon.src = \"img/folded.png\";\n } else {\n this.addBox.style.display = 'block';\n icon.src = \"img/unfolded.png\";\n }\n this.isVisible = !this.isVisible;\n } else {\n this.addBox.style.display = 'block';\n icon.src = \"img/unfolded.png\";\n this.isVisible = true;\n }\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 this.currentTab = selectingTab;\n this.currentTabElement = selEl;\n\n // Disable/enable bool operation buttons\n this.element.querySelectorAll('.bool-buttons button').forEach( button => {\n button.disabled = (selectingTab === 'name');\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\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 const sep = (searchBoxOptimadeQuery !== '' && propsOptimadeQuery !== '' ? ' AND ' : '');\n //console.log(searchBoxOptimadeQuery)\n //console.log(sep)\n //console.log(propsOptimadeQuery)\n\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 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 _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 return rootQueryObj;\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 _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 _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.className = 'FormulaBox';// this.element.setAttribute(\"id\",'formula-box');\n this.element.innerHTML=\n `\n <input type=\"text\" placeholder=\"Add formula to the search query above\">\n <button class=\"adding-formula-btn\" disabled> Add to query </button>\n `;\n this.formulaTextField = this.element.querySelector('input');\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?");
/***/ }),
......@@ -468,7 +468,7 @@ eval("let Formula = __webpack_require__(/*! ./Formula.js */ \"./src/search-mod/F
/***/ ((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\");\nlet OptimadeTranslator = __webpack_require__(/*! ./OptimadeTranslator.js */ \"./src/search-mod/OptimadeTranslator.js\");\nlet Formula = __webpack_require__(/*! ./Formula.js */ \"./src/search-mod/Formula.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 // Empty query\n if (this.searchQuery.length === 0) {\n return '';\n }\n\n // Material name query. Does not allow combinations.\n if (this.searchQuery.length === 1 && this.queryTypes[0] === 'MN') { // material case\n return `material_name=\"${this.searchQuery[0]}\"`;\n }\n\n // Translate element/formula queries into OptimadeSyntax\n const translator = new OptimadeTranslator();\n return translator.translate(this.searchQuery, this.queryTypes, allowOtherElements);\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 ( isOpen && this.searchQuery.length > 0 \n && this.searchQuery[this.searchQuery.length-1] !== 'NOT'){\n this.addItemInSearchQuery(this.currentOperator, 'S');\n }\n this.addItemInSearchQuery( (isOpen ? '(' : ')'), 'P');\n this.updateSearchQuery();\n }\n\n\n addNOT(){\n\n if ( this.searchQuery.length > 0 \n && this.searchQuery[this.searchQuery.length-1] !== '('){\n this.addItemInSearchQuery(this.currentOperator, 'S');\n }\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// 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\");\nlet OptimadeTranslator = __webpack_require__(/*! ./OptimadeTranslator.js */ \"./src/search-mod/OptimadeTranslator.js\");\nlet Formula = __webpack_require__(/*! ./Formula.js */ \"./src/search-mod/Formula.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 // \n // \n this.currentConnector;\n this.notItem; // It signals next item is preceding for a NOT\n this.inSubquery; // the current query pointer is in a subquery, paretheses open \n this.rootQuery; // Root query representation. Root item object (type=\"query\")\n this._resetState();\n /* Two type of object representing the expresions:\n - item (node): {type: 'element'|'formula'|'query', value: \"ElementName\"|\"Formula\"| Another (sub)query structure, not: true|false }\n - boolean connector: { type: 'connector', value: 'AND'|'OR'}\n\n */\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 id=\"syntax-error\">\n <div>Invalid query syntax</div>\n </div>\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 /*\n this.searchQuery = [];\n this.queryTypes = [];\n this.updateSearchQuery();*/\n // init search query\n this._resetState();\n this.renderQuery();\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 _resetState(){\n this.notItem = false;\n this.inSubquery = false;\n this.rootQuery = { type: 'query', value: [], not: false }; // no prop open indicates that is the first\n }\n\n\n\n \n getOldQueryFormat(){\n const oldFormat = [];\n this.rootQuery.value.forEach( item => {\n oldFormat.push(...getItemInOldFormat(item));\n })\n\n const searchQuery = [];\n const queryTypes = [];\n oldFormat.forEach( item => {\n searchQuery.push(item.split(':')[0]);\n queryTypes.push(item.split(':')[1]);\n })\n console.log('OLD F:', oldFormat, searchQuery, queryTypes);\n\n return [searchQuery, queryTypes]\n\n function getItemInOldFormat(item){\n const oldFormat = [];\n if (item.not){\n oldFormat.push('NOT:S');\n }\n if (item.type === 'query'){\n oldFormat.push('(:S');\n item.value.forEach( i => {\n oldFormat.push(...getItemInOldFormat(i));\n })\n oldFormat.push('):S');\n }else{ // types: element, formula, material and connector\n let itemValue = item.value+':';\n if (item.type === 'element') itemValue += 'E';\n else if (item.type === 'formula') itemValue += 'F';\n else if (item.type === 'material') itemValue += 'MN';\n else itemValue += 'S'; // connector\n oldFormat.push(itemValue);\n }\n return oldFormat;\n }\n\n }\n\n getOptimadeQuery(allowOtherElements) {\n // Empty query\n if (this.rootQuery.value.length === 0) {\n return '';\n }\n\n // Material name query. Does not allow combinations.\n if (this.rootQuery.length === 1 && this.rootQuery.value[0].type === 'material') { // material case\n return `material_name=\"${this.rootQuery.value[0].value}\"`;\n }\n\n // Translate element/formula queries into OptimadeSyntax\n const translator = new OptimadeTranslator();\n const [searchQuery, queryTypes] = this.getOldQueryFormat();\n return translator.translate(searchQuery, queryTypes, allowOtherElements);\n }\n\n setBoolOperator(operator){\n this.currentConnector = operator;\n }\n\n renderQuery(){\n\n this.searchQueryBox.innerHTML = getQueryStructHTML(this.rootQuery);\n \n if (this.searchQueryChangeListener) this.searchQueryChangeListener();\n\n function getQueryStructHTML(queryObj){\n\n if (queryObj.type === 'connector')\n return getSymbolHTML(queryObj.value);\n\n else if (!queryObj.type && queryObj.not) // NOT item wrapper, the item is not defined yet\n return getSymbolHTML(' NOT');\n\n else if (queryObj.type === 'element' || queryObj.type === 'formula' || queryObj.type === 'material') // element and formula\n return (queryObj.not ? getSymbolHTML(' NOT') : '')+\n getTagHtml(queryObj.value, (queryObj.type === 'formula' ? true : false));\n else{ // the object represents a query\n let html= '';\n queryObj.value.forEach( subqueryObj => {\n html += getQueryStructHTML(subqueryObj);\n });\n //console.log('getQueryStructHTML', queryObj, queryObj.value[queryObj.length-1])\n const notHTML = (queryObj.not ? getSymbolHTML(' NOT') : '')\n const isOpen = queryObj.open;\n return notHTML+(isOpen === undefined ? html : \n getSymbolHTML(' (')+html+( isOpen ? '' : getSymbolHTML(') ')));\n } \n }\n\n function getSymbolHTML(s){\n return `<span class=\"search-query-symbol\">${s}</span>`;\n }\n }\n\n\n // Add an item (with a preceding connector if needed) to the query \n // The item could be an NOT wrapper for a tag or subquery\n _addItem(item){ // It can be a tag item or a NOT (wrapping an item, added later) \n const queryContent = this.rootQuery.value;\n const tempQueryContent = this.inSubquery ?\n queryContent[queryContent.length-1].value : queryContent;\n\n // If the item is not at the beginning of the query (or subquery) and\n if ( tempQueryContent.length > 0 && \n // the last item is not a connector (AND|OR)\n tempQueryContent[tempQueryContent.length-1].type !== 'connector'){ \n // Add a connector (before the item)\n tempQueryContent.push( {type: 'connector', value: this.currentConnector} );\n }\n\n if (item === 'NOT') // Add an item wrapper with the NOT prop to true \n tempQueryContent.push({ not: true }); // type and value props are undefined\n else if (item === 'SUBQUERY')\n queryContent.push({ type: 'query', value: [], open: true, not: false}); \n else{ // Regular tags: element|formula|material\n const [type, tag] = item.split(':');\n tempQueryContent.push({type: type, value: tag, not: false});\n }\n console.log('ROOT QUERY ', this.rootQuery)\n this.renderQuery();\n }\n\n\n // Public method - type = element|formula|material \n addTag(tag, type){ // add an element, formula or material\n\n if (this.notItem){ // The tag to add is preceded for a NOT, the item object is already created\n const queryContent = this.rootQuery.value;\n const tempQueryContent = this.inSubquery ?\n queryContent[queryContent.length-1].value : queryContent;\n tempQueryContent[tempQueryContent.length-1].type = type;\n tempQueryContent[tempQueryContent.length-1].value = tag;\n this.notItem = false;\n this.renderQuery();\n }else\n this._addItem(type+':'+tag);\n }\n\n\n addElements(elementArray){\n let index = elementArray.length;\n while (index--) {\n this.addTag(elementArray[index], 'element');\n }\n return true;\n }\n\n\n addParentheses(isOpen){\n const queryContent = this.rootQuery.value; // ** Not checking if the it's inside a subquery\n \n if (isOpen){\n if (this.notItem){\n queryContent[queryContent.length-1].type = 'query';\n queryContent[queryContent.length-1].value = [];\n this.notItem = false;\n }else\n this._addItem('SUBQUERY');\n }\n queryContent[queryContent.length-1].open = isOpen;\n this.renderQuery();\n this.inSubquery = isOpen;\n }\n\n\n addNOT(){\n this._addItem('NOT');\n this.notItem = true; // The next item will be in a not expression\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 removeItem(this.rootQuery, item);\n \n function removeItem(queryObj, item){\n const queryContent = queryObj.value;\n for (let i = 0; i < queryContent.length; i++) {\n if (queryContent[i].value === item) {\n if (queryContent[i+1] && queryContent[i+1].type === 'connector'){\n queryContent.splice(i, 2);\n }else\n queryContent.splice(i, 1);\n break;\n\n }else if (queryContent[i].type === 'query'){\n removeItem(queryContent[i], item);\n if (queryContent[i].value.length == 0){\n if (queryContent[i+1] && queryContent[i+1].type === 'connector'){\n queryContent.splice(i, 2);\n }else\n queryContent.splice(i, 1);\n }\n }\n }\n }\n\n this.renderQuery();\n\n console.log('ROOT QUERY REMOVING ', this.rootQuery)\n\n if (util.ELEMENTS.indexOf(item) >= 0){ // It's an element (being removed)\n this.removeElementListener(item);\n }\n\n if (this.rootQuery.value.length === 0){ // The search query gets blank\n this.cleanSearchQueryListener();\n }\n\n return true;\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// EXPORTS\nmodule.exports = SearchBox;\n\n\n//# sourceURL=webpack:///./src/search-mod/SearchBox.view.js?");
/***/ })
......@@ -480,8 +480,9 @@ eval("/**\n * Copyright 2016-2020 Iker Hurtado\n *\n * Licensed under the Apache
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(__webpack_module_cache__[moduleId]) {
/******/ return __webpack_module_cache__[moduleId].exports;
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
......
......@@ -151,7 +151,6 @@
}
#flagging-form-popup button{
/*width: 200px;*/
margin: 20px 0 10px;
background-color: #EEE;
color: #777;
......@@ -393,8 +392,10 @@ div.title span.unfolded::before{
justify-content: center; /*space-between;*/
}
.search-query-wrapper{
position: relative;
flex-grow: 14;
background-color: white; border: 2px solid #E56400;
background-color: white;
border: 2px solid #E56400;
padding: 8px 6px;
}
......@@ -426,13 +427,7 @@ div.title span.unfolded::before{
.add-buttons{
display: inline-block;
width: 100%;
/*background-color: #DDD;
padding: 20px 0;
*/
margin: 20px 0 0;
}
.bool-buttons button{
......@@ -444,30 +439,33 @@ div.title span.unfolded::before{
.tab-buttons button{
display: inline-block;
background-color: #DDD;/*EEE;*/
width: 115px;
padding: 10px 30px;
margin: 0 10px;
display: inline-flex;
align-items: center;
justify-content: space-between;
border-right: 1px solid #BBB;
background-color: #CFCFCF;
width: 140px;
padding: 15px 20px;
font-size: .9em;
color: #777;
border: 1px solid #777;
color: #666;
text-align: center;
}
.tab-buttons button:last-child {
border-right: none;
}
.tab-buttons button#add-tab-selected{
background-color: #DDD;
color: #E56400;
border-color: #E56400;
font-weight: bold;
}
.tab-buttons button:disabled{
color: #BBB;
border-color: #BBB;
color: #AAA;
cursor: default;
}
.triangle-container{
width: 115px;
display: inline-block;
......@@ -500,12 +498,12 @@ div.title span.unfolded::before{
/* Formula and Material name boxes */
.FormulaBox, .MaterialNameBox{
.FormulaBox,.MaterialNameBox{
display: flex;
justify-content: center;
text-align: left;
background-color: #DDD;
padding: 40px 0;
padding: 20px 0;
}
.FormulaBox input, .MaterialNameBox input{
......@@ -521,7 +519,7 @@ div.title span.unfolded::before{
.FormulaBox input{
box-sizing: border-box;
width: 450px;
width: 580px;
}
/*
......@@ -553,6 +551,8 @@ div.title span.unfolded::before{
}
.AutocompleteTextField-dropdown > div, .AutocompleteMultiselectTextfield-dropdown > div {
display: flex;
align-items: center;
padding: 2px 10px 2px 10px;
cursor: pointer;
border: 1px solid transparent;
......@@ -564,24 +564,29 @@ div.title span.unfolded::before{
.AutocompleteMultiselectTextfield-selected-box{
padding: 3px;
padding-top: 5px;
}
.selectedItemLabel{
display: inline-block;
border-left: 1px solid #E56400;border-right: 1px solid #E56400;
color: #E56400;
/*box-shadow: 1px 1px 2px #BBB;*/
border-radius: 3px;
padding: 1px 4px;
margin-right: 8px; margin-top: 3px;
display: inline-flex;
align-items: center;
border-radius: 10px;
border: 1px solid #E56400;
height: 1.2rem;
padding: 4px 8px;
margin-right: 8px;
margin-top: 8px;
cursor: pointer;
}
.selectedItemLabel>img{
margin-left: 0.5rem;
}
/* Material name autocomplete */
.material-name-autocomplete-textfield{
width: 450px;
width: 580px;
}
.material-name-autocomplete-dropdown{
......@@ -592,7 +597,7 @@ div.title span.unfolded::before{
/* Autocomplete multiselect */
.AutocompleteMultiselectTextfield-dropdown {
width: 210px;
width: 200px;
background-color: white;
box-shadow: 0 1px 2px #E56400;
}
......@@ -626,6 +631,7 @@ div.title span.unfolded::before{
/*background-color: white;*/
}
.filter-section-box input[type="text"]{
box-sizing: border-box;
width: 200px;
padding: 6px;
border: 1px solid #DDD;
......@@ -771,7 +777,7 @@ img.remove-label{ cursor: pointer; }
}
#elementable table#pt-main{
padding-top: 25px;
padding-top: 20px;
color: #333; border: 0;
}
......@@ -847,10 +853,15 @@ img.remove-label{ cursor: pointer; }
div#specialRows{margin: 20px 40px;}
.legend{ text-align: center; padding: 16px 0;}
.legend {
text-align: center;
padding-top: 16px;
padding-bottom: 20px;
}
.legend div{
display: inline-block; color: black;
padding: 2px 12px; margin: 4px 0;
padding: 2px 12px;
margin: 4px 0;
}
.MaterialList{ /*width: 690px; margin: 0 auto 80px;*/
......@@ -1373,6 +1384,35 @@ text.structure-viewer-legend-labels{
font-size: 16px;
}
#syntax-error{
visibility: hidden;
display: flex;
align-items: center;
position: absolute;
height: 1.5rem;
color: white;
background-color: #E56400;
left: -2px;
bottom: 100%;
padding: 0.1rem 0.5rem;
}
.value-checkbox {
margin-right: 0.5rem;
}
.tab-buttons>button>img.search-fold-icon {
display: inline-block;
width: 15px;
height: 15px;
visibility: hidden;
pointer-events: none;
}
.tab-buttons>button#add-tab-selected>img.search-fold-icon {
visibility: visible;
}
/*
.tooltip {
visibility: hidden;
......
......@@ -260,15 +260,9 @@ PubSub.subscribe('show-search', search => {
titleElement.innerHTML = 'NOMAD Encyclopedia - Search';
breadcrumb.setState('search', search);
if (search === undefined) {
//LoadingPopup.reset();
searchMod.showSearchPage();
} else if (search === 'results') {
searchMod.showResultsPage();
}
showModuleDOM(searchMod.element);
searchMod.sendQuery();
});
......
......@@ -34,8 +34,8 @@ class AutocompleteMultiselectTextfield {
this.element.innerHTML = `
<input type="text" placeholder="${placeholder}" />
<div class="AutocompleteMultiselectTextfield-selected-box"></div>
<div class="AutocompleteMultiselectTextfield-dropdown ${this.id}-autocomplete-multiselect-dropdown"></div>
<div class="AutocompleteMultiselectTextfield-selected-box"></div>
`;
this.input = this.element.querySelector('input');
......@@ -144,7 +144,7 @@ class AutocompleteMultiselectTextfield {
function generateListItem(value, inputText, present) {
const listItem = document.createElement("div");
let innerHTML = `<input type="checkbox" data-value="${value}" ${present ? 'checked' : ''}>`;
let innerHTML = `<input class="value-checkbox" type="checkbox" data-value="${value}" ${present ? 'checked' : ''}>`;
if (inputText){
const pos = value.toUpperCase().indexOf(inputText.toUpperCase()); // console.log('pos', pos)
innerHTML +=
......@@ -197,7 +197,14 @@ class AutocompleteMultiselectTextfield {
const label = document.createElement('span');
label.className = 'selectedItemLabel';
label.dataset.value = value;
label.innerHTML = `${value} ❌`;
const labelText = document.createElement('div');