mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-24 10:34:17 +00:00
Some cleanup (css, html, js)
This commit is contained in:
parent
067273df6d
commit
40bd169101
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@ -71,7 +71,8 @@ class App {
|
||||
usersPatchHandler(patch) {
|
||||
let listener;
|
||||
|
||||
this.data = jsonpatch.apply_patch(this.data, patch);
|
||||
this.data = jsonpatch.applyPatch(this.data, patch).newDocument;
|
||||
//this.data = jsonpatch.apply_patch(this.data, patch);
|
||||
for (listener of this.eventListeners['users.patch']) {listener(patch);}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
36
app/static/js/jsonpatch.min.js
vendored
36
app/static/js/jsonpatch.min.js
vendored
File diff suppressed because one or more lines are too long
2
app/static/js/list.min.js
vendored
2
app/static/js/list.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"version":3,"file":"list.min.js","sources":["webpack://List/list.min.js"],"mappings":"AAAA","sourceRoot":""}
|
6
app/static/js/materialize.min.js
vendored
6
app/static/js/materialize.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,282 +0,0 @@
|
||||
/**
|
||||
* This class is used to create a Client object.
|
||||
* The client handels the client server communication.
|
||||
* It communicates with the server (e.g. connection or query)
|
||||
* and recieves data from it, if dynamicMode is true.
|
||||
* If dynamicMode is false, the client can also handle data that is already
|
||||
* loaded and is not coming in, in chunks.
|
||||
*/
|
||||
class Client {
|
||||
constructor({corpusId = null,
|
||||
socket = null,
|
||||
logging = true,
|
||||
dynamicMode = true,
|
||||
fullContext = null} = {}) {
|
||||
this.corpusId = corpusId;
|
||||
this.dynamicMode = dynamicMode;
|
||||
this.logging = logging;
|
||||
this.requestQueryProgress = 0;
|
||||
this.socket = socket;
|
||||
this.eventListeners = {};
|
||||
this.isBusy = false;
|
||||
this.fullContext = fullContext;
|
||||
/**
|
||||
* Disables all console logging.
|
||||
* This is global. So every other log message in every other Class or
|
||||
* function used in conjunction with the client either logs or does not
|
||||
* log depending on the logging flag. It is kind of hacky but not bad.
|
||||
* Credits to https://gist.github.com/kmonsoor/0244fdb4ad79a4826371e58a1a5fa984
|
||||
*/
|
||||
if (!logging) {
|
||||
(() => {
|
||||
let console = (window.console = window.console || {});
|
||||
[
|
||||
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml',
|
||||
'error', 'exception', 'group', 'groupCollapsed', 'groupEnd',
|
||||
'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'table',
|
||||
'time', 'timeEnd', 'timeStamp', 'trace', 'warn'
|
||||
].forEach((method) => {
|
||||
console[method] = () => {};
|
||||
});
|
||||
})();
|
||||
}
|
||||
console.info("Client initialized:", this);
|
||||
}
|
||||
/**
|
||||
* Registers one or more event listeners to the Client. Either socket or
|
||||
* custom javascript events. Event listeners are class instances of
|
||||
* ClientEventListener implemented further below.
|
||||
*/
|
||||
setSocketEventListeners(eventListeners) {
|
||||
for (let eventListener of eventListeners) {
|
||||
this.eventListeners[eventListener.type] = eventListener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the event listeners that have been registered with the function
|
||||
* above so that they will be triggered on their assigned
|
||||
* type strings. Type strings double as the socket event event names or
|
||||
* javascript custom event names.
|
||||
*/
|
||||
loadSocketEventListeners() {
|
||||
for (let [type, listener] of Object.entries(this.eventListeners)) {
|
||||
listener.listenerFunction(type, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions emits the 'notify-view' custom javascript event. This
|
||||
* triggers specific functions in the View depending on the caseIdentifier.
|
||||
* The detail object can hold any type of data the View needs to know about
|
||||
* to represent those to the user.
|
||||
*/
|
||||
notifyView(caseIdentifier, detailObject={}, notificationType='info',
|
||||
raiseModalFeedback=true) {
|
||||
detailObject.caseIdentifier = caseIdentifier;
|
||||
detailObject.client = this;
|
||||
detailObject.notificationType = notificationType;
|
||||
detailObject.raiseModalFeedback = raiseModalFeedback;
|
||||
const event = new CustomEvent('notify-view', { detail: detailObject });
|
||||
console[notificationType]('Client dispatching Notification with details:',
|
||||
detailObject);
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the corpus analysis session running on the server side for the
|
||||
* specified corpus via socket.io.
|
||||
*/
|
||||
connect() {
|
||||
console.info('corpus_analysis_init: Client connecting to session via',
|
||||
'socket.emit');
|
||||
this.socket.emit('corpus_analysis_init', this.corpusId);
|
||||
}
|
||||
|
||||
// Gets the meta data of the current corpus.
|
||||
getMetaData() {
|
||||
this.isBusy = true;
|
||||
console.info('corpus_analysis_meta_data: Client getting meta data via',
|
||||
'socket.emit.');
|
||||
this.socket.emit('corpus_analysis_meta_data', this.corpusId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits query to the server via socket.io. Server will send the results
|
||||
* back.
|
||||
*/
|
||||
query(queryStr) {
|
||||
this.isBusy = true;
|
||||
console.info('corpus_analysis_query: Client sending query via',
|
||||
'socket.emit for the query', queryStr);
|
||||
this.socket.emit('corpus_analysis_query', queryStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests results data either for, 'full-results', 'sub-results' or
|
||||
* 'inspect-results' (resultsType).
|
||||
* Gets full results for evere provided dataIndex (one match).
|
||||
* Full results means with full context. So the Client has to request all
|
||||
* matches from the server again!
|
||||
**/
|
||||
getResultsData(resultsType, dataIndexes, results) {
|
||||
let tmp_first_cpos = [];
|
||||
let tmp_last_cpos = [];
|
||||
let objectKey = '';
|
||||
if (resultsType === 'full-results') {
|
||||
objectKey = 'fullResultsData';
|
||||
} else if (resultsType === 'sub-results') {
|
||||
objectKey = 'subResultsData';
|
||||
} else if (resultsType === 'inspect-results') {
|
||||
objectKey = 'inspectResultsData';
|
||||
}
|
||||
// Delete old data before new data is coming in.
|
||||
results[objectKey].init();
|
||||
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]);
|
||||
}
|
||||
this.socket.emit('corpus_analysis_get_match_with_full_context',
|
||||
{type: resultsType,
|
||||
data_indexes: dataIndexes,
|
||||
first_cpos: tmp_first_cpos,
|
||||
last_cpos: tmp_last_cpos,});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets results data either for, 'full-results' or 'sub-results'
|
||||
* Gets results for every provided dataIndex (one match) without full
|
||||
* context. Because no context is needed the results data is gathered locally
|
||||
* from results.data and not from the server.
|
||||
**/
|
||||
getResultsDataWithoutContext(resultsType, dataIndexes, results, resultsList) {
|
||||
this.notifyView('results-data-recieving', {fullContext: false});
|
||||
let objectKey = '';
|
||||
if (resultsType === 'full-results') {
|
||||
console.info('Saving full-results data without full context.');
|
||||
objectKey = 'fullResultsData';
|
||||
} else if (resultsType === 'sub-results') {
|
||||
console.info('Saving sub-results data without full context.');
|
||||
objectKey = 'subResultsData';
|
||||
}
|
||||
// Get matches from results.data.
|
||||
let matches = [];
|
||||
let cpos = [];
|
||||
let match;
|
||||
for (let index of dataIndexes) {
|
||||
match = results.data.matches[index]
|
||||
matches.push(match)
|
||||
// Get cpos from match.
|
||||
let {lc, c, rc} = resultsList.helperCreateCpos(results.data.cpos_ranges,
|
||||
match);
|
||||
cpos.push(...lc);
|
||||
cpos.push(...c);
|
||||
cpos.push(...rc);
|
||||
}
|
||||
// Get cpos_lookups from cposes.
|
||||
let cpos_lookup = {};
|
||||
let textIds = new Set;
|
||||
for (let single_cpos of cpos) {
|
||||
textIds.add(results.data.cpos_lookup[single_cpos].text);
|
||||
Object.assign(cpos_lookup, { [single_cpos]: results.data.cpos_lookup[single_cpos]});
|
||||
}
|
||||
let text = {};
|
||||
let text_lookup = {};
|
||||
for (let id of textIds) {
|
||||
text[id] = results.data.text_lookup[id];
|
||||
Object.assign(text_lookup, text);
|
||||
}
|
||||
/**
|
||||
* Save the data from results.dat either in results.fullResultsData or
|
||||
* results.subResultsData.
|
||||
*/
|
||||
results[objectKey].init();
|
||||
results[objectKey].matches.push(...matches);
|
||||
results[objectKey].addData(cpos_lookup, "cpos_lookup");
|
||||
results[objectKey].addData(text_lookup, "text_lookup");
|
||||
results[objectKey].addData(results.metaData);
|
||||
results[objectKey].query = results.data.query;
|
||||
results[objectKey].corpus_type = resultsType;
|
||||
results[objectKey].match_count = matches.length;
|
||||
results[objectKey].cpos_ranges = results.data.cpos_ranges;
|
||||
results[objectKey].fullContext = false;
|
||||
if (objectKey === 'subResultsData') {
|
||||
// Remove match_count from texts, because they are useless in sub results
|
||||
for (let [key, value] of Object.entries(results[objectKey].text_lookup)) {
|
||||
delete results[objectKey].text_lookup[key].match_count;
|
||||
}
|
||||
}
|
||||
console.info('Results data without context has been saved.', results);
|
||||
this.isBusy = false;
|
||||
this.notifyView('results-data-recieved', {type: resultsType,
|
||||
results: results,
|
||||
fullContext: false});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to create an event listener listening for socket or
|
||||
* javascript custom events.
|
||||
* Input are an identifying type string, the listener function and callbacks
|
||||
* which will be executed as part of the listener function. The identifying
|
||||
* type string is also used as the socket event or custom javascript event name
|
||||
* identifier.
|
||||
*/
|
||||
class ClientEventListener {
|
||||
constructor(type, listenerFunction) {
|
||||
this.listenerCallbacks = {};
|
||||
this.listenerFunction = listenerFunction;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
// Registers callbacks to this ClientEventListener.
|
||||
setCallbacks(listenerCallbacks) {
|
||||
for (let listenerCallback of listenerCallbacks) {
|
||||
this.listenerCallbacks[listenerCallback.type] = listenerCallback;
|
||||
}
|
||||
}
|
||||
|
||||
/** Shorthand to execute all registered callbacks with same defaultArgs
|
||||
* in insertion order.
|
||||
* NOTE:
|
||||
* Since ECMAScript 2015, objects do preserve creation order for
|
||||
* string and Symbol keys. In JavaScript engines that comply with the
|
||||
* ECMAScript 2015 spec, iterating over an object with only string keys will
|
||||
* yield the keys in order of insertion.
|
||||
* So all modern Browsers.
|
||||
*/
|
||||
executeCallbacks(defaultArgs) {
|
||||
for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) {
|
||||
listenerCallback.callbackFunction(...defaultArgs,
|
||||
...listenerCallback.args);
|
||||
}
|
||||
}
|
||||
// Executes a specific registered callback by provoding a type string.
|
||||
executeCallback(defaultArgs, type) {
|
||||
let listenerCallback = this.listenerCallbacks[type];
|
||||
let args = defaultArgs.concat(listenerCallback.args) ;
|
||||
listenerCallback.callbackFunction(...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create an ListenerCallback which will be registered
|
||||
* to an ClientEventListener so the listener can invoke the associated
|
||||
* callback functions.
|
||||
*/
|
||||
class ListenerCallback {
|
||||
constructor(type, callbackFunction, argsList) {
|
||||
this.args = argsList;
|
||||
this.callbackFunction = callbackFunction;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
// Export Classes from this module.
|
||||
export {
|
||||
Client,
|
||||
ClientEventListener,
|
||||
ListenerCallback,
|
||||
};
|
@ -1,161 +0,0 @@
|
||||
/**
|
||||
* This callback is called on a socket.on "corpus_analysis_send_meta_data".
|
||||
* Handels incoming corpus metadata
|
||||
*/
|
||||
function saveMetaData(...args) {
|
||||
let [payload, client, results, rest] = args;
|
||||
client.notifyView('meta-data-recieving');
|
||||
results.metaData.init(payload)
|
||||
console.info('Metada saved:', results);
|
||||
client.isBusy = false;
|
||||
client.notifyView('meta-data-recieved');
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback should be registered to the SocketEventListener 'recieveQueryStatus'.
|
||||
* It just gets the incoming status data of the issued query
|
||||
* and does some preperation work like hiding or showing elements and deleting
|
||||
* the data from the last query.
|
||||
*/
|
||||
function prepareQueryData(...args) {
|
||||
// deletes old data from query issued before this new query
|
||||
let [payload, client, results, rest] = args;
|
||||
// always initialize the results to delete data from the query issued before
|
||||
results.init();
|
||||
results.data.match_count = payload.match_count;
|
||||
client.requestQueryProgress = 0;
|
||||
client.notifyView('query-data-prepareing', { results: results });
|
||||
}
|
||||
|
||||
/**
|
||||
* This callbacks saves the incoming query data chunks into the model results.
|
||||
*/
|
||||
function saveQueryData(...args) {
|
||||
let [payload, client, results, rest] = args;
|
||||
// Get data matches length before new chunk data is being inserted
|
||||
let dataLength = results.data.matches.length;
|
||||
if (client.dynamicMode) {
|
||||
// Incorporating new chunk data into full results
|
||||
results.data.matches.push(...payload.chunk.matches);
|
||||
results.data.addData(payload.chunk.cpos_lookup, 'cpos_lookup');
|
||||
results.data.addData(payload.chunk.text_lookup, 'text_lookup');
|
||||
/**
|
||||
* Increment match_counts per text in a global results varaible because
|
||||
* they are coming in chunkwise.
|
||||
*/
|
||||
if (payload.chunk.text_lookup) {
|
||||
for (let [text_key, value] of Object.entries(payload.chunk.text_lookup)) {
|
||||
if (!(text_key in results.tmp_match_counts)) {
|
||||
results.tmp_match_counts[text_key] = {match_count: 0};
|
||||
}
|
||||
results.tmp_match_counts[text_key].match_count += payload.chunk.text_lookup[text_key].match_count;
|
||||
}
|
||||
}
|
||||
results.data.cpos_ranges = payload.chunk.cpos_ranges;
|
||||
let queryFormElement = document.querySelector('#query-form');
|
||||
results.data.getQueryStr(queryFormElement);
|
||||
client.requestQueryProgress = payload.progress;
|
||||
client.notifyView('query-data-recieving',
|
||||
{ results: results,
|
||||
client: client,
|
||||
dataLength: dataLength });
|
||||
console.info('Query data chunk saved', results.data);
|
||||
if (client.requestQueryProgress === 100) {
|
||||
client.isBusy = false;
|
||||
// Update text_lookup with tmp_match_counts.
|
||||
for (let [text_key, value] of Object.entries(results.tmp_match_counts)) {
|
||||
results.data.text_lookup[text_key].match_count = results.tmp_match_counts[text_key].match_count;
|
||||
}
|
||||
client.notifyView('query-data-recieved');
|
||||
}
|
||||
} else {
|
||||
results.data.matches.push(...payload.matches);
|
||||
results.data.addData(payload.cpos_lookup, 'cpos_lookup');
|
||||
results.data.addData(payload.text_lookup, 'text_lookup');
|
||||
results.data.cpos_ranges = payload.cpos_ranges;
|
||||
let queryFormElement = document.querySelector('#query-form');
|
||||
results.data.getQueryStr(queryFormElement);
|
||||
client.requestQueryProgress = 100;
|
||||
client.notifyView('query-data-recieving',
|
||||
{ results: results,
|
||||
client: client,
|
||||
dataLength: dataLength });
|
||||
console.info('Query data chunk saved', results.data);
|
||||
if (client.requestQueryProgress === 100) {
|
||||
console.log(results.data);
|
||||
client.notifyView('query-data-recieved');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback gets the results data for the export. Either requesting it
|
||||
* whith full context from the server or gets it locally without full context
|
||||
* from the already present results.data. Result data is identified with the
|
||||
* dataIndexes. On index is one match.
|
||||
*/
|
||||
function getResultsData(...args) {
|
||||
let [resultsType, dataIndexes, resultsList, client, results, rest] = args;
|
||||
client.isBusy = true;
|
||||
if (resultsList.exportFullInspectContext.checked
|
||||
|| resultsType === 'inspect-results') {
|
||||
console.info('Get results with full context');
|
||||
client.getResultsData(resultsType, dataIndexes, results);
|
||||
} else {
|
||||
console.info('Get results without full context');
|
||||
client.getResultsDataWithoutContext(resultsType, dataIndexes, results,
|
||||
resultsList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming results which have been requested via getResultsData(). and
|
||||
* saves the data accorindgly into the results object.
|
||||
*/
|
||||
function saveResultsData(...args) {
|
||||
let [payload, type, client, results, rest] = args;
|
||||
let objectKey = '';
|
||||
if (type === 'full-results') {
|
||||
console.info('Saving full-results data.');
|
||||
objectKey = 'fullResultsData';
|
||||
} else if (type === 'sub-results') {
|
||||
console.info('Saving sub-results data.');
|
||||
objectKey = 'subResultsData';
|
||||
} else if (type = 'inspect-results') {
|
||||
objectKey = 'inspectResultsData';
|
||||
console.info('Saving inspect-results data');
|
||||
}
|
||||
// Save incoming data. Data is incoming one match at a time.
|
||||
results[objectKey].matches.push(...payload.matches);
|
||||
results[objectKey].addData(payload.cpos_lookup, 'cpos_lookup');
|
||||
results[objectKey].addData(payload.text_lookup, 'text_lookup');
|
||||
results[objectKey].addData(results.metaData);
|
||||
results[objectKey].query = results.data.query;
|
||||
results[objectKey].corpus_type = type;
|
||||
results[objectKey].match_count += 1;
|
||||
results[objectKey].cpos_ranges = payload.cpos_ranges;
|
||||
results[objectKey].fullContext = true;
|
||||
console.info('Results data has been saved.', results);
|
||||
// Notify view to update progress bar
|
||||
client.notifyView('results-data-recieving', {type: type,
|
||||
progress: payload.progress})
|
||||
if (payload.progress === 100) {
|
||||
client.isBusy = false;
|
||||
if (objectKey === 'fullResultsData') {
|
||||
// Get match count per text from results.data only for fullResultsData
|
||||
results[objectKey].text_lookup = results.data.text_lookup;
|
||||
}
|
||||
client.notifyView('results-data-recieved', {type: type,
|
||||
results: results,
|
||||
fullContext: true});
|
||||
}
|
||||
}
|
||||
|
||||
// export callbacks
|
||||
export {
|
||||
prepareQueryData,
|
||||
saveMetaData,
|
||||
saveQueryData,
|
||||
getResultsData,
|
||||
saveResultsData,
|
||||
};
|
@ -1,201 +0,0 @@
|
||||
/**
|
||||
* This file contains the listener functions which can be assigned to the
|
||||
* coprus_analysis client. So that the incoming data/status informations will
|
||||
* be handled. There are several listeners listening for socket .io events.
|
||||
* Further below one javascript custom event listener is specified. This
|
||||
* listener listens for javascript custom events which are being dispatched by
|
||||
* the View (resultsList).
|
||||
*/
|
||||
|
||||
// Listeners for socket io events
|
||||
|
||||
/**
|
||||
* Recieves a corpus analysis connected signal via socket.io.
|
||||
*/
|
||||
function recieveConnected(type, client) {
|
||||
client.socket.on(type, (response) => {
|
||||
/**
|
||||
* Check if request for session was OK.
|
||||
* If OK execute registered callbacks and notify View.
|
||||
*/
|
||||
if (response.code === 200) {
|
||||
console.group('Connected!')
|
||||
console.info('corpus_analysis_init: Client recieving connected codes',
|
||||
'codes via socket.on');
|
||||
console.info(`corpus_analysis_init: ${response.code} - ${response.msg}`);
|
||||
console.info('corpus_analysis_init: Initialization succeeded');
|
||||
console.info(response);
|
||||
client.notifyView('connected');
|
||||
console.groupEnd();
|
||||
// get meta data immediately
|
||||
client.getMetaData();
|
||||
} else {
|
||||
let errorText = `Error ${response.code} - ${response.msg}`;
|
||||
console.group('Connection failed!');
|
||||
console.error(`corpus_analysis_init: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error');
|
||||
console.groupEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recieves meta data from the server via socket.io.
|
||||
*/
|
||||
function recieveMetaData(type, client) {
|
||||
client.socket.on(type, (response) => {
|
||||
/**
|
||||
* Check if request for session was OK.
|
||||
* If OK execute registered callbacks and notify View.
|
||||
*/
|
||||
if (response.code === 200) {
|
||||
console.group('Client recieving meta data')
|
||||
console.info('corpus_analysis_meta_data: Client recieving meta data',
|
||||
'via socket.on');
|
||||
console.info(`corpus_analysis_meta_data: ${response.code} - ${response.msg}`);
|
||||
console.info(response);
|
||||
// executing the registered callbacks
|
||||
client.eventListeners[type].executeCallbacks([response.payload]);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
|
||||
console.group('Failed to recieve meta data.');
|
||||
console.error('corpus_analysis_meta_data: Client failed to recieve',
|
||||
'meta data via socket.on');
|
||||
console.error(`corpus_analysis_meta_data: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error');
|
||||
console.groupEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(type, client) {
|
||||
/**
|
||||
* Check if request for session was OK.
|
||||
* If OK execute registered callbacks and notify View.
|
||||
*/
|
||||
client.socket.on(type, (response) => {
|
||||
if (response.code === 200) {
|
||||
console.group('corpus_analysis_query: Client recieving query process',
|
||||
'status via socket.on');
|
||||
console.info(`corpus_analysis_query: ${response.code} - ${response.msg}`);
|
||||
console.info(response);
|
||||
// executing the registered callbacks
|
||||
client.eventListeners[type].executeCallbacks([response.payload]);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
|
||||
console.group('corpus_analysis_query: Client failed recieving',
|
||||
'query process status via socket.on');
|
||||
if (response.payload.code == 1281) {
|
||||
errorText += ' - Invalid Query';
|
||||
console.error(`corpus_analysis_query: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error', false);
|
||||
} else {
|
||||
console.error(`corpus_analysis_query: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error');
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recieves the query data from the request and handles it.
|
||||
*/
|
||||
function recieveQueryData(type, client) {
|
||||
/**
|
||||
* Check if request for session was OK.
|
||||
* If OK execute registered callbacks and notify View.
|
||||
*/
|
||||
client.socket.on(type, (response) => {
|
||||
if (response.code === 200) {
|
||||
console.group('corpus_analysis_query_results: Recieveing query data')
|
||||
console.info('Client recieving query data via socket.on');
|
||||
console.info('Recieved chunk', response);
|
||||
/**
|
||||
* Execute registered callbacks and notify View.
|
||||
*/
|
||||
client.eventListeners[type].executeCallbacks([response.payload]);
|
||||
console.info('Added chunk data to results.data.');
|
||||
console.groupEnd();
|
||||
} else {
|
||||
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
|
||||
console.group('corpus_analysis_query_results: Client failed recieving',
|
||||
'the results via socket.on');
|
||||
console.error(`corpus_analysis_query: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error');
|
||||
console.groupEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recieves the data requested by the create Results or sub results button
|
||||
*/
|
||||
function recieveResultsData(type, client) {
|
||||
client.socket.on(type, (response) => {
|
||||
/**
|
||||
* Check if request for session was OK.
|
||||
* If OK execute registered callbacks and notify View.
|
||||
*/
|
||||
if (response.code === 200) {
|
||||
console.group('Client recieving results data')
|
||||
console.info('corpus_analysis_get_match_with_full_context: Client recieving results data',
|
||||
'via socket.on');
|
||||
console.info(`corpus_analysis_get_match_with_full_context: ${response.code} - ${response.msg}`);
|
||||
console.info(response);
|
||||
// executing the registered callbacks
|
||||
client.eventListeners[type].executeCallbacks([response.payload,
|
||||
response.type]);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
|
||||
console.group('Failed to recieve results data.');
|
||||
console.error('corpus_analysis_get_match_with_full_context: Client failed to recieve',
|
||||
'results data via socket.on');
|
||||
console.error(`corpus_analysis_get_match_with_full_context: ${errorText}`);
|
||||
client.notifyView('client-failed', { msg: errorText }, 'error');
|
||||
console.groupEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the javascript custom event listener, listening for events
|
||||
* dispatched by the View.
|
||||
*/
|
||||
function recieveViewNotification(type, client) {
|
||||
document.addEventListener(type, (event) => {
|
||||
let caseIdentifier = event.detail.caseIdentifier;
|
||||
switch(caseIdentifier) {
|
||||
case 'get-results':
|
||||
console.info('Client getting full results for export.');
|
||||
// execute callback or functions
|
||||
client.eventListeners[type].executeCallback([event.detail.resultsType,
|
||||
event.detail.dataIndexes,
|
||||
event.detail.resultsList],
|
||||
caseIdentifier);
|
||||
break
|
||||
default:
|
||||
console.error('Recieved unkown notification case identifier from View');
|
||||
// do something to not crash the analysis session?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// export listeners from this module
|
||||
export {
|
||||
recieveConnected,
|
||||
recieveMetaData,
|
||||
recieveQueryStatus,
|
||||
recieveQueryData,
|
||||
recieveViewNotification,
|
||||
recieveResultsData,
|
||||
};
|
@ -1,141 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Results class bundleing the different data objects.
|
||||
class Results {
|
||||
constructor() {
|
||||
this.data = new Data();
|
||||
this.metaData = new MetaData();
|
||||
this.fullResultsData = new Data();
|
||||
this.subResultsData = new Data();
|
||||
this.inspectResultsData = new Data();
|
||||
console.info('Initialized the Results object.');
|
||||
}
|
||||
|
||||
// Reset all the data objects in the results class and thus emptying them.
|
||||
init() {
|
||||
this.data.init();
|
||||
this.metaData.init();
|
||||
this.fullResultsData.init();
|
||||
this.subResultsData.init();
|
||||
this.inspectResultsData.init();
|
||||
// Temporarly save match counts per text
|
||||
this.tmp_match_counts = {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that defines the actual data objects holding the results data
|
||||
* requested by the client. Data can be the results of a query, full results
|
||||
* data for the export, sub results data for the export or inspect results data.
|
||||
* All kinds are structured the same way.
|
||||
*/
|
||||
class Data {
|
||||
/**
|
||||
* Sets empty object structure. Also usefull to delete old results.
|
||||
* MatchCount default is 0.
|
||||
*/
|
||||
init(matchCount=0, type="results") {
|
||||
// List of all c with lc and rc CPOS.
|
||||
this.matches = [];
|
||||
/**
|
||||
* CPOS lookup object. CPOS are the key and value are infos about the CPOS
|
||||
* like lemma, ner, pos, text ID etc. CPOS from the matches correspond to
|
||||
* exactly one object in the cpos_lookup.
|
||||
*/
|
||||
this.cpos_lookup = {};
|
||||
/**
|
||||
* Same like above but for text IDs. One CPOS object always has a text ID
|
||||
* referencing on text object in the text_lookup. Text ID is the key. Values
|
||||
* are author, publishing year etc.
|
||||
*/
|
||||
this.text_lookup = {};
|
||||
this.match_count = matchCount;
|
||||
this.corpus_type = 'results';
|
||||
this.cpos_ranges = null;
|
||||
this.query = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to add json data/object data to this data instance.
|
||||
* If no key is specified the entire data will be assigned to this data
|
||||
* instance.
|
||||
*/
|
||||
addData(jsonData, key=null) {
|
||||
if (key !== null) {
|
||||
Object.assign(this[key], jsonData);
|
||||
} else if (key === null) {
|
||||
Object.assign(this, jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
// Get query as a string from the form Element.
|
||||
getQueryStr(queryFormElement) {
|
||||
// gets query
|
||||
let queryFormData;
|
||||
let queryStr;
|
||||
queryFormData = new FormData(queryFormElement);
|
||||
queryStr = queryFormData.get('query-form-query');
|
||||
this['query'] = queryStr;
|
||||
}
|
||||
|
||||
// Function creates a unique and safe filename for the download.
|
||||
createDownloadFilename(suffix) {
|
||||
let today = new Date();
|
||||
let currentDate = `${today.getUTCFullYear()}` +
|
||||
`-${(today.getUTCMonth() + 1)}` +
|
||||
`-${today.getUTCDate()}`;
|
||||
let currentTime = `${today.getUTCHours()}h` +
|
||||
`${today.getUTCMinutes()}m` +
|
||||
`${today.getUTCSeconds()}s`;
|
||||
let safeFilename = this.query.replace(/[^a-z0-9_-]/gi, "_");
|
||||
let resultFilename = `UTC-${currentDate}_${currentTime}_${safeFilename}_${suffix}`;
|
||||
return resultFilename
|
||||
}
|
||||
/**
|
||||
* Function to download data as Blob created from string.
|
||||
* Should be private but that is not yet a feature of javascript 08.04.2020
|
||||
*/
|
||||
download(downloadElement, dataStr, filename, type, filenameSlug) {
|
||||
filename += filenameSlug;
|
||||
let file = new Blob([dataStr], {type: type});
|
||||
var url = URL.createObjectURL(file);
|
||||
downloadElement.href = url;
|
||||
downloadElement.download = filename;
|
||||
}
|
||||
|
||||
// Function to download the results as JSON.
|
||||
downloadJSONRessource(resultFilename, downloadData, downloadElement) {
|
||||
/**
|
||||
* Stringify JSON object for json download.
|
||||
* Use tabs to save some space.
|
||||
*/
|
||||
let dataStr = JSON.stringify(downloadData, undefined, "\t");
|
||||
// Start actual download
|
||||
this.download(downloadElement, dataStr, resultFilename, "text/json", ".json")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the data class but just intended for meta data about the current
|
||||
* corpus the client is working with.
|
||||
*/
|
||||
class MetaData {
|
||||
// Sets empty object structure when no input is given.
|
||||
// if json object like input is given class fields are created from this
|
||||
init(json={}) {
|
||||
Object.assign(this, json);
|
||||
}
|
||||
}
|
||||
|
||||
// Export the classes
|
||||
export {
|
||||
Results,
|
||||
Data,
|
||||
MetaData
|
||||
};
|
@ -1,837 +0,0 @@
|
||||
/**
|
||||
* This class implements a ViewEventListener that is listening for the
|
||||
* specified
|
||||
*/
|
||||
class ViewEventListener {
|
||||
constructor(type, listenerFunction, args=[]) {
|
||||
this.listenerFunction = listenerFunction;
|
||||
this.type = type;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: 30,
|
||||
pagination: [{
|
||||
name: "paginationTop",
|
||||
paginationClass: "paginationTop",
|
||||
innerWindow: 8,
|
||||
outerWindow: 1
|
||||
}, {
|
||||
paginationClass: "paginationBottom",
|
||||
innerWindow: 8,
|
||||
outerWindow: 1
|
||||
}],
|
||||
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
|
||||
item: `<span></span>`
|
||||
};
|
||||
constructor(idOrElement, options) {
|
||||
super(idOrElement, 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 to sub-results.
|
||||
* If checked, it is True. If unchecked, it is false. Buttons for this
|
||||
* have the class add. The ittle round check buttons to add matches to sub
|
||||
* results. Key is match index. Value is true or false as mentioned above.
|
||||
*/
|
||||
this.subResultsIndexes = {};
|
||||
// ViewEventListeners listening for client notifications.
|
||||
this.notificationListeners = {};
|
||||
this.knownHTMLElements = new Set();
|
||||
}
|
||||
/**
|
||||
* Function to clear/reset some class field values. Usefull if a new query
|
||||
* hase been issued by the user.
|
||||
*/
|
||||
resetFields() {
|
||||
this.subResultsIndexes = {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function that takes one or more query selector
|
||||
* strings in an array as an input. The function then creates a
|
||||
* class field in the ResultsList object with the query selector
|
||||
* string as the key. The selector will be converted to a valid JavaScript
|
||||
* Field name i. e. #html-id-string -> this.htmlIdString
|
||||
* The value will be the identifed element or elements fetched with the
|
||||
* querySelector respectively querySelectorAll method.
|
||||
* If the query selector is passed as an Array of length 2, with the second
|
||||
* element defining modal options, teh identified element will be initialized
|
||||
* as a modal with the given options.
|
||||
*/
|
||||
getHTMLElements(arrayOfSelectors) {
|
||||
for (let selector of arrayOfSelectors) {
|
||||
// Check if identified Element should be initialized as a modal.
|
||||
let modalInit = false;
|
||||
let options;
|
||||
if (Array.isArray(selector)) {
|
||||
options = selector[1];
|
||||
selector = selector[0];
|
||||
modalInit = true;
|
||||
}
|
||||
// Check if the current selector has already been used.
|
||||
if (this.knownHTMLElements.has(selector)) {
|
||||
continue;
|
||||
} else {
|
||||
// Get element or elements.
|
||||
let element;
|
||||
let elements;
|
||||
if (selector.startsWith('#')) {
|
||||
element = document.querySelector(selector);
|
||||
} else {
|
||||
elements = document.querySelectorAll(selector);
|
||||
elements = [...elements];
|
||||
}
|
||||
// Create valid javascript instance field name.
|
||||
let cleanKey = [];
|
||||
selector = selector.replace(/_/g, '-');
|
||||
selector.match(/\w+/g).forEach((word) => {
|
||||
let tmp = word[0].toUpperCase() + word.slice(1);
|
||||
cleanKey.push(tmp);
|
||||
});
|
||||
cleanKey[0] = cleanKey[0].toLowerCase();
|
||||
cleanKey = cleanKey.join('');
|
||||
this[cleanKey] = element ? element: elements;
|
||||
// Initialize current element as modal if modalInit true.
|
||||
if (modalInit) {
|
||||
this[cleanKey] = M.Modal.init(this[cleanKey], options);
|
||||
}
|
||||
}
|
||||
// Add current selector to knwonHTMLElements.
|
||||
this.knownHTMLElements.add(selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ViewEventListeners to the ResultsList. Which will listen for
|
||||
* the specified event.
|
||||
*/
|
||||
setViewEventListeners(notificationListeners) {
|
||||
for (let notificationListener of notificationListeners) {
|
||||
this.notificationListeners[notificationListener.type] = notificationListener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ViewEventListeners so that hey will be listening to their
|
||||
* assigned custom events.
|
||||
*/
|
||||
loadViewEventListeners() {
|
||||
for (let [type, listener] of Object.entries(this.notificationListeners)) {
|
||||
if (listener.args.length > 0) {
|
||||
listener.listenerFunction(...listener.args);
|
||||
} else {
|
||||
listener.listenerFunction(type, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions sends events to the Client to trigger specific functions to
|
||||
* trigger new data requests from the server.
|
||||
*/
|
||||
notifyClient(caseIdentifier, detailObject={}) {
|
||||
detailObject.caseIdentifier = caseIdentifier;
|
||||
const event = new CustomEvent('notify-client', { detail: detailObject });
|
||||
console.info('Client dispatching Notification:', caseIdentifier);
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates cpos either from ranges or not.
|
||||
*/
|
||||
helperCreateCpos(cpos_values) {
|
||||
let lc, c, rc;
|
||||
/**
|
||||
* 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 = cpos_values.lc ? range(cpos_values.lc[0], cpos_values.lc[1], 1) : [];
|
||||
c = range(cpos_values.c[0], cpos_values.c[1], 1);
|
||||
rc = cpos_values.rc ? range(cpos_values.rc[0], cpos_values.rc[1], 1) : [];
|
||||
return {lc: lc, c: c, rc: rc};
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
/**
|
||||
* Used in addToSubResults and inspect to toggle the design of the check
|
||||
* buttons according to its checked unchecked status.
|
||||
*/
|
||||
helperActivateAddBtn(btn) {
|
||||
btn.classList.remove("corpus-analysis-color.lighten");
|
||||
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.
|
||||
*/
|
||||
helperDeactivateAddBtn(btn) {
|
||||
btn.classList.remove("green");
|
||||
btn.classList.add("corpus-analysis-color.lighten");
|
||||
btn.textContent = "add";
|
||||
}
|
||||
/**
|
||||
* This function is invoked when the users adds or removes a match using the
|
||||
* add-btn (+ button/or green checkmark) to/from sub-results. When the button
|
||||
* is clicked the function checks if the current dataIndex ID is already
|
||||
* saved in subResultsIndexes or not. If it is not the dataIndex will be used
|
||||
* as a key in subResultsIndexes with the value true. If it is already added
|
||||
* the entry with the key dataIndex will be deleted from subResultsIndexes.
|
||||
* Visual feedback (green checkmark if a match has been added etc.) is also
|
||||
* handled on the basis of the information stored in subResultsIndexes.
|
||||
*/
|
||||
addToSubResults(dataIndex, client, tableCall=true) {
|
||||
let toShowArray;
|
||||
dataIndex = parseInt(dataIndex);
|
||||
if (!this.subResultsIndexes[dataIndex]
|
||||
|| this.subResultsIndexes[dataIndex] === undefined) {
|
||||
// add button is activated because status is false or undefined
|
||||
this.helperActivateAddBtn(event.target);
|
||||
this.subResultsIndexes[dataIndex] = true;
|
||||
toShowArray = Object.keys(this.subResultsIndexes).map(index => parseInt(index));
|
||||
// Add 1 because indexes are zero based. User sees 1 based numbering.
|
||||
toShowArray = toShowArray.map(index => index + 1);
|
||||
// Allways sort the shown indexes for the user if new match is added.
|
||||
toShowArray = toShowArray.sort(function(a, b){return a-b});
|
||||
this.subResultsIndexesDisplay.textContent = toShowArray.join(', ');
|
||||
M.textareaAutoResize(this.subResultsIndexesDisplay);
|
||||
this.nrMarkedMatches.textContent = Object.keys(this.subResultsIndexes).length;
|
||||
} else if (this.subResultsIndexes[dataIndex]) {
|
||||
// add button is deactivated because status is true
|
||||
this.helperDeactivateAddBtn(event.target);
|
||||
delete this.subResultsIndexes[dataIndex];
|
||||
toShowArray = Object.keys(this.subResultsIndexes).map(index => parseInt(index));
|
||||
// Add 1 because indexes are zero based. User sees 1 based numbering.
|
||||
toShowArray = toShowArray.map(index => index + 1);
|
||||
// Allways sort the shown indexes for the user if new match is added.
|
||||
toShowArray = toShowArray.sort(function(a, b){return a-b});
|
||||
this.subResultsIndexesDisplay.textContent = toShowArray.join(', ');
|
||||
this.nrMarkedMatches.textContent = Object.keys(this.subResultsIndexes).length;
|
||||
M.textareaAutoResize(this.subResultsIndexesDisplay);
|
||||
}
|
||||
// Toggles create button according to the number of ids in addToSubResultsIdsToShow
|
||||
if (Object.keys(this.subResultsIndexes).length > 0 && !client.isBusy) {
|
||||
this.subResultsCreate.classList.toggle('disabled', false);
|
||||
} else if (Object.keys(this.subResultsIndexes).length === 0) {
|
||||
this.subResultsCreate.classList.toggle('disabled', true);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
this.subResultsExport.classList.add("hide");
|
||||
this.subResultsCreate.classList.remove("hide");
|
||||
/**
|
||||
* Also activate/deactivate buttons in the table/resultsList accordingly
|
||||
* if button in inspect was activated/deactivated.
|
||||
* This part only runs if tableCall is set to false when this function is
|
||||
* called.
|
||||
*/
|
||||
if (!tableCall) {
|
||||
this.getHTMLElements(['#query-results-table']);
|
||||
let container = this.queryResultsTable.querySelector(`[data-index="${dataIndex}"]`);
|
||||
let tableAddBtn = container.querySelector('.add-btn'); // gets the add button from the list view
|
||||
if (this.subResultsIndexes[dataIndex]) {
|
||||
this.helperActivateAddBtn(tableAddBtn);
|
||||
} else {
|
||||
this.helperDeactivateAddBtn(tableAddBtn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle inspect buttons depending on the Client status
|
||||
toggleInspectButtons(client) {
|
||||
if (!client.isBusy) {
|
||||
this.activateInspectButtons();
|
||||
} else if (client.isBusy) {
|
||||
this.deactivateInspectButtons();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function. Should be private if feature is available.
|
||||
activateInspectButtons() {
|
||||
let inspectBtnElements;
|
||||
inspectBtnElements = document.querySelectorAll('.inspect');
|
||||
for (let inspectBtn of inspectBtnElements) {
|
||||
inspectBtn.classList.toggle('disabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function. Should be private if feature is available.
|
||||
deactivateInspectButtons() {
|
||||
let inspectBtnElements;
|
||||
inspectBtnElements = document.querySelectorAll('.inspect');
|
||||
for (let inspectBtn of inspectBtnElements) {
|
||||
inspectBtn.classList.toggle('disabled', true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Function to inspect one match in more detail (Showing more context).
|
||||
* If in dynamic mode the view notifies the client to requests the new
|
||||
* context for the one match identified by the given dataIndex.
|
||||
* If not in dynamic mode the the needed context will be gathered from the
|
||||
* already present results in results.data.
|
||||
*/
|
||||
inspect(client, results, dataIndex, type) {
|
||||
// initialize context modal
|
||||
this.getHTMLElements([
|
||||
['#context-modal', true],
|
||||
'#context-results',
|
||||
'#create-inspect-menu',
|
||||
'#create-from-inspect',
|
||||
]);
|
||||
// Clear fields from old data on every new inspect() call.
|
||||
this.contextId = dataIndex[0];
|
||||
this.contextResults.innerHTML = '';
|
||||
// Open modal.
|
||||
this.contextModal.open();
|
||||
this.contextResults.insertAdjacentHTML('afterbegin', `
|
||||
<div class="progress">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
`);
|
||||
if (client.dynamicMode) {
|
||||
// Notify Client to get results from server.
|
||||
this.notifyClient('get-results', {resultsType: 'inspect-results',
|
||||
dataIndexes: dataIndex,
|
||||
resultsList: this});
|
||||
} else {
|
||||
// Gather results data from already present data.
|
||||
results.inspectResultsData.matches = [results.data.matches[dataIndex[0]]];
|
||||
results.inspectResultsData.cpos_ranges = results.data.cpos_ranges;
|
||||
this.showMatchContext(results, client)
|
||||
}
|
||||
// Match nr for user to display derived from data_index.
|
||||
let contextMatchNrElement = document.getElementById("context-match-nr");
|
||||
contextMatchNrElement.textContent = this.contextId + 1;
|
||||
// Add the add button to add this match to sub results with onclick event.
|
||||
let classes = `btn-floating btn waves-effect` +
|
||||
` waves-light corpus-analysis-color.lighten right`
|
||||
let addToSubResultsIdsBtn = document.createElement("a");
|
||||
addToSubResultsIdsBtn.setAttribute("class", classes + ` add`);
|
||||
addToSubResultsIdsBtn.innerHTML = '<i class="material-icons">add</i>';
|
||||
addToSubResultsIdsBtn.onclick= () => {
|
||||
this.addToSubResults(dataIndex[0], client, false)
|
||||
};
|
||||
/**
|
||||
* Checks if the match has or has not been added to sub results yet.
|
||||
* Sets the color and status of the button accordingly.
|
||||
*/
|
||||
if (this.subResultsIndexes[dataIndex[0]]) {
|
||||
this.helperActivateAddBtn(addToSubResultsIdsBtn.firstElementChild);
|
||||
} else if (!this.subResultsIndexes[dataIndex[0]]) {
|
||||
this.helperDeactivateAddBtn(addToSubResultsIdsBtn.firstElementChild);
|
||||
}
|
||||
this.createInspectMenu.innerHTML = '';
|
||||
this.createInspectMenu.appendChild(addToSubResultsIdsBtn);
|
||||
// Hide create menu if not in dynamic mode.
|
||||
if (!client.dynamicMode) {
|
||||
this.createFromInspect.classList.add('hide');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create Element from HTML String. Helper function should be private.
|
||||
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
|
||||
*/
|
||||
HTMLTStrToElement(htmlStr) {
|
||||
let template = document.createElement("template");
|
||||
htmlStr = htmlStr.trim();
|
||||
template.innerHTML = htmlStr;
|
||||
return template.content.firstChild;
|
||||
}
|
||||
/**
|
||||
* Used either as a callback if the client has been notified to get new
|
||||
* results with new full context. Or just directly invoced as a function
|
||||
* with the according input data.
|
||||
*/
|
||||
showMatchContext(results, client) {
|
||||
this.getHTMLElements([
|
||||
'#context-results',
|
||||
'#inspect-display-options-form-expert_mode_inspect',
|
||||
'#inspect-display-options-form-highlight_sentences',
|
||||
'#context-sentences'
|
||||
])
|
||||
let uniqueS = new Set();
|
||||
let uniqueContextS = new Set();
|
||||
let {lc, c, rc} = this.helperCreateCpos(results.inspectResultsData.matches[0]);
|
||||
// Create sentence strings as tokens.
|
||||
let tokenHTMLArray = [];
|
||||
let htmlTokenStr = ``;
|
||||
let tokenHTMlElement;
|
||||
let token;
|
||||
for (let cpos of lc) {
|
||||
if (client.dynamicMode) {
|
||||
token = results.inspectResultsData.cpos_lookup[cpos];
|
||||
// If client is not in dynamic mode use cpos_lookup from results.data
|
||||
} else {
|
||||
token = results.data.cpos_lookup[cpos];
|
||||
}
|
||||
uniqueS.add(token.s)
|
||||
htmlTokenStr = `<span class="token"` +
|
||||
`data-sid="${token.s}"` +
|
||||
`data-cpos="${cpos}">` +
|
||||
`${token.word}` +
|
||||
`</span>`;
|
||||
tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
|
||||
tokenHTMLArray.push(tokenHTMlElement);
|
||||
}
|
||||
for (let cpos of c) {
|
||||
if (client.dynamicMode) {
|
||||
token = results.inspectResultsData.cpos_lookup[cpos];
|
||||
// If client is not in dynamic mode use cpos_lookup from results.data
|
||||
} else {
|
||||
token = results.data.cpos_lookup[cpos];
|
||||
}
|
||||
uniqueContextS.add(token.s);
|
||||
uniqueS.add(token.s);
|
||||
htmlTokenStr = `<span class="token bold light-green"` +
|
||||
`data-sid="${token.s}"` +
|
||||
`data-cpos="${cpos}"` +
|
||||
`style="text-decoration-line: underline;">` +
|
||||
`${token.word}` +
|
||||
`</span>`;
|
||||
tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
|
||||
tokenHTMLArray.push(tokenHTMlElement);
|
||||
}
|
||||
results.inspectResultsData["context_s_ids"] = Array.from(uniqueContextS);
|
||||
for (let cpos of rc) {
|
||||
if (client.dynamicMode) {
|
||||
token = results.inspectResultsData.cpos_lookup[cpos];
|
||||
// If client is not in dynamic mode use cpos_lookup from results.data
|
||||
} else {
|
||||
token = results.data.cpos_lookup[cpos];
|
||||
}
|
||||
uniqueS.add(token.s)
|
||||
htmlTokenStr = `<span class="token"` +
|
||||
`data-sid="${token.s}"` +
|
||||
`data-cpos="${cpos}">` +
|
||||
`${token.word}` +
|
||||
`</span>`;
|
||||
tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
|
||||
tokenHTMLArray.push(tokenHTMlElement);
|
||||
}
|
||||
// Remove loading indeterminate HTML before context is inserted.
|
||||
this.contextResults.innerHTML = '';
|
||||
for (let sId of uniqueS) {
|
||||
let htmlSentence = `<span class="sentence" data-sid="${sId}"></span>`;
|
||||
let sentenceElement = this.HTMLTStrToElement(htmlSentence);
|
||||
for (let tokenElement of tokenHTMLArray) {
|
||||
if (tokenElement.dataset.sid == sId) {
|
||||
sentenceElement.appendChild(tokenElement);
|
||||
sentenceElement.insertAdjacentHTML("beforeend", ` `);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.contextResults.appendChild(sentenceElement);
|
||||
}
|
||||
// Add expert mode switch event for the modal to toggle expert mode.
|
||||
this.inspectDisplayOptionsFormExpertModeInspect.onchange = (event) => {
|
||||
if (event.target.checked) {
|
||||
this.expertModeOn("context-results", results);
|
||||
} else {
|
||||
this.expertModeOff("context-results")
|
||||
}
|
||||
};
|
||||
// Add switch event to toggle Sentence highlighting.
|
||||
this.inspectDisplayOptionsFormHighlightSentences.onchange = (event) => {
|
||||
if (event.target.checked) {
|
||||
this.higlightContextSentences();
|
||||
} else {
|
||||
this.unhighlightContextSentences();
|
||||
}
|
||||
};
|
||||
// Add range event to change nr of context sentences.
|
||||
this.contextSentences.onchange = (event) => {
|
||||
this.changeSentenceContext(event.target.value);
|
||||
}
|
||||
/**
|
||||
* Checks on new modal opening if switches are checked
|
||||
* if switches are checked functions are executed.
|
||||
*/
|
||||
if (this.inspectDisplayOptionsFormExpertModeInspect.checked) {
|
||||
this.expertModeOn("context-results", results);
|
||||
}
|
||||
if (this.inspectDisplayOptionsFormHighlightSentences.checked) {
|
||||
this.higlightContextSentences();
|
||||
}
|
||||
/**
|
||||
* Checks the value of the number of sentences to show on modal opening
|
||||
* sets context sentences accordingly
|
||||
*/
|
||||
this.changeSentenceContext(this.contextSentences.value);
|
||||
}
|
||||
|
||||
// Splits context text into sentences based on spacy sentence split
|
||||
higlightContextSentences() {
|
||||
let sentences = document.getElementById("context-results").getElementsByClassName("sentence");
|
||||
for (let s of sentences) {
|
||||
s.insertAdjacentHTML("beforeend", `<span><br><br></span>`)
|
||||
}
|
||||
}
|
||||
// Reverse operation of above function.
|
||||
unhighlightContextSentences() {
|
||||
let sentences = document.getElementById("context-results").getElementsByClassName("sentence");
|
||||
let br;
|
||||
for (let s of sentences) {
|
||||
br = s.lastChild;
|
||||
br.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Changes how many context sentences in inspect view are shown.
|
||||
changeSentenceContext(sValue, maxSValue=10) {
|
||||
sValue = maxSValue - sValue;
|
||||
// console.log(sValue);
|
||||
let sentences = document.getElementById("context-results").getElementsByClassName("sentence");
|
||||
let array = Array.from(sentences);
|
||||
let toHideArray;
|
||||
let toShowArray;
|
||||
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;
|
||||
}
|
||||
for (let s of toHideArray) {
|
||||
s.classList.add("hide");
|
||||
}
|
||||
for (let s of toShowArray) {
|
||||
s.classList.remove("hide");
|
||||
}
|
||||
}
|
||||
|
||||
// ###### Display options functions changing how results are being displayed ######
|
||||
|
||||
/**
|
||||
* Event function that changes the shown hits per page.
|
||||
* Just alters the resultsList.page property.
|
||||
*/
|
||||
changeHitsPerPage(client, results) {
|
||||
this.page = this.displayOptionsFormResultsPerPage.value;
|
||||
this.update();
|
||||
this.changeContext();
|
||||
this.toggleInspectButtons(client);
|
||||
if (this.displayOptionsFormExpertMode.checked) {
|
||||
this.expertModeOn('query-display', results);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Event function triggered on context select change also if pagination is
|
||||
* clicked.
|
||||
*/
|
||||
changeContext() {
|
||||
let newContextValue = this.displayOptionsFormResultContext.value;
|
||||
let lc = document.querySelectorAll(".left-context");
|
||||
let rc = document.querySelectorAll(".right-context");
|
||||
for (let element of lc) {
|
||||
let arrayLc = Array.from(element.childNodes);
|
||||
for (let element of arrayLc.reverse().slice(newContextValue)) {
|
||||
element.classList.add("hide");
|
||||
}
|
||||
for (let element of arrayLc.slice(0, newContextValue)) {
|
||||
element.classList.remove("hide");
|
||||
}
|
||||
}
|
||||
for (let element of rc) {
|
||||
let arrayRc = Array.from(element.childNodes);
|
||||
for (let element of arrayRc.slice(newContextValue)) {
|
||||
element.classList.add("hide");
|
||||
}
|
||||
for (let element of arrayRc.slice(0, newContextValue)) {
|
||||
element.classList.remove("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ###### Expert view event functions ######
|
||||
|
||||
// Function to create a tooltip for the current hovered token.
|
||||
tooltipEventCreate(event, results) {
|
||||
let token = results.data.cpos_lookup[event.target.dataset.cpos];
|
||||
if (!token) {
|
||||
token = results.inspectResultsData.cpos_lookup[event.target.dataset.cpos];
|
||||
}
|
||||
this.currentTooltipElement = M.Tooltip.init(event.target, {
|
||||
'html': `<table>
|
||||
<tr>
|
||||
<th>Token information</th>
|
||||
<th>Source information</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left-align">
|
||||
Word: ${token.word}<br>
|
||||
Lemma: ${token.lemma}<br>
|
||||
POS: ${token.pos}<br>
|
||||
Simple POS: ${token.simple_pos}<br>
|
||||
NER: ${token.ner}
|
||||
</td>
|
||||
<td class="left-align">
|
||||
Title: ${results.data.text_lookup[token.text].title}
|
||||
<br>
|
||||
Author: ${results.data.text_lookup[token.text].author}
|
||||
<br>
|
||||
Publishing year: ${results.data.text_lookup[token.text].publishing_year}
|
||||
</td>
|
||||
</tr>
|
||||
</table>`
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Function to destroy the current Tooltip for the current hovered tooltip
|
||||
* on mouse leave
|
||||
*/
|
||||
tooltipEventDestroy(event) {
|
||||
this.currentTooltipElement.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the expert mode on for all tokens in the DOM element identified by
|
||||
* its htmlID.
|
||||
*/
|
||||
expertModeOn(htmlId, results) {
|
||||
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, results);
|
||||
tokenElement.onmouseout = (event) => this.tooltipEventDestroy(event);
|
||||
this.eventTokens[htmlId].push(tokenElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the expert mode off for all tokens in the DOM element identified by
|
||||
* its htmlID.
|
||||
*/
|
||||
expertModeOff(htmlId) {
|
||||
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, client, imported=false) {
|
||||
// Gather values from item.
|
||||
let values = item.values();
|
||||
let {lc, c, rc} = this.helperCreateCpos(values)
|
||||
// Get infos for full match row.
|
||||
let matchRowElement = document.createElement("tr");
|
||||
matchRowElement.setAttribute("data-index", values.index)
|
||||
let lcCellElement = document.createElement("td");
|
||||
lcCellElement.classList.add("left-context");
|
||||
matchRowElement.appendChild(lcCellElement);
|
||||
for (let cpos of lc) {
|
||||
let token = chunk.cpos_lookup[cpos];
|
||||
lcCellElement.insertAdjacentHTML("beforeend",
|
||||
`<span class="token" data-cpos="${cpos}">${token.word} </span>`);
|
||||
}
|
||||
|
||||
// Get infos for hit of match and set actions.
|
||||
let textTitles = new Set();
|
||||
let aCellElement = document.createElement("td");
|
||||
aCellElement.classList.add("actions");
|
||||
let cCellElement = document.createElement("td");
|
||||
cCellElement.classList.add("match-hit");
|
||||
let textTitlesCellElement = document.createElement("td");
|
||||
textTitlesCellElement.classList.add("titles");
|
||||
let matchNrElement = document.createElement("td");
|
||||
matchNrElement.classList.add("match-nr");
|
||||
matchRowElement.appendChild(cCellElement);
|
||||
matchRowElement.appendChild(aCellElement);
|
||||
for (let cpos of c) {
|
||||
let token = chunk.cpos_lookup[cpos];
|
||||
cCellElement.insertAdjacentHTML("beforeend",
|
||||
`<span class="token" data-cpos="${cpos}">${token.word} </span>`);
|
||||
// Get text titles of every hit cpos token.
|
||||
textTitles.add(chunk.text_lookup[token.text].title);
|
||||
}
|
||||
// Add some interaction buttons.
|
||||
let css = `margin-right: 5px; margin-bottom: 5px;`
|
||||
let classes = `btn-floating btn waves-effect` +
|
||||
` waves-light corpus-analysis-color.lighten`
|
||||
// Add inspect button to trigger inspect view with more context.
|
||||
let inspectBtn = document.createElement("a");
|
||||
inspectBtn.setAttribute("style", css);
|
||||
inspectBtn.setAttribute("class", classes + ` disabled inspect`
|
||||
);
|
||||
inspectBtn.innerHTML = '<i class="material-icons inspect-btn">search</i>';
|
||||
// Add btn to be able add matches to sub-results.
|
||||
let addToSubResultsBtn = document.createElement("a");
|
||||
addToSubResultsBtn.setAttribute("style", css);
|
||||
addToSubResultsBtn.setAttribute("class", classes + ` add`);
|
||||
addToSubResultsBtn.innerHTML = '<i class="material-icons add-btn">add</i>';
|
||||
if (client.dynamicMode || client.fullContext) {
|
||||
aCellElement.appendChild(inspectBtn);
|
||||
}
|
||||
if (client.dynamicMode) {
|
||||
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
|
||||
let rcCellElement = document.createElement("td");
|
||||
rcCellElement.classList.add("right-context");
|
||||
matchRowElement.appendChild(rcCellElement);
|
||||
for (let cpos of rc) {
|
||||
let token = chunk.cpos_lookup[cpos];
|
||||
rcCellElement.insertAdjacentHTML("beforeend",
|
||||
`<span class="token" data-cpos="${cpos}">${token.word} </span>`);
|
||||
}
|
||||
return matchRowElement
|
||||
}
|
||||
/**
|
||||
* Creates the HTML table code for the metadata view in the corpus analysis
|
||||
* interface
|
||||
*/
|
||||
createMetaDataForModal(metaDataObject) {
|
||||
let html = `<div class="col s12">
|
||||
<table class="highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Meta Data Description</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>`
|
||||
for (let [outerKey, outerValue] of Object.entries(metaDataObject)) {
|
||||
// Use more descriptive names.
|
||||
if (outerKey === 'corpus_all_texts') {
|
||||
let tmpName = 'All texts in this corpus';
|
||||
html += `<tr>
|
||||
<td style="text-transform: uppercase;">${tmpName.replace(/_/g, " ")}
|
||||
</td>`
|
||||
} else if (outerKey === 'text_lookup') {
|
||||
let tmpName = 'Texts where the query resulted in matches'
|
||||
html += `<tr>
|
||||
<td style="text-transform: uppercase;">${tmpName.replace(/_/g, " ")}
|
||||
</td>`
|
||||
} else {
|
||||
html += `<tr>
|
||||
<td style="text-transform: uppercase;">${outerKey.replace(/_/g, " ")}
|
||||
</td>`
|
||||
}
|
||||
if (outerKey === "corpus_all_texts" || outerKey === "text_lookup") {
|
||||
html += `<td>
|
||||
<ul class="collapsible">`
|
||||
for (let [innerKey, innerValue] of Object.entries(outerValue)) {
|
||||
html += `<li class="text-metadata"
|
||||
data-metadata-key="${outerKey}"
|
||||
data-text-key="${innerKey}">
|
||||
<div class="collapsible-header"
|
||||
data-metadata-key="${outerKey}"
|
||||
data-text-key="${innerKey}">
|
||||
<i class="material-icons"
|
||||
data-metadata-key="${outerKey}"
|
||||
data-text-key="${innerKey}">info_outline</i>
|
||||
${innerValue['author']} - ${innerValue['publishing_year']} -
|
||||
${innerValue['title']}
|
||||
</div>
|
||||
<div class="collapsible-body">
|
||||
<span>
|
||||
<ul id="bibliographic-data-${outerKey}-${innerKey}"
|
||||
style="column-count: 2;">
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</li>`
|
||||
}
|
||||
html += `</ul>
|
||||
</td>`
|
||||
} else {
|
||||
html += `<td>${outerValue}</td>`
|
||||
}
|
||||
html += `</tr>`
|
||||
}
|
||||
html += `</tbody>
|
||||
</table>`
|
||||
return html
|
||||
}
|
||||
/**
|
||||
* Creates the text details for the texts shown in the corpus analysis
|
||||
* metadata modal table.
|
||||
*/
|
||||
createTextDetails(metaData) {
|
||||
let metadataKey = event.target.dataset.metadataKey;
|
||||
let textKey = event.target.dataset.textKey;
|
||||
let textData = metaData[metadataKey][textKey];
|
||||
let bibliographicData = document.querySelector(`#bibliographic-data-${metadataKey}-${textKey}`);
|
||||
bibliographicData.textContent = '';
|
||||
for (let [key, value] of Object.entries(textData)) {
|
||||
bibliographicData.insertAdjacentHTML("afterbegin",
|
||||
`
|
||||
<li>
|
||||
<span style="text-transform: capitalize;">${key}:</span>
|
||||
${value}
|
||||
</li>
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Export classes.
|
||||
export {
|
||||
ViewEventListener,
|
||||
ResultsList
|
||||
};
|
@ -1,244 +0,0 @@
|
||||
/**
|
||||
* This file contains all the callbacks triggered by the notificationListener.
|
||||
* Also general callbacks are defined which are doing some hiding/disabling and
|
||||
* showing/enabling of common elements when data is being transmitted or not.
|
||||
*/
|
||||
|
||||
// Callback to disable some elements for the user when the client is busy.
|
||||
function disableElementsGeneralCallback(resultsList, detail) {
|
||||
if (detail.client.isBusy) {
|
||||
resultsList.fullResultsCreate.classList.toggle('disabled', true);
|
||||
resultsList.subResultsCreate.classList.toggle('disabled', true);
|
||||
resultsList.toggleInspectButtons(detail.client);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback to enable some elements for the user when the client is not busy.
|
||||
function enableElementsGeneralCallback(resultsList, detail) {
|
||||
if (!detail.client.isBusy) {
|
||||
resultsList.fullResultsCreate.classList.toggle('disabled', false);
|
||||
if (Object.keys(resultsList.subResultsIndexes).length > 0) {
|
||||
resultsList.subResultsCreate.classList.toggle('disabled', false);
|
||||
}
|
||||
resultsList.toggleInspectButtons(detail.client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback opening the loading modal when the client is connecting to the
|
||||
* CQP server.
|
||||
*/
|
||||
function connectingCallback(resultsList, detail) {
|
||||
resultsList.getHTMLElements(['#analysis-init-modal']);
|
||||
resultsList.analysisInitModal = M.Modal.init(resultsList.analysisInitModal,
|
||||
{dismissible: false});
|
||||
resultsList.analysisInitModal.open();
|
||||
}
|
||||
|
||||
// Callback that closes the loading modal from above.
|
||||
function connectedCallback(resultsList, detail) {
|
||||
/**
|
||||
* In the past this closed the init modal. But the init modal is now being
|
||||
* closed when the meta data has also been recieved. See below.
|
||||
*/
|
||||
}
|
||||
|
||||
// Callback that closes the loading modal from above.
|
||||
function metaDataRecievedCallback(resultsList, detail) {
|
||||
resultsList.analysisInitModal.close();
|
||||
}
|
||||
|
||||
// Callback that shows the user some feedback if the client raised an error.
|
||||
function clientFailedCallback(resultsList, detail) {
|
||||
resultsList.getHTMLElements([
|
||||
'#analysis-init-progress',
|
||||
'#analysis-init-error',
|
||||
'#user-feedback',
|
||||
]);
|
||||
if (detail.raiseModalFeedback) {
|
||||
resultsList.analysisInitModal.open();
|
||||
resultsList.analysisInitProgress.classList.toggle('hide');
|
||||
resultsList.analysisInitError.classList.toggle('hide');
|
||||
resultsList.analysisInitError.textContent = detail.msg;
|
||||
} else {
|
||||
nopaque.appClient.flash(detail.msg, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// Callback doing some preperation work if a query has been issued by the user.
|
||||
function queryDataPreparingCallback(resultsList, detail) {
|
||||
// remove all items from resultsList, like from the query issued before
|
||||
resultsList.clear()
|
||||
// get needed HTML Elements
|
||||
let results = detail.results;
|
||||
resultsList.getHTMLElements([
|
||||
'#interactions-menu',
|
||||
'#recieved-match-count',
|
||||
'#total-match-count',
|
||||
'#text-lookup-titles',
|
||||
'#text-lookup-count',
|
||||
'#query-results-user-feedback',
|
||||
'#query-progress-bar',
|
||||
'#query-results-create',
|
||||
'#sub-results-indexes-display',
|
||||
'#nr-marked-matches',
|
||||
]);
|
||||
// show or enable some things for the user
|
||||
resultsList.interactionsMenu.classList.toggle('hide', false);
|
||||
resultsList.queryResultsUserFeedback.classList.toggle('hide', false);
|
||||
resultsList.queryProgressBar.classList.toggle('hide', false);
|
||||
resultsList.showCorpusFiles.classList.toggle('disabled', true);
|
||||
/**
|
||||
* Set some initial values for the user feedback
|
||||
* or reset values for new issued query
|
||||
*/
|
||||
resultsList.recievedMatchCount.textContent = 0;
|
||||
resultsList.totalMatchCount.textContent = results.data.match_count;
|
||||
resultsList.textLookupCount.textContent = 0;
|
||||
resultsList.nrMarkedMatches.textContent = 0;
|
||||
resultsList.subResultsIndexesDisplay.textContent = '';
|
||||
resultsList.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handling the incoming results of an issued query. It renders
|
||||
* the incoming matches using the resultsList for the user.
|
||||
*/
|
||||
function queryDataRecievingCallback(resultsList, detail) {
|
||||
// load the data into the resultsList and show them to the user
|
||||
let results = detail.results;
|
||||
let client = detail.client;
|
||||
let start = detail.dataLength;
|
||||
let resultItems = [];
|
||||
for (let [index, match] of Object.entries(results.data.matches).slice(start)) {
|
||||
resultItems.push({ ...match, ...{ 'index': parseInt(index) } });
|
||||
}
|
||||
if (client.dynamicMode) {
|
||||
resultsList.add(resultItems, (items) => {
|
||||
for (let item of items) {
|
||||
item.elm = resultsList.createResultRowElement(item,
|
||||
results.data,
|
||||
client);
|
||||
}
|
||||
});
|
||||
// update user feedback about query status
|
||||
resultsList.recievedMatchCount.textContent = results.data.matches.length;
|
||||
resultsList.queryProgressBar.firstElementChild.style.width = `${client.requestQueryProgress}%`;
|
||||
resultsList.textLookupCount.textContent = `${Object.keys(results.data.text_lookup).length}`;
|
||||
// updating table on finished item creation callback via createResultRowElement
|
||||
resultsList.update();
|
||||
resultsList.changeHitsPerPage(client, results);
|
||||
resultsList.changeContext();
|
||||
//activate expertMode of switch is checked
|
||||
if (resultsList.displayOptionsFormExpertMode.checked) {
|
||||
resultsList.expertModeOn('query-display', results);
|
||||
}
|
||||
} else if (!client.dynamicMode) {
|
||||
resultsList.add(resultItems, (items) => {
|
||||
for (let item of items) {
|
||||
item.elm = resultsList.createResultRowElement(item,
|
||||
results.data,
|
||||
client,
|
||||
true);
|
||||
}
|
||||
});
|
||||
// update user feedback about query status
|
||||
resultsList.recievedMatchCount.textContent = results.data.matches.length;
|
||||
resultsList.queryProgressBar.firstElementChild.style.width = `${client.requestQueryProgress}%`;
|
||||
resultsList.textLookupCount.textContent = `${Object.keys(results.data.text_lookup).length}`;
|
||||
// updating table on finished item creation callback via createResultRowElement
|
||||
resultsList.update();
|
||||
resultsList.changeHitsPerPage(client, results);
|
||||
resultsList.changeContext();
|
||||
}
|
||||
}
|
||||
|
||||
// Callback that is executed when all results from an issued query have been recieved
|
||||
function queryDataRecievedCallback(resultsList, detail) {
|
||||
// hide or disable some things for the user
|
||||
resultsList.queryResultsUserFeedback.classList.toggle('hide', true);
|
||||
resultsList.queryProgressBar.classList.toggle('hide', true);
|
||||
// reset bar progress for next query
|
||||
resultsList.queryProgressBar.firstElementChild.style.width = '0%';
|
||||
resultsList.showCorpusFiles.classList.toggle('disabled', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is handling incoming results data. Results data is needed for
|
||||
* the export and download of the data.
|
||||
*/
|
||||
function resultsDataRecievingCallback(resultsList, detail) {
|
||||
resultsList.getHTMLElements([
|
||||
'#full-results-progress-bar',
|
||||
'#sub-results-progress-bar',
|
||||
]);
|
||||
// Disable the full context switch when results are being recieved.
|
||||
resultsList.exportFullInspectContext.setAttribute('disabled', '');
|
||||
if (detail.type === 'full-results' && detail.progress) {
|
||||
resultsList.fullResultsProgressBar.firstElementChild.style.width = `${detail.progress}%`;
|
||||
resultsList.fullResultsProgressBar.classList.toggle('hide', false);
|
||||
} else if (detail.type === 'sub-results' && detail.progress) {
|
||||
resultsList.subResultsProgressBar.firstElementChild.style.width = `${detail.progress}%`;
|
||||
resultsList.subResultsProgressBar.classList.toggle('hide', false);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Callback is executed when all results data has been recieved.
|
||||
* Reactivates the resutls create buttons etc.
|
||||
*/
|
||||
function resultsDataRecievedCallback(resultsList, detail) {
|
||||
// create strings for create buttons depending on type
|
||||
const handleType = (keyPrefix, text) => {
|
||||
// Enable the full context switch when results have been recieved
|
||||
resultsList.exportFullInspectContext.removeAttribute('disabled', '');
|
||||
// hides the create element after results have been recieved and reset it
|
||||
resultsList[`${keyPrefix}Create`].classList.toggle('hide');
|
||||
resultsList[`${keyPrefix}Create`].textContent = `Create ${text}`;
|
||||
resultsList[`${keyPrefix}Create`].insertAdjacentHTML('beforeend',
|
||||
`<i class="material-icons left">build</i>`);
|
||||
// show and highlight export button
|
||||
resultsList[`${keyPrefix}Export`].classList.toggle('hide', false);
|
||||
resultsList[`${keyPrefix}Export`].classList.toggle('pulse', true);
|
||||
setTimeout(() => {
|
||||
resultsList[`${keyPrefix}Export`].classList.toggle('pulse', false);
|
||||
clearTimeout();
|
||||
}, 3000)
|
||||
}
|
||||
if (detail.type === 'full-results') {
|
||||
handleType('fullResults', 'Results');
|
||||
if (detail.fullContext) {
|
||||
resultsList.fullResultsProgressBar.firstElementChild.style.width = `0%`;
|
||||
resultsList.fullResultsProgressBar.classList.toggle('hide', true);
|
||||
}
|
||||
} else if (detail.type ==='sub-results') {
|
||||
handleType('subResults', 'Sub-Results');
|
||||
if (detail.fullContext) {
|
||||
resultsList.subResultsProgressBar.firstElementChild.style.width = `0%`;
|
||||
resultsList.subResultsProgressBar.classList.toggle('hide', true);
|
||||
}
|
||||
} else if (detail.type ==='inspect-results') {
|
||||
if (Object.keys(resultsList.subResultsIndexes).length === 0) {
|
||||
/**
|
||||
* Prevent create sub results button from being activated if it is disabled
|
||||
* and no matches have been marked by the user for sub results creation.
|
||||
*/
|
||||
resultsList.subResultsCreate.classList.toggle('disabled', true);
|
||||
}
|
||||
resultsList.showMatchContext(detail.results, detail.client);
|
||||
}
|
||||
}
|
||||
|
||||
// export the callbacks
|
||||
export {
|
||||
connectingCallback,
|
||||
connectedCallback,
|
||||
metaDataRecievedCallback,
|
||||
clientFailedCallback,
|
||||
queryDataPreparingCallback,
|
||||
queryDataRecievingCallback,
|
||||
queryDataRecievedCallback,
|
||||
resultsDataRecievingCallback,
|
||||
resultsDataRecievedCallback,
|
||||
disableElementsGeneralCallback,
|
||||
enableElementsGeneralCallback,
|
||||
};
|
@ -1,378 +0,0 @@
|
||||
/**
|
||||
* 1.)
|
||||
* This file contains the listener function that will be assigned to the
|
||||
* corpus_analysis ResultsView. The listener is listening for the notification
|
||||
* event which is being dispatched by the corpus_analysis Client. The
|
||||
* notification Event triggers the listener which will call different
|
||||
* callback functions depending on the detail information of the notification
|
||||
* event.
|
||||
* 2.)
|
||||
* This file also contains vanilla javascript Event listeners which are
|
||||
* listening for button clicks etc. the user is doing for page interaction.
|
||||
* They will be registered the same way as teh listeners above.
|
||||
*/
|
||||
|
||||
import {
|
||||
connectingCallback,
|
||||
connectedCallback,
|
||||
metaDataRecievedCallback,
|
||||
clientFailedCallback,
|
||||
queryDataPreparingCallback,
|
||||
queryDataRecievingCallback,
|
||||
queryDataRecievedCallback,
|
||||
resultsDataRecievingCallback,
|
||||
resultsDataRecievedCallback,
|
||||
disableElementsGeneralCallback,
|
||||
enableElementsGeneralCallback,
|
||||
} from './callbacks.js';
|
||||
// Import the script that implements a spinner animation for buttons.
|
||||
import {
|
||||
loadingSpinnerHTML,
|
||||
} from './spinner.js';
|
||||
|
||||
|
||||
/**
|
||||
* The Listener listening for the notification event 'notify-view' dispatched
|
||||
* by the client and execeutes callbacks accordingly.
|
||||
*/
|
||||
function recieveClientNotification(eventType, resultsList) {
|
||||
document.addEventListener(eventType, (event) => {
|
||||
let caseIdentifier = event.detail.caseIdentifier;
|
||||
switch (caseIdentifier) {
|
||||
case 'client-failed':
|
||||
console.error('View recieved notification:', caseIdentifier);
|
||||
// execute callbacks
|
||||
clientFailedCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'connecting':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
connectingCallback(resultsList, event.detail);
|
||||
// execute callbacks
|
||||
break;
|
||||
case 'connected':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
connectedCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'meta-data-recieving':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
break;
|
||||
case 'meta-data-recieved':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// execute
|
||||
metaDataRecievedCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'query-data-prepareing':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// Hide all download buttons
|
||||
resultsList.fullResultsExport.classList.toggle('hide', true);
|
||||
resultsList.subResultsExport.classList.toggle('hide', true);
|
||||
// Show all create buttons
|
||||
resultsList.fullResultsCreate.classList.toggle('hide', false);
|
||||
resultsList.subResultsCreate.classList.toggle('hide', false);
|
||||
// execute callbacks
|
||||
disableElementsGeneralCallback(resultsList, event.detail);
|
||||
queryDataPreparingCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'query-data-recieving':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// execute callbacks
|
||||
queryDataRecievingCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'query-data-recieved':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// execute callbacks
|
||||
queryDataRecievedCallback(resultsList, event.detail);
|
||||
enableElementsGeneralCallback(resultsList, event.detail);
|
||||
// create sub-results is disabled per default until matches have been added
|
||||
resultsList.subResultsCreate.classList.toggle('disabled', true);
|
||||
break;
|
||||
case 'results-data-recieving':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// execute callbacks
|
||||
disableElementsGeneralCallback(resultsList, event.detail);
|
||||
resultsDataRecievingCallback(resultsList, event.detail);
|
||||
break;
|
||||
case 'results-data-recieved':
|
||||
console.info('View recieved notification:', caseIdentifier);
|
||||
// execute callbacks
|
||||
console.info(event.detail);
|
||||
resultsDataRecievedCallback(resultsList, event.detail);
|
||||
enableElementsGeneralCallback(resultsList, event.detail);
|
||||
break;
|
||||
default:
|
||||
console.error('Recieved unkown notification case identifier from Client');
|
||||
// do something to not crash the analysis session?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This are some vanilla javascript Event listeners which are listening
|
||||
* for button clicks etc. the user is doing to interact with the page.
|
||||
* They will be registered the same way as the listeners above.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The following listener handles what functions are called when the user
|
||||
* does use the page navigation to navigate to a new page.
|
||||
*/
|
||||
function pageNavigation(resultsList, results, client) {
|
||||
for (let element of resultsList.pagination) {
|
||||
element.addEventListener("click", (event) => {
|
||||
// Shows match context according to the user picked value on a new page.
|
||||
resultsList.changeContext();
|
||||
// De- or activates expertMode on new page depending on switch value.
|
||||
if (resultsList.displayOptionsFormExpertMode.checked) {
|
||||
resultsList.expertModeOn('query-display', results);
|
||||
} else {
|
||||
resultsList.expertModeOff('query-display');
|
||||
}
|
||||
// Activates inspect buttons on new page if client is not busy.
|
||||
resultsList.toggleInspectButtons(client);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following event Listener handles the expert mode switch for the list.
|
||||
*/
|
||||
function expertModeSwitch(resultsList, results) {
|
||||
resultsList.displayOptionsFormExpertMode.onchange = (event) => {
|
||||
if (event.target.checked) {
|
||||
resultsList.expertModeOn('query-display', results);
|
||||
} else {
|
||||
resultsList.expertModeOff('query-display');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The following event Listener handles the add-btn and the inspect-btn
|
||||
* onclick events via bubbleing.
|
||||
*/
|
||||
function actionButtons(resultsList, results, client) {
|
||||
resultsList.queryResultsTable.addEventListener('click', (event) => {
|
||||
let dataIndex;
|
||||
if (event.target.classList.contains('inspect-btn')) {
|
||||
dataIndex = parseInt(event.target.closest('tr').dataset.index);
|
||||
resultsList.inspect(client, results, [dataIndex], 'inspect');
|
||||
} else if (event.target.classList.contains('add-btn')) {
|
||||
dataIndex = parseInt(event.target.closest('tr').dataset.index);
|
||||
resultsList.addToSubResults(dataIndex, client);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Following event listeners handle the change of Context size per match and
|
||||
* the number of matches shown per page.
|
||||
*/
|
||||
function displayOptions(resultsList, results, client) {
|
||||
resultsList.displayOptionsFormResultsPerPage.onchange = (event) => {
|
||||
resultsList.changeHitsPerPage(client, results);
|
||||
};
|
||||
resultsList.displayOptionsFormResultContext.onchange = (event) => {
|
||||
resultsList.changeContext();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The following event listener handles the show metadata button and its
|
||||
* functionality.
|
||||
*/
|
||||
function showMetaData(resultsList, results) {
|
||||
resultsList.showMetaData.onclick = () => {
|
||||
resultsList.metaDataModalContent.textContent = '';
|
||||
let table = resultsList.createMetaDataForModal(results.metaData);
|
||||
resultsList.metaDataModalContent.insertAdjacentHTML('afterbegin', table);
|
||||
resultsList.metaDataModal.open();
|
||||
let collapsibles = resultsList.metaDataModalContent.querySelectorAll(".text-metadata");
|
||||
for (let collapsible of collapsibles) {
|
||||
collapsible.onclick = () => {
|
||||
let elems = resultsList.metaDataModalContent.querySelectorAll('.collapsible');
|
||||
let instances = M.Collapsible.init(elems, {accordion: false});
|
||||
resultsList.createTextDetails(results.metaData);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The following event listener handles the button showing infos about matches
|
||||
* and their corresponding corpus files
|
||||
*/
|
||||
|
||||
function showCorpusFiles(resultsList, results) {
|
||||
resultsList.showCorpusFiles.onclick = () => {
|
||||
resultsList.showCorpusFilesModalContent.innerHTML = '';
|
||||
let htmlString = `
|
||||
<div id="corpus-file-table">
|
||||
<ul class="pagination paginationTop"></ul>
|
||||
<table class="responsive-table highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sort" data-sort="title">Title</th>
|
||||
<th class="sort" data-sort="year">Year</th>
|
||||
<th class="sort" data-sort="match-count">Match count in this text</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
`
|
||||
for (let [key, value] of Object.entries(results.data.text_lookup)) {
|
||||
htmlString += `
|
||||
<tr>
|
||||
<td class="title">${value.title}</td>
|
||||
<td class="year">${value.publishing_year}</td>
|
||||
<td class="match-count">${value.match_count}</td>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
htmlString += `
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="pagination paginationBottom"></ul>
|
||||
</div>
|
||||
`
|
||||
resultsList.showCorpusFilesModalContent.insertAdjacentHTML('afterbegin', htmlString);
|
||||
resultsList.showCorpusFilesModal.open();
|
||||
let options = {
|
||||
page: 10,
|
||||
pagination: [{
|
||||
name: "paginationTop",
|
||||
paginationClass: "paginationTop",
|
||||
innerWindow: 8,
|
||||
outerWindow: 1
|
||||
}, {
|
||||
paginationClass: "paginationBottom",
|
||||
innerWindow: 8,
|
||||
outerWindow: 1
|
||||
}],
|
||||
valueNames: ["title", "year", "match-count"],
|
||||
};
|
||||
let corpusFileTable = new List('corpus-file-table', options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if resultsList.exportFullInspectContext switch is changed.
|
||||
* If it has been changed reset all Download buttons.
|
||||
*/
|
||||
function exportFullContextSwitch(resultsList) {
|
||||
resultsList.exportFullInspectContext.onchange = (event) => {
|
||||
// Hide all download buttons.
|
||||
resultsList.fullResultsExport.classList.toggle('hide', true);
|
||||
resultsList.subResultsExport.classList.toggle('hide', true);
|
||||
// Show result create buttons.
|
||||
resultsList.fullResultsCreate.classList.toggle('hide', false);
|
||||
resultsList.subResultsCreate.classList.toggle('hide', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following event listeners are handeling the data export.
|
||||
* 1. Create full-results
|
||||
* 2. Create sub-results
|
||||
* 3. Download full-results
|
||||
* 4. Download sub-results
|
||||
* 5. Download single inspect-results
|
||||
*/
|
||||
|
||||
// 1. Add events for full-results create
|
||||
function createFullResults(resultsList, results) {
|
||||
resultsList.fullResultsCreate.onclick = (event) => {
|
||||
resultsList.fullResultsCreate.querySelector('i').classList.toggle('hide');
|
||||
resultsList.fullResultsCreate.textContent = 'Creating...';
|
||||
resultsList.fullResultsCreate.insertAdjacentHTML('afterbegin',
|
||||
loadingSpinnerHTML);
|
||||
// .keys() is for a zero based array. I think...
|
||||
let dataIndexes = [...Array(results.data.match_count).keys()];
|
||||
// Empty fullResultsData so that no previous data is used.
|
||||
results.fullResultsData.init();
|
||||
resultsList.notifyClient('get-results', {resultsType: 'full-results',
|
||||
dataIndexes: dataIndexes,
|
||||
resultsList: resultsList,});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add events for sub-results create
|
||||
function createSubResults(resultsList, results) {
|
||||
resultsList.subResultsCreate.onclick = (event) => {
|
||||
let dataIndexes = [];
|
||||
Object.keys(resultsList.subResultsIndexes).forEach((id) => {
|
||||
dataIndexes.push(id);
|
||||
});
|
||||
resultsList.subResultsCreate.querySelector('i').classList.toggle('hide');
|
||||
resultsList.subResultsCreate.textContent = 'Creating...';
|
||||
resultsList.subResultsCreate.insertAdjacentHTML('afterbegin',
|
||||
loadingSpinnerHTML);
|
||||
// Empty subResultsData so that no previous data is used.
|
||||
results.subResultsData.init();
|
||||
resultsList.notifyClient('get-results', {resultsType: 'sub-results',
|
||||
dataIndexes: dataIndexes,
|
||||
resultsList: resultsList,});
|
||||
}
|
||||
}
|
||||
// 3. Open download modal when full results export button is pressed
|
||||
function exportFullResults(resultsList, results) {
|
||||
resultsList.fullResultsExport.onclick = (event) => {
|
||||
resultsList.queryResultsDownloadModal.open();
|
||||
// add onclick to download JSON button and download the file
|
||||
resultsList.downloadResultsJson.onclick = (event) => {
|
||||
let suffix = 'full-results'
|
||||
if (resultsList.exportFullInspectContext.checked) {
|
||||
suffix += '_full-context';
|
||||
}
|
||||
let filename = results.fullResultsData.createDownloadFilename(suffix);
|
||||
results.fullResultsData.addData(results.metaData);
|
||||
results.fullResultsData.downloadJSONRessource(filename,
|
||||
results.fullResultsData,
|
||||
resultsList.downloadResultsJson)};
|
||||
}
|
||||
}
|
||||
// 4. Open download modal when sub results export button is pressed
|
||||
function exportSubResults(resultsList, results) {
|
||||
resultsList.subResultsExport.onclick = (event) => {
|
||||
resultsList.queryResultsDownloadModal.open();
|
||||
// add onclick to download JSON button and download the file
|
||||
resultsList.downloadResultsJson.onclick = (event) => {
|
||||
let suffix = 'sub-results'
|
||||
if (resultsList.exportFullInspectContext.checked) {
|
||||
suffix += '_full-context';
|
||||
}
|
||||
let filename = results.subResultsData.createDownloadFilename(suffix);
|
||||
results.subResultsData.addData(results.metaData);
|
||||
results.subResultsData.downloadJSONRessource(filename,
|
||||
results.subResultsData,
|
||||
resultsList.downloadResultsJson)};
|
||||
}
|
||||
}
|
||||
// 5. Open download modal when inspect-results-export button is pressed
|
||||
function exportSingleMatch(resultsList, results) {
|
||||
resultsList.inspectResultsExport.onclick = (event) => {
|
||||
resultsList.queryResultsDownloadModal.open();
|
||||
// add onclick to download JSON button and download the file
|
||||
resultsList.downloadResultsJson.onclick = (event) => {
|
||||
let filename = results.subResultsData.createDownloadFilename('inspect-results_full-context');
|
||||
results.subResultsData.addData(results.metaData);
|
||||
results.subResultsData.downloadJSONRessource(filename,
|
||||
results.inspectResultsData,
|
||||
resultsList.downloadResultsJson)};
|
||||
}
|
||||
}
|
||||
|
||||
// export listeners
|
||||
export {
|
||||
recieveClientNotification,
|
||||
pageNavigation,
|
||||
expertModeSwitch,
|
||||
actionButtons,
|
||||
displayOptions,
|
||||
showMetaData,
|
||||
showCorpusFiles,
|
||||
exportFullContextSwitch,
|
||||
createFullResults,
|
||||
createSubResults,
|
||||
exportFullResults,
|
||||
exportSubResults,
|
||||
exportSingleMatch,
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Function to show a scroll to top button if the user has scrolled down
|
||||
* 250 pixels from the with scrollToElementSelector specified Element.
|
||||
*/
|
||||
function scrollToTop(scrollToElementSelector, triggerElementSelector) {
|
||||
let scrollToThis = document.querySelector(scrollToElementSelector);
|
||||
let scrolltoTopTrigger = document.querySelector(triggerElementSelector);
|
||||
window.addEventListener('scroll', (event) => {
|
||||
if (pageYOffset > 250) {
|
||||
scrolltoTopTrigger.classList.toggle('hide', false);
|
||||
} else {
|
||||
scrolltoTopTrigger.classList.toggle('hide', true);
|
||||
}
|
||||
});
|
||||
scrolltoTopTrigger.onclick = () => {
|
||||
scrollToThis.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Export function.
|
||||
export {
|
||||
scrollToTop
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
// loading spinner animation HTML
|
||||
const loadingSpinnerHTML = `
|
||||
<div class="preloader-wrapper button-icon-spinner small active">
|
||||
<div class="spinner-layer spinner-green-only">
|
||||
<div class="circle-clipper left">
|
||||
<div class="circle"></div>
|
||||
</div><div class="gap-patch">
|
||||
<div class="circle"></div>
|
||||
</div><div class="circle-clipper right">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Export const.
|
||||
export {
|
||||
loadingSpinnerHTML
|
||||
};
|
7
app/static/js/socket.io.min.js
vendored
7
app/static/js/socket.io.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,33 +1,31 @@
|
||||
{% if current_user.setting_dark_mode %}
|
||||
<script src="{{ url_for('static', filename='js/darkreader.js') }}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/darkreader/4.9.40/darkreader.min.js" integrity="sha512-0Jbi9gWSyU5SvNS16za0aILl6l+MgM8N+TGlZxy4qPQEzqKoU9egh4h56Kz0xp2R+ZFPQMfeDn26Gh6cqu2WAg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fast-json-patch/3.1.0/fast-json-patch.min.js" integrity="sha512-KrvLlmKBiDoTa0Fke92aFoEv4xS0+cuYGP27nt39w0yLZWvVOhArmZ29uuOe3uOOBcbnkpvnLhkvYcYjahSOwg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/list.js/2.3.1/list.min.js" integrity="sha512-93wYgwrIFL+b+P3RvYxi/WUFRXXUDSLCT2JQk9zhVGXuS2mHl2axj6d+R6pP+gcU5isMHRj1u0oYE/mWyt/RjA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.0/socket.io.js" integrity="sha512-nYuHvSAhY5lFZ4ixSViOwsEKFvlxHMU2NHts1ILuJgOS6ptUmAGt/0i5czIgMOahKZ6JN84YFDA+mCdky7dD8A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="{{ url_for('static', filename='js/App.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/JobStatusNotifier.js') }}"></script>
|
||||
{% assets filters='rjsmin', output="js/RessourceDisplays.min.bundle.js",
|
||||
"js/RessourceDisplays/RessourceDisplay.js",
|
||||
"js/RessourceDisplays/CorpusDisplay.js",
|
||||
"js/RessourceDisplays/JobDisplay.js" %}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
{% assets filters='rjsmin', output="js/RessourceLists.min.bundle.js",
|
||||
"js/RessourceLists/RessourceList.js",
|
||||
"js/RessourceLists/CorpusList.js",
|
||||
"js/RessourceLists/CorpusFileList.js",
|
||||
"js/RessourceLists/JobList.js",
|
||||
"js/RessourceLists/JobInputList.js",
|
||||
"js/RessourceLists/JobResultList.js",
|
||||
"js/RessourceLists/QueryResultList.js",
|
||||
"js/RessourceLists/UserList.js" %}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
<script src="{{ url_for('static', filename='js/UploadForm.js') }}"></script>
|
||||
<script>
|
||||
{% if current_user.setting_dark_mode %}
|
||||
DarkReader.enable({brightness: 150, contrast: 100, sepia: 0});
|
||||
</script>
|
||||
{% endif %}
|
||||
<script src="{{ url_for('static', filename='js/jsonpatch.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/list.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque/App.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque/JobStatusNotifier.js') }}"></script>
|
||||
{% assets filters='rjsmin', output="js/nopaque/RessourceDisplays.min.bundle.js",
|
||||
"js/nopaque/RessourceDisplays/RessourceDisplay.js",
|
||||
"js/nopaque/RessourceDisplays/CorpusDisplay.js",
|
||||
"js/nopaque/RessourceDisplays/JobDisplay.js" %}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
{% assets filters='rjsmin', output="js/nopaque/RessourceLists.min.bundle.js",
|
||||
"js/nopaque/RessourceLists/RessourceList.js",
|
||||
"js/nopaque/RessourceLists/CorpusList.js",
|
||||
"js/nopaque/RessourceLists/CorpusFileList.js",
|
||||
"js/nopaque/RessourceLists/JobList.js",
|
||||
"js/nopaque/RessourceLists/JobInputList.js",
|
||||
"js/nopaque/RessourceLists/JobResultList.js",
|
||||
"js/nopaque/RessourceLists/QueryResultList.js",
|
||||
"js/nopaque/RessourceLists/UserList.js" %}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
<script src="{{ url_for('static', filename='js/nopaque/UploadForm.js') }}"></script>
|
||||
<script>
|
||||
{% endif %}
|
||||
// Disable all option elements with no value
|
||||
for (let optionElement of document.querySelectorAll('option[value=""]')) {optionElement.disabled = true;}
|
||||
M.AutoInit();
|
||||
|
@ -57,11 +57,11 @@
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
{% assets output="js/nopaque/CorpusAnalysis.min.bundle.js",
|
||||
"js/nopaque/CorpusAnalysis/CQiClient.js",
|
||||
"js/nopaque/CorpusAnalysis/CorpusAnalysisApp.js",
|
||||
"js/nopaque/CorpusAnalysis/CorpusAnalysisConcordance.js",
|
||||
"js/nopaque/CorpusAnalysis/CorpusAnalysisReader.js" %}
|
||||
{% assets output="js/CorpusAnalysis.min.bundle.js",
|
||||
"js/CorpusAnalysis/CQiClient.js",
|
||||
"js/CorpusAnalysis/CorpusAnalysisApp.js",
|
||||
"js/CorpusAnalysis/CorpusAnalysisConcordance.js",
|
||||
"js/CorpusAnalysis/CorpusAnalysisReader.js" %}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
<script>
|
||||
|
@ -11,8 +11,8 @@
|
||||
{% endblock metas %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{{ url_for('static', filename='css/material_icons.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/materialize.min.css') }}" media="screen,projection" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" crossorigin="anonymous" referrerpolicy="no-referrer" >
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" integrity="sha512-UJfAaOlIRtdR+0P6C3KUoTDAxVTuy3lnSXLyLKlHYJlcSU8Juge/mjeaxDNMlw9LgeIotgz5FP8eUQPhX1q10A==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
{% endblock styles %}
|
||||
{% endblock head %}
|
||||
</head>
|
||||
@ -36,7 +36,7 @@
|
||||
</footer>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/materialize.min.js') }}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js" integrity="sha512-NiWqa2rceHnN3Z5j6mSAvbwwg3tiwVNxiAQaaSMSXnRRDh5C2mk/+sKQRw8qjV1vN4nf8iK2a0b048PnHbyx+Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
{% endblock scripts %}
|
||||
{% endblock body %}
|
||||
</body>
|
||||
|
@ -1,15 +0,0 @@
|
||||
<!-- Analysis init modal. User feedback showing that the analysis session is loading. -->
|
||||
<div class="modal no-autoinit" id="analysis-init-modal">
|
||||
<div class="modal-content">
|
||||
<h4>Initializing your corpus analysis session...</h4>
|
||||
<p>If the loading takes to long or an error occured,
|
||||
<a onclick="window.location.reload()" href="#">click here</a>
|
||||
to refresh your session or
|
||||
<a href="{{ url_for('corpora.corpus', corpus_id=corpus.id) }}">go back</a>!
|
||||
</p>
|
||||
<div id="analysis-init-progress" class="progress">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
<p id="analysis-init-error" class="hide red-text"></p>
|
||||
</div>
|
||||
</div>
|
@ -1,75 +0,0 @@
|
||||
<!-- Modal showing detailed context info for one match. -->
|
||||
<div id="context-modal" class="modal">
|
||||
<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;">
|
||||
{{ 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;">
|
||||
{{ 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 class="col s12 m6 l6" id="create-from-inspect">
|
||||
<div class="section">
|
||||
<h6 style="margin-top: 0px;">Create</h6>
|
||||
<div class="divider" style="margin-bottom: 10px;"></div>
|
||||
<div class="col s12">
|
||||
Add to Sub Results
|
||||
<div class="secondary-content right" id="create-inspect-menu">
|
||||
{# The needed button is created and added via javascript #}
|
||||
</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-results-export" 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>
|
@ -1,52 +0,0 @@
|
||||
<!-- Export query results modal. Allos the user to download the results in different file formats. WIP -->
|
||||
<div id="query-results-download-modal"
|
||||
class="modal modal-fixed-footer no-autoinit">
|
||||
<div class="modal-content">
|
||||
<h4>Download your results</h4>
|
||||
<p>Download your results or sub results in different file formats like JSON
|
||||
or CSV, so you can reuse them in Excel or other software.
|
||||
You can also simply publish your results as raw data using one of these
|
||||
formats. We recommend JSON!
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<td>JSON</td>
|
||||
<td>
|
||||
<a class="btn waves-effect waves-light" id="download-results-json">
|
||||
Download
|
||||
<i class="material-icons right">file_download</i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV</td>
|
||||
<td>
|
||||
<a class="btn waves-effect waves-light disabled"
|
||||
id="download-results-csv">
|
||||
Download
|
||||
<i class="material-icons right">file_download</i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>EXCEL</td>
|
||||
<td>
|
||||
<a class="btn waves-effect waves-light disabled">Download
|
||||
<i class="material-icons right">file_download</i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTML</td>
|
||||
<td>
|
||||
<a class="btn waves-effect waves-light disabled">Download
|
||||
<i class="material-icons right">file_download</i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#!" class="modal-close waves-effect waves-light red btn">Close</a>
|
||||
</div>
|
||||
</div>
|
@ -1,18 +0,0 @@
|
||||
<!-- Modal showing the corpus files for the current query results including title ant match count per corpus file. -->
|
||||
<div id="query-builder-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Query builder</h4>
|
||||
<p>Comming soon...</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#!" class="btn modal-close waves-effect waves-green btn" id="query-builder-submit">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let queryBuilderSubmitElement = document.querySelector('#query-builder-submit');
|
||||
queryBuilderSubmitElement.addEventListener('click', () => {
|
||||
let queryElement = document.querySelector('#query-form-query');
|
||||
// queryElement.value = 'Inhalt';
|
||||
});
|
||||
</script>
|
@ -1,17 +0,0 @@
|
||||
<!-- Modal showing the corpus files for the current query results including title ant match count per corpus file. -->
|
||||
<div id="show-corpus-files-modal" class="modal bottom-sheet">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<div class="col s12 right-align">
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn red"
|
||||
style="left: 5px;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 modal-content" id="show-corpus-files-modal-content"></div>
|
||||
<div class="col s12 modal-footer">
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn red">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,17 +0,0 @@
|
||||
<!-- Modal showing the meta data for the current query results or the imported results -->
|
||||
<div id="meta-data-modal" class="modal bottom-sheet">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<div class="col s12 right-align">
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn red"
|
||||
style="left: 5px;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 modal-content" id="meta-data-modal-content"></div>
|
||||
<div class="col s12 modal-footer">
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn red">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,10 +0,0 @@
|
||||
<!-- Modal to show all metadata of one text/corpus file. Used in conjunction with the show_meta_data.html.j2 template. -->
|
||||
<div id="modal-text-details" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Bibliographic data</h4>
|
||||
<p id="bibliographic-data"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#!" class="modal-close waves-effect waves-green red btn">Close</a>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user