diff --git a/app/static/js/app.js b/app/static/js/app.js index e8ffddfc..fc52a7f2 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -2,33 +2,19 @@ nopaque.App = class App { constructor() { this.socket = io({transports: ['websocket'], upgrade: false}); - this.ui = new nopaque.UIExtension(this); - this.liveUserRegistry = new nopaque.LiveUserRegistryExtension(this); - this.users = new nopaque.UsersExtension(this); + // Endpoints + this.users = new nopaque.app.endpoints.Users(this); + + // Extensions + this.toaster = new nopaque.app.extensions.Toaster(this); + this.ui = new nopaque.app.extensions.UI(this); + this.userHub = new nopaque.app.extensions.UserHub(this); } - // onPatch(patch) { - // // Filter Patch to only include operations on users that are initialized - // let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`); - // let filteredPatch = patch.filter(operation => regExp.test(operation.path)); - - // // Handle job status updates - // let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`); - // let subFilteredPatch = filteredPatch - // .filter((operation) => {return operation.op === 'replace';}) - // .filter((operation) => {return subRegExp.test(operation.path);}); - // for (let operation of subFilteredPatch) { - // let [match, userId, jobId] = operation.path.match(subRegExp); - // this.flash(`[${this.data.users[userId].jobs[jobId].title}] New status: `, 'job'); - // } - - // // Apply Patch - // jsonpatch.applyPatch(this.data, filteredPatch); - // } - init() { + // Initialize extensions + this.toaster.init(); this.ui.init(); - this.liveUserRegistry.init(); - this.users.init(); + this.userHub.init(); } }; diff --git a/app/static/js/app/endpoints/index.js b/app/static/js/app/endpoints/index.js new file mode 100644 index 00000000..f94fbc36 --- /dev/null +++ b/app/static/js/app/endpoints/index.js @@ -0,0 +1 @@ +nopaque.app.endpoints = {}; diff --git a/app/static/js/app/users.js b/app/static/js/app/endpoints/users.js similarity index 94% rename from app/static/js/app/users.js rename to app/static/js/app/endpoints/users.js index 0b98f95d..b1bfa7fd 100644 --- a/app/static/js/app/users.js +++ b/app/static/js/app/endpoints/users.js @@ -1,12 +1,10 @@ -nopaque.UsersExtension = class UsersExtension { +nopaque.app.endpoints.Users = class Users { constructor(app) { this.app = app; this.socket = io('/users', {transports: ['websocket'], upgrade: false}); } - init() {} - async get(userId) { const response = await this.socket.emitWithAck('get', userId); diff --git a/app/static/js/app/extensions/index.js b/app/static/js/app/extensions/index.js new file mode 100644 index 00000000..163fecb9 --- /dev/null +++ b/app/static/js/app/extensions/index.js @@ -0,0 +1 @@ +nopaque.app.extensions = {}; diff --git a/app/static/js/app/extensions/toaster.js b/app/static/js/app/extensions/toaster.js new file mode 100644 index 00000000..876fc7b3 --- /dev/null +++ b/app/static/js/app/extensions/toaster.js @@ -0,0 +1,51 @@ +nopaque.app.extensions.Toaster = class Toaster { + constructor(app) { + this.app = app; + } + + init() { + this.app.userHub.addEventListener('patch', (event) => {this.#onPatch(event.detail);}); + } + + #onPatch(patch) { + // Handle job updates + const jobRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)`); + const jobPatch = patch.filter((operation) => {return jobRegExp.test(operation.path);}); + + this.#onJobPatch(jobPatch); + + // Handle corpus updates + const corpusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)`); + const corpusPatch = patch.filter((operation) => {return corpusRegExp.test(operation.path);}); + + this.#onCorpusPatch(corpusPatch); + } + + async #onJobPatch(patch) { + // Handle job status updates + const jobStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)/status$`); + const jobStatusPatch = patch.filter((operation) => {return operation.op === 'replace';}); + + for (let operation of jobStatusPatch) { + const [match, userId, jobId] = operation.path.match(jobStatusRegExp); + const user = await this.app.userHub.get(userId); + const job = user.jobs[jobId]; + + this.app.ui.flash(`[${job.title}] New status: `, 'job'); + } + } + + async #onCorpusPatch(patch) { + // Handle job status updates + const corpusStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)/status$`); + const corpusStatusPatch = patch.filter((operation) => {return operation.op === 'replace';}); + + for (let operation of corpusStatusPatch) { + const [match, userId, corpusId] = operation.path.match(corpusStatusRegExp); + const user = await this.app.userHub.get(userId); + const corpus = user.corpora[corpusId]; + + this.app.ui.flash(`[${corpus.title}] New status: `, 'job'); + } + } +} diff --git a/app/static/js/app/ui.js b/app/static/js/app/extensions/ui.js similarity index 98% rename from app/static/js/app/ui.js rename to app/static/js/app/extensions/ui.js index 150cccec..439d247d 100644 --- a/app/static/js/app/ui.js +++ b/app/static/js/app/extensions/ui.js @@ -1,4 +1,4 @@ -nopaque.UIExtension = class UIExtension { +nopaque.app.extensions.UI = class UI { constructor(app) { this.app = app; } diff --git a/app/static/js/app/user-live-registry.js b/app/static/js/app/extensions/user-hub.js similarity index 61% rename from app/static/js/app/user-live-registry.js rename to app/static/js/app/extensions/user-hub.js index 27c1fe34..41f642ea 100644 --- a/app/static/js/app/user-live-registry.js +++ b/app/static/js/app/extensions/user-hub.js @@ -1,4 +1,4 @@ -nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends EventTarget { +nopaque.app.extensions.UserHub = class UserHub extends EventTarget { #data; constructor(app) { @@ -36,35 +36,33 @@ nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends Even #onPatch(patch) { // Filter patch to only include operations on users that are initialized - let filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`); - let filteredPatch = patch.filter(operation => filterRegExp.test(operation.path)); + const filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`); + const filteredPatch = patch.filter(operation => filterRegExp.test(operation.path)); // Apply patch jsonpatch.applyPatch(this.#data, filteredPatch); // Notify event listeners - let event = new CustomEvent('patch', {detail: filteredPatch}); - this.dispatchEvent(event); + const patchEventa = new CustomEvent('patch', {detail: filteredPatch}); + this.dispatchEvent(patchEventa); - /* // Notify event listeners. Event type: "patch *" - let event = new CustomEvent('patch *', {detail: filteredPatch}); - this.dispatchEvent(event); + const patchEvent = new CustomEvent('patch *', {detail: filteredPatch}); + this.dispatchEvent(patchEvent); // Group patches by user id: {: [op, ...], ...} - let patches = {}; - let matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`); + const patches = {}; + const matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`); for (let operation of filteredPatch) { - let [match, userId] = operation.path.match(matchRegExp); + const [match, userId] = operation.path.match(matchRegExp); if (!(userId in patches)) {patches[userId] = [];} patches[userId].push(operation); } // Notify event listeners. Event type: "patch " for (let [userId, patch] of Object.entries(patches)) { - let event = new CustomEvent(`patch ${userId}`, {detail: patch}); - this.dispatchEvent(event); + const userPatchEvent = new CustomEvent(`patch ${userId}`, {detail: patch}); + this.dispatchEvent(userPatchEvent); } - */ } } diff --git a/app/static/js/app/index.js b/app/static/js/app/index.js new file mode 100644 index 00000000..2d332651 --- /dev/null +++ b/app/static/js/app/index.js @@ -0,0 +1 @@ +nopaque.app = {}; diff --git a/app/static/js/resource-displays/resource-display.js b/app/static/js/resource-displays/resource-display.js index 9591834e..4752a340 100644 --- a/app/static/js/resource-displays/resource-display.js +++ b/app/static/js/resource-displays/resource-display.js @@ -6,10 +6,10 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay { this.userId = this.displayElement.dataset.userId; this.isInitialized = false; if (this.userId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.init(user); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/corpus-file-list.js b/app/static/js/resource-lists/corpus-file-list.js index 88761a47..8da872b8 100644 --- a/app/static/js/resource-lists/corpus-file-list.js +++ b/app/static/js/resource-lists/corpus-file-list.js @@ -14,10 +14,10 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false; this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false; if (this.userId === undefined || this.corpusId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { // TODO: Make this better understandable this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files)); this.isInitialized = true; diff --git a/app/static/js/resource-lists/corpus-follower-list.js b/app/static/js/resource-lists/corpus-follower-list.js index 8aa1f4d0..31031cbc 100644 --- a/app/static/js/resource-lists/corpus-follower-list.js +++ b/app/static/js/resource-lists/corpus-follower-list.js @@ -12,10 +12,10 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop this.userId = listContainerElement.dataset.userId; this.corpusId = listContainerElement.dataset.corpusId; if (this.userId === undefined || this.corpusId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { // TODO: Check if the following is better // let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations); // let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId); diff --git a/app/static/js/resource-lists/corpus-list.js b/app/static/js/resource-lists/corpus-list.js index 21060dbc..ed242868 100644 --- a/app/static/js/resource-lists/corpus-list.js +++ b/app/static/js/resource-lists/corpus-list.js @@ -11,10 +11,10 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li this.selectedItemIds = new Set(); this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(this.aggregateData(user)); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/job-input-list.js b/app/static/js/resource-lists/job-input-list.js index 7c74e852..55b539d1 100644 --- a/app/static/js/resource-lists/job-input-list.js +++ b/app/static/js/resource-lists/job-input-list.js @@ -8,10 +8,10 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; if (this.userId === undefined || this.jobId === undefined) {return;} - // app.liveUserRegistry.addEventListener('patch', (event) => { + // app.userHub.addEventListener('patch', (event) => { // if (this.isInitialized) {this.onPatch(event.detail);} // }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(Object.values(user.jobs[this.jobId].inputs)); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/job-list.js b/app/static/js/resource-lists/job-list.js index 8c352e7e..43a3b58f 100644 --- a/app/static/js/resource-lists/job-list.js +++ b/app/static/js/resource-lists/job-list.js @@ -12,10 +12,10 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re this.selectedItemIds = new Set(); this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(Object.values(user.jobs)); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/job-result-list.js b/app/static/js/resource-lists/job-result-list.js index be3d618c..c65ac2e3 100644 --- a/app/static/js/resource-lists/job-result-list.js +++ b/app/static/js/resource-lists/job-result-list.js @@ -8,10 +8,10 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; if (this.userId === undefined || this.jobId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(Object.values(user.jobs[this.jobId].results)); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js b/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js index e44e35b9..49b97bfc 100644 --- a/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js +++ b/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js @@ -8,10 +8,10 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi this.isInitialized = false; this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(Object.values(user.spacy_nlp_pipeline_models)); this.isInitialized = true; }); diff --git a/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js b/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js index 0b0cd384..f8e5986a 100644 --- a/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js +++ b/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js @@ -8,10 +8,10 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin this.isInitialized = false; this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.liveUserRegistry.addEventListener('patch', (event) => { + app.userHub.addEventListener('patch', (event) => { if (this.isInitialized) {this.onPatch(event.detail);} }); - app.liveUserRegistry.get(this.userId).then((user) => { + app.userHub.get(this.userId).then((user) => { this.add(Object.values(user.tesseract_ocr_pipeline_models)); this.isInitialized = true; }); diff --git a/app/templates/_base/scripts.html.j2 b/app/templates/_base/scripts.html.j2 index 9559ab1c..5bb3d1de 100644 --- a/app/templates/_base/scripts.html.j2 +++ b/app/templates/_base/scripts.html.j2 @@ -9,9 +9,13 @@ output='gen/nopaque.%(version)s.js', 'js/index.js', 'js/app.js', - 'js/app/ui.js', - 'js/app/user-live-registry.js', - 'js/app/users.js', + 'js/app/index.js', + 'js/app/endpoints/index.js', + 'js/app/endpoints/users.js', + 'js/app/extensions/index.js', + 'js/app/extensions/toaster.js', + 'js/app/extensions/ui.js', + 'js/app/extensions/user-hub.js', 'js/utils.js', 'js/forms/index.js', @@ -80,16 +84,18 @@ const app = new nopaque.App(); app.init(); - {% if current_user.is_authenticated -%} + {% if current_user.is_authenticated %} const currentUserId = {{ current_user.hashid|tojson }}; - app.liveUserRegistry.add(currentUserId) + app.userHub.add(currentUserId) .catch((error) => {throw JSON.stringify(error);}); - {% if not current_user.terms_of_use_accepted -%} + {% if not current_user.terms_of_use_accepted %} M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open(); - {% endif -%} - {% endif -%} + {% endif %} + {% else %} + const currentUserId = null; + {% endif %} // Display flashed messages for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {