mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2025-07-09 22:23:17 +00:00
Some cleanup (css, html, js)
This commit is contained in:
app
static
images
js
App.jssocket.io.min.jssocket.io.min.js.map
CorpusAnalysis
JobStatusNotifier.jsRessourceDisplays
RessourceLists
CorpusFileList.jsCorpusList.jsJobInputList.jsJobList.jsJobResultList.jsQueryResultList.jsRessourceList.jsUserList.js
UploadForm.jsdarkreader.jsjsonpatch.min.jslist.min.jslist.min.js.mapmaterialize.min.jsmodules
corpus_analysis
templates
136
app/static/js/RessourceLists/CorpusFileList.js
Normal file
136
app/static/js/RessourceLists/CorpusFileList.js
Normal file
@ -0,0 +1,136 @@
|
||||
class CorpusFileList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<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 service-color darken tooltipped waves-effect waves-light" data-action="download" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">file_download</i></a>
|
||||
<a class="action-button btn-floating service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim(),
|
||||
ressourceMapper: corpusFile => {
|
||||
return {
|
||||
id: corpusFile.id,
|
||||
author: corpusFile.author,
|
||||
creationDate: corpusFile.creation_date,
|
||||
filename: corpusFile.filename,
|
||||
publishingYear: corpusFile.publishing_year,
|
||||
title: corpusFile.title
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['creationDate']},
|
||||
'author',
|
||||
'filename',
|
||||
'publishingYear',
|
||||
'title'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...CorpusFileList.options, ...options});
|
||||
this.corpusId = listElement.dataset.corpusId;
|
||||
}
|
||||
|
||||
init(user) {
|
||||
this._init(user.corpora[this.corpusId].files);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let corpusFileElement;
|
||||
let corpusFileId;
|
||||
let deleteModal;
|
||||
let deleteModalElement;
|
||||
let tmp;
|
||||
|
||||
corpusFileElement = event.target.closest('tr[data-id]');
|
||||
if (corpusFileElement === null) {return;}
|
||||
corpusFileId = corpusFileElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
tmp = document.createElement('div');
|
||||
tmp.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm corpus deletion</h4>
|
||||
<p>Do you really want to delete the corpus file <b>${app.users[this.userId].corpora[this.corpusId].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="/corpora/${this.corpusId}/files/${corpusFileId}/delete"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
deleteModalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
|
||||
deleteModal = M.Modal.init(
|
||||
deleteModalElement,
|
||||
{
|
||||
onCloseEnd: () => {
|
||||
deleteModal.destroy();
|
||||
deleteModalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
deleteModal.open();
|
||||
break;
|
||||
case 'download':
|
||||
window.location.href = `/corpora/${this.corpusId}/files/${corpusFileId}/download`;
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = `/corpora/${this.corpusId}/files/${corpusFileId}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {
|
||||
let corpusFileId;
|
||||
let filteredPatch;
|
||||
let match;
|
||||
let operation;
|
||||
let re;
|
||||
let valueName;
|
||||
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)`);
|
||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||
for (operation of filteredPatch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
this.add(operation.value);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, corpusFileId] = operation.path.match(re);
|
||||
this.remove(corpusFileId);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)/(author|filename|publishing_year|title)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, corpusFileId, valueName] = operation.path.match(re);
|
||||
this.replace(corpusFileId, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
app/static/js/RessourceLists/CorpusList.js
Normal file
126
app/static/js/RessourceLists/CorpusList.js
Normal file
@ -0,0 +1,126 @@
|
||||
class CorpusList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
|
||||
<td><b class="title"></b><br><i class="description"></i></td>
|
||||
<td><span class="badge new status status-color status-text" 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 service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim(),
|
||||
ressourceMapper: corpus => {
|
||||
return {
|
||||
id: corpus.id,
|
||||
creationDate: corpus.creation_date,
|
||||
description: corpus.description,
|
||||
status: corpus.status,
|
||||
title: corpus.title
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['creationDate']},
|
||||
{name: 'status', attr: 'data-status'},
|
||||
'description',
|
||||
'title'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...CorpusList.options, ...options});
|
||||
}
|
||||
|
||||
init(user) {
|
||||
super._init(user.corpora);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let corpusElement;
|
||||
let corpusId;
|
||||
let deleteModal;
|
||||
let deleteModalElement;
|
||||
let tmp;
|
||||
|
||||
corpusElement = event.target.closest('tr[data-id]');
|
||||
if (corpusElement === null) {return;}
|
||||
corpusId = corpusElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
tmp = document.createElement('div');
|
||||
tmp.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm corpus deletion</h4>
|
||||
<p>Do you really want to delete the corpus <b>${app.users[this.userId].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="/corpora/${corpusId}/delete"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
deleteModalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
|
||||
deleteModal = M.Modal.init(
|
||||
deleteModalElement,
|
||||
{
|
||||
onCloseEnd: () => {
|
||||
deleteModal.destroy();
|
||||
deleteModalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
deleteModal.open();
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = `/corpora/${corpusId}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {
|
||||
let corpusId;
|
||||
let filteredPatch;
|
||||
let match;
|
||||
let operation;
|
||||
let re;
|
||||
let valueName;
|
||||
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/([A-Za-z0-9]*)`);
|
||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||
for (operation of filteredPatch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {this.add(operation.value);}
|
||||
break;
|
||||
case 'remove':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, corpusId] = operation.path.match(re);
|
||||
this.remove(corpusId);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
re = new RegExp(`^/users/${this.userId}/corpora/([A-Za-z0-9]*)/(status|description|title)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, corpusId, valueName] = operation.path.match(re);
|
||||
this.replace(corpusId, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
app/static/js/RessourceLists/JobInputList.js
Normal file
54
app/static/js/RessourceLists/JobInputList.js
Normal file
@ -0,0 +1,54 @@
|
||||
class JobInputList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<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>
|
||||
`.trim(),
|
||||
ressourceMapper: jobInput => {
|
||||
return {
|
||||
id: jobInput.id,
|
||||
creationDate: jobInput.creation_date,
|
||||
filename: jobInput.filename
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [{data: ['id']}, {data: ['creationDate']}, 'filename']
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobInputList.options, ...options});
|
||||
this.jobId = listElement.dataset.jobId;
|
||||
}
|
||||
|
||||
init(user) {
|
||||
this._init(user.jobs[this.jobId].inputs);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let jobInputElement;
|
||||
let jobInputId;
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
|
||||
jobInputElement = event.target.closest('tr[data-id]');
|
||||
if (jobInputElement === null) {return;}
|
||||
jobInputId = jobInputElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
if (actionButtonElement === null) {return;}
|
||||
action = actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'download':
|
||||
window.location.href = `/jobs/${this.jobId}/inputs/${jobInputId}/download`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {return;}
|
||||
}
|
134
app/static/js/RessourceLists/JobList.js
Normal file
134
app/static/js/RessourceLists/JobList.js
Normal file
@ -0,0 +1,134 @@
|
||||
class JobList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable service-color lighten">
|
||||
<td><a class="btn-floating disabled"><i class="nopaque-icons service-color darken serviceDuplicate1 service-icon"></i></a></td>
|
||||
<td><b class="title"></b><br><i class="description"></i></td>
|
||||
<td><span class="badge new status status-color status-text" 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 serviceDuplicate2 service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim(),
|
||||
ressourceMapper: job => {
|
||||
return {
|
||||
id: job.id,
|
||||
creationDate: job.creation_date,
|
||||
description: job.description,
|
||||
service: job.service,
|
||||
serviceDuplicate1: job.service,
|
||||
serviceDuplicate2: job.service,
|
||||
status: job.status,
|
||||
title: job.title
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['creationDate']},
|
||||
{data: ['service']},
|
||||
{name: 'serviceDuplicate1', attr: 'data-service'},
|
||||
{name: 'serviceDuplicate2', attr: 'data-service'},
|
||||
{name: 'status', attr: 'data-status'},
|
||||
'description',
|
||||
'title'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobList.options, ...options});
|
||||
}
|
||||
|
||||
init(user) {
|
||||
this._init(user.jobs);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let deleteModal;
|
||||
let deleteModalElement;
|
||||
let jobElement;
|
||||
let jobId;
|
||||
let tmp;
|
||||
|
||||
jobElement = event.target.closest('tr[data-id]');
|
||||
if (jobElement === null) {return;}
|
||||
jobId = jobElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
tmp = document.createElement('div');
|
||||
tmp.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm job deletion</h4>
|
||||
<p>Do you really want to delete the job <b>${app.users[this.userId].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="/jobs/${jobId}/delete"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
deleteModalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
|
||||
deleteModal = M.Modal.init(
|
||||
deleteModalElement,
|
||||
{
|
||||
onCloseEnd: () => {
|
||||
deleteModal.destroy();
|
||||
deleteModalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
deleteModal.open();
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = `/jobs/${jobId}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {
|
||||
let filteredPatch;
|
||||
let jobId;
|
||||
let match;
|
||||
let operation;
|
||||
let re;
|
||||
let valueName;
|
||||
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)`);
|
||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||
for (operation of filteredPatch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
this.add(operation.value);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, jobId] = operation.path.match(re);
|
||||
this.remove(jobId);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/(service|status|description|title)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, jobId, valueName] = operation.path.match(re);
|
||||
this.replace(jobId, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
95
app/static/js/RessourceLists/JobResultList.js
Normal file
95
app/static/js/RessourceLists/JobResultList.js
Normal file
@ -0,0 +1,95 @@
|
||||
class JobResultList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<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>
|
||||
`.trim(),
|
||||
ressourceMapper: 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,
|
||||
creationDate: jobResult.creation_date,
|
||||
description: description,
|
||||
filename: jobResult.filename
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['creationDate']},
|
||||
'description',
|
||||
'filename'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...JobResultList.options, ...options});
|
||||
this.jobId = listElement.dataset.jobId;
|
||||
}
|
||||
|
||||
init(user) {
|
||||
super._init(user.jobs[this.jobId].results);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let jobResultElement;
|
||||
let jobResultId;
|
||||
|
||||
jobResultElement = event.target.closest('tr[data-id]');
|
||||
if (jobResultElement === null) {return;}
|
||||
jobResultId = jobResultElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
if (actionButtonElement === null) {return;}
|
||||
action = actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'download':
|
||||
window.location.href = `/jobs/${this.jobId}/results/${jobResultId}/download`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {
|
||||
let filteredPatch;
|
||||
let operation;
|
||||
let re;
|
||||
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/${this.jobId}/results/([A-Za-z0-9]*)`);
|
||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||
for (operation of filteredPatch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
re = new RegExp(`^/users/${this.userId}/jobs/${this.jobId}/results/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
this.add(operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
129
app/static/js/RessourceLists/QueryResultList.js
Normal file
129
app/static/js/RessourceLists/QueryResultList.js
Normal file
@ -0,0 +1,129 @@
|
||||
class QueryResultList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim(),
|
||||
ressourceMapper: queryResult => {
|
||||
return {
|
||||
id: queryResult.id,
|
||||
corpusTitle: queryResult.corpus_title,
|
||||
creationDate: queryResult.creation_date,
|
||||
description: queryResult.description,
|
||||
query: queryResult.query,
|
||||
title: queryResult.title
|
||||
};
|
||||
},
|
||||
sortValueName: 'creationDate',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['creationDate']},
|
||||
'corpusTitle',
|
||||
'description',
|
||||
'query',
|
||||
'title'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...QueryResultList.options, ...options});
|
||||
}
|
||||
|
||||
init(user) {
|
||||
super._init(user.query_results);
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let deleteModal;
|
||||
let deleteModalElement;
|
||||
let queryResultElement;
|
||||
let queryResultId;
|
||||
let tmp;
|
||||
|
||||
queryResultElement = event.target.closest('tr[data-id]');
|
||||
if (queryResultElement === null) {return;}
|
||||
queryResultId = queryResultElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
tmp = document.createElement('div');
|
||||
tmp.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm query result deletion</h4>
|
||||
<p>Do you really want to delete the query result <b>${app.users[this.userId].query_results[queryResultId].title}</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="/query_results/${queryResultId}/delete"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
deleteModalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
|
||||
deleteModal = M.Modal.init(
|
||||
deleteModalElement,
|
||||
{
|
||||
onCloseEnd: () => {
|
||||
deleteModal.destroy();
|
||||
deleteModalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
deleteModal.open();
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = `/query_results/${queryResultId}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usersPatchHandler(patch) {
|
||||
let filteredPatch;
|
||||
let match;
|
||||
let operation;
|
||||
let queryResultId;
|
||||
let re;
|
||||
let valueName;
|
||||
|
||||
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)`);
|
||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||
for (operation of filteredPatch) {
|
||||
switch(operation.op) {
|
||||
case 'add':
|
||||
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
this.add(operation.value);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, queryResultId] = operation.path.match(re);
|
||||
this.remove(queryResultId);
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)/(corpus_title|description|query|title)$`);
|
||||
if (re.test(operation.path)) {
|
||||
[match, queryResultId, valueName] = operation.path.match(re);
|
||||
this.replace(queryResultId, valueName, operation.value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
app/static/js/RessourceLists/RessourceList.js
Normal file
142
app/static/js/RessourceLists/RessourceList.js
Normal file
@ -0,0 +1,142 @@
|
||||
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.
|
||||
*/
|
||||
static autoInit() {
|
||||
const nopaqueRessourceListElements = document.querySelectorAll('.nopaque-ressource-list[data-ressource-type]:not(.no-autoinit)');
|
||||
let nopaqueRessourceListElement;
|
||||
|
||||
for (nopaqueRessourceListElement of nopaqueRessourceListElements) {
|
||||
switch (nopaqueRessourceListElement.dataset.ressourceType) {
|
||||
case 'Corpus':
|
||||
new CorpusList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'CorpusFile':
|
||||
new CorpusFileList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'Job':
|
||||
new JobList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'JobInput':
|
||||
new JobInputList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'JobResult':
|
||||
new JobResultList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'QueryResult':
|
||||
new QueryResultList(nopaqueRessourceListElement);
|
||||
break;
|
||||
case 'User':
|
||||
new UserList(nopaqueRessourceListElement);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
static options = {page: 5, pagination: {innerWindow: 4, outerWindow: 1}};
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
let i;
|
||||
|
||||
if (!(listElement.hasAttribute('id'))) {
|
||||
for (i = 0; true; i++) {
|
||||
if (document.querySelector(`ressource-list-${i}`)) {continue;}
|
||||
listElement.id = `ressource-list-${i}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
options = {
|
||||
...{pagination: {item: `<li><a class="page" href="#${listElement.id}"></a></li>`}},
|
||||
...options
|
||||
}
|
||||
if ('ressourceMapper' in options) {
|
||||
this.ressourceMapper = options.ressourceMapper;
|
||||
delete options.ressourceMapper;
|
||||
}
|
||||
if ('sortValueName' in options) {
|
||||
this.sortValueName = options.sortValueName;
|
||||
delete options.sortValueName;
|
||||
}
|
||||
this.listjs = new List(listElement, {...RessourceList.options, ...options});
|
||||
this.listjs.list.innerHTML = `
|
||||
<tr>
|
||||
<td class="row" colspan="100%">
|
||||
<div class="col s12"> </div>
|
||||
<div class="col s3 m2 xl1">
|
||||
<div class="preloader-wrapper active">
|
||||
<div class="spinner-layer spinner-green-only">
|
||||
<div class="circle-clipper left">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
<div class="gap-patch">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
<div class="circle-clipper right">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s9 m6 xl5">
|
||||
<span class="card-title">Waiting for data...</span>
|
||||
<p>This list is not initialized yet.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim();
|
||||
this.listjs.list.style.cursor = 'pointer';
|
||||
this.userId = this.listjs.listContainer.dataset.userId;
|
||||
this.listjs.list.addEventListener('click', event => this.onclick(event));
|
||||
if (this.userId) {
|
||||
app.addEventListener('users.patch', patch => this.usersPatchHandler(patch));
|
||||
app.getUserById(this.userId).then(
|
||||
user => this.init(user),
|
||||
error => {throw JSON.stringify(error);}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_init(ressources) {
|
||||
this.listjs.clear();
|
||||
this.add(Object.values(ressources));
|
||||
let emptyListElementHTML = `
|
||||
<tr class="show-if-only-child">
|
||||
<td colspan="100%">
|
||||
<span class="card-title"><i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here...</span>
|
||||
<p>No ressource available.</p>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim();
|
||||
this.listjs.list.insertAdjacentHTML('afterbegin', emptyListElementHTML);
|
||||
}
|
||||
|
||||
init(user) {throw 'Not implemented';}
|
||||
|
||||
onclick(event) {throw 'Not implemented';}
|
||||
|
||||
usersPatchHandler(patch) {throw 'Not implemented';}
|
||||
|
||||
add(ressources) {
|
||||
let values = Array.isArray(ressources) ? ressources : [ressources];
|
||||
|
||||
if ('ressourceMapper' in this) {
|
||||
values = values.map(value => this.ressourceMapper(value));
|
||||
}
|
||||
this.listjs.add(values, () => {
|
||||
if ('sortValueName' in this) {
|
||||
this.listjs.sort(this.sortValueName, {order: 'desc'});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
this.listjs.remove('id', id);
|
||||
}
|
||||
|
||||
replace(id, valueName, newValue) {
|
||||
this.listjs.get('id', id)[0].values({[valueName]: newValue});
|
||||
}
|
||||
}
|
100
app/static/js/RessourceLists/UserList.js
Normal file
100
app/static/js/RessourceLists/UserList.js
Normal file
@ -0,0 +1,100 @@
|
||||
class UserList extends RessourceList {
|
||||
static options = {
|
||||
item: `
|
||||
<tr class="hoverable">
|
||||
<td><span class="idDuplicate"></span></td>
|
||||
<td><span class="username"></span></td>
|
||||
<td><span class="email"></span></td>
|
||||
<td><span class="last_seen"></span></td>
|
||||
<td><span class="role"></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="edit" data-position="top" data-tooltip="Edit"><i class="material-icons">edit</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>
|
||||
`.trim(),
|
||||
ressourceMapper: user => {
|
||||
return {
|
||||
id: user.id,
|
||||
idDuplicate: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
last_seen: new Date(user.last_seen).toLocaleString("en-US"),
|
||||
role: user.role.name
|
||||
};
|
||||
},
|
||||
sortValueName: 'memberSince',
|
||||
valueNames: [
|
||||
{data: ['id']},
|
||||
{data: ['memberSince']},
|
||||
'email',
|
||||
'idDuplicate',
|
||||
'last_seen',
|
||||
'role',
|
||||
'username'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
constructor(listElement, options = {}) {
|
||||
super(listElement, {...UserList.options, ...options});
|
||||
}
|
||||
|
||||
init(users) {
|
||||
super._init(Object.values(users));
|
||||
}
|
||||
|
||||
onclick(event) {
|
||||
let action;
|
||||
let actionButtonElement;
|
||||
let deleteModal;
|
||||
let deleteModalElement;
|
||||
let tmp;
|
||||
let userElement;
|
||||
let userId;
|
||||
|
||||
userElement = event.target.closest('tr[data-id]');
|
||||
if (userElement === null) {return;}
|
||||
userId = userElement.dataset.id;
|
||||
actionButtonElement = event.target.closest('.action-button[data-action]');
|
||||
action = (actionButtonElement === null) ? 'view' : actionButtonElement.dataset.action;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
tmp = document.createElement('div');
|
||||
tmp.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Confirm user deletion</h4>
|
||||
<p>Do you really want to delete user <b>${userId}</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="/admin/users/${userId}/delete"><i class="material-icons left">delete</i>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
deleteModalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
|
||||
deleteModal = M.Modal.init(
|
||||
deleteModalElement,
|
||||
{
|
||||
onCloseEnd: () => {
|
||||
deleteModal.destroy();
|
||||
deleteModalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
deleteModal.open();
|
||||
break;
|
||||
case 'edit':
|
||||
window.location.href = `/admin/users/${userId}/edit`;
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = `/admin/users/${userId}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user