mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2026-10-19 05:00:56 +00:00
Bump Socket.IO Version and update to new List and Display logic for live data
This commit is contained in:
1 parent
1b5b935a28
commit
9fb92c5a65
12 files changed
+1113
-423
No files matched your search
@@ -547,9 +547,11 @@ class Corpus(db.Model):
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'creation_date': self.creation_date.timestamp(),
|
||||
'current_nr_of_tokens': self.current_nr_of_tokens,
|
||||
'description': self.description,
|
||||
'status': self.status,
|
||||
'last_edited_date': self.last_edited_date.timestamp(),
|
||||
'max_nr_of_tokens': self.max_nr_of_tokens,
|
||||
'title': self.title,
|
||||
'files': {file.id: file.to_dict() for file in self.files}}
|
||||
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
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 base class 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.list.list.innerHTML = `<tr class="show-if-only-child">
|
||||
<td colspan="3">
|
||||
<span class="card-title"><i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here...</span>
|
||||
<p>No ressource available (yet).</p>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
eventHandler(eventType, payload) {
|
||||
switch (eventType) {
|
||||
case 'init':
|
||||
this.init(payload);
|
||||
break;
|
||||
case 'patch':
|
||||
this.patch(payload);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown event type: ${eventType}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
if (typeof this.preprocessRessource === 'function') {
|
||||
ressources = ressources.map(ressource => this.preprocessRessource(ressource));
|
||||
}
|
||||
// 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) {
|
||||
this.list.get('id', id)[0].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.corpora = undefined;
|
||||
this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
|
||||
listElement.addEventListener('click', event => this.onclick(event));
|
||||
}
|
||||
|
||||
init(corpora) {
|
||||
this.corpora = corpora;
|
||||
super.init(corpora);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let ressourceElement = event.target.closest('tr');
|
||||
if (ressourceElement === null) {return;}
|
||||
let corpusId = ressourceElement.dataset.id;
|
||||
let actionButtonElement = event.target.closest('.action-button');
|
||||
let action = (actionButtonElement === null) ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'analyse':
|
||||
window.location.href = this.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>${this.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="${this.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 = this.corpora[corpusId].url;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: ${action}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
let id, match, re, valueName;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
// Matches the only paths that should be handled here: /corpora/{corpusId}
|
||||
re = /^\/corpora\/(\d+)$/;
|
||||
if (re.test(operation.path)) {this.add(operation.value);}
|
||||
break;
|
||||
case 'remove':
|
||||
// See case 'add' ;)
|
||||
re = /^\/corpora\/(\d+)$/;
|
||||
if (re.test(operation.path)) {
|
||||
[match, id] = operation.path.match(re);
|
||||
this.remove(id);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
// Matches the only paths that should be handled here: /corpora/{corpusId}/{status || description || title}
|
||||
re = /^\/corpora\/(\d+)\/(status|description|title)$/;
|
||||
if (re.test(operation.path)) {
|
||||
[match, id, valueName] = operation.path.match(re);
|
||||
this.replace(id, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preprocessRessource(corpus) {
|
||||
return {id: corpus.id,
|
||||
status: corpus.status,
|
||||
description: corpus.description,
|
||||
title: corpus.title};
|
||||
}
|
||||
}
|
||||
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 CorpusFileList extends RessourceList {
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...CorpusFileList.options, ...options});
|
||||
this.corpus = undefined;
|
||||
this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.corpusId);
|
||||
listElement.addEventListener('click', event => this.onclick(event));
|
||||
}
|
||||
|
||||
init(corpus) {
|
||||
this.corpus = corpus;
|
||||
super.init(this.corpus.files);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let ressourceElement = event.target.closest('tr');
|
||||
if (ressourceElement === null) {return;}
|
||||
let corpusFileId = ressourceElement.dataset.id;
|
||||
let actionButtonElement = event.target.closest('.action-button');
|
||||
if (actionButtonElement === null) {return;}
|
||||
let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
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 file <b>${this.corpus.files[corpusFileId].filename}</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" href="${this.corpus.files[corpusFileId].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 'download':
|
||||
window.location.href = this.corpus.files[corpusFileId].download_url;
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = this.corpus.files[corpusFileId].url;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: "${action}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
let re;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
// Matches the only paths that should be handled here: /corpora/{this.corpus.id}/files/{corpusFileId}
|
||||
re = new RegExp('^/corpora/' + this.corpus.id + '/files/(\\d+)$');
|
||||
if (re.test(operation.path)) {this.add(operation.value);}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preprocessRessource(corpusFile) {
|
||||
return {id: corpusFile.id, author: corpusFile.author, filename: corpusFile.filename, 'publishing-year': corpusFile.publishing_year, title: corpusFile.title};
|
||||
}
|
||||
}
|
||||
CorpusFileList.options = {
|
||||
item: `<tr>
|
||||
<td><span class="filename"></span></td>
|
||||
<td><span class="author"></span></td>
|
||||
<td><span class="title"></span></td>
|
||||
<td><span class="publishing-year"></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="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</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']}, 'author', 'filename', 'publishing-year', 'title']
|
||||
};
|
||||
|
||||
|
||||
class JobList extends RessourceList {
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobList.options, ...options});
|
||||
this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
|
||||
listElement.addEventListener('click', event => this.onclick(event));
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let ressourceElement = event.target.closest('tr');
|
||||
if (ressourceElement === null) {return;}
|
||||
let jobId = ressourceElement.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) {
|
||||
let id, match, re, valueName;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
// Matches the only paths that should be handled here: /jobs/{jobId}
|
||||
re = /^\/jobs\/(\d+)$/;
|
||||
if (re.test(operation.path)) {this.add(operation.value);}
|
||||
break;
|
||||
case 'remove':
|
||||
// See case add ;)
|
||||
re = /^\/jobs\/(\d+)$/;
|
||||
if (re.test(operation.path)) {
|
||||
[match, id] = operation.path.match(re);
|
||||
this.remove(id);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
// Matches the only paths that should be handled here: /jobs/{jobId}/{service || status || description || title}
|
||||
re = /^\/jobs\/(\d+)\/(status|description|title)$/;
|
||||
if (re.test(operation.path)) {
|
||||
[match, id, valueName] = operation.path.match(re);
|
||||
this.replace(id, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preprocessRessource(job) {
|
||||
return {id: job.id,
|
||||
service: job.service,
|
||||
status: job.status,
|
||||
description: job.description,
|
||||
title: job.title};
|
||||
}
|
||||
}
|
||||
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 JobInputList extends RessourceList {
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobInputList.options, ...options});
|
||||
this.job = undefined;
|
||||
this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
|
||||
listElement.addEventListener('click', event => this.onclick(event));
|
||||
}
|
||||
|
||||
init(job) {
|
||||
this.job = job;
|
||||
super.init(this.job.inputs);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let ressourceElement = event.target.closest('tr');
|
||||
if (ressourceElement === null) {return;}
|
||||
let jobInputId = ressourceElement.dataset.id;
|
||||
let actionButtonElement = event.target.closest('.action-button');
|
||||
if (actionButtonElement === null) {return;}
|
||||
let action = actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'download':
|
||||
window.location.href = this.job.inputs[jobInputId].download_url;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: "${action}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preprocessRessource(jobInput) {
|
||||
return {id: jobInput.id, filename: jobInput.filename};
|
||||
}
|
||||
}
|
||||
JobInputList.options = {
|
||||
item: `<tr>
|
||||
<td><span class="filename"></span></td>
|
||||
<td class="right-align">
|
||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
|
||||
</td>
|
||||
</tr>`,
|
||||
valueNames: [{data: ['id']}, 'filename']
|
||||
};
|
||||
|
||||
|
||||
class JobResultList extends RessourceList {
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobResultList.options, ...options});
|
||||
this.job = undefined;
|
||||
this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
|
||||
listElement.addEventListener('click', event => this.onclick(event));
|
||||
}
|
||||
|
||||
init(job) {
|
||||
this.job = job;
|
||||
super.init(this.job.results);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let ressourceElement = event.target.closest('tr');
|
||||
if (ressourceElement === null) {return;}
|
||||
let jobResultId = ressourceElement.dataset.id;
|
||||
let actionButtonElement = event.target.closest('.action-button');
|
||||
if (actionButtonElement === null) {return;}
|
||||
let action = actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'download':
|
||||
window.location.href = this.job.results[jobResultId].download_url;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: "${action}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
let re;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
// Matches the only paths that should be handled here: /jobs/{this.job.id}/results/{jobResultId}
|
||||
re = new RegExp('^/jobs/' + this.job.id + '/results/(\\d+)$');
|
||||
if (re.test(operation.path)) {this.add(operation.value);}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preprocessRessource(jobResult) {
|
||||
let description;
|
||||
if (jobResult.filename.endsWith('.pdf.zip')) {
|
||||
description = 'PDF files with text layer';
|
||||
} else if (jobResult.filename.endsWith('.txt.zip')) {
|
||||
description = 'Raw text files';
|
||||
} else if (jobResult.filename.endsWith('.vrt.zip')) {
|
||||
description = 'VRT compliant files including the NLP data';
|
||||
} else if (jobResult.filename.endsWith('.xml.zip')) {
|
||||
description = 'TEI compliant files';
|
||||
} else if (jobResult.filename.endsWith('.poco.zip')) {
|
||||
description = 'HOCR and image files for post correction (PoCo)';
|
||||
} else {
|
||||
description = 'All result files created during this job';
|
||||
}
|
||||
return {id: jobResult.id, description: description, filename: jobResult.filename};
|
||||
}
|
||||
}
|
||||
JobResultList.options = {
|
||||
item: `<tr>
|
||||
<td><span class="description"></span></td>
|
||||
<td><span class="filename"></span></td>
|
||||
<td class="right-align">
|
||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
|
||||
</td>
|
||||
</tr>`,
|
||||
valueNames: [{data: ['id']}, 'description', 'filename']
|
||||
};
|
||||
|
||||
|
||||
class QueryResultList extends RessourceList {
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...QueryResultList.options, ...options});
|
||||
this.user.eventListeners.queryResult.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
|
||||
}
|
||||
}
|
||||
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']
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
class RessourceDisplay {
|
||||
constructor(displayElement) {
|
||||
if (displayElement.dataset.userId) {
|
||||
if (displayElement.dataset.userId in nopaque.appClient.users) {
|
||||
this.user = nopaque.appClient.users[displayElement.dataset.userId];
|
||||
} else {
|
||||
console.error(`User not found: ${displayElement.dataset.userId}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.user = nopaque.appClient.users.self;
|
||||
}
|
||||
this.displayElement = displayElement;
|
||||
}
|
||||
|
||||
eventHandler(eventType, payload) {
|
||||
switch (eventType) {
|
||||
case 'init':
|
||||
this.init(payload);
|
||||
break;
|
||||
case 'patch':
|
||||
this.patch(payload);
|
||||
break;
|
||||
default:
|
||||
console.log(`Unknown event type: ${eventType}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
init() {console.error('init method not implemented!');}
|
||||
|
||||
patch() {console.error('patch method not implemented!');}
|
||||
|
||||
setElement(element, value) {
|
||||
switch (element.tagName) {
|
||||
case 'INPUT':
|
||||
element.value = value;
|
||||
M.updateTextFields();
|
||||
break;
|
||||
default:
|
||||
element.innerText = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CorpusDisplay extends RessourceDisplay {
|
||||
constructor(displayElement) {
|
||||
super(displayElement);
|
||||
this.corpus = undefined;
|
||||
this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.corpusId);
|
||||
}
|
||||
|
||||
init(corpus) {
|
||||
this.corpus = corpus;
|
||||
this.setCreationDate(this.corpus.creation_date);
|
||||
this.setDescription(this.corpus.description);
|
||||
this.setLastEditedDate(this.corpus.last_edited_date);
|
||||
this.setStatus(this.corpus.status);
|
||||
this.setTitle(this.corpus.title);
|
||||
this.setTokenRatio(this.corpus.current_nr_of_tokens, this.corpus.max_nr_of_tokens);
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
let re;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'replace':
|
||||
// Matches: /jobs/{this.job.id}/status
|
||||
re = new RegExp('^/corpora/' + this.corpus.id + '/last_edited_date');
|
||||
if (re.test(operation.path)) {this.setLastEditedDate(operation.value); break;}
|
||||
// Matches: /jobs/{this.job.id}/status
|
||||
re = new RegExp('^/corpora/' + this.corpus.id + '/status$');
|
||||
if (re.test(operation.path)) {this.setStatus(operation.value); break;}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-title')) {this.setElement(element, title);}
|
||||
}
|
||||
|
||||
setTokenRatio(currentNrOfTokens, maxNrOfTokens) {
|
||||
let tokenRatio = `${currentNrOfTokens}/${maxNrOfTokens}`;
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-token-ratio')) {this.setElement(element, tokenRatio);}
|
||||
}
|
||||
|
||||
setDescription(description) {
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-description')) {this.setElement(element, description);}
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-status')) {this.setElement(element, status);}
|
||||
for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
|
||||
for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
|
||||
if (['complete', 'failed', 'unprepared'].includes(status)) {
|
||||
element.classList.add('hide');
|
||||
} else {
|
||||
element.classList.remove('hide');
|
||||
}
|
||||
}
|
||||
for (let element of this.displayElement.querySelectorAll('.build-corpus-trigger')) {
|
||||
if (['complete', 'failed'].includes(status)) {
|
||||
element.classList.remove('hide');
|
||||
} else {
|
||||
element.classList.add('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCreationDate(creationDateTimestamp) {
|
||||
let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-creation-date')) {this.setElement(element, creationDate);}
|
||||
}
|
||||
|
||||
setLastEditedDate(endDateTimestamp) {
|
||||
let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
|
||||
for (let element of this.displayElement.querySelectorAll('.corpus-end-date')) {this.setElement(element, endDate);}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class JobDisplay extends RessourceDisplay {
|
||||
constructor(displayElement) {
|
||||
super(displayElement);
|
||||
this.job = undefined;
|
||||
this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.jobId);
|
||||
}
|
||||
|
||||
init(job) {
|
||||
this.job = job;
|
||||
this.setCreationDate(this.job.creation_date);
|
||||
this.setEndDate(this.job.creation_date);
|
||||
this.setDescription(this.job.description);
|
||||
this.setService(this.job.service);
|
||||
this.setServiceArgs(this.job.service_args);
|
||||
this.setServiceVersion(this.job.service_version);
|
||||
this.setStatus(this.job.status);
|
||||
this.setTitle(this.job.title);
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
let re;
|
||||
for (let operation of patch) {
|
||||
switch(operation.op) {
|
||||
case 'replace':
|
||||
// Matches: /jobs/{this.job.id}/status
|
||||
re = new RegExp('^/jobs/' + this.job.id + '/end_date');
|
||||
if (re.test(operation.path)) {this.setEndDate(operation.value); break;}
|
||||
// Matches: /jobs/{this.job.id}/status
|
||||
re = new RegExp('^/jobs/' + this.job.id + '/status$');
|
||||
if (re.test(operation.path)) {this.setStatus(operation.value); break;}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-title')) {this.setElement(element, title);}
|
||||
}
|
||||
|
||||
setDescription(description) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-description')) {this.setElement(element, description);}
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-status')) {
|
||||
this.setElement(element, status);
|
||||
}
|
||||
for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
|
||||
for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
|
||||
if (['complete', 'failed'].includes(status)) {
|
||||
element.classList.add('hide');
|
||||
} else {
|
||||
element.classList.remove('hide');
|
||||
}
|
||||
}
|
||||
for (let element of this.displayElement.querySelectorAll('.restart-job-trigger')) {
|
||||
if (['complete', 'failed'].includes(status)) {
|
||||
element.classList.remove('hide');
|
||||
} else {
|
||||
element.classList.add('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCreationDate(creationDateTimestamp) {
|
||||
let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
|
||||
for (let element of this.displayElement.querySelectorAll('.job-creation-date')) {this.setElement(element, creationDate);}
|
||||
}
|
||||
|
||||
setEndDate(endDateTimestamp) {
|
||||
let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
|
||||
for (let element of this.displayElement.querySelectorAll('.job-end-date')) {this.setElement(element, endDate);}
|
||||
}
|
||||
|
||||
setService(service) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-service')) {this.setElement(element, service);}
|
||||
}
|
||||
|
||||
setServiceArgs(serviceArgs) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-service-args')) {this.setElement(element, serviceArgs);}
|
||||
}
|
||||
|
||||
setServiceVersion(serviceVersion) {
|
||||
for (let element of this.displayElement.querySelectorAll('.job-service-version')) {this.setElement(element, serviceVersion);}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
class AppClient {
|
||||
constructor(currentUserId) {
|
||||
this.socket = io({transports: ['websocket']});
|
||||
this.users = {};
|
||||
this.users.self = this.loadUser(currentUserId);
|
||||
}
|
||||
|
||||
loadUser(userId) {
|
||||
if (userId in this.users) {return this.users[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 = {
|
||||
corpus: {
|
||||
addEventListener(listener, corpusId='*') {
|
||||
if (corpusId in this) {this[corpusId].push(listener);} else {this[corpusId] = [listener];}
|
||||
}
|
||||
},
|
||||
job: {
|
||||
addEventListener(listener, jobId='*') {
|
||||
if (jobId in this) {this[jobId].push(listener);} else {this[jobId] = [listener];}
|
||||
}
|
||||
},
|
||||
queryResult: {
|
||||
addEventListener(listener, queryResultId='*') {
|
||||
if (queryResultId in this) {this[queryResultId].push(listener);} else {this[queryResultId] = [listener];}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
init(data) {
|
||||
this.data = data;
|
||||
|
||||
if (Object.keys(this.data.corpora).length > 0) {
|
||||
//for (listener of this.eventListeners.corporaInit) {listener(this.data.corpora);}
|
||||
for (let [corpusId, eventListeners] of Object.entries(this.eventListeners.corpus)) {
|
||||
if (corpusId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.corpora);}
|
||||
} else {
|
||||
if (corpusId in this.data.corpora) {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.corpora[corpusId]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(this.data.jobs).length > 0) {
|
||||
//for (listener of this.eventListeners.jobsInit) {listener(this.data.jobs);}
|
||||
for (let [jobId, eventListeners] of Object.entries(this.eventListeners.job)) {
|
||||
if (jobId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.jobs);}
|
||||
} else {
|
||||
if (jobId in this.data.jobs) {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.jobs[jobId]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(this.data.query_results).length > 0) {
|
||||
//for (listener of this.eventListeners.queryResultsInit) {listener(this.data.query_results);}
|
||||
for (let [queryResultId, eventListeners] of Object.entries(this.eventListeners.queryResult)) {
|
||||
if (queryResultId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.query_results);}
|
||||
} else {
|
||||
if (queryResultId in this.data.query_results) {
|
||||
for (let eventListener of eventListeners) {eventListener('init', this.data.query_results[queryResultId]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patch(patch) {
|
||||
this.data = jsonpatch.apply_patch(this.data, patch);
|
||||
|
||||
let corporaPatch = patch.filter(operation => operation.path.startsWith("/corpora"));
|
||||
if (corporaPatch.length > 0) {
|
||||
for (let [corpusId, eventListeners] of Object.entries(this.eventListeners.corpus)) {
|
||||
if (corpusId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', corporaPatch);}
|
||||
} else {
|
||||
let corpusPatch = corporaPatch.filter(operation => operation.path.startsWith(`/corpora/${corpusId}`));
|
||||
if (corpusPatch.length > 0) {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', corpusPatch);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let jobsPatch = patch.filter(operation => operation.path.startsWith("/jobs"));
|
||||
if (jobsPatch.length > 0) {
|
||||
for (let [jobId, eventListeners] of Object.entries(this.eventListeners.job)) {
|
||||
if (jobId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', jobsPatch);}
|
||||
} else {
|
||||
let jobPatch = jobsPatch.filter(operation => operation.path.startsWith(`/jobs/${jobId}`));
|
||||
if (jobPatch.length > 0) {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', jobPatch);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let queryResultsPatch = patch.filter(operation => operation.path.startsWith("/query_results"));
|
||||
if (queryResultsPatch.length > 0) {
|
||||
for (let [queryResultId, eventListeners] of Object.entries(this.eventListeners.queryResult)) {
|
||||
if (queryResultId === '*') {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', queryResultsPatch);}
|
||||
} else {
|
||||
let queryResultPatch = queryResultsPatch.filter(operation => operation.path.startsWith(`/query_results/${queryResultId}`));
|
||||
if (queryResultPatch.length > 0) {
|
||||
for (let eventListener of eventListeners) {eventListener('patch', queryResultPatch);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
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() + "%";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Socket.IO v3.0.4
|
||||
* (c) 2014-2020 Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:Function("return this")(),(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=18)}([function(t,e,n){function r(t){if(t)return function(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}(t)}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n,r=this._callbacks["$"+t];if(!r)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var o=0;o<r.length;o++)if((n=r[o])===e||n.fn===e){r.splice(o,1);break}return 0===r.length&&delete this._callbacks["$"+t],this},r.prototype.emit=function(t){this._callbacks=this._callbacks||{};for(var e=new Array(arguments.length-1),n=this._callbacks["$"+t],r=1;r<arguments.length;r++)e[r-1]=arguments[r];if(n){r=0;for(var o=(n=n.slice(0)).length;r<o;++r)n[r].apply(this,e)}return this},r.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks["$"+t]||[]},r.prototype.hasListeners=function(t){return!!this.listeners(t).length}},function(t,e,n){var r=n(24),o=n(25),i=String.fromCharCode(30);t.exports={protocol:4,encodePacket:r,encodePayload:function(t,e){var n=t.length,o=new Array(n),s=0;t.forEach((function(t,c){r(t,!1,(function(t){o[c]=t,++s===n&&e(o.join(i))}))}))},decodePacket:o,decodePayload:function(t,e){for(var n=t.split(i),r=[],s=0;s<n.length;s++){var c=o(n[s],e);if(r.push(c),"error"===c.type)break}return r}}},function(t,e){t.exports="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")()},function(t,e,n){function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function o(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}function i(t,e){return(i=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function s(t){var e=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(t){return!1}}();return function(){var n,r=a(t);if(e){var o=a(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return c(this,n)}}function c(t,e){return!e||"object"!==r(e)&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function a(t){return(a=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}var u=n(1),f=function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&i(t,e)}(a,t);var e,n,r,c=s(a);function a(t){var e;return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,a),(e=c.call(this)).opts=t,e.query=t.query,e.readyState="",e.socket=t.socket,e}return e=a,(n=[{key:"onError",value:function(t,e){var n=new Error(t);return n.type="TransportError",n.description=e,this.emit("error",n),this}},{key:"open",value:function(){return"closed"!==this.readyState&&""!==this.readyStaLine truncated
|
||||
//# sourceMappingURL=socket.io.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["webpack://io/webpack/universalModuleDefinition","webpack://io/webpack/bootstrap","webpack://io/./node_modules/component-emitter/index.js","webpack://io/./node_modules/engine.io-parser/lib/index.js","webpack://io/./node_modules/engine.io-client/lib/globalThis.browser.js","webpack://io/./node_modules/engine.io-client/lib/transport.js","webpack://io/./node_modules/parseqs/index.js","webpack://io/./node_modules/socket.io-parser/dist/index.js","webpack://io/./node_modules/parseuri/index.js","webpack://io/./build/manager.js","webpack://io/./node_modules/engine.io-client/lib/transports/index.js","webpack://io/./node_modules/engine.io-client/lib/xmlhttprequest.js","webpack://io/./node_modules/engine.io-client/lib/transports/polling.js","webpack://io/./node_modules/engine.io-parser/lib/commons.js","webpack://io/./node_modules/yeast/index.js","webpack://io/./node_modules/engine.io-client/lib/util.js","webpack://io/./build/socket.js","webpack://io/./node_modules/socket.io-parser/dist/is-binary.js","webpack://io/./build/on.js","webpack://io/./node_modules/component-bind/index.js","webpack://io/./build/index.js","webpack://io/./build/url.js","webpack://io/./node_modules/engine.io-client/lib/index.js","webpack://io/./node_modules/engine.io-client/lib/socket.js","webpack://io/./node_modules/has-cors/index.js","webpack://io/./node_modules/engine.io-client/lib/transports/polling-xhr.js","webpack://io/./node_modules/engine.io-parser/lib/encodePacket.browser.js","webpack://io/./node_modules/engine.io-parser/lib/decodePacket.browser.js","webpack://io/./node_modules/base64-arraybuffer/lib/base64-arraybuffer.js","webpack://io/./node_modules/engine.io-client/lib/transports/polling-jsonp.js","webpack://io/./node_modules/engine.io-client/lib/transports/websocket.js","webpack://io/./node_modules/engine.io-client/lib/transports/websocket-constructor.browser.js","webpack://io/./node_modules/socket.io-parser/dist/binary.js","webpack://io/./node_modules/backo2/index.js"],"names":["root","factory","exports","module","define","amd","self","window","global","Function","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","Emitter","obj","mixin","on","addEventListener","event","fn","this","_callbacks","push","once","off","apply","arguments","removeListener","removeAllListeners","removeEventListener","length","cb","callbacks","splice","emit","args","Array","len","slice","listeners","hasListeners","encodePacket","require","decodePacket","SEPARATOR","String","fromCharCode","protocol","encodePayload","packets","callback","encodedPackets","count","forEach","packet","encodedPacket","join","decodePayload","encodedPayload","binaryType","split","decodedPacket","type","parser","Transport","opts","query","readyState","socket","msg","desc","err","Error","description","doOpen","doClose","onClose","write","writable","data","onPacket","encode","str","encodeURIComponent","decode","qs","qry","pairs","pair","decodeURIComponent","Decoder","Encoder","PacketType","binary_1","is_binary_1","EVENT","ACK","hasBinary","encodeAsString","BINARY_EVENT","BINARY_ACK","encodeAsBinary","attachments","nsp","id","JSON","stringify","deconstruction","deconstructPacket","pack","buffers","unshift","decodeString","reconstructor","BinaryReconstructor","isBinary","base64","takeBinaryData","Number","charAt","undefined","start","buf","substring","next","payload","parse","e","tryParse","substr","isPayloadValid","finishedReconstruction","CONNECT","DISCONNECT","CONNECT_ERROR","isArray","reconPack","binData","reconstructPacket","re","parts","src","b","indexOf","replace","exec","uri","source","host","authority","ipv6uri","pathNames","path","names","queryKey","$0","$1","$2","Manager","eio","socket_1","on_1","Backoff","nsps","subs","reconnection","reconnectionAttempts","Infinity","reconnectionDelay","reconnectionDelayMax","randomizationFactor","backoff","min","max","jitter","timeout","_readyState","_parser","encoder","decoder","_autoConnect","autoConnect","open","v","_reconnection","_reconnectionAttempts","_a","_reconnectionDelay","setMin","_randomizationFactor","setJitter","_reconnectionDelayMax","setMax","_timeout","_reconnecting","attempts","reconnect","engine","skipReconnect","openSub","onopen","errorSub","cleanup","maybeReconnectOnOpen","destroy","timer","setTimeout","close","clearTimeout","add","Socket","keys","active","_close","options","subsLength","shift","reset","reason","delay","duration","onreconnect","attempt","XMLHttpRequest","XHR","JSONP","websocket","polling","xd","xs","jsonp","location","isSSL","port","hostname","secure","xdomain","xscheme","forceJSONP","hasCORS","globalThis","enablesXDR","XDomainRequest","concat","parseqs","yeast","Polling","poll","onPause","pause","total","doPoll","index","onOpen","doWriLine truncated
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* Socket.IO v2.3.1
|
||||
* (c) 2014-2020 Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var r,n=i(t),s=n.source,p=n.id,h=n.path,u=c[p]&&h in c[p].nsps,f=e.forceNew||e["force new connection"]||!1===e.multiplex||u;return f?r=a(s,e):(c[p]||(c[p]=a(s,e)),r=c[p]),n.query&&!e.query&&(e.query=n.query),r.socket(n.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=r(1),s=r(4),a=r(9);r(3)("socket.io-client");t.exports=e=n;var c=e.managers={};e.protocol=s.protocol,e.connect=n,e.Manager=r(9),e.Socket=r(34)},function(t,e,r){"use strict";function n(t,e){var r=t;e=e||"undefined"!=typeof location&&location,null==t&&(t=e.protocol+"//"+e.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?e.protocol+t:e.host+t),/^(https?|wss?):\/\//.test(t)||(t="undefined"!=typeof e?e.protocol+"//"+t:"https://"+t),r=o(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var n=r.host.indexOf(":")!==-1,i=n?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+i+":"+r.port,r.href=r.protocol+"://"+i+(e&&e.port===r.port?"":":"+r.port),r}var o=r(2);r(3)("socket.io-client:url");t.exports=n},function(t,e){function r(t,e){var r=/\/{2,9}/g,n=e.replace(r,"/").split("/");return"/"!=e.substr(0,1)&&0!==e.length||n.splice(0,1),"/"==e.substr(e.length-1,1)&&n.splice(n.length-1,1),n}function n(t,e){var r={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(t,e,n){e&&(r[e]=n)}),r}var o=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,i=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,s=t.indexOf("["),a=t.indexOf("]");s!=-1&&a!=-1&&(t=t.substring(0,s)+t.substring(s,a).replace(/:/g,";")+t.substring(a,t.length));for(var c=o.exec(t||""),p={},h=14;h--;)p[i[h]]=c[h]||"";return s!=-1&&a!=-1&&(p.source=e,p.host=p.host.substring(1,p.host.length-1).replace(/;/g,":"),p.authority=p.authority.replace("[","").replace("]","").replace(/;/g,":"),p.ipv6uri=!0),p.pathNames=r(p,p.path),p.queryKey=n(p,p.query),p}},function(t,e){"use strict";t.exports=function(){return function(){}}},function(t,e,r){function n(){}function o(t){var r=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(r+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(r+=t.nsp+","),null!=t.id&&(r+=t.id),null!=t.data){var n=i(t.data);if(n===!1)return m;r+=n}return r}function i(t){try{return JSON.stringify(t)}catch(t){return!1}}function s(t,e){function r(t){var r=l.deconstructPacket(t),n=o(r.packet),i=r.buffers;i.unshift(n),e(i)}l.removeBlobs(t,r)}function a(){this.reconstructor=null}function c(t){var r=0,n={type:Number(t.charAt(0))};if(null==e.types[n.type])return u("unknown packet type "+n.type);if(e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type){for(var o="";"-"!==t.charAt(++r)&&(o+=t.charAt(r),r!=t.length););if(o!=Number(o)||"-"!==t.charAt(r))throw new Error("Illegal attachments");n.attachments=Number(o)}if("/"===t.charAt(r+1))for(n.nsp="";++r;){var i=t.charAt(r);if(","===i)break;if(n.nsp+=i,r===t.length)break}else n.nsp="/";var s=t.charAt(r+1);if(""!==s&&Number(s)==s){for(n.id="";++r;){var i=t.charAt(r);if(null==i||Number(i)!=i){--r;break}if(n.id+=t.charAt(r),r===t.length)break}n.id=Number(n.id)}if(t.charAt(++r)){var a=p(t.substr(r)),c=a!==!1&&(n.type===e.ERROR||d(a));if(!c)return u("invalid payload");n.data=a}return n}function p(t){try{return JSON.parse(t)}catch(t){return!1}}function h(t){this.reconPack=t,this.buffers=[]}function u(t){return{type:e.ERROR,data:"parser error: "+t}}var f=(r(3)("socket.io-parser"),r(5)),l=r(6),d=r(7),y=r(8);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=n,e.Decoder=a;var m=e.ERROR+'"encode error"';n.prototype.encode=function(t,r){if(e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,r);else{var n=o(t);r([n])}},f(a.prototype),a.prototype.add=function(t){var r;if("string"==typeof t)r=c(t),e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type?(this.reconstructor=new h(r),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",r)):this.emit("decoded",r);else{if(!y(t)Line truncated
|
||||
this.extraHeaders=t.extraHeaders,this.localAddress=t.localAddress}var o=r(19),i=r(5);t.exports=n,i(n.prototype),n.prototype.onError=function(t,e){var r=new Error(t);return r.type="TransportError",r.description=e,this.emit("error",r),this},n.prototype.open=function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this},n.prototype.close=function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this},n.prototype.send=function(t){if("open"!==this.readyState)throw new Error("Transport not open");this.write(t)},n.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},n.prototype.onData=function(t){var e=o.decodePacket(t,this.socket.binaryType);this.onPacket(e)},n.prototype.onPacket=function(t){this.emit("packet",t)},n.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},function(t,e,r){function n(t,r){var n="b"+e.packets[t.type]+t.data.data;return r(n)}function o(t,r,n){if(!r)return e.encodeBase64Packet(t,n);var o=t.data,i=new Uint8Array(o),s=new Uint8Array(1+o.byteLength);s[0]=v[t.type];for(var a=0;a<i.length;a++)s[a+1]=i[a];return n(s.buffer)}function i(t,r,n){if(!r)return e.encodeBase64Packet(t,n);var o=new FileReader;return o.onload=function(){e.encodePacket({type:t.type,data:o.result},r,!0,n)},o.readAsArrayBuffer(t.data)}function s(t,r,n){if(!r)return e.encodeBase64Packet(t,n);if(g)return i(t,r,n);var o=new Uint8Array(1);o[0]=v[t.type];var s=new w([o.buffer,t.data]);return n(s)}function a(t){try{t=d.decode(t,{strict:!1})}catch(t){return!1}return t}function c(t,e,r){for(var n=new Array(t.length),o=l(t.length,r),i=function(t,r,o){e(r,function(e,r){n[t]=r,o(e,n)})},s=0;s<t.length;s++)i(s,t[s],o)}var p,h=r(20),u=r(21),f=r(22),l=r(23),d=r(24);"undefined"!=typeof ArrayBuffer&&(p=r(25));var y="undefined"!=typeof navigator&&/Android/i.test(navigator.userAgent),m="undefined"!=typeof navigator&&/PhantomJS/i.test(navigator.userAgent),g=y||m;e.protocol=3;var v=e.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},b=h(v),k={type:"error",data:"parser error"},w=r(26);e.encodePacket=function(t,e,r,i){"function"==typeof e&&(i=e,e=!1),"function"==typeof r&&(i=r,r=null);var a=void 0===t.data?void 0:t.data.buffer||t.data;if("undefined"!=typeof ArrayBuffer&&a instanceof ArrayBuffer)return o(t,e,i);if("undefined"!=typeof w&&a instanceof w)return s(t,e,i);if(a&&a.base64)return n(t,i);var c=v[t.type];return void 0!==t.data&&(c+=r?d.encode(String(t.data),{strict:!1}):String(t.data)),i(""+c)},e.encodeBase64Packet=function(t,r){var n="b"+e.packets[t.type];if("undefined"!=typeof w&&t.data instanceof w){var o=new FileReader;return o.onload=function(){var t=o.result.split(",")[1];r(n+t)},o.readAsDataURL(t.data)}var i;try{i=String.fromCharCode.apply(null,new Uint8Array(t.data))}catch(e){for(var s=new Uint8Array(t.data),a=new Array(s.length),c=0;c<s.length;c++)a[c]=s[c];i=String.fromCharCode.apply(null,a)}return n+=btoa(i),r(n)},e.decodePacket=function(t,r,n){if(void 0===t)return k;if("string"==typeof t){if("b"===t.charAt(0))return e.decodeBase64Packet(t.substr(1),r);if(n&&(t=a(t),t===!1))return k;var o=t.charAt(0);return Number(o)==o&&b[o]?t.length>1?{type:b[o],data:t.substring(1)}:{type:b[o]}:k}var i=new Uint8Array(t),o=i[0],s=f(t,1);return w&&"blob"===r&&(s=new w([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var r=b[t.charAt(0)];if(!p)return{type:r,data:{base64:!0,data:t.substr(1)}};var n=p.decode(t.substr(1));return"blob"===e&&w&&(n=new w([n])),{type:r,data:n}},e.encodePayload=function(t,r,n){function o(t){return t.length+":"+t}function i(t,n){e.encodePacket(t,!!s&&r,!1,function(t){n(null,o(t))})}"function"==typeof r&&(n=r,r=null);var s=u(t);return r&&s?w&&!g?e.encodePayloadAsBlob(t,n):e.encodePayloadAsArrayBuffer(t,n):t.length?void c(t,i,function(t,e){return n(e.join(""))}):n("0:")},e.decodePayload=function(t,r,n){if("string"!=typeof t)return e.decodePayloadAsBinary(t,r,n);"function"==typeof r&&(n=r,r=null);var o;if(""===t)return n(k,0,1);for(var i,s,a="",c=0,p=t.length;c<p;c++){var h=t.charAt(c);if(":"===h){if(""===a||a!=(i=Number(a)))return n(k,0,1);if(s=t.substr(c+1,i),a!=s.length)return n(k,0,1);if(s.length){if(o=e.decodePacket(s,r,!1),k.type===o.type&&k.data===o.data)return n(k,0,1);var u=n(o,c+i,p);if(!1===u)return}c+=i,a=""}else a+=h}return""!==a?n(k,0,1):void 0},e.encodePayloadAsArrayBuffer=function(t,r){function n(t,r){e.encodePacket(t,!0,!0,function(t){return r(null,t)})}return t.length?void c(t,n,function(t,e){var n=e.reduce(function(t,e){var r;return r="string"==typeof e?e.length:e.byteLength,t+r.toString().length+r+2},0),o=new Uint8Array(n),i=0;return e.forEach(function(t){var e="string"==typeof t,r=t;if(e){for(var n=new Uint8Array(t.length),s=0;s<t.length;s++)n[s]=t.charCodeAt(s);r=n.buffer}e?o[i++]=0:o[i++]=1;for(var a=r.byteLength.toString(),s=0;s<a.length;s++)o[i++]=parseInt(a[s]);o[i++]=255;for(var n=new Uint8Array(r),s=0;s<n.length;s++)oLine truncated
|
||||
var u,f,l,d=String.fromCharCode;t.exports={version:"2.1.2",encode:a,decode:h}},function(t,e){!function(t){"use strict";e.encode=function(e){var r,n=new Uint8Array(e),o=n.length,i="";for(r=0;r<o;r+=3)i+=t[n[r]>>2],i+=t[(3&n[r])<<4|n[r+1]>>4],i+=t[(15&n[r+1])<<2|n[r+2]>>6],i+=t[63&n[r+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(e){var r,n,o,i,s,a=.75*e.length,c=e.length,p=0;"="===e[e.length-1]&&(a--,"="===e[e.length-2]&&a--);var h=new ArrayBuffer(a),u=new Uint8Array(h);for(r=0;r<c;r+=4)n=t.indexOf(e[r]),o=t.indexOf(e[r+1]),i=t.indexOf(e[r+2]),s=t.indexOf(e[r+3]),u[p++]=n<<2|o>>4,u[p++]=(15&o)<<4|i>>2,u[p++]=(3&i)<<6|63&s;return h}}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},function(t,e){function r(t){return t.map(function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var r=new Uint8Array(t.byteLength);r.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=r.buffer}return e}return t})}function n(t,e){e=e||{};var n=new i;return r(t).forEach(function(t){n.append(t)}),e.type?n.getBlob(e.type):n.getBlob()}function o(t,e){return new Blob(r(t),e||{})}var i="undefined"!=typeof i?i:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(t){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(t){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;"undefined"!=typeof Blob&&(n.prototype=Blob.prototype,o.prototype=Blob.prototype),t.exports=function(){return s?a?Blob:o:c?n:void 0}()},function(t,e){e.encode=function(t){var e="";for(var r in t)t.hasOwnProperty(r)&&(e.length&&(e+="&"),e+=encodeURIComponent(r)+"="+encodeURIComponent(t[r]));return e},e.decode=function(t){for(var e={},r=t.split("&"),n=0,o=r.length;n<o;n++){var i=r[n].split("=");e[decodeURIComponent(i[0])]=decodeURIComponent(i[1])}return e}},function(t,e){t.exports=function(t,e){var r=function(){};r.prototype=e.prototype,t.prototype=new r,t.prototype.constructor=t}},function(t,e){"use strict";function r(t){var e="";do e=s[t%a]+e,t=Math.floor(t/a);while(t>0);return e}function n(t){var e=0;for(h=0;h<t.length;h++)e=e*a+c[t.charAt(h)];return e}function o(){var t=r(+new Date);return t!==i?(p=0,i=t):t+"."+r(p++)}for(var i,s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),a=64,c={},p=0,h=0;h<a;h++)c[s[h]]=h;o.encode=r,o.decode=n,t.exports=o},function(t,e,r){function n(){}function o(t){i.call(this,t),this.query=this.query||{},c||(c=a.___eio=a.___eio||[]),this.index=c.length;var e=this;c.push(function(t){e.onData(t)}),this.query.j=this.index,"function"==typeof addEventListener&&addEventListener("beforeunload",function(){e.script&&(e.script.onerror=n)},!1)}var i=r(17),s=r(28),a=r(15);t.exports=o;var c,p=/\n/g,h=/\\n/g;s(o,i),o.prototype.supportsBinary=!1,o.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),i.prototype.doClose.call(this)},o.prototype.doPoll=function(){var t=this,e=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),e.async=!0,e.src=this.uri(),e.onerror=function(e){t.onError("jsonp poll error",e)};var r=document.getElementsByTagName("script")[0];r?r.parentNode.insertBefore(e,r):(document.head||document.body).appendChild(e),this.script=e;var n="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);n&&setTimeout(function(){var t=document.createElement("iframe");document.body.appendChild(t),document.body.removeChild(t)},100)},o.prototype.doWrite=function(t,e){function r(){n(),e()}function n(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(t){o.onError("jsonp polling iframe removal error",t)}try{var t='<iframe src="javascript:0" name="'+o.iframeId+'">';i=document.createElement(t)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),c=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=c,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),n(),t=t.replace(h,"\\\n"),this.area.value=t.replace(p,"\\n");try{this.form.submit()}catch(t){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&r()}:this.iframe.onload=r}},function(t,e,r){function n(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowLine truncated
|
||||
//# sourceMappingURL=socket.io.slim.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///socket.io.slim.js","webpack:///webpack/bootstrap 0185998b17feaab39f37","webpack:///./lib/index.js","webpack:///./lib/url.js","webpack:///./~/parseuri/index.js","webpack:///./support/noop.js","webpack:///./~/socket.io-parser/index.js","webpack:///./~/component-emitter/index.js","webpack:///./~/socket.io-parser/binary.js","webpack:///./~/isarray/index.js","webpack:///./~/socket.io-parser/is-buffer.js","webpack:///./lib/manager.js","webpack:///./~/engine.io-client/lib/index.js","webpack:///./~/engine.io-client/lib/socket.js","webpack:///./~/engine.io-client/lib/transports/index.js","webpack:///./~/engine.io-client/lib/xmlhttprequest.js","webpack:///./~/has-cors/index.js","webpack:///./~/engine.io-client/lib/globalThis.browser.js","webpack:///./~/engine.io-client/lib/transports/polling-xhr.js","webpack:///./~/engine.io-client/lib/transports/polling.js","webpack:///./~/engine.io-client/lib/transport.js","webpack:///./~/engine.io-parser/lib/browser.js","webpack:///./~/engine.io-parser/lib/keys.js","webpack:///./~/has-binary2/index.js","webpack:///./~/arraybuffer.slice/index.js","webpack:///./~/after/index.js","webpack:///./~/engine.io-parser/lib/utf8.js","webpack:///./~/engine.io-parser/~/base64-arraybuffer/lib/base64-arraybuffer.js","webpack:///./~/blob/index.js","webpack:///./~/parseqs/index.js","webpack:///./~/component-inherit/index.js","webpack:///./~/yeast/index.js","webpack:///./~/engine.io-client/lib/transports/polling-jsonp.js","webpack:///./~/engine.io-client/lib/transports/websocket.js","webpack:///./~/indexof/index.js","webpack:///./lib/socket.js","webpack:///./~/to-array/index.js","webpack:///./lib/on.js","webpack:///./~/component-bind/index.js","webpack:///./~/backo2/index.js"],"names":["root","factory","exports","module","define","amd","this","modules","__webpack_require__","moduleId","installedModules","id","loaded","call","m","c","p","lookup","uri","opts","_typeof","undefined","io","parsed","url","source","path","sameNamespace","cache","nsps","newConnection","forceNew","multiplex","Manager","query","socket","Symbol","iterator","obj","constructor","prototype","parser","managers","protocol","connect","Socket","loc","location","host","charAt","test","parseuri","port","ipv6","indexOf","href","pathNames","regx","names","replace","split","substr","length","splice","queryKey","data","$0","$1","$2","re","parts","str","src","b","e","substring","exec","i","authority","ipv6uri","Encoder","encodeAsString","type","BINARY_EVENT","BINARY_ACK","attachments","nsp","payload","tryStringify","ERROR_PACKET","JSON","stringify","encodeAsBinary","callback","writeEncoding","bloblessData","deconstruction","binary","deconstructPacket","pack","packet","buffers","unshift","removeBlobs","Decoder","reconstructor","decodeString","Number","types","error","buf","Error","next","tryParse","isPayloadValid","ERROR","isArray","parse","BinaryReconstructor","reconPack","msg","Emitter","isBuf","CONNECT","DISCONNECT","EVENT","ACK","encode","encoding","add","emit","base64","takeBinaryData","destroy","finishedReconstruction","binData","push","reconstructPacket","mixin","key","on","addEventListener","event","fn","_callbacks","once","off","apply","arguments","removeListener","removeAllListeners","removeEventListener","callbacks","cb","args","Array","slice","len","listeners","hasListeners","_deconstructPacket","placeholder","_placeholder","num","newData","Date","_reconstructPacket","toString","Object","withNativeBlob","Blob","withNativeFile","File","packetData","_removeBlobs","curKey","containingObject","pendingBlobs","fileReader","FileReader","onload","result","readAsArrayBuffer","arr","withNativeBuffer","Buffer","isBuffer","withNativeArrayBuffer","ArrayBuffer","isView","buffer","subs","reconnection","reconnectionAttempts","Infinity","reconnectionDelay","reconnectionDelayMax","randomizationFactor","backoff","Backoff","min","max","jitter","timeout","readyState","connecting","lastPing","packetBuffer","_parser","encoder","decoder","autoConnect","open","eio","bind","has","hasOwnProperty","emitAll","updateSocketIds","generateId","engine","v","_reconnection","_reconnectionAttempts","_reconnectionDelay","setMin","_randomizationFactor","setJitter","_reconnectionDelayMax","setMax","_timeout","maybeReconnectOnOpen","reconnecting","attempts","reconnect","self","skipReconnect","openSub","onopen","errorSub","cleanup","err","timer","setTimeout","close","clearTimeout","onping","onpong","ondata","ondecoded","onerror","onConnecting","index","encodedPackets","write","options","processPacketQueue","shift","subsLength","sub","disconnect","reset","onclose","reason","delay","duration","onreconnect","attempt","hostname","secure","agent","parseqs","decode","upgrade","forceJSONP","jsonp","forceBase64","enablesXDR","withCredentials","timestampParam","timestampRequests","transports","transportOptions","writeBuffer","prevBufferLen","policyPort","rememberUpgrade","binaryType","onlyBinaryULine truncated
|
||||
@@ -2,85 +2,93 @@
|
||||
{% from '_colors.html.j2' import colors %}
|
||||
|
||||
{% set scheme_primary_color = colors.corpus_analysis_darken %}
|
||||
{% set scheme_secondary_color = colors.corpus_analysis %}
|
||||
{% set scheme_secondary_color = colors.corpus_analysis_lighten %}
|
||||
{% block main_attribs %} style="background-color: {{ scheme_secondary_color }};"{% endblock main_attribs %}
|
||||
|
||||
{% block nav_content %}
|
||||
{% include 'corpora/_breadcrumbs.html.j2' %}
|
||||
{% endblock nav_content %}
|
||||
|
||||
{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1 id="title">{{ corpus.title }}</h1>
|
||||
<p id="description">{{ corpus.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m4">
|
||||
<span class="chip status white-text hide" id="status"></span>
|
||||
<div class="active preloader-wrapper small hide status-spinner" id="progress-indicator">
|
||||
<div class="spinner-layer spinner-blue-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 class="col s12" data-corpus-id="{{ corpus.id }}" data-user-id="{{ corpus.creator.id }}" id="corpus-display">
|
||||
<div class="row">
|
||||
<div class="col s8 m9 l10">
|
||||
<h1 id="title"><span class="corpus-title"></span></h1>
|
||||
</div>
|
||||
<div class="col s4 m3 l2 right-align">
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<span class="chip status white-text"></span>
|
||||
<div class="active preloader-wrapper small status-spinner">
|
||||
<div class="spinner-layer spinner-blue-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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m8">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="card-content" style="border-top: 10px solid {{ scheme_primary_color }}">
|
||||
<span class="card-title">Chronometrics</span>
|
||||
<div class="row">
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input disabled value="{{ corpus.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="creation-date" type="text" class="validate">
|
||||
<label for="creation-date">Creation date</label>
|
||||
<input class="corpus-creation-date validate" disabled id="corpus-creation-date" type="text">
|
||||
<label for="corpus-creation-date">Creation date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input disabled value="{{ corpus.last_edited_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="last_edited_date" type="text" class="validate">
|
||||
<label for="creation-date">Last edited</label>
|
||||
<input class="corpus-last-edited-date validate" disabled id="corpus-last-edited-date" type="text">
|
||||
<label for="corpus-last-edited-date">Last edited</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input disabled value="{{ corpus.current_nr_of_tokens }} / {{ corpus.max_nr_of_tokens }}" id="nr_of_tokens" type="text" class="validate">
|
||||
<label for="creation-date">Nr. of tokens used
|
||||
<i class="material-icons tooltipped" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i>
|
||||
</label>
|
||||
<input class="corpus-token-ratio validate" disabled id="corpus-token-ratio" type="text">
|
||||
<label for="corpus-token-ratio">Nr. of tokens used <sup><i class="material-icons tooltipped tiny" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i></sup></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
<a href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="analyze"><i class="material-icons left">search</i>Analyze</a>
|
||||
<a href="{{ url_for('corpora.prepare_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="build"><i class="material-icons left">build</i>Build</a>
|
||||
<a class="btn hide waves-effect waves-light download" id="corpus_create_zip"><i class="material-icons left">import_export</i>Export Corpus</a>
|
||||
<a data-target="delete-corpus-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||
<a class="btn disabled hide waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">search</i>Analyze</a>
|
||||
<a class="btn disabled hide waves-effect waves-light" href="{{ url_for('corpora.prepare_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">build</i>Build</a>
|
||||
<a class="btn hide waves-effect waves-light" id="corpus-export"><i class="material-icons left">import_export</i>Export Corpus</a>
|
||||
<a class="btn modal-trigger red waves-effect waves-light" data-target="delete-corpus-modal"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delete-corpus-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm corpus deletion</h4>
|
||||
<p>Do you really want to delete the corpus <span class="corpus-title"></span>? All files will be permanently deleted!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn modal-close waves-effect waves-light" href="#!">Cancel</a>
|
||||
<a class="btn modal-close red waves-effect waves-light" href="{{ url_for('corpora.delete_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12"></div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="col s12" id="corpus-files" data-corpus-id="{{ corpus.id }}" data-user-id="{{ corpus.creator.id }}">
|
||||
<div class="card">
|
||||
<div class="card-content" id="corpus-files" style="overflow: hidden;">
|
||||
<div class="card-content">
|
||||
<span class="card-title" id="files">Corpus files</span>
|
||||
<div class="input-field">
|
||||
<i class="material-icons prefix">search</i>
|
||||
<input id="search-results" class="search" type="search"></input>
|
||||
<label for="search-results">Search results</label>
|
||||
<input class="search" id="search-corpus-files" type="search"></input>
|
||||
<label for="search-corpus-files">Search corpus files</label>
|
||||
</div>
|
||||
<table class="highlight responsive-table">
|
||||
<thead>
|
||||
@@ -89,18 +97,10 @@
|
||||
<th class="sort" data-sort="author">Author</th>
|
||||
<th class="sort" data-sort="title">Title</th>
|
||||
<th class="sort" data-sort="publishing-year">Publishing year</th>
|
||||
<th>{# Actions #}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% if corpus_files|length == 0 %}
|
||||
<tr class="show-if-only-child">
|
||||
<td colspan="5">
|
||||
<span class="card-title"><i class="material-icons left">book</i>Nothing here...</span>
|
||||
<p>Corpus is empty. Add texts using the option below.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tbody class="list"></tbody>
|
||||
</table>
|
||||
<ul class="pagination"></ul>
|
||||
</div>
|
||||
@@ -111,140 +111,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div id="delete-corpus-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm corpus deletion</h4>
|
||||
<p>Do you really want to delete the corpus {{ corpus.title }}? 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 href="{{ url_for('corpora.delete_corpus', corpus_id=corpus.id) }}" class="btn modal-close red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="module">
|
||||
import {
|
||||
RessourceList
|
||||
} from '../../static/js/nopaque.lists.js';
|
||||
|
||||
class InformationUpdater {
|
||||
constructor(corpusId, foreignCorpusFlag) {
|
||||
this.corpusId = corpusId;
|
||||
this.foreignCorpusFlag = foreignCorpusFlag;
|
||||
|
||||
if (this.foreignCorpusFlag) {
|
||||
nopaque.foreignCorporaSubscribers.push(this);
|
||||
} else {
|
||||
nopaque.corporaSubscribers.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
_init() {
|
||||
let corpus;
|
||||
|
||||
corpus = (this.foreignCorpusFlag ? nopaque.foreignUser.corpora[this.corpusId]
|
||||
: nopaque.user.corpora[this.corpusId]);
|
||||
|
||||
// Status
|
||||
this.setStatus(corpus.status);
|
||||
}
|
||||
|
||||
_update(patch) {
|
||||
let pathArray;
|
||||
|
||||
for (let operation of patch) {
|
||||
/* "/corpora/{corpusId}/valueName" -> ["{corpusId}", ...] */
|
||||
pathArray = operation.path.split("/").slice(2);
|
||||
if (pathArray[0] != this.corpusId) {continue;}
|
||||
switch(operation.op) {
|
||||
case "add":
|
||||
location.reload();
|
||||
break;
|
||||
case "delete":
|
||||
location.reload();
|
||||
break;
|
||||
case "replace":
|
||||
if (pathArray[1] === "status") {
|
||||
this.setStatus(operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
let analyzeElement, buildElement, numFiles, progressIndicatorElement, statusElement;
|
||||
|
||||
numFiles = Object.keys((this.foreignCorpusFlag ? nopaque.foreignUser.corpora[this.corpusId] : nopaque.user.corpora[this.corpusId]).files).length;
|
||||
|
||||
progressIndicatorElement = document.getElementById("progress-indicator");
|
||||
if (["queued", "running", "start analysis", "stop analysis"].includes(status)) {
|
||||
progressIndicatorElement.classList.remove("hide");
|
||||
} else {
|
||||
progressIndicatorElement.classList.add("hide");
|
||||
}
|
||||
|
||||
statusElement = document.getElementById("status");
|
||||
statusElement.dataset.status = status;
|
||||
statusElement.classList.remove("hide");
|
||||
|
||||
analyzeElement = document.getElementById("analyze");
|
||||
if (["analysing", "prepared", "start analysis"].includes(status)) {
|
||||
analyzeElement.classList.remove("disabled", "hide");
|
||||
} else {
|
||||
analyzeElement.classList.add("disabled", "hide");
|
||||
}
|
||||
|
||||
buildElement = document.getElementById("build");
|
||||
if (status === "unprepared" && numFiles > 0) {
|
||||
buildElement.classList.remove("disabled", "hide");
|
||||
} else {
|
||||
buildElement.classList.add("disabled", "hide");
|
||||
}
|
||||
|
||||
let downloadBtn = document.querySelector('#corpus_create_zip');
|
||||
if (status === "prepared") {
|
||||
downloadBtn.classList.toggle('hide', false);
|
||||
} else {
|
||||
downloadBtn.classList.toggle('hide', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{% if corpus.creator == current_user %}
|
||||
var informationUpdater = new InformationUpdater({{ corpus.id }}, false);
|
||||
{% else %}
|
||||
var informationUpdater = new InformationUpdater({{ corpus.id }}, true);
|
||||
nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }});
|
||||
{% endif %}
|
||||
|
||||
let corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile");
|
||||
corpusFilesList._add({{ corpus_files|tojson|safe }});
|
||||
|
||||
// Events to handle full corpus download
|
||||
let downloadBtn = document.querySelector('#corpus_create_zip');
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
nopaque.flash('Compressing your corpus', 'corpus')
|
||||
nopaque.socket.emit('corpus_create_zip', {{ corpus.id }});
|
||||
downloadBtn.classList.toggle('disabled', true);
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
nopaque.socket.on('corpus_zip_created', () => {
|
||||
nopaque.flash('Downloading your corpus', 'corpus');
|
||||
downloadBtn.classList.toggle('disabled', false);
|
||||
// Little trick to call the download view after ziping has finished
|
||||
let fakeBtn = document.createElement('a');
|
||||
fakeBtn.href = '{{ url_for('corpora.export_corpus',
|
||||
corpus_id=corpus.id) }}';
|
||||
fakeBtn.click();
|
||||
});
|
||||
});
|
||||
<script>
|
||||
nopaque.appClient.loadUser({{ corpus.creator.id }});
|
||||
let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display'));
|
||||
let corpusFileList = new CorpusFileList(document.querySelector('#corpus-files'));
|
||||
//let jobInputList = new CorpusFileList(document.querySelector('#job-inputs'));
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
@@ -2,112 +2,132 @@
|
||||
{% from '_colors.html.j2' import colors %}
|
||||
|
||||
{% if job.service == 'file-setup' %}
|
||||
{% set border_color = colors.file_setup_darken %}
|
||||
{% set main_class = 'file-setup-color lighten' %}
|
||||
{% set scheme_color = colors.file_setup_darken %}
|
||||
{% set scheme_primary_color = colors.file_setup_darken %}
|
||||
{% set scheme_secondary_color = colors.file_setup_lighten %}
|
||||
{% elif job.service == 'nlp' %}
|
||||
{% set border_color = colors.nlp_darken %}
|
||||
{% set main_class = 'nlp-color lighten' %}
|
||||
{% set scheme_color = colors.nlp_darken %}
|
||||
{% set scheme_primary_color = colors.nlp_darken %}
|
||||
{% set scheme_secondary_color = colors.nlp_lighten %}
|
||||
{% elif job.service == 'ocr' %}
|
||||
{% set border_color = colors.ocr_darken %}
|
||||
{% set main_class = 'ocr-color lighten' %}
|
||||
{% set scheme_color = colors.ocr_darken %}
|
||||
{% set scheme_primary_color = colors.ocr_darken %}
|
||||
{% set scheme_secondary_color = colors.ocr_lighten %}
|
||||
{% endif %}
|
||||
{% block main_attribs %} style="background-color: {{ scheme_secondary_color }};"{% endblock main_attribs %}
|
||||
|
||||
{% block nav_content %}
|
||||
{% include 'jobs/_breadcrumbs.html.j2' %}
|
||||
{% endblock nav_content %}
|
||||
|
||||
{% block main_attribs %} class="{{ main_class }}"{% endblock main_attribs %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1>[{{ job.service }}] {{ job.title }}</h1>
|
||||
</div>
|
||||
<div class="col s12" data-job-id="{{ job.id }}" data-user-id="{{ job.creator.id }}" id="job-display">
|
||||
<div class="row">
|
||||
<div class="col s8 m9 l10">
|
||||
<h1 id="title">[<span class="job-service"></span>] <span class="job-title"></span></h1>
|
||||
</div>
|
||||
<div class="col s4 m3 l2 right-align">
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<span class="chip status white-text"></span>
|
||||
<div class="active preloader-wrapper small status-spinner">
|
||||
<div class="spinner-layer spinner-blue-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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="card" style="border-top: 10px solid {{border_color}}">
|
||||
<div class="card" style="border-top: 10px solid {{ scheme_primary_color }}">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s8 m9 l10">
|
||||
<span class="card-title title">{{ job.title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col s4 m3 l2 right-align">
|
||||
<span class="chip status white-text"></span>
|
||||
<div class="active preloader-wrapper small status-spinner" id="progress-indicator">
|
||||
<div class="spinner-layer spinner-blue-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>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<p class="description">{{ job.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="col s12"> </div>
|
||||
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input disabled id="creation-date" type="text" value="{{ job.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}">
|
||||
<label for="creation-date">Creation date</label>
|
||||
<input class="job-description" disabled id="job-description" type="text">
|
||||
<label for="job-description">Description</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input class="end-date" disabled id="end-date" type="text" value="">
|
||||
<label for="end-date">End date</label>
|
||||
<input class="job-creation-date" disabled id="job-creation-date" type="text">
|
||||
<label for="job-creation-date">Creation date</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m6">
|
||||
<div class="input-field">
|
||||
<input class="job-end-date" disabled id="job-end-date" type="text">
|
||||
<label for="job-end-date">End date</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m4">
|
||||
<div class="input-field">
|
||||
<input disabled id="service" type="text" value="{{ job.service }}">
|
||||
<label for="service">Service</label>
|
||||
<input class="job-service" disabled id="job-service" type="text">
|
||||
<label for="job-service">Service</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m4">
|
||||
<div class="input-field">
|
||||
<input disabled id="service-args" type="text" value="{{ job.service_args|e }}">
|
||||
<label for="service-args">Service arguments</label>
|
||||
<input class="job-service-args" disabled id="job-service-args" type="text">
|
||||
<label for="job-service-args">Service arguments</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m4">
|
||||
<div class="input-field">
|
||||
<input disabled id="service-version" type="text" value="{{ job.service_version }}">
|
||||
<label for="service-version">Service version</label>
|
||||
<input class="job-service-version" disabled id="job-service-version" type="text">
|
||||
<label for="job-service-version">Service version</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
{% if current_user.is_administrator() and job.status == 'failed' %}
|
||||
<a href="{{ url_for('jobs.restart', job_id=job.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">repeat</i>Restart</a>
|
||||
{% if current_user.is_administrator() %}
|
||||
<a class="btn hide modal-trigger restart-job-trigger waves-effect waves-light" data-target="restart-job-modal"><i class="material-icons left">repeat</i>Restart</a>
|
||||
{% endif %}
|
||||
<!-- <a href="#" class="btn disabled waves-effect waves-light"><i class="material-icons left">settings</i>Export Parameters</a> -->
|
||||
<a data-target="delete-job-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a>
|
||||
<a class="btn modal-trigger red waves-effect waves-light" data-target="delete-job-modal"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delete-job-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm deletion</h4>
|
||||
<p>Do you really want to delete the job <span class="job-title"></span>? All associated 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="{{ url_for('jobs.delete_job', job_id=job.id) }}"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if current_user.is_administrator() %}
|
||||
<div id="restart-job-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm restart</h4>
|
||||
<p>Do you really want to restart the job <span class="job-title"></span>? All log and result 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="{{ url_for('jobs.restart', job_id=job.id) }}"><i class="material-icons left">restart</i>Restart</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="col s12" id="job-inputs" data-job-id="{{ job.id }}" data-user-id="{{ job.creator.id }}">
|
||||
<div class="card">
|
||||
<div class="card-content" id="inputs">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s12 m2">
|
||||
<span class="card-title"><i class="left material-icons" style="font-size: inherit;">input</i>Inputs</span>
|
||||
@@ -118,11 +138,10 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sort" data-sort="filename">Filename</th>
|
||||
<th>{# Actions #}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
</tbody>
|
||||
<tbody class="list"></tbody>
|
||||
</table>
|
||||
<ul class="pagination"></ul>
|
||||
</div>
|
||||
@@ -131,7 +150,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="col s12" id="job-results" data-job-id="{{ job.id }}" data-user-id="{{ job.creator.id }}">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
@@ -143,24 +162,14 @@
|
||||
<table class="highlight responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Result Type</th>
|
||||
<th>Archive Name</th>
|
||||
<th>{# Actions #}</th>
|
||||
<th>Description</th>
|
||||
<th>Filename</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="results">
|
||||
<tr class="show-if-only-child">
|
||||
<td colspan="3">
|
||||
<span class="card-title">
|
||||
<i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here...
|
||||
</span>
|
||||
<p>
|
||||
No results available (yet). Is the job already completed?
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="list"></tbody>
|
||||
</table>
|
||||
<ul class="pagination"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,158 +177,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div id="delete-job-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm deletion</h4>
|
||||
<p>Do you really want to delete the job {{ job.title }}? All associated 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="{{ url_for('jobs.delete_job', job_id=job.id) }}"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="module">
|
||||
import {RessourceList} from '../../static/js/nopaque.lists.js';
|
||||
class InformationUpdater {
|
||||
constructor(jobId, foreignJobFlag) {
|
||||
this.jobId = jobId;
|
||||
this.foreignJobFlag = foreignJobFlag;
|
||||
|
||||
if (this.foreignJobFlag) {
|
||||
nopaque.foreignJobsSubscribers.push(this);
|
||||
} else {
|
||||
nopaque.jobsSubscribers.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
_init() {
|
||||
let job;
|
||||
|
||||
job = (this.foreignJobFlag ? nopaque.foreignUser.jobs[this.jobId]
|
||||
: nopaque.user.jobs[this.jobId]);
|
||||
// Results
|
||||
this.addResults(job.results);
|
||||
// End date
|
||||
this.setEndDate(job.end_date);
|
||||
// Status
|
||||
this.setStatus(job.status);
|
||||
}
|
||||
|
||||
_update(patch) {
|
||||
let pathArray;
|
||||
|
||||
for (let operation of patch) {
|
||||
/* "/jobs/{jobId}/..." -> ["{jobId}", ...] */
|
||||
pathArray = operation.path.split("/").slice(2);
|
||||
if (pathArray[0] != this.jobId) {continue;}
|
||||
switch(operation.op) {
|
||||
case "add":
|
||||
if (pathArray[1] === "results") {
|
||||
this.addResults([operation.value]);
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
location.reload();
|
||||
break;
|
||||
case "replace":
|
||||
if (pathArray[1] === "end_date") {
|
||||
this.setEndDate(operation.value);
|
||||
} else if (pathArray[1] === "status") {
|
||||
this.setStatus(operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addResults(results) {
|
||||
let resultsArray, resultsElements, resultsHTML, resultType;
|
||||
resultsArray = Object.values(results);
|
||||
resultsArray.sort(function (a, b) {
|
||||
if (a.filename < b.filename) {return -1;}
|
||||
if (a.filename > b.filename) {return 1;}
|
||||
return 0;
|
||||
});
|
||||
resultsHTML = ``;
|
||||
for (let result of resultsArray) {
|
||||
if (result.filename.endsWith(".pdf.zip")) {
|
||||
resultType = "PDF file with text layer";
|
||||
} else if (result.filename.endsWith(".txt.zip")) {
|
||||
resultType = "Raw text files";
|
||||
} else if (result.filename.endsWith(".vrt.zip")) {
|
||||
resultType = "VRT(XML dialect) files holding the NLP data";
|
||||
} else if (result.filename.endsWith(".xml.zip")) {
|
||||
resultType = "XML files";
|
||||
} else if (result.filename.endsWith(".poco.zip")) {
|
||||
resultType = "HCOR und image files needed for Post correction(PoCo)";
|
||||
} else {
|
||||
resultType = "All result files created during this job";
|
||||
}
|
||||
resultsHTML += `
|
||||
<tr>
|
||||
<td>${resultType}</td>
|
||||
<td>${result.filename}</td>
|
||||
<td class="right-align">
|
||||
<a class="btn-floating tooltipped waves-effect waves-light"
|
||||
download href="/jobs/${result.job_id}/results/${result.id}/download"
|
||||
data-position="top"
|
||||
data-tooltip="Download">
|
||||
<i class="material-icons">file_download</i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
};
|
||||
resultsHTML += `
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
resultsElements = document.querySelectorAll(".results");
|
||||
for (let resultsElement of resultsElements) {
|
||||
resultsElement.innerHTML += resultsHTML;
|
||||
}
|
||||
}
|
||||
|
||||
setEndDate(timestamp) {
|
||||
let endDate;
|
||||
|
||||
if (timestamp === null) {
|
||||
endDate = "N.a.";
|
||||
} else {
|
||||
endDate = new Date(timestamp * 1000).toLocaleString("en-US");
|
||||
}
|
||||
document.getElementById("end-date").value = endDate;
|
||||
M.updateTextFields();
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
let progressIndicator, statusElements;
|
||||
if (status === "complete" || status === "failed") {
|
||||
progressIndicator = document.getElementById("progress-indicator");
|
||||
progressIndicator.classList.add("hide");
|
||||
}
|
||||
statusElements = document.querySelectorAll(".status");
|
||||
for (let statusElement of statusElements) {
|
||||
statusElement.dataset.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{% if job.creator == current_user %}
|
||||
var informationUpdater = new InformationUpdater({{ job.id }}, false);
|
||||
{% else %}
|
||||
var informationUpdater = new InformationUpdater({{ job.id }}, true);
|
||||
nopaque.socket.emit("foreign_user_data_stream_init", {{ job.user_id }});
|
||||
{% endif %}
|
||||
let jobInputsList = new RessourceList("inputs", null, "JobInput");
|
||||
jobInputsList._add({{ job_inputs|tojson|safe }});
|
||||
<script>
|
||||
nopaque.appClient.loadUser({{ job.creator.id }});
|
||||
let jobDisplay = new JobDisplay(document.querySelector('#job-display'));
|
||||
let jobInputList = new JobInputList(document.querySelector('#job-inputs'));
|
||||
let jobResultList = new JobResultList(document.querySelector('#job-results'));
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
@@ -252,9 +252,10 @@
|
||||
{% 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.slim.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque.lists.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque/index.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque/RessourceDisplays.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/nopaque/RessorceLists.js') }}"></script>
|
||||
<script>
|
||||
// Disable all option elements with no value
|
||||
for (let optionElement of document.querySelectorAll('option[value=""]')) {optionElement.disabled = true;}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
cqi
|
||||
dnspython==1.16.0
|
||||
docker
|
||||
eventlet
|
||||
Flask
|
||||
@@ -7,7 +6,7 @@ Flask-Login
|
||||
Flask-Mail
|
||||
Flask-Migrate
|
||||
Flask-Paranoid
|
||||
Flask-SocketIO
|
||||
Flask-SocketIO~=5.0.0
|
||||
Flask-SQLAlchemy
|
||||
Flask-WTF
|
||||
jsonpatch
|
||||
|
||||
Reference in new issue
Block a user