diff --git a/web/app/corpora/events.py b/web/app/corpora/events.py index fa40115f..58b2d9a0 100644 --- a/web/app/corpora/events.py +++ b/web/app/corpora/events.py @@ -129,22 +129,31 @@ def corpus_analysis_query(query): @socketio_login_required def corpus_analysis_inspect_match(payload): type = payload['type'] - data_index = payload["data_index"] + data_indexes = payload['data_indexes'] + first_cpos = payload['first_cpos'] + last_cpos = payload['last_cpos'] client = corpus_analysis_clients.get(request.sid) if client is None: response = {'code': 424, 'desc': 'No client found for this session', 'msg': 'Failed Dependency', 'type': type, - 'data_index': data_index} + 'data_indexes': data_indexes} socketio.emit('corpus_analysis_inspect_match', response, room=request.sid) return try: corpus = client.corpora.get('CORPUS') s = corpus.structural_attributes.get('s') - payload = s.export(payload['first_cpos'], payload['last_cpos'], - context=10) + payload = {} + payload['matches'] = [] + payload['cpos_lookup'] = {} + payload['text_lookup'] = {} + for index, f_cpos, l_cpos in zip(data_indexes, first_cpos, last_cpos): + tmp_match = s.export(f_cpos, l_cpos, context=10) + payload['matches'].append(tmp_match['matches'][0]) + payload['cpos_lookup'].update(tmp_match['cpos_lookup']) + payload['text_lookup'].update(tmp_match['text_lookup']) payload['cpos_ranges'] = True except cqi.errors.CQiException as e: payload = {'code': e.code, 'desc': e.description, 'msg': e.name} @@ -153,14 +162,14 @@ def corpus_analysis_inspect_match(payload): 'msg': 'Internal Server Error', 'payload': payload, 'type': type, - 'data_index': data_index} + 'data_indexes': data_indexes} else: response = {'code': 200, 'desc': None, 'msg': 'OK', 'payload': payload, 'type': type, - 'data_index': data_index} + 'data_indexes': data_indexes} socketio.emit('corpus_analysis_inspect_match', response, room=request.sid) diff --git a/web/app/static/css/nopaque.css b/web/app/static/css/nopaque.css index 24027647..57173da1 100644 --- a/web/app/static/css/nopaque.css +++ b/web/app/static/css/nopaque.css @@ -19,6 +19,15 @@ main { font-weight: bold; } +/* preloader circle in the size of a button icon */ +.button-icon-spinner { + bottom: -8px !important; + right: -16px !important; + margin-right: 12px !important; + width: 19.5px !important; + height: 19.5px !important; +} + /* CSS for clickable th elements in tables. Needed for sortable table data with list js. On click on th header elements will be sorted accordingly. Also a caret indicator will show up how the column is sorted right now.; */ diff --git a/web/app/static/js/nopaque.CorpusAnalysisClient.js b/web/app/static/js/nopaque.CorpusAnalysisClient.js index 31db4e84..f96086e1 100644 --- a/web/app/static/js/nopaque.CorpusAnalysisClient.js +++ b/web/app/static/js/nopaque.CorpusAnalysisClient.js @@ -88,16 +88,14 @@ class CorpusAnalysisClient { // inspect callback handeling based on type socket.on("corpus_analysis_inspect_match", (response) => { + console.log(response); if (response.type === "inspect") { if (this.callbacks.query_match_context != undefined) { this.callbacks.query_match_context(response); } - } else if (response.type === "sub-subcorpus"){ - if (this.callbacks.show_sub_subcorpus_choices != undefined) { - this.callbacks.show_sub_subcorpus_choices(response); - } - if (this.callbacks.save_sub_subcorpus_choices != undefined) { - this.callbacks.save_sub_subcorpus_choices(response); + } else if (response.type === "sub-results"){ + if (this.callbacks.save_sub_results_choices != undefined) { + this.callbacks.save_sub_results_choices(response); } } }); diff --git a/web/app/static/js/nopaque.Results.js b/web/app/static/js/nopaque.Results.js index b592973f..8fb55db4 100644 --- a/web/app/static/js/nopaque.Results.js +++ b/web/app/static/js/nopaque.Results.js @@ -3,6 +3,7 @@ class Results { this.data = data; this.jsList = jsList; this.metaData = metaData + this.subResultsData = new Data(); } clearAll() { @@ -10,7 +11,9 @@ class Results { this.jsList.update(); this.data.init(); this.metaData.init(); + this.subResultsData.init(); } + } @@ -22,11 +25,16 @@ class Data { this["cpos_lookup"] = {}; // object contains all this key value pair this["text_lookup"] = {}; // same as above for all text ids this["match_count"] = matchCount; - this["corpus_type"] = "subcorpus" + this["corpus_type"] = "results"; + this["query"] = ""; } - addData(jsonData) { - Object.assign(this, jsonData); + addData(jsonData, key=null) { + if (key !== null) { + Object.assign(this[key], jsonData); + } else if (key === null) { + Object.assign(this, jsonData) + } } // get query as string from form Element @@ -80,6 +88,19 @@ class Data { // start actual download this.download(downloadElement, dataStr, resultFilename, "text/json", ".json") } + + // createSubResultsData from subResultsTmpData + createSubResultsData() { + let tmp = [...results.jsList.addToSubResultsIdsToShow].sort(function(a, b){return a-b}); + let dataIndexes = []; + tmp.forEach((index) => dataIndexes.push(index - 1)); + console.log(dataIndexes); + results.jsList.getMatchWithContext(dataIndexes, "sub-results"); + // TODO: save incoming matche infos with saveSubResultsChoices. + // TODO: trigger this function on dl btn click and seta flag that it has run to avoid double execution + // also set this flag to false if addToSubResultsIdsToShow has been altered + } + } class MetaData { diff --git a/web/app/static/js/nopaque.callbacks.js b/web/app/static/js/nopaque.callbacks.js index cbf8cf0b..9f849735 100644 --- a/web/app/static/js/nopaque.callbacks.js +++ b/web/app/static/js/nopaque.callbacks.js @@ -3,16 +3,34 @@ function recvMetaData(payload) { console.log(results.metaData); } -function saveSubSubcorpusChoices(payload) { - console.log("SAVE"); - console.log(payload); +function saveSubResultsChoices(response) { + console.log("Recieve match with context"); + results.subResultsData.init(); + results.subResultsData.matches.push(...response.payload.matches); + results.subResultsData.addData(response.payload.cpos_lookup, "cpos_lookup"); + results.subResultsData.addData(response.payload.text_lookup, "text_lookup"); + results.subResultsData.addData(results.metaData); + results.subResultsData.query = results.data.query; + results.subResultsData.corpus_type = "sub-results"; + results.subResultsData.match_count = [...response.payload.matches].length; + results.subResultsData.cpos_ranges = response.payload.cpos_ranges; + console.log(results.subResultsData); + subResultsCreateElement.getElementsByClassName("button-icon-spinner")[0].remove(); + subResultsCreateElement.getElementsByTagName("i")[0].classList.remove("hide"); + subResultsCreateElement.firstElementChild.classList.add("disabled"); + subResultsExportElement.classList.remove("disabled"); } function querySetup(payload) { // This is called when a query was successfull // some hiding and resetting + let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0]; + textarea.innerText = ""; + M.textareaAutoResize(textarea); + results.jsList.addToSubResultsStatus = {}; + results.jsList.addToSubResultsIdsToShow = new Set(); queryResultsExportElement.classList.add("disabled"); - addToSubSubcorpusElement.setAttribute("disabled", ""); + addToSubResultsElement.setAttribute("disabled", ""); queryResultsDeterminateElement.style.width = "0%"; queryResultsProgressElement.classList.remove("hide"); queryResultsUserFeedbackElement.classList.remove("hide"); @@ -72,11 +90,11 @@ function queryRenderResults(payload) { queryResultsProgressElement.classList.add("hide"); queryResultsUserFeedbackElement.classList.add("hide"); queryResultsExportElement.classList.remove("disabled"); - addToSubSubcorpusElement.removeAttribute("disabled"); + addToSubResultsElement.removeAttribute("disabled"); results.jsList.activateInspect(); - // inital expert mode check and sub subcorpus activation - if (addToSubSubcorpusElement.checked) { - results.jsList.activateAddToSubSubcorpus(); + // inital expert mode check and sub results activation + if (addToSubResultsElement.checked) { + results.jsList.activateAddToSubResults(); } if (expertModeSwitchElement.checked) { results.jsList.expertModeOn("query-display"); diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index 10f446b2..150c6c78 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -134,7 +134,8 @@ class ResultsList extends List { this.currentExpertTokenElements = {}; // all token elements which have added // classes like chip and hoverable for expert view. Collected //here to delete later on - this.addToSubSubcorpuStatus = {}; + this.addToSubResultsStatus = {}; + this.addToSubResultsIdsToShow = new Set(); } // handels interactionElements during a pagination navigation @@ -178,60 +179,74 @@ class ResultsList extends List { return displayOptionsData } - // ###### Functions to add one match to a sub-subcorpus ###### + // ###### Functions to add one match to a sub-results ###### // activate add button - activateAddToSubSubcorpus() { - let addToSubSubcorpusBtnElements = document.getElementsByClassName("add"); - for (let addToSubSubcorpusBtn of addToSubSubcorpusBtnElements) { - addToSubSubcorpusBtn.classList.remove("hide"); + activateAddToSubResults() { + subResultsIdListElement.classList.remove("hide"); + subResultsCreateElement.classList.remove("hide"); + let addToSubResultsBtnElements = document.getElementsByClassName("add"); + for (let addToSubResultsBtn of addToSubResultsBtnElements) { + addToSubResultsBtn.classList.remove("hide"); } } // deactivate add button - deactivateAddToSubSubcorpus() { - let addToSubSubcorpusBtnElements = document.getElementsByClassName("add"); - for (let addToSubSubcorpusBtn of addToSubSubcorpusBtnElements) { - addToSubSubcorpusBtn.classList.add("hide"); + deactivateAddToSubResults() { + subResultsIdListElement.classList.add("hide"); + subResultsCreateElement.classList.add("hide"); + let addToSubResultsBtnElements = document.getElementsByClassName("add"); + for (let addToSubResultsBtn of addToSubResultsBtnElements) { + addToSubResultsBtn.classList.add("hide"); } } - // add match on click to a SubSubcorpus - addToSubSubcorpus(dataIndex) { - if (!this.addToSubSubcorpuStatus[dataIndex] - || this.addToSubSubcorpuStatus === undefined) { + // add match id on click to a List of marked matches for SubSubcorpora + // remove match id on click from same list + addToSubResults(dataIndex) { + let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0] + if (!this.addToSubResultsStatus[dataIndex] + || this.addToSubResultsStatus === undefined) { // add button is activated - nopaque.flash(`[Match ${dataIndex + 1}] Marked for Sub-Subcorpus!`); + nopaque.flash(`[Match ${dataIndex + 1}] Marked for Sub-Results!`); event.target.classList.remove("grey"); event.target.classList.add("green"); event.target.innerText = "check"; - this.addToSubSubcorpuStatus[dataIndex] = true; - this.getMatchWithContext(dataIndex, "sub-subcorpus"); - } else if (this.addToSubSubcorpuStatus[dataIndex]) { + this.addToSubResultsStatus[dataIndex] = true; + this.addToSubResultsIdsToShow.add(dataIndex + 1); + textarea.innerText = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); + M.textareaAutoResize(textarea); + } else if (this.addToSubResultsStatus[dataIndex]) { // add button is deactivated - nopaque.flash(`[Match ${dataIndex + 1}] Unmarked for Sub-Subcorpus!`); + nopaque.flash(`[Match ${dataIndex + 1}] Unmarked for Sub-results!`); event.target.classList.remove("green"); event.target.classList.add("grey"); event.target.innerText = "add"; - this.addToSubSubcorpuStatus[dataIndex] = false; + this.addToSubResultsStatus[dataIndex] = false; + this.addToSubResultsIdsToShow.delete(dataIndex + 1); + textarea.innerText = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); + M.textareaAutoResize(textarea); } + if ([...this.addToSubResultsIdsToShow].length > 0) { + subResultsCreateElement.firstElementChild.classList.remove("disabled"); + } else if ([...this.addToSubResultsIdsToShow].length === 0) { + subResultsCreateElement.firstElementChild.classList.add("disabled"); + } + subResultsExportElement.classList.add("disabled"); } - // handels incoming match info from socket on event - showSubSubcorpusChoices(response) { - console.log("SHOW"); - console.log(response.data_index); - } - - // gets full info for one match - getMatchWithContext(dataIndex, type) { - this.contextId = dataIndex; - let contextResultsElement; - contextResultsElement = document.getElementById("context-results"); - contextResultsElement.innerHTML = ""; // clear it from old inspects + // 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_index: dataIndex, - first_cpos: results.data.matches[dataIndex].c[0], - last_cpos: results.data.matches[dataIndex].c[1], + data_indexes: dataIndexes, + first_cpos: tmp_first_cpos, + last_cpos: tmp_last_cpos, } ); } @@ -250,10 +265,22 @@ class ResultsList extends List { } } - //gets result cpos infos for one dataIndex to send back to the server + // gets result cpos infos for one dataIndex (list of length 1) to send back to + // the server inspect(dataIndex, type) { + this.contextId = dataIndex[0]; + let contextResultsElement; + contextResultsElement = document.getElementById("context-results"); + contextResultsElement.innerHTML = ""; // clear it from old inspects this.getMatchWithContext(dataIndex, type); contextModal.open(); + let css = `margin-right: 10px;` + let classes = `btn-floating btn waves-effect` + + `waves-light grey right` + addToSubResultsFromInspectElement = document.createElement("a"); + addToSubResultsFromInspectElement.setAttribute("class", classes + `add`); + addToSubResultsFromInspectElement.innerHTML = 'add'; + addToSubResultsFromInspectElement.onclick= (event) => {this.addToSubResults(dataIndex)}; } // create Element from HTML String helper function @@ -286,11 +313,13 @@ class ResultsList extends List { let uniqueS; this.contextData = response.payload; + 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"] = "sub-subcorpus" + this.contextData["corpus_type"] = "inspect-result" Object.assign(this.contextData, results.metaData); + console.log(this.contextData); contextResultsElement = document.getElementById("context-results"); modalExpertModeSwitchElement = document.getElementById("inspect-display-options-form-expert_mode_inspect"); highlightSentencesSwitchElement = document.getElementById("inspect-display-options-form-highlight_sentences"); @@ -595,7 +624,7 @@ class ResultsList extends List { } createResultRowElement(item, chunk) { - let addToSubSubcorpusBtn; + let addToSubResultsBtn; let c; let cCellElement; let cpos; @@ -663,16 +692,16 @@ class ResultsList extends List { ); inspectBtn.setAttribute("style", css) inspectBtn.innerHTML = 'search'; - inspectBtn.onclick = () => {this.inspect(values.index, "inspect")}; - // # add btn to add matches to sub-subcorpus. hidden per default - addToSubSubcorpusBtn = document.createElement("a"); - addToSubSubcorpusBtn.setAttribute("class", classes + ` hide add` + inspectBtn.onclick = () => {this.inspect([values.index], "inspect")}; + // # add btn to add matches to sub-results. hidden per default + addToSubResultsBtn = document.createElement("a"); + addToSubResultsBtn.setAttribute("class", classes + ` hide add` ); - addToSubSubcorpusBtn.setAttribute("style", css) - addToSubSubcorpusBtn.innerHTML = 'add'; - addToSubSubcorpusBtn.onclick= (event) => {this.addToSubSubcorpus(values.index)} + addToSubResultsBtn.setAttribute("style", css) + addToSubResultsBtn.innerHTML = 'add'; + addToSubResultsBtn.onclick= (event) => {this.addToSubResults(values.index)} cCellElement.appendChild(inspectBtn); - cCellElement.appendChild(addToSubSubcorpusBtn); + cCellElement.appendChild(addToSubResultsBtn); // add text titles at front as first td of one row textTitlesCellElement.innerText = [...textTitles].join(", "); matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML); diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index f6745587..7fa96309 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -74,43 +74,109 @@