Further js refactoring

This commit is contained in:
Patrick Jentsch 2024-12-05 14:26:05 +01:00
parent df2bffe0fd
commit c97b2a886e
18 changed files with 110 additions and 68 deletions

View File

@ -2,33 +2,19 @@ nopaque.App = class App {
constructor() { constructor() {
this.socket = io({transports: ['websocket'], upgrade: false}); this.socket = io({transports: ['websocket'], upgrade: false});
this.ui = new nopaque.UIExtension(this); // Endpoints
this.liveUserRegistry = new nopaque.LiveUserRegistryExtension(this); this.users = new nopaque.app.endpoints.Users(this);
this.users = new nopaque.UsersExtension(this);
// Extensions
this.toaster = new nopaque.app.extensions.Toaster(this);
this.ui = new nopaque.app.extensions.UI(this);
this.userHub = new nopaque.app.extensions.UserHub(this);
} }
// onPatch(patch) {
// // Filter Patch to only include operations on users that are initialized
// let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
// let filteredPatch = patch.filter(operation => regExp.test(operation.path));
// // Handle job status updates
// let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`);
// let subFilteredPatch = filteredPatch
// .filter((operation) => {return operation.op === 'replace';})
// .filter((operation) => {return subRegExp.test(operation.path);});
// for (let operation of subFilteredPatch) {
// let [match, userId, jobId] = operation.path.match(subRegExp);
// this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
// }
// // Apply Patch
// jsonpatch.applyPatch(this.data, filteredPatch);
// }
init() { init() {
// Initialize extensions
this.toaster.init();
this.ui.init(); this.ui.init();
this.liveUserRegistry.init(); this.userHub.init();
this.users.init();
} }
}; };

View File

@ -0,0 +1 @@
nopaque.app.endpoints = {};

View File

@ -1,12 +1,10 @@
nopaque.UsersExtension = class UsersExtension { nopaque.app.endpoints.Users = class Users {
constructor(app) { constructor(app) {
this.app = app; this.app = app;
this.socket = io('/users', {transports: ['websocket'], upgrade: false}); this.socket = io('/users', {transports: ['websocket'], upgrade: false});
} }
init() {}
async get(userId) { async get(userId) {
const response = await this.socket.emitWithAck('get', userId); const response = await this.socket.emitWithAck('get', userId);

View File

@ -0,0 +1 @@
nopaque.app.extensions = {};

View File

@ -0,0 +1,51 @@
nopaque.app.extensions.Toaster = class Toaster {
constructor(app) {
this.app = app;
}
init() {
this.app.userHub.addEventListener('patch', (event) => {this.#onPatch(event.detail);});
}
#onPatch(patch) {
// Handle job updates
const jobRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)`);
const jobPatch = patch.filter((operation) => {return jobRegExp.test(operation.path);});
this.#onJobPatch(jobPatch);
// Handle corpus updates
const corpusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)`);
const corpusPatch = patch.filter((operation) => {return corpusRegExp.test(operation.path);});
this.#onCorpusPatch(corpusPatch);
}
async #onJobPatch(patch) {
// Handle job status updates
const jobStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)/status$`);
const jobStatusPatch = patch.filter((operation) => {return operation.op === 'replace';});
for (let operation of jobStatusPatch) {
const [match, userId, jobId] = operation.path.match(jobStatusRegExp);
const user = await this.app.userHub.get(userId);
const job = user.jobs[jobId];
this.app.ui.flash(`[<a href="/jobs/${jobId}">${job.title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
}
}
async #onCorpusPatch(patch) {
// Handle job status updates
const corpusStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)/status$`);
const corpusStatusPatch = patch.filter((operation) => {return operation.op === 'replace';});
for (let operation of corpusStatusPatch) {
const [match, userId, corpusId] = operation.path.match(corpusStatusRegExp);
const user = await this.app.userHub.get(userId);
const corpus = user.corpora[corpusId];
this.app.ui.flash(`[<a href="/corpora/${corpusId}">${corpus.title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
}
}
}

View File

@ -1,4 +1,4 @@
nopaque.UIExtension = class UIExtension { nopaque.app.extensions.UI = class UI {
constructor(app) { constructor(app) {
this.app = app; this.app = app;
} }

View File

@ -1,4 +1,4 @@
nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends EventTarget { nopaque.app.extensions.UserHub = class UserHub extends EventTarget {
#data; #data;
constructor(app) { constructor(app) {
@ -36,35 +36,33 @@ nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends Even
#onPatch(patch) { #onPatch(patch) {
// Filter patch to only include operations on users that are initialized // Filter patch to only include operations on users that are initialized
let filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`); const filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`);
let filteredPatch = patch.filter(operation => filterRegExp.test(operation.path)); const filteredPatch = patch.filter(operation => filterRegExp.test(operation.path));
// Apply patch // Apply patch
jsonpatch.applyPatch(this.#data, filteredPatch); jsonpatch.applyPatch(this.#data, filteredPatch);
// Notify event listeners // Notify event listeners
let event = new CustomEvent('patch', {detail: filteredPatch}); const patchEventa = new CustomEvent('patch', {detail: filteredPatch});
this.dispatchEvent(event); this.dispatchEvent(patchEventa);
/*
// Notify event listeners. Event type: "patch *" // Notify event listeners. Event type: "patch *"
let event = new CustomEvent('patch *', {detail: filteredPatch}); const patchEvent = new CustomEvent('patch *', {detail: filteredPatch});
this.dispatchEvent(event); this.dispatchEvent(patchEvent);
// Group patches by user id: {<user-id>: [op, ...], ...} // Group patches by user id: {<user-id>: [op, ...], ...}
let patches = {}; const patches = {};
let matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`); const matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`);
for (let operation of filteredPatch) { for (let operation of filteredPatch) {
let [match, userId] = operation.path.match(matchRegExp); const [match, userId] = operation.path.match(matchRegExp);
if (!(userId in patches)) {patches[userId] = [];} if (!(userId in patches)) {patches[userId] = [];}
patches[userId].push(operation); patches[userId].push(operation);
} }
// Notify event listeners. Event type: "patch <user-id>" // Notify event listeners. Event type: "patch <user-id>"
for (let [userId, patch] of Object.entries(patches)) { for (let [userId, patch] of Object.entries(patches)) {
let event = new CustomEvent(`patch ${userId}`, {detail: patch}); const userPatchEvent = new CustomEvent(`patch ${userId}`, {detail: patch});
this.dispatchEvent(event); this.dispatchEvent(userPatchEvent);
} }
*/
} }
} }

View File

@ -0,0 +1 @@
nopaque.app = {};

View File

@ -6,10 +6,10 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
this.userId = this.displayElement.dataset.userId; this.userId = this.displayElement.dataset.userId;
this.isInitialized = false; this.isInitialized = false;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.init(user); this.init(user);
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -14,10 +14,10 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false; this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false; this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false;
if (this.userId === undefined || this.corpusId === undefined) {return;} if (this.userId === undefined || this.corpusId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
// TODO: Make this better understandable // TODO: Make this better understandable
this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files)); this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files));
this.isInitialized = true; this.isInitialized = true;

