mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
Some Renaming
This commit is contained in:
parent
bda7a782ec
commit
18c96fce4b
@ -1,207 +0,0 @@
|
|||||||
class AppClient {
|
|
||||||
constructor(currentUserId) {
|
|
||||||
this.socket = io({transports: ['websocket']});
|
|
||||||
this.users = {};
|
|
||||||
this.users.self = this.loadUser(currentUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadUser(userId) {
|
|
||||||
let user = new User();
|
|
||||||
this.users[userId] = user;
|
|
||||||
this.socket.on(`user_${userId}_init`, msg => user.init(JSON.parse(msg)));
|
|
||||||
this.socket.on(`user_${userId}_patch`, msg => user.patch(JSON.parse(msg)));
|
|
||||||
this.socket.emit('start_user_session', userId);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class User {
|
|
||||||
constructor() {
|
|
||||||
this.data = undefined;
|
|
||||||
this.eventListeners = {
|
|
||||||
corporaInit: [],
|
|
||||||
corporaPatch: [],
|
|
||||||
jobsInit: [],
|
|
||||||
jobsPatch: [],
|
|
||||||
queryResultsInit: [],
|
|
||||||
queryResultsPatch: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
init(data) {
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
let listener;
|
|
||||||
for (listener of this.eventListeners.corporaInit) {
|
|
||||||
listener(this.data.corpora);
|
|
||||||
}
|
|
||||||
for (listener of this.eventListeners.jobsInit) {
|
|
||||||
listener(this.data.jobs);
|
|
||||||
}
|
|
||||||
for (listener of this.eventListeners.queryResultsInit) {
|
|
||||||
listener(this.data.query_results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(patch) {
|
|
||||||
this.data = jsonpatch.apply_patch(this.data, patch);
|
|
||||||
|
|
||||||
let corporaPatch = patch.filter(operation => operation.path.startsWith("/corpora"));
|
|
||||||
let jobsPatch = patch.filter(operation => operation.path.startsWith("/jobs"));
|
|
||||||
let queryResultsPatch = patch.filter(operation => operation.path.startsWith("/query_results"));
|
|
||||||
|
|
||||||
for (let listener of this.eventListeners.corporaPatch) {
|
|
||||||
if (corporaPatch.length > 0) {listener(corporaPatch);}
|
|
||||||
}
|
|
||||||
for (let listener of this.eventListeners.jobsPatch) {
|
|
||||||
if (jobsPatch.length > 0) {listener(jobsPatch);}
|
|
||||||
}
|
|
||||||
for (let listener of this.eventListeners.queryResultsPatch) {
|
|
||||||
if (queryResultsPatch.length > 0) {listener(queryResultsPatch);}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let operation of jobsPatch) {
|
|
||||||
if (operation.op !== 'replace') {continue;}
|
|
||||||
// Matches the only path that should be handled here: /jobs/{jobId}/status
|
|
||||||
if (/^\/jobs\/(\d+)\/status$/.test(operation.path)) {
|
|
||||||
let [match, jobId] = operation.path.match(/^\/jobs\/(\d+)\/status$/);
|
|
||||||
if (this.data.settings.job_status_site_notifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;}
|
|
||||||
nopaque.flash(`[<a href="/jobs/${jobId}">${this.data.jobs[jobId].title}</a>] New status: ${operation.value}`, "job");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListener(type, listener) {
|
|
||||||
switch (type) {
|
|
||||||
case 'corporaInit':
|
|
||||||
this.eventListeners.corporaInit.push(listener);
|
|
||||||
if (this.data !== undefined) {listener(this.data.corpora);}
|
|
||||||
break;
|
|
||||||
case 'corporaPatch':
|
|
||||||
this.eventListeners.corporaPatch.push(listener);
|
|
||||||
break;
|
|
||||||
case 'jobsInit':
|
|
||||||
this.eventListeners.jobsInit.push(listener);
|
|
||||||
if (this.data !== undefined) {listener(this.data.jobs);}
|
|
||||||
break;
|
|
||||||
case 'jobsPatch':
|
|
||||||
this.eventListeners.jobsPatch.push(listener);
|
|
||||||
break;
|
|
||||||
case 'queryResultsInit':
|
|
||||||
this.eventListeners.queryResultsInit.push(listener);
|
|
||||||
if (this.data !== undefined) {listener(this.data.query_results);}
|
|
||||||
break;
|
|
||||||
case 'queryResultsPatch':
|
|
||||||
this.eventListeners.queryResultsPatch.push(listener);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Unknown event type: ${type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The nopaque object is used as a namespace for nopaque specific functions and
|
|
||||||
* variables.
|
|
||||||
*/
|
|
||||||
var nopaque = {};
|
|
||||||
|
|
||||||
nopaque.flash = function(message, category) {
|
|
||||||
let toast;
|
|
||||||
let toastActionElement;
|
|
||||||
|
|
||||||
switch (category) {
|
|
||||||
case "corpus":
|
|
||||||
message = `<i class="left material-icons">book</i>${message}`;
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
message = `<i class="left material-icons red-text">error</i>${message}`;
|
|
||||||
break;
|
|
||||||
case "job":
|
|
||||||
message = `<i class="left material-icons">work</i>${message}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
message = `<i class="left material-icons">notifications</i>${message}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast = M.toast({html: `<span>${message}</span>
|
|
||||||
<button data-action="close" class="btn-flat toast-action white-text">
|
|
||||||
<i class="material-icons">close</i>
|
|
||||||
</button>`});
|
|
||||||
toastActionElement = toast.el.querySelector('.toast-action[data-action="close"]');
|
|
||||||
toastActionElement.addEventListener('click', () => {toast.dismiss();});
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.Forms = {};
|
|
||||||
nopaque.Forms.init = function() {
|
|
||||||
var abortRequestElement, parentElement, progressElement, progressModal,
|
|
||||||
progressModalElement, request, submitElement;
|
|
||||||
|
|
||||||
for (let form of document.querySelectorAll(".nopaque-submit-form")) {
|
|
||||||
submitElement = form.querySelector('button[type="submit"]');
|
|
||||||
submitElement.addEventListener("click", function() {
|
|
||||||
for (let selectElement of form.querySelectorAll('select')) {
|
|
||||||
if (selectElement.value === "") {
|
|
||||||
parentElement = selectElement.closest(".input-field");
|
|
||||||
parentElement.querySelector(".select-dropdown").classList.add("invalid");
|
|
||||||
for (let helperTextElement of parentElement.querySelectorAll(".helper-text")) {
|
|
||||||
helperTextElement.remove();
|
|
||||||
}
|
|
||||||
parentElement.insertAdjacentHTML("beforeend", `<span class="helper-text red-text">Please select an option.</span>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
if (form.dataset.hasOwnProperty("progressModal")) {
|
|
||||||
progressModalElement = document.getElementById(form.dataset.progressModal);
|
|
||||||
progressModal = M.Modal.getInstance(progressModalElement);
|
|
||||||
progressModal.options.dismissible = false;
|
|
||||||
abortRequestElement = progressModalElement.querySelector(".abort-request");
|
|
||||||
abortRequestElement.addEventListener("click", function() {request.abort();});
|
|
||||||
progressElement = progressModalElement.querySelector(".determinate");
|
|
||||||
}
|
|
||||||
form.addEventListener("submit", function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var formData;
|
|
||||||
|
|
||||||
formData = new FormData(form);
|
|
||||||
// Initialize progress modal
|
|
||||||
if (progressModalElement) {
|
|
||||||
progressElement.style.width = "0%";
|
|
||||||
progressModal.open();
|
|
||||||
}
|
|
||||||
request.open("POST", window.location.href);
|
|
||||||
request.send(formData);
|
|
||||||
});
|
|
||||||
request.addEventListener("load", function(event) {
|
|
||||||
var fieldElement;
|
|
||||||
|
|
||||||
if (request.status === 201) {
|
|
||||||
window.location.href = JSON.parse(this.responseText).redirect_url;
|
|
||||||
}
|
|
||||||
if (request.status === 400) {
|
|
||||||
console.log(request);
|
|
||||||
for (let [field, errors] of Object.entries(JSON.parse(this.responseText))) {
|
|
||||||
fieldElement = form.querySelector(`input[name$="${field}"]`).closest(".input-field");
|
|
||||||
for (let error of errors) {
|
|
||||||
fieldElement.insertAdjacentHTML("beforeend", `<span class="helper-text red-text">${error}</span>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (progressModalElement) {
|
|
||||||
progressModal.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.status === 500) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (progressModalElement) {
|
|
||||||
request.upload.addEventListener("progress", function(event) {
|
|
||||||
progressElement.style.width = Math.floor(100 * event.loaded / event.total).toString() + "%";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,258 +0,0 @@
|
|||||||
class RessourceList {
|
|
||||||
/* A wrapper class for the list.js list.
|
|
||||||
* This class is not meant to be used directly, instead it should be used as
|
|
||||||
* a template for concrete ressource list implementations.
|
|
||||||
*/
|
|
||||||
constructor(listElement, options = {}) {
|
|
||||||
if (listElement.dataset.userId) {
|
|
||||||
if (listElement.dataset.userId in nopaque.appClient.users) {
|
|
||||||
this.user = nopaque.appClient.users[listElement.dataset.userId];
|
|
||||||
} else {
|
|
||||||
console.error(`User not found: ${listElement.dataset.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.user = nopaque.appClient.users.self;
|
|
||||||
}
|
|
||||||
this.list = new List(listElement, {...RessourceList.options, ...options});
|
|
||||||
this.valueNames = ['id'];
|
|
||||||
for (let element of this.list.valueNames) {
|
|
||||||
switch (typeof element) {
|
|
||||||
case 'object':
|
|
||||||
if (element.hasOwnProperty('name')) {this.valueNames.push(element.name);}
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
this.valueNames.push(element);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Unknown value name definition: ${element}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(ressources) {
|
|
||||||
this.list.clear();
|
|
||||||
this.add(Object.values(ressources));
|
|
||||||
this.list.sort('id', {order: 'desc'});
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(patch) {
|
|
||||||
/*
|
|
||||||
* It's not possible to generalize a patch Handler for all type of
|
|
||||||
* ressources. So this method is meant to be an interface.
|
|
||||||
*/
|
|
||||||
console.error('patch method not implemented!');
|
|
||||||
}
|
|
||||||
|
|
||||||
add(values) {
|
|
||||||
let ressources = Array.isArray(values) ? values : [values];
|
|
||||||
// Discard ressource values, that are not defined to be used in the list.
|
|
||||||
ressources = ressources.map(ressource => {
|
|
||||||
let cleanedRessource = {};
|
|
||||||
for (let [valueName, value] of Object.entries(ressource)) {
|
|
||||||
if (this.valueNames.includes(valueName)) {cleanedRessource[valueName] = value;}
|
|
||||||
}
|
|
||||||
return cleanedRessource;
|
|
||||||
});
|
|
||||||
// Set a callback function ('() => {return;}') to force List.js perform the
|
|
||||||
// add method asynchronous: https://listjs.com/api/#add
|
|
||||||
this.list.add(ressources, () => {return;});
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(id) {
|
|
||||||
this.list.remove('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
replace(id, valueName, newValue) {
|
|
||||||
if (this.valueNames.includes(valueName)) {
|
|
||||||
let item = this.list.get('id', id)[0];
|
|
||||||
item.values({[valueName]: newValue});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
|
|
||||||
|
|
||||||
|
|
||||||
class CorpusList extends RessourceList {
|
|
||||||
constructor(listElement, options = {}) {
|
|
||||||
super(listElement, {...CorpusList.options, ...options});
|
|
||||||
this.user.addEventListener('corporaInit', corpora => this.init(corpora));
|
|
||||||
this.user.addEventListener('corporaPatch', patch => this.patch(patch));
|
|
||||||
listElement.addEventListener('click', (event) => {this.onclick(event)});
|
|
||||||
}
|
|
||||||
|
|
||||||
onclick(event) {
|
|
||||||
let corpusId = event.target.closest('tr').dataset.id;
|
|
||||||
let actionButtonElement = event.target.closest('.action-button');
|
|
||||||
let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
|
||||||
switch (action) {
|
|
||||||
case 'analyse':
|
|
||||||
window.location.href = nopaque.user.corpora[corpusId].analysis_url;
|
|
||||||
case 'delete':
|
|
||||||
let deleteModalHTML = `<div class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm corpus deletion</h4>
|
|
||||||
<p>Do you really want to delete the corpus <b>${nopaque.user.corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light" href="${nopaque.user.corpora[corpusId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
let deleteModalParentElement = document.querySelector('main');
|
|
||||||
deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
|
|
||||||
let deleteModalElement = deleteModalParentElement.lastChild;
|
|
||||||
let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
|
|
||||||
deleteModal.open();
|
|
||||||
break;
|
|
||||||
case 'view':
|
|
||||||
// TODO: handle unprepared corpora
|
|
||||||
window.location.href = nopaque.user.corpora[corpusId].url;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Unknown action: ${action}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(patch) {
|
|
||||||
for (let operation of patch) {
|
|
||||||
switch(operation.op) {
|
|
||||||
case 'add':
|
|
||||||
// Matches the only paths that should be handled here: /corpora/{corpusId}
|
|
||||||
if (/^\/corpora\/(\d+)$/.test(operation.path)) {this.add(operation.value);}
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
// See case 'add' ;)
|
|
||||||
if (/^\/corpora\/(\d+)$/.test(operation.path)) {
|
|
||||||
let [match, id] = operation.path.match(/^\/corpora\/(\d+)$/);
|
|
||||||
this.remove(corpusId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'replace':
|
|
||||||
// Matches the only paths that should be handled here: /corpora/{corpusId}/{status || description || title}
|
|
||||||
if (/^\/corpora\/(\d+)\/(status|description|title)$/.test(operation.path)) {
|
|
||||||
let [match, id, valueName] = operation.path.match(/^\/corpora\/(\d+)\/(status|description|title)$/);
|
|
||||||
this.replace(id, valueName, operation.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CorpusList.options = {
|
|
||||||
item: `<tr>
|
|
||||||
<td><a class="btn-floating disabled"><i class="material-icons">book</i></a></td>
|
|
||||||
<td><b class="title"></b><br><i class="description"></i></td>
|
|
||||||
<td><span class="badge new status" data-badge-caption=""></span></td>
|
|
||||||
<td class="right-align">
|
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
|
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title']
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class JobList extends RessourceList {
|
|
||||||
constructor(listElement, options = {}) {
|
|
||||||
super(listElement, {...JobList.options, ...options});
|
|
||||||
this.user.addEventListener('jobsInit', jobs => this.init(jobs));
|
|
||||||
this.user.addEventListener('jobsPatch', patch => this.patch(patch));
|
|
||||||
listElement.addEventListener('click', (event) => {this.onclick(event)});
|
|
||||||
}
|
|
||||||
|
|
||||||
onclick(event) {
|
|
||||||
let jobId = event.target.closest('tr').dataset.id;
|
|
||||||
let actionButtonElement = event.target.closest('.action-button');
|
|
||||||
let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
|
||||||
switch (action) {
|
|
||||||
case 'delete':
|
|
||||||
let deleteModalHTML = `<div class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm job deletion</h4>
|
|
||||||
<p>Do you really want to delete the job <b>${this.user.data.jobs[jobId].title}</b>? All files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light" href="${this.user.data.jobs[jobId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
let deleteModalParentElement = document.querySelector('main');
|
|
||||||
deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
|
|
||||||
let deleteModalElement = deleteModalParentElement.lastChild;
|
|
||||||
let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
|
|
||||||
deleteModal.open();
|
|
||||||
break;
|
|
||||||
case 'view':
|
|
||||||
window.location.href = this.user.data.jobs[jobId].url;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Unknown action: "${action}"`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(patch) {
|
|
||||||
for (let operation of patch) {
|
|
||||||
switch(operation.op) {
|
|
||||||
case 'add':
|
|
||||||
// Matches the only paths that should be handled here: /jobs/{jobId}
|
|
||||||
if (/^\/jobs\/(\d+)$/.test(operation.path)) {this.add(operation.value);}
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
// See case add ;)
|
|
||||||
if (/^\/jobs\/(\d+)$/.test(operation.path)) {
|
|
||||||
let [match, id] = operation.path.match(/^\/jobs\/(\d+)$/);
|
|
||||||
this.remove(jobId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'replace':
|
|
||||||
// Matches the only paths that should be handled here: /jobs/{jobId}/{service || status || description || title}
|
|
||||||
if (/^\/jobs\/(\d+)\/(service|status|description|title)$/.test(operation.path)) {
|
|
||||||
let [match, id, valueName] = operation.path.match(/^\/jobs\/(\d+)\/(service|status|description|title)$/);
|
|
||||||
this.replace(id, valueName, operation.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JobList.options = {
|
|
||||||
item: `<tr>
|
|
||||||
<td><a class="btn-floating disabled"><i class="material-icons service"></i></a></td>
|
|
||||||
<td><b class="title"></b><br><i class="description"></i></td>
|
|
||||||
<td><span class="badge new status" data-badge-caption=""></span></td>
|
|
||||||
<td class="right-align">
|
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title']
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class QueryResultList extends RessourceList {
|
|
||||||
constructor(listElement, options = {}) {
|
|
||||||
super(listElement, {...QueryResultList.options, ...options});
|
|
||||||
this.user.addEventListener('queryResultsInit', queryResults => this.init(queryResults));
|
|
||||||
this.user.addEventListener('queryResultsPatch', patch => this.init(patch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QueryResultList.options = {
|
|
||||||
item: `<tr>
|
|
||||||
<td><b class="title"></b><br><i class="description"></i><br></td>
|
|
||||||
<td><span class="corpus_title"></span><br><span class="query"></span></td>
|
|
||||||
<td class="right-align">
|
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
|
|
||||||
};
|
|
@ -1,420 +0,0 @@
|
|||||||
class RessourceList extends List {
|
|
||||||
constructor(idOrElement, subscriberList, type, options) {
|
|
||||||
if (!type || !["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User"].includes(type)) {
|
|
||||||
throw "Unknown Type!";
|
|
||||||
}
|
|
||||||
super(idOrElement, {...RessourceList.options['common'],
|
|
||||||
...RessourceList.options[type],
|
|
||||||
...(options ? options : {})});
|
|
||||||
if (subscriberList) {subscriberList.push(this);}
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_init(ressources) {
|
|
||||||
this.clear();
|
|
||||||
this._add(Object.values(ressources));
|
|
||||||
this.sort("id", {order: "desc"});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_update(patch) {
|
|
||||||
let item, pathArray;
|
|
||||||
|
|
||||||
for (let operation of patch) {
|
|
||||||
/* "/{ressourceName}/{ressourceId}/..." -> ["{ressourceId}", "..."] */
|
|
||||||
pathArray = operation.path.split("/").slice(2);
|
|
||||||
switch(operation.op) {
|
|
||||||
case "add":
|
|
||||||
if (pathArray.includes("results")) {break;}
|
|
||||||
this._add([operation.value]);
|
|
||||||
break;
|
|
||||||
case "remove":
|
|
||||||
this.remove("id", pathArray[0]);
|
|
||||||
break;
|
|
||||||
case "replace":
|
|
||||||
item = this.get("id", pathArray[0])[0];
|
|
||||||
switch(pathArray[1]) {
|
|
||||||
case "status":
|
|
||||||
item.values({status: operation.value,
|
|
||||||
"analyse-link": ["analysing", "prepared", "start analysis"].includes(operation.value) ? `/corpora/${pathArray[0]}/analyse` : ""});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_add(values, callback) {
|
|
||||||
this.add(values.map(x => RessourceList.dataMappers[this.type](x)), callback);
|
|
||||||
// Initialize modal and tooltipped elements in list
|
|
||||||
M.AutoInit(this.listContainer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
RessourceList.dataMappers = {
|
|
||||||
// A data mapper describes entitys rendered per row. One key value pair holds
|
|
||||||
// the data to be rendered in the list.js table. Key has to correspond
|
|
||||||
// with the ValueNames defined below in RessourceList.options ValueNames.
|
|
||||||
// Links are declared with double ticks(") around them. The key for links
|
|
||||||
// have to correspond with the class of an <a> element in the
|
|
||||||
// RessourceList.options item blueprint.
|
|
||||||
|
|
||||||
/* ### Corpus mapper ### */
|
|
||||||
Corpus: corpus => ({
|
|
||||||
creation_date: corpus.creation_date,
|
|
||||||
description: corpus.description,
|
|
||||||
id: corpus.id,
|
|
||||||
link: `/corpora/${corpus.id}`,
|
|
||||||
status: corpus.status,
|
|
||||||
title: corpus.title,
|
|
||||||
title1: corpus.title,
|
|
||||||
"analyse-link": ["analysing", "prepared", "start analysis"].includes(corpus.status) ? `/corpora/${corpus.id}/analyse` : "",
|
|
||||||
"delete-link": `/corpora/${corpus.id}/delete`,
|
|
||||||
"delete-modal": `delete-corpus-${corpus.id}-modal`,
|
|
||||||
"delete-modal-trigger": `delete-corpus-${corpus.id}-modal`,
|
|
||||||
}),
|
|
||||||
/* ### CorpusFile mapper ### TODO: replace delete-modal with delete-onclick */
|
|
||||||
CorpusFile: corpus_file => ({
|
|
||||||
author: corpus_file.author,
|
|
||||||
filename: corpus_file.filename,
|
|
||||||
id: corpus_file.id,
|
|
||||||
link: `${corpus_file.corpus_id}/files/${corpus_file.id}`,
|
|
||||||
"publishing-year": corpus_file.publishing_year,
|
|
||||||
title: corpus_file.title,
|
|
||||||
title1: corpus_file.title,
|
|
||||||
"delete-link": `/corpora/${corpus_file.corpus_id}/files/${corpus_file.id}/delete`,
|
|
||||||
"delete-modal": `delete-corpus-file-${corpus_file.id}-modal`,
|
|
||||||
"delete-modal-trigger": `delete-corpus-file-${corpus_file.id}-modal`,
|
|
||||||
"download-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/download`,
|
|
||||||
}),
|
|
||||||
/* ### Job mapper ### */
|
|
||||||
Job: job => ({
|
|
||||||
creation_date: job.creation_date,
|
|
||||||
description: job.description,
|
|
||||||
id: job.id,
|
|
||||||
link: `/jobs/${job.id}`,
|
|
||||||
service: job.service.name,
|
|
||||||
status: job.status,
|
|
||||||
title: job.title,
|
|
||||||
title1: job.title,
|
|
||||||
"delete-link": `/jobs/${job.id}/delete`,
|
|
||||||
"delete-modal": `delete-job-${job.id}-modal`,
|
|
||||||
"delete-modal-trigger": `delete-job-${job.id}-modal`,
|
|
||||||
}),
|
|
||||||
/* ### JobInput mapper ### */
|
|
||||||
JobInput: job_input => ({
|
|
||||||
filename: job_input.filename,
|
|
||||||
id: job_input.job_id,
|
|
||||||
"download-link": `${job_input.job_id}/inputs/${job_input.id}/download`
|
|
||||||
}),
|
|
||||||
/* ### QueryResult mapper ### */
|
|
||||||
QueryResult: query_result => ({
|
|
||||||
corpus_name: query_result.query_metadata.corpus_name,
|
|
||||||
description: query_result.description,
|
|
||||||
id: query_result.id,
|
|
||||||
link: `/corpora/result/${query_result.id}`,
|
|
||||||
query: query_result.query_metadata.query,
|
|
||||||
title: query_result.title,
|
|
||||||
"delete-link": `/corpora/result/${query_result.id}/delete`,
|
|
||||||
"delete-modal": `delete-query-result-${query_result.id}-modal`,
|
|
||||||
"delete-modal-trigger": `delete-query-result-${query_result.id}-modal`,
|
|
||||||
"inspect-link": `/corpora/result/${query_result.id}/inspect`,
|
|
||||||
}),
|
|
||||||
/* ### User mapper ### */
|
|
||||||
User: user => ({
|
|
||||||
confirmed: user.confirmed,
|
|
||||||
email: user.email,
|
|
||||||
id: user.id,
|
|
||||||
link: `users/${user.id}`,
|
|
||||||
role: user.role.name,
|
|
||||||
username: user.username,
|
|
||||||
username2: user.username,
|
|
||||||
"delete-link": `/admin/users/${user.id}/delete`,
|
|
||||||
"delete-modal": `delete-user-${user.id}-modal`,
|
|
||||||
"delete-modal-trigger": `delete-user-${user.id}-modal`,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
RessourceList.options = {
|
|
||||||
// common list.js options for 5 rows per page etc.
|
|
||||||
common: {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]},
|
|
||||||
// extended list.js options for 10 rows per page etc.
|
|
||||||
extended: {
|
|
||||||
page: 10,
|
|
||||||
pagination: [
|
|
||||||
{
|
|
||||||
name: "paginationTop",
|
|
||||||
paginationClass: "paginationTop",
|
|
||||||
innerWindow: 8,
|
|
||||||
outerWindow: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
paginationClass: "paginationBottom",
|
|
||||||
innerWindow: 8,
|
|
||||||
outerWindow: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
/* Type specific List.js options. Usually only "item" and "valueNames" gets
|
|
||||||
* defined here but it is possible to define other List.js options.
|
|
||||||
* item: https://listjs.com/api/#item
|
|
||||||
* valueNames: https://listjs.com/api/#valueNames
|
|
||||||
*/
|
|
||||||
Corpus: {
|
|
||||||
item: `<tr>
|
|
||||||
<td>
|
|
||||||
<a class="btn-floating disabled">
|
|
||||||
<i class="material-icons service">book</i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="title"></b><br>
|
|
||||||
<i class="description"></i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge new status" data-badge-caption=""></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="right-align">
|
|
||||||
<a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Edit">
|
|
||||||
<i class="material-icons">edit</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light analyse-link" data-position="top" data-tooltip="Analyse">
|
|
||||||
<i class="material-icons">search</i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="modal delete-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm corpus deletion</h4>
|
|
||||||
<p>Do you really want to delete the corpus <b class="title1"></b>? All files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light delete-link"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"creation_date",
|
|
||||||
"description",
|
|
||||||
"title",
|
|
||||||
"title1",
|
|
||||||
{data: ["id"]},
|
|
||||||
{name: "analyse-link", attr: "href"},
|
|
||||||
{name: "delete-link", attr: "href"},
|
|
||||||
{name: "delete-modal-trigger", attr: "data-target"},
|
|
||||||
{name: "delete-modal", attr: "id"},
|
|
||||||
{name: "link", attr: "href"},
|
|
||||||
{name: "status", attr: "data-status"},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
CorpusFile: {
|
|
||||||
item: `<tr>
|
|
||||||
<td class="filename" style="word-break: break-word;"></td>
|
|
||||||
<td class="author" style="word-break: break-word;"></td>
|
|
||||||
<td class="title" style="word-break: break-word;"></td>
|
|
||||||
<td class="publishing-year" style="word-break: break-word;"></td>
|
|
||||||
<td>
|
|
||||||
<div class="right-align">
|
|
||||||
<a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light download-link" data-position="top" data-tooltip="Download">
|
|
||||||
<i class="material-icons">file_download</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Edit">
|
|
||||||
<i class="material-icons">edit</i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="modal delete-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm corpus file deletion</h4>
|
|
||||||
<p>Do you really want to delete the corpus file <b class="title1"></b>? It be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light delete-link"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"author",
|
|
||||||
"filename",
|
|
||||||
"publishing-year",
|
|
||||||
"title",
|
|
||||||
"title1",
|
|
||||||
{data: ["id"]},
|
|
||||||
{name: "delete-link", attr: "href"},
|
|
||||||
{name: "delete-modal-trigger", attr: "data-target"},
|
|
||||||
{name: "delete-modal", attr: "id"},
|
|
||||||
{name: "download-link", attr: "href"},
|
|
||||||
{name: "link", attr: "href"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Job: {
|
|
||||||
item: `<tr>
|
|
||||||
<td>
|
|
||||||
<a class="btn-floating disabled">
|
|
||||||
<i class="material-icons service"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="title"></b><br>
|
|
||||||
<i class="description"></i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge new status" data-badge-caption=""></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="right-align">
|
|
||||||
<a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Go to job">
|
|
||||||
<i class="material-icons">send</i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="modal delete-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm job deletion</h4>
|
|
||||||
<p>Do you really want to delete the job <b class="title1"></b>? All files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light delete-link"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"creation_date",
|
|
||||||
"description",
|
|
||||||
"title",
|
|
||||||
"title1",
|
|
||||||
{data: ["id"]},
|
|
||||||
{name: "delete-link", attr: "href"},
|
|
||||||
{name: "delete-modal-trigger", attr: "data-target"},
|
|
||||||
{name: "delete-modal", attr: "id"},
|
|
||||||
{name: "link", attr: "href"},
|
|
||||||
{name: "service", attr: "data-service"},
|
|
||||||
{name: "status", attr: "data-status"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
JobInput: {
|
|
||||||
item : `<tr>
|
|
||||||
<td class="filename"></td>
|
|
||||||
<td class="right-align">
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light download-link" data-position="top" data-tooltip="Download">
|
|
||||||
<i class="material-icons">file_download</i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"filename",
|
|
||||||
"id",
|
|
||||||
{name: "download-link", attr: "href"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
QueryResult: {
|
|
||||||
item: `<tr>
|
|
||||||
<td>
|
|
||||||
<b class="title"></b><br>
|
|
||||||
<i class="description"></i><br>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="corpus_name"></span><br>
|
|
||||||
<span class="query"></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="right-align">
|
|
||||||
<a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Info">
|
|
||||||
<i class="material-icons">info</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light inspect-link" data-position="top" data-tooltip="Analyse">
|
|
||||||
<i class="material-icons">search</i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="modal delete-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm query result deletion</h4>
|
|
||||||
<p>Do you really want to delete the query result <b class="title1"></b>? It will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light delete-link"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"corpus_name",
|
|
||||||
"description",
|
|
||||||
"query",
|
|
||||||
"title",
|
|
||||||
"title2",
|
|
||||||
{data: ["id"]},
|
|
||||||
{name: "delete-link", attr: "href"},
|
|
||||||
{name: "delete-modal-trigger", attr: "data-target"},
|
|
||||||
{name: "delete-modal", attr: "id"},
|
|
||||||
{name: "inspect-link", attr: "href"},
|
|
||||||
{name: "link", attr: "href"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
User: {
|
|
||||||
item: `<tr>
|
|
||||||
<td class="id"></td>
|
|
||||||
<td class="username"></td>
|
|
||||||
<td class="email"></td>
|
|
||||||
<td class="role"></td>
|
|
||||||
<td>
|
|
||||||
<div class="right-align">
|
|
||||||
<a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</a>
|
|
||||||
<a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Go to user">
|
|
||||||
<i class="material-icons">send</i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="modal delete-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm corpus deletion</h4>
|
|
||||||
<p>Do you really want to delete the job <b class="title1"></b>? All files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a class="btn modal-close red waves-effect waves-light delete-link"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`,
|
|
||||||
valueNames: [
|
|
||||||
"username",
|
|
||||||
"username2",
|
|
||||||
"email",
|
|
||||||
"role",
|
|
||||||
"id",
|
|
||||||
{name: "link", attr: "href"},
|
|
||||||
{name: "delete-link", attr: "href"},
|
|
||||||
{name: "delete-modal-trigger", attr: "data-target"},
|
|
||||||
{name: "delete-modal", attr: "id"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export { RessourceList, };
|
|
@ -6,6 +6,7 @@ class CorpusList extends RessourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(corpora) {
|
init(corpora) {
|
||||||
|
console.log('CorpusList.init called');
|
||||||
this.corpora = corpora;
|
this.corpora = corpora;
|
||||||
super.init(corpora);
|
super.init(corpora);
|
||||||
}
|
}
|
@ -24,6 +24,18 @@ class User {
|
|||||||
corpus: {
|
corpus: {
|
||||||
addEventListener(listener, corpusId='*') {
|
addEventListener(listener, corpusId='*') {
|
||||||
if (corpusId in this) {this[corpusId].push(listener);} else {this[corpusId] = [listener];}
|
if (corpusId in this) {this[corpusId].push(listener);} else {this[corpusId] = [listener];}
|
||||||
|
if (this.data !== undefined) {
|
||||||
|
console.log('User data is already initialized');
|
||||||
|
if (corpusId === '*') {
|
||||||
|
console.log('*');
|
||||||
|
listener(this.data.corpora);
|
||||||
|
} else if (corpusId in this.data.corpora) {
|
||||||
|
console.log(corpusId);
|
||||||
|
listener(this.data.corpora[corpusId]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('User data is not initialized');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
job: {
|
job: {
|
||||||
@ -40,6 +52,7 @@ class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(data) {
|
init(data) {
|
||||||
|
console.log('User.init called');
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
for (let [corpusId, eventListeners] of Object.entries(this.eventListeners.corpus)) {
|
for (let [corpusId, eventListeners] of Object.entries(this.eventListeners.corpus)) {
|
||||||
|
@ -254,17 +254,17 @@
|
|||||||
<script src="{{ url_for('static', filename='js/list.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/socket.io.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/main.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/main.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/displays/RessourceDisplay.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceDisplays/RessourceDisplay.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/displays/CorpusDisplay.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceDisplays/CorpusDisplay.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/displays/JobDisplay.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceDisplays/JobDisplay.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/RessourceList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/RessourceList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/CorpusList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/CorpusList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/CorpusFileList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/CorpusFileList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/JobList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/JobList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/JobInputList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/JobInputList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/JobResultList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/JobResultList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/QueryResultList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/QueryResultList.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/nopaque/lists/UserList.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nopaque/RessourceLists/UserList.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
// Disable all option elements with no value
|
// Disable all option elements with no value
|
||||||
for (let optionElement of document.querySelectorAll('option[value=""]')) {optionElement.disabled = true;}
|
for (let optionElement of document.querySelectorAll('option[value=""]')) {optionElement.disabled = true;}
|
||||||
@ -273,8 +273,7 @@
|
|||||||
M.Dropdown.init(document.querySelectorAll('#nav-more-dropdown-trigger'), {alignment: 'right', constrainWidth: false, coverTrigger: false});
|
M.Dropdown.init(document.querySelectorAll('#nav-more-dropdown-trigger'), {alignment: 'right', constrainWidth: false, coverTrigger: false});
|
||||||
nopaque.Forms.init();
|
nopaque.Forms.init();
|
||||||
for (let flashedMessage of {{ get_flashed_messages(with_categories=True)|tojson }}) {nopaque.flash(flashedMessage[1], flashedMessage[0]);}
|
for (let flashedMessage of {{ get_flashed_messages(with_categories=True)|tojson }}) {nopaque.flash(flashedMessage[1], flashedMessage[0]);}
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
nopaque.appClient = new AppClient({{ current_user.id }});
|
nopaque.appClient = new AppClient({{ current_user.id }});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Loading…
Reference in New Issue
Block a user