class RessourceList extends List { constructor(idOrElement, subscriberList, type, options={}) { if (!['corpus', 'job'].includes(type)) { console.error("Unknown Type!"); return; } super(idOrElement, {...RessourceList.options['common'], ...RessourceList.options[type], ...options}); this.type = type; subscriberList.push(this); } _init(ressources) { this.addRessources(Object.values(ressources)); this.sort("creation_date", {order: "desc"}); } _update(patch) { let item, pathArray; for (let operation of patch) { /* "/ressourceId/valueName" -> ["ressourceId", "valueName"] */ pathArray = operation.path.split("/").slice(1); switch(operation.op) { case "add": if (pathArray.includes("results")) {break;} this.addRessources([operation.value]); break; case "remove": this.remove("id", pathArray[0]); break; case "replace": item = this.get("id", pathArray[0])[0]; switch(pathArray[1]) { case "status": item.values({status: operation.value}); break; default: break; } default: break; } } } addRessources(ressources) { this.add(ressources.map(x => RessourceList.dataMapper[this.type](x))); } } RessourceList.dataMapper = { corpus: corpus => ({creation_date: corpus.creation_date, description: corpus.description, id: corpus.id, "analyse-link": `/corpora/${corpus.id}/analyse`, "edit-link": `/corpora/${corpus.id}`, status: corpus.status, title: corpus.title}), job: job => ({creation_date: job.creation_date, description: job.description, id: job.id, link: `/jobs/${job.id}`, service: job.service, status: job.status, title: job.title}) }; RessourceList.options = { common: {page: 4, pagination: {innerWindow: 8, outerWindow: 1}}, corpus: {item: ` book
edit Analysesearch `, valueNames: ["creation_date", "description", "title", {data: ["id"]}, {name: "analyse-link", attr: "href"}, {name: "edit-link", attr: "href"}, {name: "status", attr: "data-status"}]}, job: {item: `
Viewsend `, valueNames: ["creation_date", "description", "title", {data: ["id"]}, {name: "link", attr: "href"}, {name: "service", attr: "data-service"}, {name: "status", attr: "data-status"}]} }; class ResultsList extends List { // get display options from display options form element static getDisplayOptions(displayOptionsFormElement) { // gets display options parameters let displayOptionsFormData let displayOptionsData; displayOptionsFormData = new FormData(displayOptionsFormElement); displayOptionsData = {"resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"), "resultsContex": displayOptionsFormData.get("display-options-form-result_context"), "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")}; return displayOptionsData } // ###### Functions to inspect one match, to show more details ###### // activate inspect buttons if queryFinished is true static activateInspect() { if (progress === 100) { let inspectBtnElements; inspectBtnElements = document.getElementsByClassName("inspect"); for (let inspectBtn of inspectBtnElements) { inspectBtn.classList.remove("disabled"); } } else { return } } //gets result cpos infos for one dataIndex to send back to the server inspect(dataIndex) { // This function should be in the AnalysisClient class as a method. console.log("Inspect!"); console.log(results.resultsJSON.matches[dataIndex].c); let contextResultsElement = document.getElementById("context-results"); contextResultsElement.innerHTML = ""; // clear it from old inspects contextModal.open(); nopaque.socket.emit("corpus_analysis_inspect_match", {payload: { first_cpos: results.resultsJSON.matches[dataIndex].c[0], last_cpos: results.resultsJSON.matches[dataIndex].c[1] } }); } showMatchContext(response) { let contextData = response.payload let contextResultsElement; let contextModalLoading; let contextModalReady; let expertModeSwitchElement let partElement let token; let tokenElement; let tokenElements; let lc; let c; let rc; console.log("###### match_context ######"); console.log("Incoming data:", contextData); expertModeSwitchElement = document.getElementById("display-options-form-expert_mode"); contextResultsElement = document.getElementById("context-results"); if (contextData.cpos_ranges == true) { // python range like function from MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range) const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step)); lc = range(contextData.match.lc[0], contextData.match.lc[1], 1) c = range(contextData.match.c[0], contextData.match.c[1], 1) rc = range(contextData.match.rc[0], contextData.match.rc[1], 1) } else { lc = contextData.match.lc; c = contextData.match.c; rc = contextData.match.rc; } partElement = document.createElement("p"); for (let cpos of lc) { token = contextData["cpos_lookup"][cpos]; partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); contextResultsElement.append(partElement); } for (let cpos of c) { token = contextData["cpos_lookup"][cpos]; partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); contextResultsElement.append(partElement); } for (let cpos of rc) { token = contextData["cpos_lookup"][cpos]; partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); contextResultsElement.append(partElement); } if (expertModeSwitchElement.checked) { tokenElements = partElement.getElementsByClassName("token"); console.log(this); this.expertModeOn(tokenElements, contextData); } } // ###### Display options changing live how the matches are being displayed ###### // Event function that changes the shown hits per page. // Just alters the resultsList.page property changeHitsPerPage(event) { try { resultsList.page = event.target.value; resultsList.update(); nopaque.flash("Updated matches per page.") } catch (e) { console.log("resultsList has no results right now. Live update of items per page is useless for now."); } } // Event function triggered on context select change and also if pagination is clicked changeContext(event) { let newContextValue; let lc; let rc; let array; try { if (event.type === "change") { nopaque.flash("Updated context per match!"); } } catch (e) { // console.log(e); // console.log("This error is expected."); } finally { newContextValue = document.getElementById("display-options-form-result_context").value; console.log("Context value is:", newContextValue); lc = document.getElementsByClassName("left-context"); rc = document.getElementsByClassName("right-context"); for (let element of lc) { array = Array.from(element.childNodes); for (let element of array.slice(newContextValue)) { element.classList.add("hide"); } for (let element of array.slice(0, newContextValue)) { element.classList.remove("hide"); } } for (let element of rc) { array = Array.from(element.childNodes); for (let element of array.slice(newContextValue)) { element.classList.add("hide"); } for (let element of array.slice(0, newContextValue)) { element.classList.remove("hide"); } } } } // ###### Expert view event functions ###### // Event function to check if pagination is used and then look if // expertModeSwitchElement is checked // if checked than expertModeOn is executed // if unchecked expertModeOff is executed eventHandlerCheck(event) { console.log("pagination used!"); console.log(expertModeSwitchElement.checked); if (expertModeSwitchElement.checked) { this.expertModeOn(event.currentTarget.tokenElements, resultsJSON); } else if (!expertModeSwitchElement.checked) { event.preventDefault(); console.log("prevented! Destroy"); this.expertModeOff(event.currentTarget.tokenElements); } } // function to apply extra information and animation to every token expertModeOn(tokenElements, results) { let token; console.log("expertModeOn!"); for (let tokenElement of tokenElements) { tokenElement.classList.add("chip"); tokenElement.classList.add("hoverable"); tokenElement.classList.add("expert-view"); token = results["cpos_lookup"][tokenElement.dataset.cpos]; tokenElement.addEventListener("mouseover", (event) => { console.log("Mouseover!"); console.log(event.target); token = results["cpos_lookup"][event.target.dataset.cpos]; this.addToolTipToTokenElement(event.target, token); }); } } // fuction that creates Tooltip for one token and extracts the corresponding // infos from the result JSON addToolTipToTokenElement(tokenElement, token) { M.Tooltip.init(tokenElement, {"html": `
Token information Source information
Word: ${token["word"]}
Lemma: ${token["lemma"]}
POS: ${token["pos"]}
Simple POS: ${token["simple_pos"]}
NER: ${token["ner"]}
Title: ${resultsJSON["text_lookup"][token["text"]]["title"]}
Author: ${resultsJSON["text_lookup"][token["text"]]["author"]}
Publishing year: ${resultsJSON["text_lookup"][token["text"]]["publishing_year"]}
`}); } // function to remove extra informations and animations from tokens expertModeOff(tokenElements) { console.log("expertModeOff!"); for (let tokenElement of tokenElements) { tokenElement.classList.remove("chip"); tokenElement.classList.remove("hoverable"); tokenElement.classList.remove("expert-view"); tokenElement.outerHTML = tokenElement.outerHTML; // this is actually a workaround, but it works pretty fast } } createResultRowElement(item, chunk) { let values, cpos, token, matchRowElement, lcCellElement, hitCellElement, rcCellElement, textTitlesCellElement, matchNrElement, lc, c, rc; // gather values from item values = item.values(); if (chunk.cpos_ranges == true) { // python range like function from MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range) const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step)); lc = range(values.lc[0], values.lc[1], 1) c = range(values.c[0], values.c[1], 1) rc = range(values.rc[0], values.rc[1], 1) } else { lc = values.lc; c = values.c; rc = values.rc; } // get infos for full match row matchRowElement = document.createElement("tr"); matchRowElement.setAttribute("data-index", values["index"]) lcCellElement = document.createElement("td"); lcCellElement.classList.add("left-context"); matchRowElement.appendChild(lcCellElement); for (cpos of lc) { token = chunk["cpos_lookup"][cpos]; lcCellElement.insertAdjacentHTML("beforeend", `${token["word"]} `); } // get infos for hit of match let textTitles = new Set(); hitCellElement = document.createElement("td"); hitCellElement.classList.add("match-hit"); textTitlesCellElement = document.createElement("td"); textTitlesCellElement.classList.add("titles"); matchNrElement = document.createElement("td"); matchNrElement.classList.add("match-nr"); matchRowElement.appendChild(hitCellElement); for (cpos of c) { token = chunk["cpos_lookup"][cpos]; hitCellElement.insertAdjacentHTML("beforeend", `${token["word"]} `); // get text titles of every hit cpos token textTitles.add(chunk["text_lookup"][token["text"]]["text_title"]); // add button to trigger more context to every match td var inspectBtn = document.createElement("a"); inspectBtn.setAttribute("class", "btn-floating btn-flat waves-effect waves-light grey right inspect disabled"); inspectBtn.innerHTML = 'search'; inspectBtn.onclick = () => {this.inspect(values["index"])}; } // add text titles at front as first td of one row hitCellElement.appendChild(inspectBtn); textTitlesCellElement.innerText = [...textTitles].join(", "); matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML); matchNrElement.innerText = values["index"] + 1; matchRowElement.insertAdjacentHTML("afterbegin", matchNrElement.outerHTML); // get infos for right context of match rcCellElement = document.createElement("td"); rcCellElement.classList.add("right-context"); matchRowElement.appendChild(rcCellElement); for (cpos of rc) { token = chunk["cpos_lookup"][cpos]; rcCellElement.insertAdjacentHTML("beforeend", `${token["word"]} `); } return matchRowElement } }