From aafb3ca3ec98c1d3b4e9f09ac032fc80ce784c8e Mon Sep 17 00:00:00 2001
From: Patrick Jentsch
Date: Tue, 3 Dec 2024 15:59:08 +0100
Subject: [PATCH] Update javascript app structure
---
app/__init__.py | 3 +
app/blueprints/users/__init__.py | 2 +-
app/decorators.py | 4 +-
app/models/event_listeners.py | 15 ++-
app/namespaces/cqi_over_sio/__init__.py | 38 +++---
...tensions.py => cqi_extension_functions.py} | 28 ++---
app/namespaces/cqi_over_sio/utils.py | 2 +-
app/namespaces/users.py | 116 ++++++++++++++++++
app/static/js/app.js | 5 +-
app/static/js/app.users.js | 53 --------
app/static/js/{app.ui.js => app/ui.js} | 0
app/static/js/app/user-live-registry.js | 70 +++++++++++
app/static/js/app/users.js | 43 +++++++
.../js/resource-displays/resource-display.js | 21 ++--
.../js/resource-lists/corpus-file-list.js | 9 +-
.../js/resource-lists/corpus-follower-list.js | 11 +-
app/static/js/resource-lists/corpus-list.js | 8 +-
.../js/resource-lists/job-input-list.js | 6 +-
app/static/js/resource-lists/job-list.js | 8 +-
.../js/resource-lists/job-result-list.js | 8 +-
.../spacy-nlp-pipeline-model-list.js | 8 +-
.../tesseract-ocr-pipeline-model-list.js | 16 +--
app/templates/_base/scripts.html.j2 | 51 ++++----
23 files changed, 342 insertions(+), 183 deletions(-)
rename app/namespaces/cqi_over_sio/{cqi_extensions.py => cqi_extension_functions.py} (95%)
create mode 100644 app/namespaces/users.py
delete mode 100644 app/static/js/app.users.js
rename app/static/js/{app.ui.js => app/ui.js} (100%)
create mode 100644 app/static/js/app/user-live-registry.js
create mode 100644 app/static/js/app/users.js
diff --git a/app/__init__.py b/app/__init__.py
index eb4f7bd6..9953a1cf 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -132,6 +132,9 @@ def create_app(config: Config = Config) -> Flask:
# region SocketIO Namespaces
from .namespaces.cqi_over_sio import CQiOverSocketIONamespace
socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio'))
+
+ from .namespaces.users import UsersNamespace
+ socketio.on_namespace(UsersNamespace('/users'))
# endregion SocketIO Namespaces
# region Database event Listeners
diff --git a/app/blueprints/users/__init__.py b/app/blueprints/users/__init__.py
index d305e242..21e9c382 100644
--- a/app/blueprints/users/__init__.py
+++ b/app/blueprints/users/__init__.py
@@ -15,4 +15,4 @@ def before_request():
pass
-from . import cli, events, json_routes, routes, settings
+from . import cli, json_routes, routes, settings
diff --git a/app/decorators.py b/app/decorators.py
index 85fdd4ba..d54cfcd3 100644
--- a/app/decorators.py
+++ b/app/decorators.py
@@ -26,7 +26,7 @@ def socketio_login_required(f):
def wrapper(*args, **kwargs):
if current_user.is_authenticated:
return f(*args, **kwargs)
- return {'code': 401, 'body': 'Unauthorized'}
+ return {'status': 401, 'statusText': 'Unauthorized'}
return wrapper
@@ -35,7 +35,7 @@ def socketio_permission_required(permission):
@wraps(f)
def wrapper(*args, **kwargs):
if not current_user.can(permission):
- return {'code': 403, 'body': 'Forbidden'}
+ return {'status': 403, 'statusText': 'Forbidden'}
return f(*args, **kwargs)
return wrapper
return decorator
diff --git a/app/models/event_listeners.py b/app/models/event_listeners.py
index 98abe9ff..b9f9a652 100644
--- a/app/models/event_listeners.py
+++ b/app/models/event_listeners.py
@@ -42,8 +42,9 @@ def resource_after_delete(mapper, connection, resource):
'path': resource.jsonpatch_path
}
]
+ namespace = '/users'
room = f'/users/{resource.user_hashid}'
- socketio.emit('users.patch', jsonpatch, room=room)
+ socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
def cfa_after_delete(mapper, connection, cfa):
@@ -54,8 +55,9 @@ def cfa_after_delete(mapper, connection, cfa):
'path': jsonpatch_path
}
]
+ namespace = '/users'
room = f'/users/{cfa.corpus.user.hashid}'
- socketio.emit('users.patch', jsonpatch, room=room)
+ socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
def resource_after_insert(mapper, connection, resource):
@@ -69,8 +71,9 @@ def resource_after_insert(mapper, connection, resource):
'value': jsonpatch_value
}
]
+ namespace = '/users'
room = f'/users/{resource.user_hashid}'
- socketio.emit('users.patch', jsonpatch, room=room)
+ socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
def cfa_after_insert(mapper, connection, cfa):
@@ -83,8 +86,9 @@ def cfa_after_insert(mapper, connection, cfa):
'value': jsonpatch_value
}
]
+ namespace = '/users'
room = f'/users/{cfa.corpus.user.hashid}'
- socketio.emit('users.patch', jsonpatch, room=room)
+ socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
def resource_after_update(mapper, connection, resource):
@@ -109,8 +113,9 @@ def resource_after_update(mapper, connection, resource):
}
)
if jsonpatch:
+ namespace = '/users'
room = f'/users/{resource.user_hashid}'
- socketio.emit('users.patch', jsonpatch, room=room)
+ socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
def job_after_update(mapper, connection, job):
diff --git a/app/namespaces/cqi_over_sio/__init__.py b/app/namespaces/cqi_over_sio/__init__.py
index 0783ba3e..b219aeef 100644
--- a/app/namespaces/cqi_over_sio/__init__.py
+++ b/app/namespaces/cqi_over_sio/__init__.py
@@ -9,8 +9,8 @@ from threading import Lock
from app import db, docker_client, hashids, socketio
from app.decorators import socketio_login_required
from app.models import Corpus, CorpusStatus
-from . import cqi_extensions
-from .utils import CQiOverSocketIOSessionManager
+from . import cqi_extension_functions
+from .utils import SessionManager
'''
@@ -85,6 +85,16 @@ CQI_API_FUNCTION_NAMES = [
]
+CQI_EXTENSION_FUNCTION_NAMES = [
+ 'ext_corpus_update_db',
+ 'ext_corpus_static_data',
+ 'ext_corpus_paginate_corpus',
+ 'ext_cqp_paginate_subcorpus',
+ 'ext_cqp_partial_export_subcorpus',
+ 'ext_cqp_export_subcorpus',
+]
+
+
class CQiOverSocketIONamespace(Namespace):
@socketio_login_required
def on_connect(self):
@@ -135,25 +145,25 @@ class CQiOverSocketIONamespace(Namespace):
cqi_client = CQiClient(cqpserver_ip_address)
cqi_client_lock = Lock()
- CQiOverSocketIOSessionManager.setup()
- CQiOverSocketIOSessionManager.set_corpus_id(corpus_id)
- CQiOverSocketIOSessionManager.set_cqi_client(cqi_client)
- CQiOverSocketIOSessionManager.set_cqi_client_lock(cqi_client_lock)
+ SessionManager.setup()
+ SessionManager.set_corpus_id(corpus_id)
+ SessionManager.set_cqi_client(cqi_client)
+ SessionManager.set_cqi_client_lock(cqi_client_lock)
return {'code': 200, 'msg': 'OK'}
@socketio_login_required
def on_exec(self, fn_name: str, fn_args: dict = {}) -> dict:
try:
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
- cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
+ cqi_client = SessionManager.get_cqi_client()
+ cqi_client_lock = SessionManager.get_cqi_client_lock()
except KeyError:
return {'code': 424, 'msg': 'Failed Dependency'}
if fn_name in CQI_API_FUNCTION_NAMES:
fn = getattr(cqi_client.api, fn_name)
- elif fn_name in cqi_extensions.CQI_EXTENSION_FUNCTION_NAMES:
- fn = getattr(cqi_extensions, fn_name)
+ elif fn_name in cqi_extension_functions.CQI_EXTENSION_FUNCTION_NAMES:
+ fn = getattr(cqi_extension_functions, fn_name)
else:
return {'code': 400, 'msg': 'Bad Request'}
@@ -198,10 +208,10 @@ class CQiOverSocketIONamespace(Namespace):
def on_disconnect(self):
try:
- corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
- cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
- CQiOverSocketIOSessionManager.teardown()
+ corpus_id = SessionManager.get_corpus_id()
+ cqi_client = SessionManager.get_cqi_client()
+ cqi_client_lock = SessionManager.get_cqi_client_lock()
+ SessionManager.teardown()
except KeyError:
return
diff --git a/app/namespaces/cqi_over_sio/cqi_extensions.py b/app/namespaces/cqi_over_sio/cqi_extension_functions.py
similarity index 95%
rename from app/namespaces/cqi_over_sio/cqi_extensions.py
rename to app/namespaces/cqi_over_sio/cqi_extension_functions.py
index e0322672..ac05fa85 100644
--- a/app/namespaces/cqi_over_sio/cqi_extensions.py
+++ b/app/namespaces/cqi_over_sio/cqi_extension_functions.py
@@ -8,22 +8,12 @@ import json
import math
from app import db
from app.models import Corpus
-from .utils import CQiOverSocketIOSessionManager
-
-
-CQI_EXTENSION_FUNCTION_NAMES = [
- 'ext_corpus_update_db',
- 'ext_corpus_static_data',
- 'ext_corpus_paginate_corpus',
- 'ext_cqp_paginate_subcorpus',
- 'ext_cqp_partial_export_subcorpus',
- 'ext_cqp_export_subcorpus',
-]
+from .utils import SessionManager
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
- corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ corpus_id = SessionManager.get_corpus_id()
+ cqi_client = SessionManager.get_cqi_client()
db_corpus = Corpus.query.get(corpus_id)
cqi_corpus = cqi_client.corpora.get(corpus)
db_corpus.num_tokens = cqi_corpus.size
@@ -32,7 +22,7 @@ def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
def ext_corpus_static_data(corpus: str) -> dict:
- corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
+ corpus_id = SessionManager.get_corpus_id()
db_corpus = Corpus.query.get(corpus_id)
static_data_file_path = db_corpus.path / 'cwb' / 'static.json.gz'
@@ -40,7 +30,7 @@ def ext_corpus_static_data(corpus: str) -> dict:
with static_data_file_path.open('rb') as f:
return f.read()
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ cqi_client = SessionManager.get_cqi_client()
cqi_corpus = cqi_client.corpora.get(corpus)
cqi_p_attrs = cqi_corpus.positional_attributes.list()
cqi_s_attrs = cqi_corpus.structural_attributes.list()
@@ -168,7 +158,7 @@ def ext_corpus_paginate_corpus(
page: int = 1,
per_page: int = 20
) -> dict:
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ cqi_client = SessionManager.get_cqi_client()
cqi_corpus = cqi_client.corpora.get(corpus)
# Sanity checks
if (
@@ -215,7 +205,7 @@ def ext_cqp_paginate_subcorpus(
per_page: int = 20
) -> dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ cqi_client = SessionManager.get_cqi_client()
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
# Sanity checks
@@ -262,7 +252,7 @@ def ext_cqp_partial_export_subcorpus(
context: int = 50
) -> dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ cqi_client = SessionManager.get_cqi_client()
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_partial_export = _partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
@@ -271,7 +261,7 @@ def ext_cqp_partial_export_subcorpus(
def ext_cqp_export_subcorpus(subcorpus: str, context: int = 50) -> dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
- cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
+ cqi_client = SessionManager.get_cqi_client()
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_export = _export_subcorpus(cqi_subcorpus, context=context)
diff --git a/app/namespaces/cqi_over_sio/utils.py b/app/namespaces/cqi_over_sio/utils.py
index 5bef9f82..c94f0e0d 100644
--- a/app/namespaces/cqi_over_sio/utils.py
+++ b/app/namespaces/cqi_over_sio/utils.py
@@ -3,7 +3,7 @@ from threading import Lock
from flask import session
-class CQiOverSocketIOSessionManager:
+class SessionManager:
@staticmethod
def setup():
session['cqi_over_sio'] = {}
diff --git a/app/namespaces/users.py b/app/namespaces/users.py
new file mode 100644
index 00000000..03f017ef
--- /dev/null
+++ b/app/namespaces/users.py
@@ -0,0 +1,116 @@
+from flask import current_app, Flask
+from flask_login import current_user
+from flask_socketio import join_room, leave_room, Namespace
+from app import db, hashids, socketio
+from app.decorators import socketio_login_required
+from app.models import User
+
+
+def _delete_user(app: Flask, user_id: int):
+ with app.app_context():
+ user = User.query.get(user_id)
+ user.delete()
+ db.session.commit()
+
+
+class UsersNamespace(Namespace):
+ @socketio_login_required
+ def on_get(self, 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'}
+
+ return {
+ 'body': user.to_json_serializeable(
+ backrefs=True,
+ relationships=True
+ ),
+ 'status': 200,
+ 'statusText': 'OK'
+ }
+
+ @socketio_login_required
+ def on_subscribe(self, 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'}
+
+ join_room(f'/users/{user.hashid}')
+
+ return {'status': 200, 'statusText': 'OK'}
+
+ @socketio_login_required
+ def on_unsubscribe(self, 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'}
+
+ leave_room(f'/users/{user.hashid}')
+
+ return {'status': 200, 'statusText': 'OK'}
+
+ @socketio_login_required
+ def on_delete(self, 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'
+ }
diff --git a/app/static/js/app.js b/app/static/js/app.js
index 439bb9c1..e8ffddfc 100644
--- a/app/static/js/app.js
+++ b/app/static/js/app.js
@@ -1,10 +1,9 @@
nopaque.App = class App {
constructor() {
- this.data = {};
-
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);
}
@@ -29,5 +28,7 @@ nopaque.App = class App {
init() {
this.ui.init();
+ this.liveUserRegistry.init();
+ this.users.init();
}
};
diff --git a/app/static/js/app.users.js b/app/static/js/app.users.js
deleted file mode 100644
index fc3f0d3e..00000000
--- a/app/static/js/app.users.js
+++ /dev/null
@@ -1,53 +0,0 @@
-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/app.ui.js b/app/static/js/app/ui.js
similarity index 100%
rename from app/static/js/app.ui.js
rename to app/static/js/app/ui.js
diff --git a/app/static/js/app/user-live-registry.js b/app/static/js/app/user-live-registry.js
new file mode 100644
index 00000000..27c1fe34
--- /dev/null
+++ b/app/static/js/app/user-live-registry.js
@@ -0,0 +1,70 @@
+nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends EventTarget {
+ #data;
+
+ constructor(app) {
+ super();
+
+ this.app = app;
+
+ this.#data = {
+ users: {},
+ promises: {}
+ };
+ }
+
+ init() {
+ this.app.users.socket.on('patch', (patch) => {this.#onPatch(patch)});
+ }
+
+ add(userId) {
+ if (!(userId in this.#data.promises)) {
+ this.#data.promises[userId] = this.#add(userId);
+ }
+
+ return this.#data.promises[userId];
+ }
+
+ async #add(userId) {
+ await this.app.users.subscribe(userId);
+ this.#data.users[userId] = await this.app.users.get(userId);
+ }
+
+ async get(userId) {
+ await this.add(userId);
+ return this.#data.users[userId];
+ }
+
+ #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));
+
+ // Apply patch
+ jsonpatch.applyPatch(this.#data, filteredPatch);
+
+ // Notify event listeners
+ let event = new CustomEvent('patch', {detail: filteredPatch});
+ this.dispatchEvent(event);
+
+ /*
+ // Notify event listeners. Event type: "patch *"
+ let event = new CustomEvent('patch *', {detail: filteredPatch});
+ this.dispatchEvent(event);
+
+ // Group patches by user id: {: [op, ...], ...}
+ let patches = {};
+ let matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`);
+ for (let operation of filteredPatch) {
+ let [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);
+ }
+ */
+ }
+}
diff --git a/app/static/js/app/users.js b/app/static/js/app/users.js
new file mode 100644
index 00000000..0b98f95d
--- /dev/null
+++ b/app/static/js/app/users.js
@@ -0,0 +1,43 @@
+nopaque.UsersExtension = class UsersExtension {
+ 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);
+
+ if (response.status !== 200) {
+ throw new Error(`[${response.status}] ${response.statusText}`);
+ }
+
+ return response.body;
+ }
+
+ async subscribe(userId) {
+ const response = await this.socket.emitWithAck('subscribe', userId);
+
+ if (response.status != 200) {
+ throw new Error(`[${response.status}] ${response.statusText}`);
+ }
+ }
+
+ async unsubscribe(userId) {
+ const response = await this.socket.emitWithAck('unsubscribe', userId);
+
+ if (response.status != 200) {
+ throw new Error(`[${response.status}] ${response.statusText}`);
+ }
+ }
+
+ async delete(userId) {
+ const response = await this.socket.emitWithAck('delete', userId);
+
+ if (response.status != 202) {
+ throw new Error(`[${response.status}] ${response.statusText}`);
+ }
+ }
+}
diff --git a/app/static/js/resource-displays/resource-display.js b/app/static/js/resource-displays/resource-display.js
index e1036ef9..9591834e 100644
--- a/app/static/js/resource-displays/resource-display.js
+++ b/app/static/js/resource-displays/resource-display.js
@@ -5,19 +5,14 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
this.displayElement = displayElement;
this.userId = this.displayElement.dataset.userId;
this.isInitialized = false;
- if (this.userId) {
- app.users.subscribe(this.userId)
- .then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
- });
- app.users.get(this.userId)
- .then((user) => {
- this.init(user);
- this.isInitialized = true;
- });
- }
+ if (this.userId === undefined) {return;}
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
+ });
+ app.liveUserRegistry.get(this.userId).then((user) => {
+ this.init(user);
+ this.isInitialized = true;
+ });
}
init(user) {throw 'Not implemented';}
diff --git a/app/static/js/resource-lists/corpus-file-list.js b/app/static/js/resource-lists/corpus-file-list.js
index 3d819398..88761a47 100644
--- a/app/static/js/resource-lists/corpus-file-list.js
+++ b/app/static/js/resource-lists/corpus-file-list.js
@@ -14,12 +14,11 @@ 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.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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 98c78605..8aa1f4d0 100644
--- a/app/static/js/resource-lists/corpus-follower-list.js
+++ b/app/static/js/resource-lists/corpus-follower-list.js
@@ -12,15 +12,16 @@ 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.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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);
// this.add(filteredList);
+
+ // TODO: Make this better understandable
this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations));
this.isInitialized = true;
});
diff --git a/app/static/js/resource-lists/corpus-list.js b/app/static/js/resource-lists/corpus-list.js
index 6fac836f..21060dbc 100644
--- a/app/static/js/resource-lists/corpus-list.js
+++ b/app/static/js/resource-lists/corpus-list.js
@@ -11,12 +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.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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 7de10e45..7c74e852 100644
--- a/app/static/js/resource-lists/job-input-list.js
+++ b/app/static/js/resource-lists/job-input-list.js
@@ -8,8 +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.users.subscribe(this.userId);
- app.users.get(this.userId).then((user) => {
+ // app.liveUserRegistry.addEventListener('patch', (event) => {
+ // if (this.isInitialized) {this.onPatch(event.detail);}
+ // });
+ app.liveUserRegistry.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 a3fb87f1..8c352e7e 100644
--- a/app/static/js/resource-lists/job-list.js
+++ b/app/static/js/resource-lists/job-list.js
@@ -12,12 +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.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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 bc48e9a2..be3d618c 100644
--- a/app/static/js/resource-lists/job-result-list.js
+++ b/app/static/js/resource-lists/job-result-list.js
@@ -8,12 +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.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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 6b9f5e2a..e44e35b9 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,10 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
this.isInitialized = false;
this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
- app.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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 609dc1d1..0b0cd384 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,21 +8,11 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
this.isInitialized = false;
this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
- app.users.subscribe(this.userId).then((response) => {
- app.socket.on('users.patch', (patch) => {
- if (this.isInitialized) {this.onPatch(patch);}
- });
+ app.liveUserRegistry.addEventListener('patch', (event) => {
+ if (this.isInitialized) {this.onPatch(event.detail);}
});
- app.users.get(this.userId).then((user) => {
+ app.liveUserRegistry.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', '');
- }
- if (user.role.name !== ('Administrator' || 'Contributor')) {
- for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) {
- switchElement.setAttribute('disabled', '');
- }
- }
this.isInitialized = true;
});
}
diff --git a/app/templates/_base/scripts.html.j2 b/app/templates/_base/scripts.html.j2
index fd37daef..9559ab1c 100644
--- a/app/templates/_base/scripts.html.j2
+++ b/app/templates/_base/scripts.html.j2
@@ -9,8 +9,9 @@
output='gen/nopaque.%(version)s.js',
'js/index.js',
'js/app.js',
- 'js/app.ui.js',
- 'js/app.users.js',
+ 'js/app/ui.js',
+ 'js/app/user-live-registry.js',
+ 'js/app/users.js',
'js/utils.js',
'js/forms/index.js',
@@ -75,41 +76,35 @@
{% endassets -%}