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');}
);
});