Update javascript app structure

This commit is contained in:
Patrick Jentsch
2024-12-03 15:59:08 +01:00
parent 12a3ac1d5d
commit aafb3ca3ec
23 changed files with 342 additions and 183 deletions

View File

@ -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();
}
};

View File

@ -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];
}
}

View File

@ -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: {<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 <user-id>"
for (let [userId, patch] of Object.entries(patches)) {
let event = new CustomEvent(`patch ${userId}`, {detail: patch});
this.dispatchEvent(event);
}
*/
}
}

View File

@ -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}`);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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