Intermediate result viewer push

This commit is contained in:
Stephan Porada 2020-07-14 09:24:04 +02:00
parent cdd421e48a
commit b2d093e9fe
5 changed files with 164 additions and 17 deletions

View File

@ -1,7 +1,7 @@
from . import results from . import results
from . import tasks from . import tasks
from .. import db from .. import db
from ..corpora.forms import DisplayOptionsForm from ..corpora.forms import DisplayOptionsForm, InspectDisplayOptionsForm
from ..models import Result, ResultFile, User from ..models import Result, ResultFile, User
from .forms import ImportResultsForm from .forms import ImportResultsForm
from datetime import datetime from datetime import datetime
@ -138,6 +138,8 @@ def result_inspect(result_id):
prefix='display-options-form', prefix='display-options-form',
result_context=request.args.get('context', 20), result_context=request.args.get('context', 20),
results_per_page=request.args.get('results_per_page', 30)) 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 = Result.query.get_or_404(result_id)
result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'], result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'],
result.file[0].dir, result.file[0].dir,
@ -148,6 +150,7 @@ def result_inspect(result_id):
abort(403) abort(403)
return render_template('results/result_inspect.html.j2', return render_template('results/result_inspect.html.j2',
display_options_form=display_options_form, display_options_form=display_options_form,
inspect_display_options_form=inspect_display_options_form,
result=result, result=result,
result_json=result_json, result_json=result_json,
title='Result Insepct') title='Result Insepct')

View File

@ -96,9 +96,6 @@ class Data {
tmp.forEach((index) => dataIndexes.push(index - 1)); tmp.forEach((index) => dataIndexes.push(index - 1));
console.log(dataIndexes); console.log(dataIndexes);
results.jsList.getMatchWithContext(dataIndexes, "sub-results"); 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
} }
} }

View File

