From f94d21fa96145f88dbf996b9e76c0915d8be2317 Mon Sep 17 00:00:00 2001 From: Stephan Porada Date: Mon, 10 Aug 2020 14:48:45 +0200 Subject: [PATCH] Add rudimentary rework of corpus analysis --- .../modules/nopaque.CorpusAnalysisClient.js | 25 +----- .../js/modules/nopaque.listenerCallbacks.js | 84 +++++++++++++++++++ .../js/modules/nopaque.listenerFunctions.js | 11 ++- web/app/static/js/nopaque.Results.js | 6 +- web/app/static/js/nopaque.lists.js | 46 ++++++---- .../templates/corpora/analyse_corpus.html.j2 | 42 +++++++--- web/app/templates/nopaque.html.j2 | 41 +-------- 7 files changed, 163 insertions(+), 92 deletions(-) create mode 100644 web/app/static/js/modules/nopaque.listenerCallbacks.js diff --git a/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js b/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js index a2659b76..7eeb6216 100644 --- a/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js +++ b/web/app/static/js/modules/nopaque.CorpusAnalysisClient.js @@ -20,7 +20,7 @@ class CorpusAnalysisClient { } } - loadListeners() { + loadSocketEventListeners() { for (let [type, listener] of Object.entries(this.socketEventListeners)) { listener(this); } @@ -31,16 +31,6 @@ class CorpusAnalysisClient { this.displays[type] = corpusAnalysisDisplay; } - // /** - // * Initializes the interactive corpus analysis session via socket.io. - // * This function uses helper functions. - // */ - // initSession() { - // let request = this.requestSession(); - // let recvieveSession = this.recvieveSession(); - // console.info('corpus_analysis_init: Client waiting for response'); // this happens inbetween the two functions above - // } - /** * Requests a corpus analysis session via socket.io. * Opens a loading modal at the start of the request @@ -56,17 +46,6 @@ class CorpusAnalysisClient { this.socket.emit('corpus_analysis_init', this.corpusId); } - // /** - // * Sends a query to the server and handles the response to that query. - // * This function uses helper functions. - // */ - // query(queryStr) { - // let requestQueryData = this.requestQueryData(queryStr); - // let recieveQueryProcessStatus = this.recieveQueryProcessStatus(); - // let recieveQueryData = this.recieveQueryData(); - // console.info('corpus_analysis_query: Client waiting for query data'); // this happens inbetween the two functions above - // } - /** * Sends the query string to the server. * Should be a private method if ES2020 is finalized (Maybe?) @@ -165,5 +144,5 @@ class SocketEventListener { } } -// export both Classes from this module +// export Classes from this module export {CorpusAnalysisClient, CorpusAnalysisDisplay, SocketEventListener}; \ No newline at end of file diff --git a/web/app/static/js/modules/nopaque.listenerCallbacks.js b/web/app/static/js/modules/nopaque.listenerCallbacks.js new file mode 100644 index 00000000..b0369c6b --- /dev/null +++ b/web/app/static/js/modules/nopaque.listenerCallbacks.js @@ -0,0 +1,84 @@ +/** + * This callback is called when the SocketEventListener 'recieveQueryData' + * has been triggered. It takes the incoming chunk and renders the results in a + * the results.jsList. It can eiterh hande llive incoming data or imported + * results data. + */ +function queryRenderResults(payload, client, imported=false) { + console.info("Current recieved chunk:", payload.chunk); + if (payload.chunk.cpos_ranges == true) { + client.results.data["cpos_ranges"] = true; + } else { + client.results.data["cpos_ranges"] = false; + } + /** + * resultItem is a list where + */ + let resultItems = []; + // get infos for full match row + for (let [index, match] of payload.chunk.matches.entries()) { + resultItems.push({...match, ...{"index": index + client.results.data.matches.length}}); + } + if (!imported) { + // update progress bar + // queryResultsDeterminateElement.style.width = `${payload.progress}%`; + client.results.jsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = client.results.jsList.createResultRowElement(item, payload.chunk); + } + }); + helperQueryRenderResults(payload, client); + // if (progress === 100) { + // resultsCreateElement.classList.remove("disabled"); + // queryResultsProgressElement.classList.add("hide"); + // queryResultsUserFeedbackElement.classList.add("hide"); + // resultsExportElement.classList.remove("disabled"); + // addToSubResultsElement.removeAttribute("disabled"); + // // inital expert mode check and sub results activation + // client.results.jsList.activateInspect(); + // if (addToSubResultsElement.checked) { + // client.results.jsList.activateAddToSubResults(); + // } + // if (expertModeSwitchElement.checked) { + // client.results.jsList.expertModeOn("query-display"); + // } + // } + } else if (imported) { + client.results.jsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = client.results.jsList.createResultRowElement(item, payload.chunk, + true); + } + helperQueryRenderResults(payload, client); + progress = 100; + client.results.jsList.activateInspect(); + if (expertModeSwitchElement.checked) { + client.results.jsList.expertModeOn("query-display"); + } + }); + } +} + +function helperQueryRenderResults (payload, client) { + // updating table on finished item creation callback via createResultRowElement + client.results.jsList.update(); + client.results.jsList.changeContext(); // sets lr context on first result load + // incorporating new chunk results into full results + client.results.data.matches.push(...payload.chunk.matches); + client.results.data.addData(payload.chunk.cpos_lookup, "cpos_lookup"); + client.results.data.addData(payload.chunk.text_lookup, "text_lookup"); + // complete metaData + // client.results.metaData.add(); + // show user current and total match count + // receivedMatchCountElement.innerText = `${client.results.data.matches.length}`; + // textLookupCountElement.innerText = `${Object.keys(client.results.data.text_lookup).length}`; + let titles = new Array(); + for (let [key, value] of Object.entries(client.results.data.text_lookup)) { + titles.push(`${value.title} (${value.publishing_year})`); + }; + // textTitlesElement.innerText = `${titles.join(", ")}`; + // upate progress status + // progress = payload.progress; // global declaration +} + +export { queryRenderResults } \ No newline at end of file diff --git a/web/app/static/js/modules/nopaque.listenerFunctions.js b/web/app/static/js/modules/nopaque.listenerFunctions.js index ce94ca91..ad63d3ae 100644 --- a/web/app/static/js/modules/nopaque.listenerFunctions.js +++ b/web/app/static/js/modules/nopaque.listenerFunctions.js @@ -1,3 +1,5 @@ +import {queryRenderResults} from './nopaque.listenerCallbacks.js' + /** * Recieves a corpus analysis session via socket.io. * Closes the loading modal that has been opend with requestSession at the @@ -34,11 +36,13 @@ function recieveSession(client) { * Recieves the query process status before any actual results are being * transmitted. So it recieves error codes if a query failed or * was invalid etc. + * Also prepares the result.jsList for the incoming data. */ function recieveQueryStatus(client) { client.socket.on('corpus_analysis_query', (response) => { console.group('corpus_analysis_query: Client recieving query process', 'status via socket.on'); + client.results.clearAll(); console.info(response); console.groupEnd(); }); @@ -51,10 +55,13 @@ function recieveQueryData(client) { client.socket.on('corpus_analysis_query_results', (response) => { console.group('corpus_analysis_query_results: Client recieving query', 'data via socket.on'); - console.info(response); + console.info('Recieved chunk', response); + queryRenderResults(response.payload, client); + console.info('Added chunk data to results.data and rendered it with', + 'results.jsList') console.groupEnd(); }); } // export listeners from this module -export {recieveSession, recieveQueryStatus, recieveQueryData}; \ No newline at end of file +export { recieveSession, recieveQueryStatus, recieveQueryData }; \ No newline at end of file diff --git a/web/app/static/js/nopaque.Results.js b/web/app/static/js/nopaque.Results.js index 2b3c1caa..c05af727 100644 --- a/web/app/static/js/nopaque.Results.js +++ b/web/app/static/js/nopaque.Results.js @@ -22,7 +22,7 @@ class Results { class Data { // Sets empty object structure. Also usefull to delete old results. // matchCount default is 0 - init(matchCount = 0) { + init(matchCount = 0, type = "results") { this["matches"] = []; // list of all c with lc and rc this["cpos_lookup"] = {}; // object contains all this key value pair this["text_lookup"] = {}; // same as above for all text ids @@ -122,4 +122,6 @@ class MetaData { init(json = {}) { Object.assign(this, json); } -} \ No newline at end of file +} + +export {Results, Data, MetaData}; \ No newline at end of file diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index 2f577e32..033519ab 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -425,15 +425,31 @@ RessourceList.options = { class ResultsList extends List { - constructor(idOrElement, options={}) { - super(idOrElement, options); - this.eventTokens = {}; // all span tokens which are holdeing events if expert - // mode is on. Collected here to delete later on - this.currentExpertTokenElements = {}; // all token elements which have added - // classes like chip and hoverable for expert view. Collected - //here to delete later on - this.addToSubResultsStatus = {}; // 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.addToSubResultsIdsToShow = new Set(); // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user. + 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; + this.eventTokens = {}; // all span tokens which are holdeing events if expert + // mode is on. Collected here to delete later on + this.currentExpertTokenElements = {}; // all token elements which have added + // classes like chip and hoverable for expert view. Collected + //here to delete later on + this.addToSubResultsStatus = {}; // 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.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) { @@ -479,12 +495,11 @@ class ResultsList extends List { } // get display options from display options form element - static getDisplayOptions(displayOptionsFormElement) { + static getDisplayOptions(htmlId) { // gets display options parameters - let displayOptionsFormData - let displayOptionsData; - displayOptionsFormData = new FormData(displayOptionsFormElement); - displayOptionsData = + 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"), @@ -1182,5 +1197,6 @@ class ResultsList extends List { `); } } +}; -} +export {RessourceList, ResultsList}; diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index d96bdbea..a5845fdb 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -75,25 +75,43 @@ CorpusAnalysisDisplay, SocketEventListener} from '../../static/js/modules/nopaque.CorpusAnalysisClient.js'; import {recieveSession, recieveQueryStatus, - recieveQueryData} from '../../static/js/modules/nopaque.listenerFunctions.js' + recieveQueryData} from '../../static/js/modules/nopaque.listenerFunctions.js'; + import {Results, Data, MetaData} from '../../static/js/nopaque.Results.js'; + import {ResultsList} from '../../static/js/nopaque.lists.js'; /** * Second Phase: * Asynchronus and event driven code */ document.addEventListener("DOMContentLoaded", () => { - // init some modals + // Initialize the CorpusAnalysisClient + const client = new CorpusAnalysisClient({{ corpus_id }}, nopaque.socket); + console.info("CorpusAnalysisClient created as client:", client); + // Initialize modals which are shown depending on events or client status const initLoadingElement = document.getElementById("init-display"); const initLoadingModal = M.Modal.init(initLoadingElement, {"dismissible": false}); - // set up display elements + // Set up display elements which hare show depending on the client status const initLoadingDisplay = new CorpusAnalysisDisplay(initLoadingModal); - // set up CorpusAnalysisClient - const client = new CorpusAnalysisClient({{ corpus_id }}, nopaque.socket); - console.info("CorpusAnalysisClient created as client:", client); - // register display elements to client + // Register those display elements to client client.setDisplay("init", initLoadingDisplay); - // register listeners and load them + /** + * Initializing the results object holding all the data of a query. + * Also holds the metadata of one query. + * resultsListOptions is set to determine how many results per apge are + * shown etc. + * Lastly it also contains the object ResultsList which is a list.js + * subclass which handles the visual reprensetnation of the query data. + */ + let displayOptionsData = ResultsList.getDisplayOptions('display-options-form'); + ResultsList.options.page = displayOptionsData["resultsPerPage"]; + let resultsList = new ResultsList("result-list", ResultsList.options); + let resultsMetaData = new MetaData(); + let results = new Results(new Data(), resultsList, resultsMetaData); + // make results part of the client + client.results = results; + console.info('Initialized the Results object.') + // register listeners listening to socket.io events and load them const listenForSession = new SocketEventListener('corpus_analysis_init', recieveSession); const listenForQueryStatus = new SocketEventListener('corpus_analysis_query', @@ -102,10 +120,10 @@ recieveQueryData); client.setSocketEventListeners([listenForSession, listenForQueryStatus, listenForQueryData]); - client.loadListeners(); + client.loadSocketEventListeners(); // Session initialization client.requestSession(); - // send a query and recieve its answer data + // Send a query and recieve its answer data let queryFormElement = document.getElementById("query-form"); queryFormElement.addEventListener("submit", (event) => { try { @@ -129,8 +147,8 @@ // Prevent page from reloading on submit event.preventDefault(); // Get query string and send query to server - // results.data.getQueryStr(queryFormElement); - client.requestQueryData('"this" []* "that" within 10 words;'); + results.data.getQueryStr(queryFormElement); + client.requestQueryData(results.data.query); }); }); diff --git a/web/app/templates/nopaque.html.j2 b/web/app/templates/nopaque.html.j2 index 8a9511ca..2e0f1c90 100644 --- a/web/app/templates/nopaque.html.j2 +++ b/web/app/templates/nopaque.html.j2 @@ -47,7 +47,9 @@ - +