View File

@ -12,10 +12,10 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.corpusId = listContainerElement.dataset.corpusId; this.corpusId = listContainerElement.dataset.corpusId;
if (this.userId === undefined || this.corpusId === undefined) {return;} if (this.userId === undefined || this.corpusId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
// TODO: Check if the following is better // TODO: Check if the following is better
// let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations); // let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations);
// let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId); // let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId);

View File

@ -11,10 +11,10 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
this.selectedItemIds = new Set(); this.selectedItemIds = new Set();
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(this.aggregateData(user)); this.add(this.aggregateData(user));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -8,10 +8,10 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId; this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;} if (this.userId === undefined || this.jobId === undefined) {return;}
// app.liveUserRegistry.addEventListener('patch', (event) => { // app.userHub.addEventListener('patch', (event) => {
// if (this.isInitialized) {this.onPatch(event.detail);} // if (this.isInitialized) {this.onPatch(event.detail);}
// }); // });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(Object.values(user.jobs[this.jobId].inputs)); this.add(Object.values(user.jobs[this.jobId].inputs));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -12,10 +12,10 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
this.selectedItemIds = new Set(); this.selectedItemIds = new Set();
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(Object.values(user.jobs)); this.add(Object.values(user.jobs));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -8,10 +8,10 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId; this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;} if (this.userId === undefined || this.jobId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(Object.values(user.jobs[this.jobId].results)); this.add(Object.values(user.jobs[this.jobId].results));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -8,10 +8,10 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(Object.values(user.spacy_nlp_pipeline_models)); this.add(Object.values(user.spacy_nlp_pipeline_models));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -8,10 +8,10 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.liveUserRegistry.addEventListener('patch', (event) => { app.userHub.addEventListener('patch', (event) => {
if (this.isInitialized) {this.onPatch(event.detail);} if (this.isInitialized) {this.onPatch(event.detail);}
}); });
app.liveUserRegistry.get(this.userId).then((user) => { app.userHub.get(this.userId).then((user) => {
this.add(Object.values(user.tesseract_ocr_pipeline_models)); this.add(Object.values(user.tesseract_ocr_pipeline_models));
this.isInitialized = true; this.isInitialized = true;
}); });

View File

@ -9,9 +9,13 @@
output='gen/nopaque.%(version)s.js', output='gen/nopaque.%(version)s.js',
'js/index.js', 'js/index.js',
'js/app.js', 'js/app.js',
'js/app/ui.js', 'js/app/index.js',
'js/app/user-live-registry.js', 'js/app/endpoints/index.js',
'js/app/users.js', 'js/app/endpoints/users.js',
'js/app/extensions/index.js',
'js/app/extensions/toaster.js',
'js/app/extensions/ui.js',
'js/app/extensions/user-hub.js',
'js/utils.js', 'js/utils.js',
'js/forms/index.js', 'js/forms/index.js',
@ -80,16 +84,18 @@
const app = new nopaque.App(); const app = new nopaque.App();
app.init(); app.init();
{% if current_user.is_authenticated -%} {% if current_user.is_authenticated %}
const currentUserId = {{ current_user.hashid|tojson }}; const currentUserId = {{ current_user.hashid|tojson }};
app.liveUserRegistry.add(currentUserId) app.userHub.add(currentUserId)
.catch((error) => {throw JSON.stringify(error);}); .catch((error) => {throw JSON.stringify(error);});
{% if not current_user.terms_of_use_accepted -%} {% if not current_user.terms_of_use_accepted %}
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open(); M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
{% endif -%} {% endif %}
{% endif -%} {% else %}
const currentUserId = null;
{% endif %}
// Display flashed messages // Display flashed messages
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) { for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {