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) => { fetch(`/corpora/${corpusId}/followers/${followerId}/role`, {method: 'POST', headers: {Accept: 'application/json', 'Content-Type': 'application/json'}, body: JSON.stringify({role: roleName})}) .then( (response) => { if (response.ok) { app.flash('Role updated', 'corpus'); resolve(response); return; } 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) => { fetch(`/corpora/${corpusId}/followers/${followerId}/unfollow`, {method: 'POST', headers: {Accept: 'application/json'}}) .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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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( ` ` ); 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 tesseractOCRPipelineModelToggleIsPublicRequest(userId, tesseractOCRPipelineModelId, is_public) { return new Promise((resolve, reject) => { let tesseractOCRPipelineModel; try { tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId]; } catch (error) { tesseractOCRPipelineModel = {}; } fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) .then( (response) => { if (response.status === 403) { app.flash('Forbidden', 'error'); reject(response); } resolve(response); }, (response) => { app.flash('Something went wrong', 'error'); reject(response); } ); }); } static spaCyNLPPipelineModelToggleIsPublicRequest(userId, spaCyNLPPipelineModelId) { return new Promise((resolve, reject) => { let spaCyNLPPipelineModel; try { spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId]; } catch (error) { spaCyNLPPipelineModel = {}; } fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) .then( (response) => { if (response.status === 403) { app.flash('Forbidden', 'error'); reject(response); } resolve(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); } ); }); } }