diff --git a/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js b/web/app/static/js/modules/corpus_analysis/client/Client.js similarity index 88% rename from web/app/static/js/modules/nopaque.CorpusAnalysisClient.js rename to web/app/static/js/modules/corpus_analysis/client/Client.js index b64c24d8..990c6851 100644 --- a/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js +++ b/web/app/static/js/modules/corpus_analysis/client/Client.js @@ -1,12 +1,12 @@ /** - * This class is used to create a CorpusAnalysisClient object. + * This class is used to create a Client object. * The client handels the client server communication. * It requests data (e.g. the analysis session or query results) from the * the server and recieves them, if it dynamicMode is true. * If dynamicMode is false, the client can also handle data that is already * loaded and not coming in in chunks. */ -class CorpusAnalysisClient { +class Client { constructor({corpusId = null, socket = null, logging = true, @@ -39,7 +39,7 @@ class CorpusAnalysisClient { } } - // Registers one or more SocketEventListeners to the CorpusAnalysisClient. + // Registers one or more SocketEventListeners to the Client. setSocketEventListeners(socketEventListeners) { for (let socketEventListener of socketEventListeners) { this.socketEventListeners[socketEventListener.type] = socketEventListener; @@ -56,7 +56,15 @@ class CorpusAnalysisClient { } } - // Registers a CorpusAnalysisDisplay object to the CorpusAnalysisClient. + + // TODO: get rid of this disply stuff and send commands to the viewer + // on what to do/show/hide etc + + notifyView(SendWhatToDo) { + + } + + // Registers a CorpusAnalysisDisplay object to the Client. setDisplay(type, corpusAnalysisDisplay) { this.displays[type] = corpusAnalysisDisplay; } @@ -87,27 +95,26 @@ class CorpusAnalysisClient { } /** - * Requests a corpus analysis session via socket.io. - * Opens a loading modal at the start of the request. - * Will be closed if session has been successfully recieved. + * Connects to the corpus analysis session for the specified corpus via + * socket.io. */ - requestSession() { - console.info('corpus_analysis_init: Client requesting session via', + connect() { + console.info('corpus_analysis_init: Client connecting to session via', 'socket.emit'); - if (this.displays.init != undefined) { - this.displays.init.element.M_Modal.open(); - this.displays.init.setVisibilityByStatus('waiting'); - } + // if (this.displays.init != undefined) { + // this.displays.init.element.M_Modal.open(); + // this.displays.init.setVisibilityByStatus('waiting'); + // } this.socket.emit('corpus_analysis_init', this.corpusId); } /** - * Request query data for the query string that has been sent to the server. + * Emits query to the server via socket.io. Server will send the results + * back. */ - requestQueryData(queryStr) { - console.info('corpus_analysis_query: Client requesting query data via', + query(queryStr) { + console.info('corpus_analysis_query: Client sending query via', 'socket.emit for the query', queryStr); - // TODO: Display stuff ? this.socket.emit('corpus_analysis_query', queryStr); } } @@ -236,8 +243,8 @@ class ListenerCallback { // export Classes from this module export { - CorpusAnalysisClient, - CorpusAnalysisDisplay, + Client, SocketEventListener, + CorpusAnalysisDisplay, ListenerCallback, }; \ No newline at end of file diff --git a/web/app/static/js/modules/nopaque.listenerCallbacks.js b/web/app/static/js/modules/corpus_analysis/client/callbacks.js similarity index 100% rename from web/app/static/js/modules/nopaque.listenerCallbacks.js rename to web/app/static/js/modules/corpus_analysis/client/callbacks.js diff --git a/web/app/static/js/modules/nopaque.listenerFunctions.js b/web/app/static/js/modules/corpus_analysis/client/listeners.js similarity index 100% rename from web/app/static/js/modules/nopaque.listenerFunctions.js rename to web/app/static/js/modules/corpus_analysis/client/listeners.js diff --git a/web/app/static/js/modules/corpus_analysis/main.js b/web/app/static/js/modules/corpus_analysis/main.js new file mode 100644 index 00000000..e69de29b diff --git a/web/app/static/js/nopaque.Results.js b/web/app/static/js/modules/corpus_analysis/model/Results.js similarity index 95% rename from web/app/static/js/nopaque.Results.js rename to web/app/static/js/modules/corpus_analysis/model/Results.js index 2f7778f1..37435531 100644 --- a/web/app/static/js/nopaque.Results.js +++ b/web/app/static/js/modules/corpus_analysis/model/Results.js @@ -1,3 +1,9 @@ +/** + * These classes are implementing the data store of the corpus_analysis + * package. If we follow the idea of the Model View Controller Pattern these + * classes combined in the Results class define the Model. + */ + class Results { constructor(data, jsList , metaData) { this.data = data; diff --git a/web/app/static/js/modules/nopaque.InteractionElement.js b/web/app/static/js/modules/corpus_analysis/view/InteractionElement.js similarity index 100% rename from web/app/static/js/modules/nopaque.InteractionElement.js rename to web/app/static/js/modules/corpus_analysis/view/InteractionElement.js diff --git a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js new file mode 100644 index 00000000..fa0e8b36 --- /dev/null +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -0,0 +1,790 @@ +/** + * This class is implements a View which handles the reprensentation of the + * data that has been fetched by the Client of the corpus_analysis. This view + * only handles how the data is shown to the user. View extends the list.js + * List class. + */ + +class ResultsList extends List { + /** + * If no options are given when a new instance of this class is created + * the options below are used. + */ + static options = { + page: 10, + pagination: [{ + name: "paginationTop", + paginationClass: "paginationTop", + innerWindow: 8, + outerWindow: 1 + }, { + paginationClass: "paginationBottom", + innerWindow: 8, + outerWindow: 1 + }], + valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}], + item: `` + }; + constructor(idOrElement, options) { + super(idOrElement, options); + this.options = options; + /** + * All span tokens which are holding events if expert + * mode is on. Collected here to delete later on. + */ + this.eventTokens = {}; + /** + * all token elements which have added + * classes like chip and hoverable for expert view. Collected + * here to delete later on + */ + this.currentExpertTokenElements = {}; + // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons. + this.addToSubResultsStatus = {}; + this.addToSubResultsIdsToShow = new Set(); // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user. + } + + helperCreateCpos(cpos_ranges, cpos_values) { + let lc; + let c; + let rc; + if (cpos_ranges) { + // 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(cpos_values.lc[0], cpos_values.lc[1], 1) + c = range(cpos_values.c[0], cpos_values.c[1], 1) + rc = range(cpos_values.rc[0], cpos_values.rc[1], 1) + } else { + lc = cpos_values.lc; + c = cpos_values.c; + rc = cpos_values.rc; + } + return {lc: lc, c: c, rc: rc}; + } + + // handels interactionElements during a pagination navigation + // loops over interactionElements and executes callback functions accordingly + pageChangeEventInteractionHandler(interactionElements) { + // get elements to check thier status + for (let interaction of interactionElements.interactions) { + if (interaction.checkStatus) { + if (interaction.element.checked) { + let f_on = interaction.bindThisToCallback("on"); + let args_on = interaction.callbacks.on.args; + f_on(...args_on); + } else { + let f_off = interaction.bindThisToCallback("off"); + let args_off = interaction.callbacks.off.args; + f_off(...args_off); + } + } else { + let f = interaction.bindThisToCallback("noCheck"); + let args = interaction.callbacks.noCheck.args; + f(...args); + } + } + } + + // get display options from display options form element + static getDisplayOptions(htmlId) { + // gets display options parameters + let displayOptionsFormElement = document.getElementById(htmlId); + let displayOptionsFormData = new FormData(displayOptionsFormElement); + let 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 add one match to a sub-results ###### + // activate the add buttons + activateAddToSubResults() { + subResultsIdListElement.classList.remove("hide"); + if (subResultsExportElement.classList.contains("hide")) { + subResultsCreateElement.classList.remove("hide"); + } + let addToSubResultsBtnElements = document.getElementsByClassName("add"); + for (let addToSubResultsBtn of addToSubResultsBtnElements) { + addToSubResultsBtn.classList.remove("hide"); + } + } + // deactivate the add buttons + deactivateAddToSubResults() { + subResultsIdListElement.classList.add("hide"); + subResultsCreateElement.classList.add("hide"); + let addToSubResultsBtnElements = document.getElementsByClassName("add"); + for (let addToSubResultsBtn of addToSubResultsBtnElements) { + addToSubResultsBtn.classList.add("hide"); + } + } + + // Used in addToSubResults and inspect to toggle the design of the check + // buttons according to its checked unchecked status. + helperActivateBtn(btn) { + btn.classList.remove("grey"); + btn.classList.add("green"); + btn.textContent = "check"; + } + + // Used in addToSubResults and inspect to toggle the design of the check + // buttons according to its checked unchecked status. + helperDeactivateBtn(btn) { + btn.classList.remove("green"); + btn.classList.add("grey"); + btn.textContent = "add"; + } + + // Either adds or removes a match to the sub-results. For this it checks + // onclick if the current button has been checked or not. For this the + // function checks if its status in addToSubResultsStatus is either flase or + // true. Adds match to sub-results if status is false if status is true it + // removes it. + addToSubResults(dataIndex, tableCall=true) { + let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0]; + if (!this.addToSubResultsStatus[dataIndex] + || this.addToSubResultsStatus === undefined) { + // add button is activated because status is either false or undefined + this.helperActivateBtn(event.target); + this.addToSubResultsStatus[dataIndex] = true; // sets status to true + this.addToSubResultsIdsToShow.add(dataIndex + 1); // + 1 because user does not see zero indexd data indexes + textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order + M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually + nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; + } else if (this.addToSubResultsStatus[dataIndex]) { + // add button is deactivated because status is true + this.helperDeactivateBtn(event.target); + this.addToSubResultsStatus[dataIndex] = false; // sets status to false + this.addToSubResultsIdsToShow.delete(dataIndex + 1); // + 1 because user does not see zero indexd data indexes + textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order + nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; + M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually + } + // Toggles the create button according to the number of ids in addToSubResultsIdsToShow + if ([...this.addToSubResultsIdsToShow].length > 0) { + subResultsCreateElement.classList.remove("disabled"); + } else if ([...this.addToSubResultsIdsToShow].length === 0) { + subResultsCreateElement.classList.add("disabled"); + } + if (resultCreationRunning) { + subResultsCreateElement.classList.add("disabled"); + } + // After a match as been added or removed the export button will be + // hidden because the sub-results have been altered and have to be built + // again. Thus subResultsCreateElement has to be shown again. + subResultsExportElement.classList.add("hide"); + subResultsCreateElement.classList.remove("hide"); + // Also activate/deactivate buttons in the table/jsList results accordingly + //if button in inspect was activated/deactivated. + // This part only runs if tableCall is false. + if (!tableCall) { + let tableAddBtn = document.getElementById("query-results").querySelectorAll(`[data-index="${dataIndex}"]`)[0].getElementsByClassName('add')[0].firstElementChild; // gets the add button from the list view + if (this.addToSubResultsStatus[dataIndex]) { + this.helperActivateBtn(tableAddBtn); + } else { + this.helperDeactivateBtn(tableAddBtn); + } + } + } + + // Triggers emit to get full match context from server for a number of + // matches identified by their data_index. + getMatchWithContext(dataIndexes, type) { + let tmp_first_cpos = []; + let tmp_last_cpos = []; + for (let dataIndex of dataIndexes) { + tmp_first_cpos.push(results.data.matches[dataIndex].c[0]); + tmp_last_cpos.push(results.data.matches[dataIndex].c[1]); + } + nopaque.socket.emit("corpus_analysis_inspect_match", + { + type: type, + data_indexes: dataIndexes, + first_cpos: tmp_first_cpos, + last_cpos: tmp_last_cpos, + } + ); + } + + // ###### Functions to inspect one match, to show more details ###### + // activate inspect buttons if progress is 100 + activateInspect() { + if (this.requestQueryProgress === 100) { + let inspectBtnElements; + inspectBtnElements = document.getElementsByClassName("inspect"); + for (let inspectBtn of inspectBtnElements) { + inspectBtn.classList.remove("disabled"); + } + } else { + return + } + } + + // deactivate inspect buttons + deactivateInspect() { + let inspectBtnElements; + inspectBtnElements = document.getElementsByClassName("inspect"); + for (let inspectBtn of inspectBtnElements) { + inspectBtn.classList.add("disabled"); + } + } + + // ### functions to inspect imported Matches + // This function creates an object that is similar to the object that is + // being recieved as an answere to the getMatchWithContext Method, which is + // triggering an socket.io event. + // It is used as an input for show match context in the context of imported + // results to be able to inspect matches. + createFakeResponse() { + contextModal.open(); + // match nr for user to display derived from data_index + let contextMatchNrElement = document.getElementById("context-match-nr"); + contextMatchNrElement.textContent = this.contextId + 1; + let cpos_lookup; + let fake_response = {}; + let contextResultsElement; + // function to create one match object from entire imported results + // that is passed into the results.jsList.showMatchContext() function + fake_response["payload"] = {}; + let dataIndex = event.target.closest("tr").dataset.index; + this.contextId = dataIndex; + fake_response.payload["matches"] = [results.data.matches[dataIndex]]; + contextResultsElement = document.getElementById("context-results"); + contextResultsElement.innerHTML = ""; + let {lc, c, rc} = this.helperCreateCpos(results.data.cpos_ranges, + fake_response.payload.matches[0]); + cpos_lookup = {}; + for (let cpos of lc) { + cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; + } + for (let cpos of c) { + cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; + } + for (let cpos of rc) { + cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; + } + fake_response.payload["cpos_lookup"] = cpos_lookup + fake_response.payload["cpos_ranges"] = results.data.cpos_ranges; + fake_response.payload["query"] = results.data.query; + fake_response.payload["context_id"] = dataIndex + 1; + fake_response.payload["match_count"] = fake_response.payload.matches.length + fake_response.payload["corpus_type"] = "inspect-result" + return fake_response + } + + // gets result cpos infos for one dataIndex (list of length 1) to send back to + // the server + inspect(dataIndex, type) { + let contextResultsElement; + // get result infos from server and show them in context modal + this.contextId = dataIndex[0]; + contextResultsElement = document.getElementById("context-results"); + contextResultsElement.innerHTML = ""; // clear it from old inspects + this.getMatchWithContext(dataIndex, type); + // match nr for user to display derived from data_index + let contextMatchNrElement = document.getElementById("context-match-nr"); + contextMatchNrElement.textContent = this.contextId + 1; + contextModal.open(); + // add a button to add this match to sub results with onclick event + let classes = `btn-floating btn waves-effect` + + `waves-light grey right` + let addToSubResultsIdsBtn = document.createElement("a"); + addToSubResultsIdsBtn.setAttribute("class", classes + ` add`); + addToSubResultsIdsBtn.innerHTML = 'add'; + addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)}; + // checks if a button has already been added to the inspect modal and removes it + if (addToSubResultsFromInspectElement.children.length > 0) { + addToSubResultsFromInspectElement.firstElementChild.remove(); + } + // Changes the design of the add button according to its checked status + // upon opening the inspect modal. + if (this.addToSubResultsStatus[dataIndex[0]]) { + this.helperActivateBtn(addToSubResultsIdsBtn.firstElementChild); + } else if (!this.addToSubResultsStatus[dataIndex[0]]) { + this.helperDeactivateBtn(addToSubResultsIdsBtn.firstElementChild); + } + addToSubResultsFromInspectElement.appendChild(addToSubResultsIdsBtn); + } + + // create Element from HTML String helper function + HTMLTStrToElement(htmlStr) { + // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + let template = document.createElement("template"); + htmlStr = htmlStr.trim(); + template.innerHTML = htmlStr; + return template.content.firstChild; + } + + // Used as a callback to handle incoming match context results when inspect + // has been used. + showMatchContext(response) { + this.contextData; + let contextModalLoading; + let contextModalReady; + let contextResultsElement; + let highlightSentencesSwitchElement; + let htmlTokenStr; + let modalExpertModeSwitchElement; + let modalTokenElements; + let nrOfContextSentences; + let partElement; + let token; + let tokenHTMLArray; + let tokenHTMlElement; + let uniqueContextS; + let uniqueS; + + this.contextData = response.payload; + console.log(this.contextData); + this.contextData["cpos_ranges"] = response.payload.cpos_ranges; + this.contextData["query"] = results.data.query; + this.contextData["context_id"] = this.contextId; + this.contextData["match_count"] = this.contextData.matches.length + this.contextData["corpus_type"] = "inspect-result" + Object.assign(this.contextData, results.metaData); + contextResultsElement = document.getElementById("context-results"); + modalExpertModeSwitchElement = document.getElementById("inspect-display-options-form-expert_mode_inspect"); + highlightSentencesSwitchElement = document.getElementById("inspect-display-options-form-highlight_sentences"); + nrOfContextSentences = document.getElementById("context-sentences"); + uniqueS = new Set(); + uniqueContextS = new Set(); + let {lc, c, rc} = this.helperCreateCpos(this.contextData.cpos_ranges, + this.contextData.matches[0]) + // create sentence strings as tokens + tokenHTMLArray = []; + for (let cpos of lc) { + token = this.contextData.cpos_lookup[cpos]; + uniqueS.add(token.s) + htmlTokenStr = `` + + `${token.word}` + + ``; + tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) + tokenHTMLArray.push(tokenHTMlElement); + } + for (let cpos of c) { + token = this.contextData.cpos_lookup[cpos]; + uniqueContextS.add(token.s); + uniqueS.add(token.s); + htmlTokenStr = `` + + `${token.word}` + + ``; + tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) + tokenHTMLArray.push(tokenHTMlElement); + } + this.contextData["context_s_ids"] = Array.from(uniqueContextS); + for (let cpos of rc) { + token = this.contextData.cpos_lookup[cpos]; + uniqueS.add(token.s) + htmlTokenStr = `` + + `${token.word}` + + ``; + tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) + tokenHTMLArray.push(tokenHTMlElement); + } + for (let sId of uniqueS) { + let htmlSentence = ``; + let sentenceElement = this.HTMLTStrToElement(htmlSentence); + for (let tokenElement of tokenHTMLArray) { + if (tokenElement.dataset.sid == sId) { + sentenceElement.appendChild(tokenElement); + sentenceElement.insertAdjacentHTML("beforeend", ` `); + } else { + continue; + } + } + contextResultsElement.appendChild(sentenceElement); + } + + + // add inspect display options events + modalExpertModeSwitchElement.onchange = (event) => { + if (event.target.checked) { + this.expertModeOn("context-results"); + } else { + this.expertModeOff("context-results") + } + }; + + highlightSentencesSwitchElement.onchange = (event) => { + if (event.target.checked) { + this.higlightContextSentences(); + } else { + this.unhighlightContextSentences(); + } + }; + + nrOfContextSentences.onchange = (event) => { + // console.log(event.target.value); + this.changeSentenceContext(event.target.value); + } + + // checks on new modal opening if switches are checked + // if switches are checked functions are executed + if (modalExpertModeSwitchElement.checked) { + this.expertModeOn("context-results"); + } + + if (highlightSentencesSwitchElement.checked) { + this.higlightContextSentences(); + } + + // checks the value of the number of sentences to show on modal opening + // sets context sentences accordingly + this.changeSentenceContext(nrOfContextSentences.value) + } + + // splits context text into sentences based on spacy sentence split + higlightContextSentences() { + let sentences; + sentences = document.getElementById("context-results").getElementsByClassName("sentence"); + for (let s of sentences) { + s.insertAdjacentHTML("beforeend", `

`) + } + } + + unhighlightContextSentences() { + let sentences; + let br; + sentences = document.getElementById("context-results").getElementsByClassName("sentence"); + for (let s of sentences) { + br = s.lastChild; + br.remove(); + } + } + + // changes how many context sentences in inspect view are shown + changeSentenceContext(sValue, maxSValue=10) { + let array; + let sentences; + let toHideArray; + let toShowArray; + sValue = maxSValue - sValue; + // console.log(sValue); + sentences = document.getElementById("context-results").getElementsByClassName("sentence"); + array = Array.from(sentences); + if (sValue != 0) { + toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue))); + toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue))) + } else { + toHideArray = []; + toShowArray = array; + } + // console.log(array); + // console.log("#######"); + // console.log(toHideArray); + for (let s of toHideArray) { + s.classList.add("hide"); + } + for (let s of toShowArray) { + s.classList.remove("hide"); + } + } + + // ###### 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 { + // console.log(this); + this.page = event.target.value; + this.update(); + this.activateInspect(); + this.pageChangeEventInteractionHandler(interactionElements); + if (expertModeSwitchElement.checked) { + this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips + } + nopaque.flash("Updated matches per page.", "corpus") + } catch (e) { + // console.log(e); + // console.log("resultsList has no results right now."); + } + } + + // Event function triggered on context select change + // also if pagination is clicked + changeContext(event) { + let array; + let lc; + let newContextValue; + let rc; + try { + if (event.type === "change") { + nopaque.flash("Updated context per match!", "corpus"); + } + } catch (e) { + } finally { + newContextValue = document.getElementById("display-options-form-result_context").value; + 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.reverse().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 ###### + // function to create a tooltip for the current hovered token + tooltipEventCreate(event, client) { + // console.log("Create Tooltip on mouseover."); + let token = client.results.data.cpos_lookup[event.target.dataset.cpos]; + if (!token) { + token = this.contextData.cpos_lookup[event.target.dataset.cpos]; + } + this.addToolTipToTokenElement(event.target, token, client); + } + + // Function to destroy the current Tooltip for the current hovered tooltip + // on mouse leave + tooltipEventDestroy() { + // console.log("Tooltip destroy on leave."); + this.currentTooltipElement.destroy(); + } + + // turn the expert mode on for all tokens in the DOM element identified by its htmlID + expertModeOn(htmlId, client) { + if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { + this.currentExpertTokenElements[htmlId] = []; + } + let container = document.getElementById(htmlId); + let tokens = container.querySelectorAll("span.token"); + this.currentExpertTokenElements[htmlId].push(...tokens); + this.eventTokens[htmlId] = []; + for (let tokenElement of this.currentExpertTokenElements[htmlId]) { + tokenElement.classList.add("chip", "hoverable", "expert-view"); + const eventCreate = (event, arg) => this.tooltipEventCreate(event, arg); + tokenElement.onmouseover = (event) => eventCreate(event, client); + tokenElement.onmouseout = () => this.tooltipEventDestroy(); + this.eventTokens[htmlId].push(tokenElement); + } + } + + // fuction that creates Tooltip for one token and extracts the corresponding + // infos from the result JSON + addToolTipToTokenElement(tokenElement, token, client) { + this.currentTooltipElement; + this.currentTooltipElement = M.Tooltip.init(tokenElement, + {"html": ` + + + + + + + + +
Token informationSource information
+ Word: ${token.word}
+ Lemma: ${token.lemma}
+ POS: ${token.pos}
+ Simple POS: ${token.simple_pos}
+ NER: ${token.ner} +
+ Title: ${client.results.data.text_lookup[token.text].title} +
+ Author: ${client.results.data.text_lookup[token.text].author} +
+ Publishing year: ${client.results.data.text_lookup[token.text].publishing_year} +
`} + ); + } + + // function to remove extra informations and animations from tokens + expertModeOff(htmlId) { + // console.log("Expert mode is off."); + if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { + this.currentExpertTokenElements[htmlId] = []; + } + if (!Array.isArray(this.eventTokens[htmlId])) { + this.eventTokens[htmlId] = []; + } + for (let tokenElement of this.currentExpertTokenElements[htmlId]) { + tokenElement.classList.remove("chip", "hoverable", "expert-view"); + } + this.currentExpertTokenElements[htmlId] = []; + + for (let eventToken of this.eventTokens[htmlId]) { + eventToken.onmouseover = ""; + eventToken.onmouseout = ""; + } + this.eventTokens[htmlId] = []; + } + + createResultRowElement(item, chunk, imported=false) { + let aCellElement; + let addToSubResultsBtn; + let cCellElement; + let cpos; + let fakeResponse; // used if imported results are being created; + let inspectBtn + let lcCellElement; + let matchNrElement; + let matchRowElement; + let rcCellElement; + let textTitles; + let textTitlesCellElement; + let token; + let values; + // gather values from item + values = item.values(); + let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges, + values) + // 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 and set actions + textTitles = new Set(); + aCellElement = document.createElement("td"); + aCellElement.classList.add("actions"); + cCellElement = document.createElement("td"); + cCellElement.classList.add("match-hit"); + textTitlesCellElement = document.createElement("td"); + textTitlesCellElement.classList.add("titles"); + matchNrElement = document.createElement("td"); + matchNrElement.classList.add("match-nr"); + matchRowElement.appendChild(cCellElement); + matchRowElement.appendChild(aCellElement); + for (cpos of c) { + token = chunk.cpos_lookup[cpos]; + cCellElement.insertAdjacentHTML("beforeend", + `${token.word} `); + // get text titles of every hit cpos token + textTitles.add(chunk.text_lookup[token.text].title); + } + // add some interaction buttons + // # some btn css rules and classes + let css = `margin-right: 5px; margin-bottom: 5px;` + let classes = `btn-floating btn waves-effect` + + `waves-light grey` + // # add button to trigger more context to every match td + inspectBtn = document.createElement("a"); + inspectBtn.setAttribute("style", css); + inspectBtn.setAttribute("class", classes + ` disabled inspect` + ); + inspectBtn.innerHTML = 'search'; + // # add btn to add matches to sub-results. hidden per default + addToSubResultsBtn = document.createElement("a"); + addToSubResultsBtn.setAttribute("style", css); + addToSubResultsBtn.setAttribute("class", classes + ` hide add` + ); + addToSubResultsBtn.innerHTML = 'add'; + aCellElement.appendChild(inspectBtn); + aCellElement.appendChild(addToSubResultsBtn); + // add text titles at front as first td of one row + textTitlesCellElement.textContent = [...textTitles].join(", "); + matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML); + matchNrElement.textContent = 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 + } + + // creates the HTML table code for the metadata vie in the corpus analysis interface + createMetaDataForModal(metaDataObject) { + let html = `
+ + + + + + + + ` + for (let [outerKey, outerValue] of Object.entries(metaDataObject)) { + html += ` + ` + if (outerKey === "corpus_all_texts" || outerKey === "text_lookup") { + html += `` + } else { + html += `` + } + html += `` + } + html += ` +
Metadata DescriptionValue
${outerKey.replace(/_/g, " ")} +
    ` + for (let [innerKey, innerValue] of Object.entries(outerValue)) { + html += `` + } + html += `
+
${outerValue}
` + return html + } + + // Creates the text details for the texts shown in the corpus analysis metadata modal. + createTextDetails(metaDataObject) { + let metadataKey = event.target.dataset.metadataKey; + let textKey = event.target.dataset.textKey; + let textData = metaDataObject[metadataKey][textKey]; + let bibliographicData = document.getElementById(`bibliographic-data-${metadataKey}-${textKey}`); + bibliographicData.innerHTML = ""; + for (let [key, value] of Object.entries(textData)) { + bibliographicData.insertAdjacentHTML("afterbegin", + ` +
  • ${key}: ${value}
  • + `); + } + } +}; diff --git a/web/app/static/js/modules/corpus_analysis/view/displays.js b/web/app/static/js/modules/corpus_analysis/view/displays.js new file mode 100644 index 00000000..e69de29b diff --git a/web/app/static/js/modules/nopaque.scrollToTop.js b/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js similarity index 83% rename from web/app/static/js/modules/nopaque.scrollToTop.js rename to web/app/static/js/modules/corpus_analysis/view/scrollToTop.js index 82b6e774..d5aed247 100644 --- a/web/app/static/js/modules/nopaque.scrollToTop.js +++ b/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js @@ -1,6 +1,6 @@ /** - * Function to show a scrol lto top button if the user has scrolled down - * 250 pixels from teh headline element. + * Function to show a scroll to top button if the user has scrolled down + * 250 pixels from the headline element. */ function scrollToTop() { let headline = document.querySelector(".headline"); diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index f7b640ed..306d706e 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -423,789 +423,4 @@ RessourceList.options = { }, }; - -class ResultsList extends List { - /** - * If no options are given when a new instance of this class is created - * the options below are used. - */ - static options = { - page: 10, - pagination: [{ - name: "paginationTop", - paginationClass: "paginationTop", - innerWindow: 8, - outerWindow: 1 - }, { - paginationClass: "paginationBottom", - innerWindow: 8, - outerWindow: 1 - }], - valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}], - item: `` - }; - constructor(idOrElement, options) { - super(idOrElement, options); - this.options = options; - /** - * All span tokens which are holding events if expert - * mode is on. Collected here to delete later on. - */ - this.eventTokens = {}; - /** - * all token elements which have added - * classes like chip and hoverable for expert view. Collected - * here to delete later on - */ - this.currentExpertTokenElements = {}; - // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons. - this.addToSubResultsStatus = {}; - this.addToSubResultsIdsToShow = new Set(); // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user. - } - - helperCreateCpos(cpos_ranges, cpos_values) { - let lc; - let c; - let rc; - if (cpos_ranges) { - // 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(cpos_values.lc[0], cpos_values.lc[1], 1) - c = range(cpos_values.c[0], cpos_values.c[1], 1) - rc = range(cpos_values.rc[0], cpos_values.rc[1], 1) - } else { - lc = cpos_values.lc; - c = cpos_values.c; - rc = cpos_values.rc; - } - return {lc: lc, c: c, rc: rc}; - } - - // handels interactionElements during a pagination navigation - // loops over interactionElements and executes callback functions accordingly - pageChangeEventInteractionHandler(interactionElements) { - // get elements to check thier status - for (let interaction of interactionElements.interactions) { - if (interaction.checkStatus) { - if (interaction.element.checked) { - let f_on = interaction.bindThisToCallback("on"); - let args_on = interaction.callbacks.on.args; - f_on(...args_on); - } else { - let f_off = interaction.bindThisToCallback("off"); - let args_off = interaction.callbacks.off.args; - f_off(...args_off); - } - } else { - let f = interaction.bindThisToCallback("noCheck"); - let args = interaction.callbacks.noCheck.args; - f(...args); - } - } - } - - // get display options from display options form element - static getDisplayOptions(htmlId) { - // gets display options parameters - let displayOptionsFormElement = document.getElementById(htmlId); - let displayOptionsFormData = new FormData(displayOptionsFormElement); - let 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 add one match to a sub-results ###### - // activate the add buttons - activateAddToSubResults() { - subResultsIdListElement.classList.remove("hide"); - if (subResultsExportElement.classList.contains("hide")) { - subResultsCreateElement.classList.remove("hide"); - } - let addToSubResultsBtnElements = document.getElementsByClassName("add"); - for (let addToSubResultsBtn of addToSubResultsBtnElements) { - addToSubResultsBtn.classList.remove("hide"); - } - } - // deactivate the add buttons - deactivateAddToSubResults() { - subResultsIdListElement.classList.add("hide"); - subResultsCreateElement.classList.add("hide"); - let addToSubResultsBtnElements = document.getElementsByClassName("add"); - for (let addToSubResultsBtn of addToSubResultsBtnElements) { - addToSubResultsBtn.classList.add("hide"); - } - } - - // Used in addToSubResults and inspect to toggle the design of the check - // buttons according to its checked unchecked status. - helperActivateBtn(btn) { - btn.classList.remove("grey"); - btn.classList.add("green"); - btn.textContent = "check"; - } - - // Used in addToSubResults and inspect to toggle the design of the check - // buttons according to its checked unchecked status. - helperDeactivateBtn(btn) { - btn.classList.remove("green"); - btn.classList.add("grey"); - btn.textContent = "add"; - } - - // Either adds or removes a match to the sub-results. For this it checks - // onclick if the current button has been checked or not. For this the - // function checks if its status in addToSubResultsStatus is either flase or - // true. Adds match to sub-results if status is false if status is true it - // removes it. - addToSubResults(dataIndex, tableCall=true) { - let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0]; - if (!this.addToSubResultsStatus[dataIndex] - || this.addToSubResultsStatus === undefined) { - // add button is activated because status is either false or undefined - this.helperActivateBtn(event.target); - this.addToSubResultsStatus[dataIndex] = true; // sets status to true - this.addToSubResultsIdsToShow.add(dataIndex + 1); // + 1 because user does not see zero indexd data indexes - textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order - M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually - nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; - } else if (this.addToSubResultsStatus[dataIndex]) { - // add button is deactivated because status is true - this.helperDeactivateBtn(event.target); - this.addToSubResultsStatus[dataIndex] = false; // sets status to false - this.addToSubResultsIdsToShow.delete(dataIndex + 1); // + 1 because user does not see zero indexd data indexes - textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order - nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; - M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually - } - // Toggles the create button according to the number of ids in addToSubResultsIdsToShow - if ([...this.addToSubResultsIdsToShow].length > 0) { - subResultsCreateElement.classList.remove("disabled"); - } else if ([...this.addToSubResultsIdsToShow].length === 0) { - subResultsCreateElement.classList.add("disabled"); - } - if (resultCreationRunning) { - subResultsCreateElement.classList.add("disabled"); - } - // After a match as been added or removed the export button will be - // hidden because the sub-results have been altered and have to be built - // again. Thus subResultsCreateElement has to be shown again. - subResultsExportElement.classList.add("hide"); - subResultsCreateElement.classList.remove("hide"); - // Also activate/deactivate buttons in the table/jsList results accordingly - //if button in inspect was activated/deactivated. - // This part only runs if tableCall is false. - if (!tableCall) { - let tableAddBtn = document.getElementById("query-results").querySelectorAll(`[data-index="${dataIndex}"]`)[0].getElementsByClassName('add')[0].firstElementChild; // gets the add button from the list view - if (this.addToSubResultsStatus[dataIndex]) { - this.helperActivateBtn(tableAddBtn); - } else { - this.helperDeactivateBtn(tableAddBtn); - } - } - } - - // Triggers emit to get full match context from server for a number of - // matches identified by their data_index. - getMatchWithContext(dataIndexes, type) { - let tmp_first_cpos = []; - let tmp_last_cpos = []; - for (let dataIndex of dataIndexes) { - tmp_first_cpos.push(results.data.matches[dataIndex].c[0]); - tmp_last_cpos.push(results.data.matches[dataIndex].c[1]); - } - nopaque.socket.emit("corpus_analysis_inspect_match", - { - type: type, - data_indexes: dataIndexes, - first_cpos: tmp_first_cpos, - last_cpos: tmp_last_cpos, - } - ); - } - - // ###### Functions to inspect one match, to show more details ###### - // activate inspect buttons if progress is 100 - activateInspect() { - if (this.requestQueryProgress === 100) { - let inspectBtnElements; - inspectBtnElements = document.getElementsByClassName("inspect"); - for (let inspectBtn of inspectBtnElements) { - inspectBtn.classList.remove("disabled"); - } - } else { - return - } - } - - // deactivate inspect buttons - deactivateInspect() { - let inspectBtnElements; - inspectBtnElements = document.getElementsByClassName("inspect"); - for (let inspectBtn of inspectBtnElements) { - inspectBtn.classList.add("disabled"); - } - } - - // ### functions to inspect imported Matches - // This function creates an object that is similar to the object that is - // being recieved as an answere to the getMatchWithContext Method, which is - // triggering an socket.io event. - // It is used as an input for show match context in the context of imported - // results to be able to inspect matches. - createFakeResponse() { - contextModal.open(); - // match nr for user to display derived from data_index - let contextMatchNrElement = document.getElementById("context-match-nr"); - contextMatchNrElement.textContent = this.contextId + 1; - let cpos_lookup; - let fake_response = {}; - let contextResultsElement; - // function to create one match object from entire imported results - // that is passed into the results.jsList.showMatchContext() function - fake_response["payload"] = {}; - let dataIndex = event.target.closest("tr").dataset.index; - this.contextId = dataIndex; - fake_response.payload["matches"] = [results.data.matches[dataIndex]]; - contextResultsElement = document.getElementById("context-results"); - contextResultsElement.innerHTML = ""; - let {lc, c, rc} = this.helperCreateCpos(results.data.cpos_ranges, - fake_response.payload.matches[0]); - cpos_lookup = {}; - for (let cpos of lc) { - cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; - } - for (let cpos of c) { - cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; - } - for (let cpos of rc) { - cpos_lookup[cpos] = results.data.cpos_lookup[cpos]; - } - fake_response.payload["cpos_lookup"] = cpos_lookup - fake_response.payload["cpos_ranges"] = results.data.cpos_ranges; - fake_response.payload["query"] = results.data.query; - fake_response.payload["context_id"] = dataIndex + 1; - fake_response.payload["match_count"] = fake_response.payload.matches.length - fake_response.payload["corpus_type"] = "inspect-result" - return fake_response - } - - // gets result cpos infos for one dataIndex (list of length 1) to send back to - // the server - inspect(dataIndex, type) { - let contextResultsElement; - // get result infos from server and show them in context modal - this.contextId = dataIndex[0]; - contextResultsElement = document.getElementById("context-results"); - contextResultsElement.innerHTML = ""; // clear it from old inspects - this.getMatchWithContext(dataIndex, type); - // match nr for user to display derived from data_index - let contextMatchNrElement = document.getElementById("context-match-nr"); - contextMatchNrElement.textContent = this.contextId + 1; - contextModal.open(); - // add a button to add this match to sub results with onclick event - let classes = `btn-floating btn waves-effect` + - `waves-light grey right` - let addToSubResultsIdsBtn = document.createElement("a"); - addToSubResultsIdsBtn.setAttribute("class", classes + ` add`); - addToSubResultsIdsBtn.innerHTML = 'add'; - addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)}; - // checks if a button has already been added to the inspect modal and removes it - if (addToSubResultsFromInspectElement.children.length > 0) { - addToSubResultsFromInspectElement.firstElementChild.remove(); - } - // Changes the design of the add button according to its checked status - // upon opening the inspect modal. - if (this.addToSubResultsStatus[dataIndex[0]]) { - this.helperActivateBtn(addToSubResultsIdsBtn.firstElementChild); - } else if (!this.addToSubResultsStatus[dataIndex[0]]) { - this.helperDeactivateBtn(addToSubResultsIdsBtn.firstElementChild); - } - addToSubResultsFromInspectElement.appendChild(addToSubResultsIdsBtn); - } - - // create Element from HTML String helper function - HTMLTStrToElement(htmlStr) { - // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 - let template = document.createElement("template"); - htmlStr = htmlStr.trim(); - template.innerHTML = htmlStr; - return template.content.firstChild; - } - - // Used as a callback to handle incoming match context results when inspect - // has been used. - showMatchContext(response) { - this.contextData; - let contextModalLoading; - let contextModalReady; - let contextResultsElement; - let highlightSentencesSwitchElement; - let htmlTokenStr; - let modalExpertModeSwitchElement; - let modalTokenElements; - let nrOfContextSentences; - let partElement; - let token; - let tokenHTMLArray; - let tokenHTMlElement; - let uniqueContextS; - let uniqueS; - - this.contextData = response.payload; - console.log(this.contextData); - this.contextData["cpos_ranges"] = response.payload.cpos_ranges; - this.contextData["query"] = results.data.query; - this.contextData["context_id"] = this.contextId; - this.contextData["match_count"] = this.contextData.matches.length - this.contextData["corpus_type"] = "inspect-result" - Object.assign(this.contextData, results.metaData); - contextResultsElement = document.getElementById("context-results"); - modalExpertModeSwitchElement = document.getElementById("inspect-display-options-form-expert_mode_inspect"); - highlightSentencesSwitchElement = document.getElementById("inspect-display-options-form-highlight_sentences"); - nrOfContextSentences = document.getElementById("context-sentences"); - uniqueS = new Set(); - uniqueContextS = new Set(); - let {lc, c, rc} = this.helperCreateCpos(this.contextData.cpos_ranges, - this.contextData.matches[0]) - // create sentence strings as tokens - tokenHTMLArray = []; - for (let cpos of lc) { - token = this.contextData.cpos_lookup[cpos]; - uniqueS.add(token.s) - htmlTokenStr = `` + - `${token.word}` + - ``; - tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) - tokenHTMLArray.push(tokenHTMlElement); - } - for (let cpos of c) { - token = this.contextData.cpos_lookup[cpos]; - uniqueContextS.add(token.s); - uniqueS.add(token.s); - htmlTokenStr = `` + - `${token.word}` + - ``; - tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) - tokenHTMLArray.push(tokenHTMlElement); - } - this.contextData["context_s_ids"] = Array.from(uniqueContextS); - for (let cpos of rc) { - token = this.contextData.cpos_lookup[cpos]; - uniqueS.add(token.s) - htmlTokenStr = `` + - `${token.word}` + - ``; - tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr) - tokenHTMLArray.push(tokenHTMlElement); - } - for (let sId of uniqueS) { - let htmlSentence = ``; - let sentenceElement = this.HTMLTStrToElement(htmlSentence); - for (let tokenElement of tokenHTMLArray) { - if (tokenElement.dataset.sid == sId) { - sentenceElement.appendChild(tokenElement); - sentenceElement.insertAdjacentHTML("beforeend", ` `); - } else { - continue; - } - } - contextResultsElement.appendChild(sentenceElement); - } - - - // add inspect display options events - modalExpertModeSwitchElement.onchange = (event) => { - if (event.target.checked) { - this.expertModeOn("context-results"); - } else { - this.expertModeOff("context-results") - } - }; - - highlightSentencesSwitchElement.onchange = (event) => { - if (event.target.checked) { - this.higlightContextSentences(); - } else { - this.unhighlightContextSentences(); - } - }; - - nrOfContextSentences.onchange = (event) => { - // console.log(event.target.value); - this.changeSentenceContext(event.target.value); - } - - // checks on new modal opening if switches are checked - // if switches are checked functions are executed - if (modalExpertModeSwitchElement.checked) { - this.expertModeOn("context-results"); - } - - if (highlightSentencesSwitchElement.checked) { - this.higlightContextSentences(); - } - - // checks the value of the number of sentences to show on modal opening - // sets context sentences accordingly - this.changeSentenceContext(nrOfContextSentences.value) - } - - // splits context text into sentences based on spacy sentence split - higlightContextSentences() { - let sentences; - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - for (let s of sentences) { - s.insertAdjacentHTML("beforeend", `

    `) - } - } - - unhighlightContextSentences() { - let sentences; - let br; - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - for (let s of sentences) { - br = s.lastChild; - br.remove(); - } - } - - // changes how many context sentences in inspect view are shown - changeSentenceContext(sValue, maxSValue=10) { - let array; - let sentences; - let toHideArray; - let toShowArray; - sValue = maxSValue - sValue; - // console.log(sValue); - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - array = Array.from(sentences); - if (sValue != 0) { - toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue))); - toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue))) - } else { - toHideArray = []; - toShowArray = array; - } - // console.log(array); - // console.log("#######"); - // console.log(toHideArray); - for (let s of toHideArray) { - s.classList.add("hide"); - } - for (let s of toShowArray) { - s.classList.remove("hide"); - } - } - - // ###### 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 { - // console.log(this); - this.page = event.target.value; - this.update(); - this.activateInspect(); - this.pageChangeEventInteractionHandler(interactionElements); - if (expertModeSwitchElement.checked) { - this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips - } - nopaque.flash("Updated matches per page.", "corpus") - } catch (e) { - // console.log(e); - // console.log("resultsList has no results right now."); - } - } - - // Event function triggered on context select change - // also if pagination is clicked - changeContext(event) { - let array; - let lc; - let newContextValue; - let rc; - try { - if (event.type === "change") { - nopaque.flash("Updated context per match!", "corpus"); - } - } catch (e) { - } finally { - newContextValue = document.getElementById("display-options-form-result_context").value; - 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.reverse().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 ###### - // function to create a tooltip for the current hovered token - tooltipEventCreate(event, client) { - // console.log("Create Tooltip on mouseover."); - let token = client.results.data.cpos_lookup[event.target.dataset.cpos]; - if (!token) { - token = this.contextData.cpos_lookup[event.target.dataset.cpos]; - } - this.addToolTipToTokenElement(event.target, token, client); - } - - // Function to destroy the current Tooltip for the current hovered tooltip - // on mouse leave - tooltipEventDestroy() { - // console.log("Tooltip destroy on leave."); - this.currentTooltipElement.destroy(); - } - - // turn the expert mode on for all tokens in the DOM element identified by its htmlID - expertModeOn(htmlId, client) { - if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { - this.currentExpertTokenElements[htmlId] = []; - } - let container = document.getElementById(htmlId); - let tokens = container.querySelectorAll("span.token"); - this.currentExpertTokenElements[htmlId].push(...tokens); - this.eventTokens[htmlId] = []; - for (let tokenElement of this.currentExpertTokenElements[htmlId]) { - tokenElement.classList.add("chip", "hoverable", "expert-view"); - const eventCreate = (event, arg) => this.tooltipEventCreate(event, arg); - tokenElement.onmouseover = (event) => eventCreate(event, client); - tokenElement.onmouseout = () => this.tooltipEventDestroy(); - this.eventTokens[htmlId].push(tokenElement); - } - } - - // fuction that creates Tooltip for one token and extracts the corresponding - // infos from the result JSON - addToolTipToTokenElement(tokenElement, token, client) { - this.currentTooltipElement; - this.currentTooltipElement = M.Tooltip.init(tokenElement, - {"html": ` - - - - - - - - -
    Token informationSource information
    - Word: ${token.word}
    - Lemma: ${token.lemma}
    - POS: ${token.pos}
    - Simple POS: ${token.simple_pos}
    - NER: ${token.ner} -
    - Title: ${client.results.data.text_lookup[token.text].title} -
    - Author: ${client.results.data.text_lookup[token.text].author} -
    - Publishing year: ${client.results.data.text_lookup[token.text].publishing_year} -
    `} - ); - } - - // function to remove extra informations and animations from tokens - expertModeOff(htmlId) { - // console.log("Expert mode is off."); - if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { - this.currentExpertTokenElements[htmlId] = []; - } - if (!Array.isArray(this.eventTokens[htmlId])) { - this.eventTokens[htmlId] = []; - } - for (let tokenElement of this.currentExpertTokenElements[htmlId]) { - tokenElement.classList.remove("chip", "hoverable", "expert-view"); - } - this.currentExpertTokenElements[htmlId] = []; - - for (let eventToken of this.eventTokens[htmlId]) { - eventToken.onmouseover = ""; - eventToken.onmouseout = ""; - } - this.eventTokens[htmlId] = []; - } - - createResultRowElement(item, chunk, imported=false) { - let aCellElement; - let addToSubResultsBtn; - let cCellElement; - let cpos; - let fakeResponse; // used if imported results are being created; - let inspectBtn - let lcCellElement; - let matchNrElement; - let matchRowElement; - let rcCellElement; - let textTitles; - let textTitlesCellElement; - let token; - let values; - // gather values from item - values = item.values(); - let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges, - values) - // 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 and set actions - textTitles = new Set(); - aCellElement = document.createElement("td"); - aCellElement.classList.add("actions"); - cCellElement = document.createElement("td"); - cCellElement.classList.add("match-hit"); - textTitlesCellElement = document.createElement("td"); - textTitlesCellElement.classList.add("titles"); - matchNrElement = document.createElement("td"); - matchNrElement.classList.add("match-nr"); - matchRowElement.appendChild(cCellElement); - matchRowElement.appendChild(aCellElement); - for (cpos of c) { - token = chunk.cpos_lookup[cpos]; - cCellElement.insertAdjacentHTML("beforeend", - `${token.word} `); - // get text titles of every hit cpos token - textTitles.add(chunk.text_lookup[token.text].title); - } - // add some interaction buttons - // # some btn css rules and classes - let css = `margin-right: 5px; margin-bottom: 5px;` - let classes = `btn-floating btn waves-effect` + - `waves-light grey` - // # add button to trigger more context to every match td - inspectBtn = document.createElement("a"); - inspectBtn.setAttribute("style", css); - inspectBtn.setAttribute("class", classes + ` disabled inspect` - ); - inspectBtn.innerHTML = 'search'; - // # add btn to add matches to sub-results. hidden per default - addToSubResultsBtn = document.createElement("a"); - addToSubResultsBtn.setAttribute("style", css); - addToSubResultsBtn.setAttribute("class", classes + ` hide add` - ); - addToSubResultsBtn.innerHTML = 'add'; - aCellElement.appendChild(inspectBtn); - aCellElement.appendChild(addToSubResultsBtn); - // add text titles at front as first td of one row - textTitlesCellElement.textContent = [...textTitles].join(", "); - matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML); - matchNrElement.textContent = 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 - } - - // creates the HTML table code for the metadata vie in the corpus analysis interface - createMetaDataForModal(metaDataObject) { - let html = `
    - - - - - - - - ` - for (let [outerKey, outerValue] of Object.entries(metaDataObject)) { - html += ` - ` - if (outerKey === "corpus_all_texts" || outerKey === "text_lookup") { - html += `` - } else { - html += `` - } - html += `` - } - html += ` -
    Metadata DescriptionValue
    ${outerKey.replace(/_/g, " ")} -
      ` - for (let [innerKey, innerValue] of Object.entries(outerValue)) { - html += `` - } - html += `
    -
    ${outerValue}
    ` - return html - } - - // Creates the text details for the texts shown in the corpus analysis metadata modal. - createTextDetails(metaDataObject) { - let metadataKey = event.target.dataset.metadataKey; - let textKey = event.target.dataset.textKey; - let textData = metaDataObject[metadataKey][textKey]; - let bibliographicData = document.getElementById(`bibliographic-data-${metadataKey}-${textKey}`); - bibliographicData.innerHTML = ""; - for (let [key, value] of Object.entries(textData)) { - bibliographicData.insertAdjacentHTML("afterbegin", - ` -
  • ${key}: ${value}
  • - `); - } - } -}; - -export {RessourceList, ResultsList}; +export { RessourceList, }; diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index 674603cf..e980d1c8 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -65,173 +65,7 @@ {% include 'modals/export_query_results.html.j2' %} {% include 'modals/context_modal.html.j2' %} - - {% endblock %} diff --git a/web/app/templates/corpora/analyse_corpus.html.j2.new.bak b/web/app/templates/corpora/analyse_corpus.html.j2.new.bak new file mode 100644 index 00000000..674603cf --- /dev/null +++ b/web/app/templates/corpora/analyse_corpus.html.j2.new.bak @@ -0,0 +1,237 @@ +{% extends "nopaque.html.j2" %} + +{% set headline = ' ' %} + +{% set full_width = True %} +{% set imported = False %} + +{% block page_content %} +
    +
    +
    + +
    +
    +
    +
    + search + {{ query_form.query() }} + {{ query_form.query.label }} + + + help + + CQP query language tutorial + + +
    +
    +
    +
    + {{ M.render_field(query_form.submit, material_icon='send') }} +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + {% include 'interactions/infos.html.j2' %} + {% include 'interactions/export.html.j2' %} + {% include 'interactions/create.html.j2' %} + {% include 'interactions/display.html.j2' %} + {% include 'interactions/analysis.html.j2' %} + {% include 'interactions/cite.html.j2' %} +
    + {% include 'tables/query_results.html.j2' %} +
    +
    +
    + + +{% include 'interactions/scroll_to_top.html.j2' %} + + +{% include 'modals/show_metadata.html.j2' %} +{% include 'modals/show_text_details.html.j2' %} +{% include 'modals/analysis_init.html.j2' %} +{% include 'modals/export_query_results.html.j2' %} +{% include 'modals/context_modal.html.j2' %} + + + +{% endblock %}