diff --git a/web/app/results/views.py b/web/app/results/views.py index 6cc66641fd81161bbbd2c3b924213dbc42e4c6af..733ac962cfd3cb17cfeab23bd893e4a523c9dfff 100644 --- a/web/app/results/views.py +++ b/web/app/results/views.py @@ -1,7 +1,7 @@ from . import results from . import tasks from .. import db -from ..corpora.forms import DisplayOptionsForm +from ..corpora.forms import DisplayOptionsForm, InspectDisplayOptionsForm from ..models import Result, ResultFile, User from .forms import ImportResultsForm from datetime import datetime @@ -138,6 +138,8 @@ def result_inspect(result_id): prefix='display-options-form', result_context=request.args.get('context', 20), results_per_page=request.args.get('results_per_page', 30)) + inspect_display_options_form = InspectDisplayOptionsForm( + prefix='inspect-display-options-form') result = Result.query.get_or_404(result_id) result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'], result.file[0].dir, @@ -148,6 +150,7 @@ def result_inspect(result_id): abort(403) return render_template('results/result_inspect.html.j2', display_options_form=display_options_form, + inspect_display_options_form=inspect_display_options_form, result=result, result_json=result_json, title='Result Insepct') diff --git a/web/app/static/js/nopaque.Results.js b/web/app/static/js/nopaque.Results.js index 8fb55db48fc1a87f780a2df87ed27949fb971d52..ca5af8c37aff398a003a8558b30cc0620c5fd17b 100644 --- a/web/app/static/js/nopaque.Results.js +++ b/web/app/static/js/nopaque.Results.js @@ -96,9 +96,6 @@ class Data { 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 } } diff --git a/web/app/static/js/nopaque.callbacks.js b/web/app/static/js/nopaque.callbacks.js index 1b3cba22cbfe3f6da53f8e057a95e14b67097836..43a62e053de9447b1b957a85c8a17251a7a901ff 100644 --- a/web/app/static/js/nopaque.callbacks.js +++ b/web/app/static/js/nopaque.callbacks.js @@ -80,13 +80,21 @@ function queryRenderResults(payload, imported=false) { for (let [index, match] of payload.chunk.matches.entries()) { resultItems.push({...match, ...{"index": index + results.data.matches.length}}); } - results.jsList.add(resultItems, (items) => { - for (let item of items) { - item.elm = results.jsList.createResultRowElement(item, payload.chunk); - } - results.jsList.update(); - results.jsList.changeContext(); // sets lr context on first result load - }); + if (!imported) { + results.jsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = results.jsList.createResultRowElement(item, payload.chunk); + } + }); + } else { + results.jsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = results.jsList.createResultRowElement(item, payload.chunk, + true); + } + }); + } + results.jsList.changeContext(); // sets lr context on first result load // incorporating new chunk results into full results results.data.matches.push(...payload.chunk.matches); Object.assign(results.data.cpos_lookup, payload.chunk.cpos_lookup); @@ -116,6 +124,7 @@ function queryRenderResults(payload, imported=false) { results.jsList.expertModeOn("query-display"); } } else if (imported) { + progress = 100; results.jsList.activateInspect(); 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 88a4e09ee33f207f499b3b7f1e4ecda3fcd8199b..1415bfa0377aba30de94e9acc2581f7e4a676990 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -534,16 +534,74 @@ class ResultsList extends List { } } + // ### 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(); + let lc; + let c; + let rc; + 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; + fake_response.payload["matches"] = [results.data.matches[dataIndex]]; + contextResultsElement = document.getElementById("context-results"); + contextResultsElement.innerHTML = ""; + if (results.data.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(fake_response.payload.matches[0].lc[0], + fake_response.payload.matches[0].lc[1], + 1); + c = range(fake_response.payload.matches[0].c[0], + fake_response.payload.matches[0].c[1], + 1) + rc = range(fake_response.payload.matches[0].rc[0], + fake_response.payload.matches[0].rc[1], + 1); + } else { + lc = fake_response.payload.matches[0].lc; + c = fake_response.payload.matches[0].c; + rc = fake_response.payload.matches[0].rc; + } + 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 contextMatchNrElement; + let contextResultsElement; // get result infos from server and show them in context modal this.contextId = dataIndex[0]; // match nr for user to display derived from data_index contextMatchNrElement = document.getElementById("context-match-nr"); contextMatchNrElement.innerText = this.contextId + 1; - let contextResultsElement; contextResultsElement = document.getElementById("context-results"); contextResultsElement.innerHTML = ""; // clear it from old inspects this.getMatchWithContext(dataIndex, type); @@ -846,7 +904,9 @@ class ResultsList extends List { if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { this.currentExpertTokenElements[htmlId] = []; } - this.currentExpertTokenElements[htmlId].push( ...document.getElementById(htmlId).getElementsByClassName("token")); + let container = document.getElementById(htmlId); + let tokens = container.querySelectorAll("span.token"); + this.currentExpertTokenElements[htmlId].push(...tokens); this.tooltipEventCreateBind = this.tooltipEventCreate.bind(this); this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this); this.eventTokens[htmlId] = []; @@ -909,12 +969,13 @@ class ResultsList extends List { this.eventTokens[htmlId] = []; } - createResultRowElement(item, chunk) { + createResultRowElement(item, chunk, imported=false) { let aCellElement; let addToSubResultsBtn; let c; let cCellElement; let cpos; + let fakeResponse; // used if imported results are being created; let inspectBtn let lc; let lcCellElement; @@ -982,7 +1043,14 @@ class ResultsList extends List { inspectBtn.setAttribute("class", classes + ` disabled inspect` ); inspectBtn.innerHTML = '<i class="material-icons">search</i>'; - inspectBtn.onclick = () => {this.inspect([values.index], "inspect")}; + if (imported) { + inspectBtn.onclick = () => { + fakeResponse = this.createFakeResponse(); + this.showMatchContext(fakeResponse); + }; + } else { + inspectBtn.onclick = () => {this.inspect([values.index], "inspect")}; + } // # add btn to add matches to sub-results. hidden per default addToSubResultsBtn = document.createElement("a"); addToSubResultsBtn.setAttribute("style", css); diff --git a/web/app/templates/results/result_inspect.html.j2 b/web/app/templates/results/result_inspect.html.j2 index 73910f8251c005df132e128aa74659131878e249..e28b964aa525797235da7ea9502536793a58c5d9 100644 --- a/web/app/templates/results/result_inspect.html.j2 +++ b/web/app/templates/results/result_inspect.html.j2 @@ -77,6 +77,70 @@ </div> </div> +<!-- Context modal used for detailed information about one match --> +<div id="context-modal" class="modal modal-fixed-footer"> + <div class="modal-content"> + <form> + <div class="row" style="margin-bottom: 0px; margin-top: -20px;"> + <div class="col s12 m6 l6"> + <div class="section"> + <h6 style="margin-top: 0px;">Display</h6> + <div class="divider" style="margin-bottom: 10px;"></div> + <div class="col s12" style="margin-bottom: 10px;" id="display-inspect"> + {{ inspect_display_options_form.expert_mode_inspect.label.text }} + <div class="switch right"> + <label> + {{ inspect_display_options_form.expert_mode_inspect() }} + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12" style="margin-bottom: 10px;" id="create-inspect"> + {{ inspect_display_options_form.highlight_sentences.label.text }} + <div class="switch right"> + <label> + {{ inspect_display_options_form.highlight_sentences() }} + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12" style="margin-bottom: 10px;"> + Sentences around match + <div class="input-field right" style="margin-top: -2rem; + margin-bottom: -2rem; + height: 0px;"> + <p class="range-field"> + <input type="range" + id="context-sentences" + min="1" + max="10" + value="3" /> + </p> + </div> + </div> + </div> + </div> + </div> + </form> + <div class="row section"> + <h5 style="margin-top: 0px;">Context for match: + <span id="context-match-nr"></span></h5> + <div class="divider" style="margin-bottom: 10px;"></div> + <div class="col s12" > + <div id="context-results"> + </div> + </div> + </div> + </div> + <div class="modal-footer"> + {# <a id="inspect-download-context" class="left waves-effect waves-light btn"> + Export Single Context + <i class="material-icons right">file_download</i> + </a> #} + <a href="#!" class="modal-close waves-effect waves-light red btn">Close</a> + </div> +</div> + <script src="{{ url_for('static', filename='js/nopaque.Results.js') }}"> </script> <script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}"> @@ -96,12 +160,14 @@ var expertModeSwitchElement; // Expert mode switch Element var matchCountElement; // Total nr. of matches will be displayed in this element var interactionElements; // Interaction elements and their parameters + var contextModal; // Modal to open on inspect for further match context // ###### Defining local scope variables let displayOptionsFormElement; // Form holding the display informations let resultItems; // array of built html result items row element. This is called when results are transmitted and being recieved let hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option let paginationElements; + let inspectBtnElements; // ###### Initializing variables ###### displayOptionsFormElement = document.getElementById("display-options-form"); @@ -116,6 +182,7 @@ hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page"); contextPerItemElement = document.getElementById("display-options-form-result_context"); paginationElements = document.getElementsByClassName("pagination"); + contextModal = document.getElementById("context-modal"); // js list options displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement); @@ -135,10 +202,13 @@ }; document.addEventListener("DOMContentLoaded", () => { + // Initialize some Modals + contextModal = M.Modal.init(contextModal, {"dismissible": true}); + // ###### recreating chunk structure to reuse callback queryRenderResults() full_result_json = {{ result_json|tojson|safe }}; result_json = {}; - result_json.chunk = {}; + result_json["chunk"] = {}; result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup; result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges; result_json.chunk["matches"] = full_result_json.matches; @@ -198,7 +268,7 @@ } // render results in table imported parameter is true - queryRenderResults(result_json, true) + queryRenderResults(result_json, true); // live update of hits per page if hits per page value is changed let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);