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; 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]);} } } } 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]);} } } } 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(`[${this.data.jobs[jobId].title}] 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 = `book${message}`; break; case "error": message = `error${message}`; break; case "job": message = `work${message}`; break; default: message = `notifications${message}`; } toast = M.toast({html: `${message} `}); 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", `Please select an option.`); } } }); 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", `${error}`); } } 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() + "%"; }); } } }