mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2025-10-15 03:01:57 +00:00
Update javascript app structure
This commit is contained in:
126
app/static/js/app/ui.js
Normal file
126
app/static/js/app/ui.js
Normal file
@@ -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 = '<i class="material-icons left">book</i>';
|
||||
break;
|
||||
}
|
||||
case 'job': {
|
||||
iconPrefix = '<i class="nopaque-icons left">J</i>';
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
iconPrefix = '<i class="material-icons left error-color-text">error</i>';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
iconPrefix = '<i class="material-icons left">notifications</i>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let toast = M.toast(
|
||||
{
|
||||
html: `
|
||||
<span>${iconPrefix}${message}</span>
|
||||
<button class="btn-flat toast-action white-text" data-toast-action="dismiss">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
`.trim()
|
||||
}
|
||||
);
|
||||
let dismissToastElement = toast.el.querySelector('.toast-action[data-toast-action="dismiss"]');
|
||||
dismissToastElement.addEventListener('click', () => {toast.dismiss();});
|
||||
}
|
||||
}
|
70
app/static/js/app/user-live-registry.js
Normal file
70
app/static/js/app/user-live-registry.js
Normal 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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
43
app/static/js/app/users.js
Normal file
43
app/static/js/app/users.js
Normal 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}`);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user