@ -80,13 +80,21 @@ function queryRenderResults(payload, imported=false) {
for (let [index, match] of payload.chunk.matches.entries()) { for (let [index, match] of payload.chunk.matches.entries()) {
resultItems.push({...match, ...{"index": index + results.data.matches.length}}); resultItems.push({...match, ...{"index": index + results.data.matches.length}});
} }
results.jsList.add(resultItems, (items) => { if (!imported) {
for (let item of items) { results.jsList.add(resultItems, (items) => {
item.elm = results.jsList.createResultRowElement(item, payload.chunk); 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 });
}); } 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 // incorporating new chunk results into full results
results.data.matches.push(...payload.chunk.matches); results.data.matches.push(...payload.chunk.matches);
Object.assign(results.data.cpos_lookup, payload.chunk.cpos_lookup); Object.assign(results.data.cpos_lookup, payload.chunk.cpos_lookup);
@ -116,6 +124,7 @@ function queryRenderResults(payload, imported=false) {
results.jsList.expertModeOn("query-display"); results.jsList.expertModeOn("query-display");
} }
} else if (imported) { } else if (imported) {
progress = 100;
results.jsList.activateInspect(); results.jsList.activateInspect();
if (expertModeSwitchElement.checked) { if (expertModeSwitchElement.checked) {
results.jsList.expertModeOn("query-display"); results.jsList.expertModeOn("query-display");

View File

@ -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 // gets result cpos infos for one dataIndex (list of length 1) to send back to
// the server // the server
inspect(dataIndex, type) { inspect(dataIndex, type) {
let contextMatchNrElement; let contextMatchNrElement;
let contextResultsElement;
// get result infos from server and show them in context modal // get result infos from server and show them in context modal
this.contextId = dataIndex[0]; this.contextId = dataIndex[0];
// match nr for user to display derived from data_index // match nr for user to display derived from data_index
contextMatchNrElement = document.getElementById("context-match-nr"); contextMatchNrElement = document.getElementById("context-match-nr");
contextMatchNrElement.innerText = this.contextId + 1; contextMatchNrElement.innerText = this.contextId + 1;
let contextResultsElement;
contextResultsElement = document.getElementById("context-results"); contextResultsElement = document.getElementById("context-results");
contextResultsElement.innerHTML = ""; // clear it from old inspects contextResultsElement.innerHTML = ""; // clear it from old inspects
this.getMatchWithContext(dataIndex, type); this.getMatchWithContext(dataIndex, type);
@ -846,7 +904,9 @@ class ResultsList extends List {
if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
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.tooltipEventCreateBind = this.tooltipEventCreate.bind(this);
this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this); this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this);
this.eventTokens[htmlId] = []; this.eventTokens[htmlId] = [];
@ -909,12 +969,13 @@ class ResultsList extends List {
this.eventTokens[htmlId] = []; this.eventTokens[htmlId] = [];
} }
createResultRowElement(item, chunk) { createResultRowElement(item, chunk, imported=false) {
let aCellElement; let aCellElement;
let addToSubResultsBtn; let addToSubResultsBtn;
let c; let c;
let cCellElement; let cCellElement;
let cpos; let cpos;
let fakeResponse; // used if imported results are being created;
let inspectBtn let inspectBtn
let lc; let lc;
let lcCellElement; let lcCellElement;
@ -982,7 +1043,14 @@ class ResultsList extends List {
inspectBtn.setAttribute("class", classes + ` disabled inspect` inspectBtn.setAttribute("class", classes + ` disabled inspect`
); );
inspectBtn.innerHTML = '<i class="material-icons">search</i>'; 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 // # add btn to add matches to sub-results. hidden per default
addToSubResultsBtn = document.createElement("a"); addToSubResultsBtn = document.createElement("a");
addToSubResultsBtn.setAttribute("style", css); addToSubResultsBtn.setAttribute("style", css);

View File

@ -77,6 +77,70 @@
</div> </div>
</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 src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
</script> </script>
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}"> <script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
@ -96,12 +160,14 @@
var expertModeSwitchElement; // Expert mode switch Element var expertModeSwitchElement; // Expert mode switch Element
var matchCountElement; // Total nr. of matches will be displayed in this element var matchCountElement; // Total nr. of matches will be displayed in this element
var interactionElements; // Interaction elements and their parameters var interactionElements; // Interaction elements and their parameters
var contextModal; // Modal to open on inspect for further match context
// ###### Defining local scope variables // ###### Defining local scope variables
let displayOptionsFormElement; // Form holding the display informations 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 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 hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option
let paginationElements; let paginationElements;
let inspectBtnElements;
// ###### Initializing variables ###### // ###### Initializing variables ######
displayOptionsFormElement = document.getElementById("display-options-form"); displayOptionsFormElement = document.getElementById("display-options-form");
@ -116,6 +182,7 @@
hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page"); hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
contextPerItemElement = document.getElementById("display-options-form-result_context"); contextPerItemElement = document.getElementById("display-options-form-result_context");
paginationElements = document.getElementsByClassName("pagination"); paginationElements = document.getElementsByClassName("pagination");
contextModal = document.getElementById("context-modal");
// js list options // js list options
displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement); displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
@ -135,10 +202,13 @@
}; };
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// Initialize some Modals
contextModal = M.Modal.init(contextModal, {"dismissible": true});
// ###### recreating chunk structure to reuse callback queryRenderResults() // ###### recreating chunk structure to reuse callback queryRenderResults()
full_result_json = {{ result_json|tojson|safe }}; full_result_json = {{ result_json|tojson|safe }};
result_json = {}; result_json = {};
result_json.chunk = {}; result_json["chunk"] = {};
result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup; result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges; result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
result_json.chunk["matches"] = full_result_json.matches; result_json.chunk["matches"] = full_result_json.matches;
@ -198,7 +268,7 @@
} }
// render results in table imported parameter is true // 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 // live update of hits per page if hits per page value is changed
let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList); let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);