mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-04 04:12:45 +00:00 
			
		
		
		
	Update JS code structure
This commit is contained in:
		@@ -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(`[<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');
 | 
			
		||||
  //   }
 | 
			
		||||
 | 
			
		||||
    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 = '<i class="left material-icons">book</i>';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'error': {
 | 
			
		||||
        iconPrefix = '<i class="error-color-text left material-icons">error</i>';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'job': {
 | 
			
		||||
        iconPrefix = '<i class="left nopaque-icons">J</i>';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'settings': {
 | 
			
		||||
        iconPrefix = '<i class="left material-icons">settings</i>';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        iconPrefix = '<i class="left material-icons">notifications</i>';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let toast = M.toast(
 | 
			
		||||
      {
 | 
			
		||||
        html: `
 | 
			
		||||
          <span>${iconPrefix}${message}</span>
 | 
			
		||||
          <button class="action-button btn-flat toast-action white-text" data-action="close">
 | 
			
		||||
            <i class="material-icons">close</i>
 | 
			
		||||
          </button>
 | 
			
		||||
        `.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(`[<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);
 | 
			
		||||
  }
 | 
			
		||||
  //   // 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();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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();});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								app/static/js/app.users.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/static/js/app.users.js
									
									
									
									
									
										Normal file
									
								
							@@ -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];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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');
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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', '');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user