From 12a3ac1d5dae0829f20b3660086c7b96d6437a1a Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Mon, 2 Dec 2024 09:34:17 +0100 Subject: [PATCH] Update JS code structure --- app/blueprints/users/events.py | 50 ++++- app/models/event_listeners.py | 10 +- app/static/js/app.js | 208 ++---------------- app/static/js/app.ui.js | 126 +++++++++++ app/static/js/app.users.js | 53 +++++ .../corpus-analysis/concordance-extension.js | 8 +- .../js/corpus-analysis/reader-extension.js | 4 +- app/static/js/forms/base-form.js | 2 +- app/static/js/requests/index.js | 8 +- .../js/resource-displays/resource-display.js | 6 +- .../js/resource-lists/corpus-file-list.js | 6 +- .../js/resource-lists/corpus-follower-list.js | 6 +- app/static/js/resource-lists/corpus-list.js | 6 +- .../js/resource-lists/job-input-list.js | 4 +- app/static/js/resource-lists/job-list.js | 6 +- .../js/resource-lists/job-result-list.js | 6 +- .../spacy-nlp-pipeline-model-list.js | 6 +- .../tesseract-ocr-pipeline-model-list.js | 6 +- app/templates/_base/scripts.html.j2 | 8 +- app/templates/corpora/corpus.html.j2 | 4 +- app/templates/corpora/public_corpus.html.j2 | 4 +- 21 files changed, 295 insertions(+), 242 deletions(-) create mode 100644 app/static/js/app.ui.js create mode 100644 app/static/js/app.users.js diff --git a/app/blueprints/users/events.py b/app/blueprints/users/events.py index 254a5ca6..4f1284a6 100644 --- a/app/blueprints/users/events.py +++ b/app/blueprints/users/events.py @@ -1,11 +1,51 @@ +from flask import current_app, Flask from flask_login import current_user from flask_socketio import join_room, leave_room -from app import hashids, socketio +from app import db, hashids, socketio from app.decorators import socketio_login_required from app.models import User -@socketio.on('users.get_user') +def _delete_user(app: Flask, user_id: int): + with app.app_context(): + user = User.query.get(user_id) + user.delete() + db.session.commit() + + +@socketio.on('users.delete') +@socketio_login_required +def delete_user(user_hashid: str) -> dict: + user_id = hashids.decode(user_hashid) + + if not isinstance(user_id, int): + return {'status': 400, 'statusText': 'Bad Request'} + + user = User.query.get(user_id) + + if user is None: + return {'status': 404, 'statusText': 'Not found'} + + if not ( + user == current_user + or current_user.is_administrator + ): + return {'status': 403, 'statusText': 'Forbidden'} + + socketio.start_background_task( + _delete_user, + current_app._get_current_object(), + user.id + ) + + return { + 'body': f'User "{user.username}" marked for deletion', + 'status': 202, + 'statusText': 'Accepted' + } + + +@socketio.on('users.get') @socketio_login_required def get_user(user_hashid: str) -> dict: user_id = hashids.decode(user_hashid) @@ -34,7 +74,7 @@ def get_user(user_hashid: str) -> dict: } -@socketio.on('users.subscribe_user') +@socketio.on('users.subscribe') @socketio_login_required def subscribe_user(user_hashid: str) -> dict: user_id = hashids.decode(user_hashid) @@ -58,9 +98,9 @@ def subscribe_user(user_hashid: str) -> dict: return {'status': 200, 'statusText': 'OK'} -@socketio.on('users.unsubscribe_user') +@socketio.on('users.unsubscribe') @socketio_login_required -def on_unsubscribe_user(user_hashid: str) -> dict: +def unsubscribe_user(user_hashid: str) -> dict: user_id = hashids.decode(user_hashid) if not isinstance(user_id, int): diff --git a/app/models/event_listeners.py b/app/models/event_listeners.py index 115f2a73..98abe9ff 100644 --- a/app/models/event_listeners.py +++ b/app/models/event_listeners.py @@ -43,7 +43,7 @@ def resource_after_delete(mapper, connection, resource): } ] room = f'/users/{resource.user_hashid}' - socketio.emit('patch_user', jsonpatch, room=room) + socketio.emit('users.patch', jsonpatch, room=room) def cfa_after_delete(mapper, connection, cfa): @@ -55,7 +55,7 @@ def cfa_after_delete(mapper, connection, cfa): } ] room = f'/users/{cfa.corpus.user.hashid}' - socketio.emit('patch_user', jsonpatch, room=room) + socketio.emit('users.patch', jsonpatch, room=room) def resource_after_insert(mapper, connection, resource): @@ -70,7 +70,7 @@ def resource_after_insert(mapper, connection, resource): } ] room = f'/users/{resource.user_hashid}' - socketio.emit('patch_user', jsonpatch, room=room) + socketio.emit('users.patch', jsonpatch, room=room) def cfa_after_insert(mapper, connection, cfa): @@ -84,7 +84,7 @@ def cfa_after_insert(mapper, connection, cfa): } ] room = f'/users/{cfa.corpus.user.hashid}' - socketio.emit('patch_user', jsonpatch, room=room) + socketio.emit('users.patch', jsonpatch, room=room) def resource_after_update(mapper, connection, resource): @@ -110,7 +110,7 @@ def resource_after_update(mapper, connection, resource): ) if jsonpatch: room = f'/users/{resource.user_hashid}' - socketio.emit('patch_user', jsonpatch, room=room) + socketio.emit('users.patch', jsonpatch, room=room) def job_after_update(mapper, connection, job): diff --git a/app/static/js/app.js b/app/static/js/app.js index c1aafc59..439bb9c1 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -1,201 +1,33 @@ nopaque.App = class App { - #promises; - constructor() { - this.data = { - users: {} - }; - - this.#promises = { - getUser: {}, - subscribeUser: {} - }; + this.data = {}; this.socket = io({transports: ['websocket'], upgrade: false}); - this.socket.on('patch_user', (patch) => {this.onPatch(patch);}); + this.ui = new nopaque.UIExtension(this); + this.users = new nopaque.UsersExtension(this); } - getUser(userId) { - if (userId in this.#promises.getUser) { - return this.#promises.getUser[userId]; - } + // 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)); - this.#promises.getUser[userId] = new Promise((resolve, reject) => { - this.socket.emit('users.get_user', userId, (response) => { - if (response.status === 200) { - this.data.users[userId] = response.body; - resolve(this.data.users[userId]); - } else { - reject(`[${response.status}] ${response.statusText}`); - } - }); - }); + // // 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'); + // } - return this.#promises.getUser[userId]; - } - - subscribeUser(userId) { - if (userId in this.#promises.subscribeUser) { - return this.#promises.subscribeUser[userId]; - } - - this.#promises.subscribeUser[userId] = new Promise((resolve, reject) => { - this.socket.emit('users.subscribe_user', userId, (response) => { - if (response.status === 200) { - resolve(response); - } else { - reject(response); - } - }); - }); - - return this.#promises.subscribeUser[userId]; - } - - flash(message, category) { - let iconPrefix = ''; - switch (category) { - case 'corpus': { - iconPrefix = 'book'; - break; - } - case 'error': { - iconPrefix = 'error'; - break; - } - case 'job': { - iconPrefix = 'J'; - break; - } - case 'settings': { - iconPrefix = 'settings'; - break; - } - default: { - iconPrefix = 'notifications'; - break; - } - } - let toast = M.toast( - { - html: ` - ${iconPrefix}${message} - - `.trim() - } - ); - let toastCloseActionElement = toast.el.querySelector('.action-button[data-action="close"]'); - toastCloseActionElement.addEventListener('click', () => {toast.dismiss();}); - } - - 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); - } + // // Apply Patch + // jsonpatch.applyPatch(this.data, filteredPatch); + // } init() { - this.initUi(); - } - - initUi() { - /* Pre-Initialization fixes */ - // #region - - // Flask-WTF sets the standard HTML maxlength Attribute on input/textarea - // elements to specify their maximum length (in characters). Unfortunatly - // Materialize won't recognize the maxlength Attribute, instead it uses - // the data-length Attribute. It's conversion time :) - for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) { - elem.dataset.length = elem.getAttribute('maxlength'); - elem.removeAttribute('maxlength'); - } - - // To work around some limitations with the Form setup of Flask-WTF. - // HTML option elements with an empty value are considered as placeholder - // elements. The user should not be able to actively select these options. - // So they get the disabled attribute. - for (let optionElement of document.querySelectorAll('option[value=""]')) { - optionElement.disabled = true; - } - - // TODO: Check why we are doing this. - for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) { - for (let c of optgroupElement.children) { - optgroupElement.parentElement.insertAdjacentElement('afterbegin', c); - } - optgroupElement.remove(); - } - // #endregion - - - /* Initialize Materialize Components */ - // #region - - // Automatically initialize Materialize Components that do not require - // additional configuration. - M.AutoInit(); - - // CharacterCounters - // Materialize didn't include the CharacterCounter plugin within the - // AutoInit method (maybe they forgot it?). Anyway... We do it here. :) - M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)')); - - // Header navigation processes and services Dropdown. - M.Dropdown.init( - document.querySelector('#navbar-data-processing-and-analysis-dropdown-trigger'), - { - constrainWidth: false, - container: document.querySelector('#dropdowns'), - coverTrigger: false - } - ); - - // Header navigation account Dropdown. - M.Dropdown.init( - document.querySelector('#navbar-account-dropdown-trigger'), - { - alignment: 'right', - constrainWidth: false, - container: document.querySelector('#dropdowns'), - coverTrigger: false - } - ); - - // Terms of use modal - M.Modal.init( - document.querySelector('#terms-of-use-modal'), - { - dismissible: false, - onCloseEnd: (modalElement) => { - nopaque.requests.users.entity.acceptTermsOfUse(); - } - } - ); - // #endregion - - - /* Initialize nopaque Components */ - // #region - nopaque.resource_displays.AutoInit(); - nopaque.resource_lists.AutoInit(); - nopaque.forms.AutoInit(); - // #endregion + this.ui.init(); } }; diff --git a/app/static/js/app.ui.js b/app/static/js/app.ui.js new file mode 100644 index 00000000..150cccec --- /dev/null +++ b/app/static/js/app.ui.js @@ -0,0 +1,126 @@ +nopaque.UIExtension = class UIExtension { + constructor(app) { + this.app = app; + } + + init() { + /* Pre-Initialization fixes */ + // #region + + // Flask-WTF sets the standard HTML maxlength Attribute on input/textarea + // elements to specify their maximum length (in characters). Unfortunatly + // Materialize won't recognize the maxlength Attribute, instead it uses + // the data-length Attribute. It's conversion time :) + for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) { + elem.dataset.length = elem.getAttribute('maxlength'); + elem.removeAttribute('maxlength'); + } + + // To work around some limitations with the Form setup of Flask-WTF. + // HTML option elements with an empty value are considered as placeholder + // elements. The user should not be able to actively select these options. + // So they get the disabled attribute. + for (let optionElement of document.querySelectorAll('option[value=""]')) { + optionElement.disabled = true; + } + + // TODO: Check why we are doing this. + for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) { + for (let c of optgroupElement.children) { + optgroupElement.parentElement.insertAdjacentElement('afterbegin', c); + } + optgroupElement.remove(); + } + // #endregion + + + /* Initialize Materialize Components */ + // #region + + // Automatically initialize Materialize Components that do not require + // additional configuration. + M.AutoInit(); + + // CharacterCounters + // Materialize didn't include the CharacterCounter plugin within the + // AutoInit method (maybe they forgot it?). Anyway... We do it here. :) + M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)')); + + // Header navigation processes and services Dropdown. + M.Dropdown.init( + document.querySelector('#navbar-data-processing-and-analysis-dropdown-trigger'), + { + constrainWidth: false, + container: document.querySelector('#dropdowns'), + coverTrigger: false + } + ); + + // Header navigation account Dropdown. + M.Dropdown.init( + document.querySelector('#navbar-account-dropdown-trigger'), + { + alignment: 'right', + constrainWidth: false, + container: document.querySelector('#dropdowns'), + coverTrigger: false + } + ); + + // Terms of use modal + M.Modal.init( + document.querySelector('#terms-of-use-modal'), + { + dismissible: false, + onCloseEnd: (modalElement) => { + nopaque.requests.users.entity.acceptTermsOfUse(); + } + } + ); + // #endregion + + + /* Initialize nopaque Components */ + // #region + nopaque.resource_displays.AutoInit(); + nopaque.resource_lists.AutoInit(); + nopaque.forms.AutoInit(); + // #endregion + } + + flash(message, category) { + let iconPrefix; + + switch (category) { + case 'corpus': { + iconPrefix = 'book'; + break; + } + case 'job': { + iconPrefix = 'J'; + break; + } + case 'error': { + iconPrefix = 'error'; + break; + } + default: { + iconPrefix = 'notifications'; + break; + } + } + + let toast = M.toast( + { + html: ` + ${iconPrefix}${message} + + `.trim() + } + ); + let dismissToastElement = toast.el.querySelector('.toast-action[data-toast-action="dismiss"]'); + dismissToastElement.addEventListener('click', () => {toast.dismiss();}); + } +} diff --git a/app/static/js/app.users.js b/app/static/js/app.users.js new file mode 100644 index 00000000..fc3f0d3e --- /dev/null +++ b/app/static/js/app.users.js @@ -0,0 +1,53 @@ +nopaque.UsersExtension = class UsersExtension { + #data; + #promises; + + constructor(app) { + this.app = app; + + this.#data = {}; + this.app.data.users = this.#data; + + this.#promises = { + get: {}, + subscribe: {} + }; + } + + async #get(userId) { + const response = await this.app.socket.emitWithAck('users.get', userId); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + this.#data[userId] = response.body; + return this.#data[userId]; + } + + get(userId) { + if (userId in this.#promises.get) { + return this.#promises.get[userId]; + } + + this.#promises.get[userId] = this.#get(userId); + return this.#promises.get[userId]; + } + + async #subscribe(userId) { + const response = await this.app.socket.emitWithAck('users.subscribe', userId); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + } + + subscribe(userId) { + if (userId in this.#promises.subscribe) { + return this.#promises.subscribe[userId]; + } + + this.#promises.subscribe[userId] = this.#subscribe(userId); + return this.#promises.subscribe[userId]; + } +} diff --git a/app/static/js/corpus-analysis/concordance-extension.js b/app/static/js/corpus-analysis/concordance-extension.js index f32d6781..7137a37d 100644 --- a/app/static/js/corpus-analysis/concordance-extension.js +++ b/app/static/js/corpus-analysis/concordance-extension.js @@ -66,7 +66,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension { errorString += `${error.constructor.name}`; this.elements.error.innerText = errorString; this.elements.error.classList.remove('hide'); - app.flash(errorString, 'error'); + app.ui.flash(errorString, 'error'); this.elements.progress.classList.add('hide'); } this.app.enableActionElements(); @@ -239,7 +239,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension { if (subcorpus.selectedItems.size === 0) { this.elements.progress.classList.add('hide'); this.app.enableActionElements(); - app.flash('No matches selected', 'error'); + app.ui.flash('No matches selected', 'error'); return; } promise = subcorpus.o.partialExport([...subcorpus.selectedItems], 50); @@ -298,7 +298,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension { let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus]; subcorpus.o.drop().then( (cQiStatus) => { - app.flash(`${subcorpus.o.name} deleted`, 'corpus'); + app.ui.flash(`${subcorpus.o.name} deleted`, 'corpus'); delete this.data.subcorpora[subcorpus.o.name]; this.settings.selectedSubcorpus = undefined; for (let subcorpusName in this.data.subcorpora) { @@ -320,7 +320,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension { }, (cqiError) => { let errorString = `${cqiError.code}: ${cqiError.constructor.name}`; - app.flash(errorString, 'error'); + app.ui.flash(errorString, 'error'); } ); }); diff --git a/app/static/js/corpus-analysis/reader-extension.js b/app/static/js/corpus-analysis/reader-extension.js index 3c7fec91..bca2a10d 100644 --- a/app/static/js/corpus-analysis/reader-extension.js +++ b/app/static/js/corpus-analysis/reader-extension.js @@ -46,7 +46,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension { if ('description' in error) {errorString += `: ${error.description}`;} this.elements.error.innerText = errorString; this.elements.error.classList.remove('hide'); - app.flash(errorString, 'error'); + app.ui.flash(errorString, 'error'); this.elements.progress.classList.add('hide'); } this.app.enableActionElements(); @@ -205,7 +205,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension { ` ); this.elements.corpusPagination.appendChild(pageElement); - + for (let paginateTriggerElement of this.elements.corpusPagination.querySelectorAll('.pagination-trigger[data-target]')) { paginateTriggerElement.addEventListener('click', (event) => { event.preventDefault(); diff --git a/app/static/js/forms/base-form.js b/app/static/js/forms/base-form.js index 5b4b9e41..29551306 100644 --- a/app/static/js/forms/base-form.js +++ b/app/static/js/forms/base-form.js @@ -101,7 +101,7 @@ nopaque.forms.BaseForm = class BaseForm { } } if (request.status === 500) { - app.flash('Internal Server Error', 'error'); + app.ui.flash('Internal Server Error', 'error'); } modal.close(); }); diff --git a/app/static/js/requests/index.js b/app/static/js/requests/index.js index 177eca88..a61d04ce 100644 --- a/app/static/js/requests/index.js +++ b/app/static/js/requests/index.js @@ -18,23 +18,23 @@ nopaque.requests.JSONfetch = (input, init={}) => { } if (response.status === 204) { return; - } + } response.json() .then( (json) => { let message = json.message; let category = json.category || 'message'; if (message) { - app.flash(message, category); + app.ui.flash(message, category); } }, (error) => { - app.flash(`[${response.status}]: ${response.statusText}`, 'error'); + app.ui.flash(`[${response.status}]: ${response.statusText}`, 'error'); } ); }, (response) => { - app.flash('Something went wrong', 'error'); + app.ui.flash('Something went wrong', 'error'); reject(response); } ); diff --git a/app/static/js/resource-displays/resource-display.js b/app/static/js/resource-displays/resource-display.js index 9d327131..e1036ef9 100644 --- a/app/static/js/resource-displays/resource-display.js +++ b/app/static/js/resource-displays/resource-display.js @@ -6,13 +6,13 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay { this.userId = this.displayElement.dataset.userId; this.isInitialized = false; if (this.userId) { - app.subscribeUser(this.userId) + app.users.subscribe(this.userId) .then((response) => { - app.socket.on('patch_user', (patch) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId) + app.users.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 fd3acea1..3d819398 100644 --- a/app/static/js/resource-lists/corpus-file-list.js +++ b/app/static/js/resource-lists/corpus-file-list.js @@ -14,12 +14,12 @@ 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.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.get(this.userId).then((user) => { 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 0a0679d0..98c78605 100644 --- a/app/static/js/resource-lists/corpus-follower-list.js +++ b/app/static/js/resource-lists/corpus-follower-list.js @@ -12,12 +12,12 @@ 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.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.get(this.userId).then((user) => { // let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations); // let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId); // this.add(filteredList); diff --git a/app/static/js/resource-lists/corpus-list.js b/app/static/js/resource-lists/corpus-list.js index b362bd78..6fac836f 100644 --- a/app/static/js/resource-lists/corpus-list.js +++ b/app/static/js/resource-lists/corpus-list.js @@ -11,12 +11,12 @@ 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.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.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 270c6d2d..7de10e45 100644 --- a/app/static/js/resource-lists/job-input-list.js +++ b/app/static/js/resource-lists/job-input-list.js @@ -8,8 +8,8 @@ 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.subscribeUser(this.userId); - app.getUser(this.userId).then((user) => { + app.users.subscribe(this.userId); + app.users.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 336562ff..a3fb87f1 100644 --- a/app/static/js/resource-lists/job-list.js +++ b/app/static/js/resource-lists/job-list.js @@ -12,12 +12,12 @@ 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.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.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 f361306c..bc48e9a2 100644 --- a/app/static/js/resource-lists/job-result-list.js +++ b/app/static/js/resource-lists/job-result-list.js @@ -8,12 +8,12 @@ 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.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.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 eb340a78..6b9f5e2a 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,12 +8,12 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi this.isInitialized = false; this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.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 86d673d9..609dc1d1 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,12 +8,12 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin this.isInitialized = false; this.userId = listContainerElement.dataset.userId; if (this.userId === undefined) {return;} - app.subscribeUser(this.userId).then((response) => { - app.socket.on('patch_user', (patch) => { + app.users.subscribe(this.userId).then((response) => { + app.socket.on('users.patch', (patch) => { if (this.isInitialized) {this.onPatch(patch);} }); }); - app.getUser(this.userId).then((user) => { + app.users.get(this.userId).then((user) => { this.add(Object.values(user.tesseract_ocr_pipeline_models)); for (let uncheckedCheckbox of this.listjs.list.querySelectorAll('input[data-checked="True"]')) { uncheckedCheckbox.setAttribute('checked', ''); diff --git a/app/templates/_base/scripts.html.j2 b/app/templates/_base/scripts.html.j2 index 1f6d5af0..fd37daef 100644 --- a/app/templates/_base/scripts.html.j2 +++ b/app/templates/_base/scripts.html.j2 @@ -9,6 +9,8 @@ output='gen/nopaque.%(version)s.js', 'js/index.js', 'js/app.js', + 'js/app.ui.js', + 'js/app.users.js', 'js/utils.js', 'js/forms/index.js', @@ -82,11 +84,11 @@ const currentUserId = {{ current_user.hashid|tojson }}; // Subscribe to the current user's data events - app.subscribeUser(currentUserId) + app.users.subscribe(currentUserId) .catch((error) => {throw JSON.stringify(error);}); // Get the current user's data - app.getUser(currentUserId, true, true) + app.users.get(currentUserId, true, true) .catch((error) => {throw JSON.stringify(error);}); {% if not current_user.terms_of_use_accepted -%} @@ -96,7 +98,7 @@ // Display flashed messages for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) { - app.flash(message, message); + app.ui.flash(message, message); } diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 740f08b5..56dba7cb 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -364,8 +364,8 @@ shareLinkModalCreateButtonElement.addEventListener('click', (event) => { shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => { navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value) .then( - () => {app.flash('Copied!');}, - () => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');} + () => {app.ui.flash('Copied!');}, + () => {app.ui.flash('Could not copy to clipboard. Please copy manually.', 'error');} ); }); diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 index 9a835601..a765526f 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/public_corpus.html.j2 @@ -396,8 +396,8 @@ shareLinkModalCreateButtonElement.addEventListener('click', (event) => { shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => { navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value) .then( - () => {app.flash('Copied!');}, - () => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');} + () => {app.ui.flash('Copied!');}, + () => {app.ui.flash('Could not copy to clipboard. Please copy manually.', 'error');} ); });