nopaque/app/static/js/Utils.js
2023-03-06 15:04:06 +01:00

792 lines
27 KiB
JavaScript

class Utils {
static escape(text) {
// https://codereview.stackexchange.com/a/126722
var table = {
'<': 'lt',
'>': 'gt',
'"': 'quot',
'\'': 'apos',
'&': 'amp',
'\r': '#10',
'\n': '#13'
};
return text.toString().replace(/[<>"'\r\n&]/g, (chr) => {
return '&' + table[chr] + ';';
});
};
static HTMLToElement(HTMLString) {
let templateElement = document.createElement('template');
templateElement.innerHTML = HTMLString.trim();
return templateElement.content.firstChild;
}
static generateElementId(prefix='', suffix='') {
for (let i = 0; true; i++) {
if (document.querySelector(`#${prefix}${i}${suffix}`) !== null) {continue;}
return `${prefix}${i}${suffix}`;
}
}
static isObject(object) {
return object !== null && typeof object === 'object' && !Array.isArray(object);
}
static mergeObjectsDeep(...objects) {
let mergedObject = {};
if (objects.length === 0) {
return mergedObject;
}
if (!Utils.isObject(objects[0])) {throw 'Cannot merge non-object';}
if (objects.length === 1) {
return Utils.mergeObjectsDeep(mergedObject, objects[0]);
}
if (!Utils.isObject(objects[1])) {throw 'Cannot merge non-object';}
for (let key in objects[0]) {
if (objects[0].hasOwnProperty(key)) {
if (objects[1].hasOwnProperty(key)) {
if (Utils.isObject(objects[0][key]) && Utils.isObject(objects[1][key])) {
mergedObject[key] = Utils.mergeObjectsDeep(objects[0][key], objects[1][key]);
} else {
mergedObject[key] = objects[1][key];
}
} else {
mergedObject[key] = objects[0][key];
}
}
}
for (let key in objects[1]) {
if (objects[1].hasOwnProperty(key)) {
if (!objects[0].hasOwnProperty(key)) {
mergedObject[key] = objects[1][key];
}
}
}
if (objects.length === 2) {
return mergedObject;
}
return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2));
}
static updateCorpusIsPublicRequest(corpusId, isPublic) {
return new Promise((resolve, reject) => {
let fetchRessource = `/corpora/${corpusId}/is_public`;
let fetchOptions = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(isPublic)
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
app.flash(`Corpus is now ${isPublic ? 'public' : 'private'}`, 'corpus');
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static updateCorpusFollowerRole(corpusId, followerId, roleName) {
return new Promise((resolve, reject) => {
let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/role`;
let fetchOptions = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({role: roleName})
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
app.flash('Role updated', 'corpus');
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static addCorpusFollowersRequest(corpusId, usernames) {
return new Promise((resolve, reject) => {
let fetchRessource = `/corpora/${corpusId}/followers/add`;
let fetchOptions = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(usernames)
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
app.flash(`${usernames.length > 1 ? 'Users are' : 'User is'} following now`, 'corpus');
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static buildCorpusRequest(userId, corpusId) {
return new Promise((resolve, reject) => {
let corpus;
try {
corpus = app.data.users[userId].corpora[corpusId];
} catch (error) {
corpus = {};
}
fetch(`/corpora/${corpusId}/build`, {method: 'POST', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
app.flash(`Corpus "${corpus?.title}" marked for building`, 'corpus');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static unfollowCorpusRequest(corpusId, followerId) {
return new Promise((resolve, reject) => {
let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/unfollow`;
let fetchOptions = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
app.flash(`User unfollowed from Corpus`, 'corpus');
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static deleteCorpusRequest(userId, corpusId) {
return new Promise((resolve, reject) => {
let corpus;
try {
corpus = app.data.users[userId].corpora[corpusId];
} catch (error) {
corpus = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Corpus deletion</h4>
<p>Do you really want to delete the Corpus <b>${corpus?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let corpusTitle = corpus?.title;
fetch(`/corpora/${corpusId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Corpus "${corpusTitle}" marked for deletion`, 'corpus');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteCorpusFileRequest(userId, corpusId, corpusFileId) {
return new Promise((resolve, reject) => {
let corpusFile;
try {
corpusFile = app.data.users[userId].corpora[corpusId].files[corpusFileId];
} catch (error) {
corpusFile = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Corpus File deletion</h4>
<p>Do you really want to delete the Corpus File <b>${corpusFile?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let corpusFileTitle = corpusFile?.title;
fetch(`/corpora/${corpusId}/files/${corpusFileId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Corpus File "${corpusFileTitle}" deleted`, 'corpus');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId) {
return new Promise((resolve, reject) => {
let spaCyNLPPipelineModel;
try {
spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
} catch (error) {
spaCyNLPPipelineModel = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm SpaCy NLP Pipeline Model deletion</h4>
<p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${spaCyNLPPipelineModel?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let spaCyNLPPipelineModelTitle = spaCyNLPPipelineModel?.title;
fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`, {method: 'DELETE'})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`SpaCy NLP Pipeline Model "${spaCyNLPPipelineModelTitle}" marked for deletion`);
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId) {
return new Promise((resolve, reject) => {
let tesseractOCRPipelineModel;
try {
tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
} catch (error) {
tesseractOCRPipelineModel = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Tesseract OCR Pipeline Model deletion</h4>
<p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${tesseractOCRPipelineModel?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let tesseractOCRPipelineModelTitle = tesseractOCRPipelineModel?.title;
fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`, {method: 'DELETE'})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Tesseract OCR Pipeline Model "${tesseractOCRPipelineModelTitle}" marked for deletion`);
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteProfileAvatarRequest(userId) {
return new Promise((resolve, reject) => {
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Avatar deletion</h4>
<p>Do you really want to delete your Avatar?</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
fetch(`/users/${userId}/avatar`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Avatar marked for deletion`);
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteJobRequest(userId, jobId) {
return new Promise((resolve, reject) => {
let job;
try {
job = app.data.users[userId].jobs[jobId];
} catch (error) {
job = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Job deletion</h4>
<p>Do you really want to delete the Job <b>${job?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let jobTitle = job?.title;
fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" marked for deletion`, 'job');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static getJobLogRequest(userId, jobId) {
return new Promise((resolve, reject) => {
fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
return response.text();
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
)
.then(
(text) => {
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Job logs</h4>
<pre><code>${text}</code></pre>
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Close</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
modal.open();
resolve(text);
}
);
});
}
static restartJobRequest(userId, jobId) {
return new Promise((resolve, reject) => {
let job;
try {
job = app.data.users[userId].jobs[jobId];
} catch (error) {
job = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Job restart</h4>
<p>Do you really want to restart the Job <b>${job?.title}</b>? All Job Results will be permanently deleted.</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Restart</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let jobTitle = job?.title;
fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" restarted.`, 'job');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static deleteUserRequest(userId) {
return new Promise((resolve, reject) => {
let user;
try {
user = app.data.users[userId];
} catch (error) {
user = {};
}
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm User deletion</h4>
<p>Do you really want to delete the User <b>${user?.username}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => {
let userName = user?.username;
fetch(`/users/${userId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`User "${userName}" marked for deletion`);
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
modal.open();
});
}
static updateTesseractOCRPipelineModelIsPublicRequest(tesseractOCRPipelineModelId, newIsPublicValue) {
return new Promise((resolve, reject) => {
let fetchRessource = `/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/is_public`;
let fetchOptions = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(newIsPublicValue)
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
response.json().then((data) => {app.flash(data);});
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static updateSpaCyNLPPipelineModelIsPublicRequest(SpaCyNLPPipelineModelId, newIsPublicValue) {
return new Promise((resolve, reject) => {
let fetchRessource = `/contributions/spacy-nlp-pipeline-models/${SpaCyNLPPipelineModelId}/is_public`;
let fetchOptions = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(newIsPublicValue)
};
fetch(fetchRessource, fetchOptions)
.then(
(response) => {
if (response.ok) {
response.json().then((data) => {app.flash(data);});
resolve(response);
} else {
app.flash(`${response.statusText}`, 'error');
reject(response);
}
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
});
}
static generateCorpusShareLinkRequest(corpusId, role, expiration) {
return new Promise((resolve, reject) => {
const data = {role: role, expiration: expiration};
fetch(`/corpora/${corpusId}/generate-corpus-share-link`, {method: 'POST', headers: {Accept: 'text/plain'}, body: JSON.stringify(data)})
.then(
(response) => {
if (!response.ok) {
app.flash(`Something went wrong: ${response.status} ${response.statusText}`, 'error');
reject(response);
return;
}
return response.text();
},
(response) => {
// Something went wrong during the HTTP request
app.flash('Something went wrong', 'error');
reject(response);
}
)
.then(
(corpusShareLink) => {resolve(corpusShareLink);},
(error) => {
// Something went wrong during ReadableStream processing
app.flash('Something went wrong', 'error');
reject(error);
}
);
});
}
}