mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-04 04:12:45 +00:00 
			
		
		
		
	Seperate Lists and Displays in seperated files
This commit is contained in:
		@@ -1,500 +0,0 @@
 | 
			
		||||
class RessourceList {
 | 
			
		||||
  /* A wrapper class for the list.js list.
 | 
			
		||||
   * This class is not meant to be used directly, instead it should be used as
 | 
			
		||||
   * a base class for concrete ressource list implementations.
 | 
			
		||||
   */
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    if (listElement.dataset.userId) {
 | 
			
		||||
      if (listElement.dataset.userId in nopaque.appClient.users) {
 | 
			
		||||
        this.user = nopaque.appClient.users[listElement.dataset.userId];
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`User not found: ${listElement.dataset.userId}`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.user = nopaque.appClient.users.self;
 | 
			
		||||
    }
 | 
			
		||||
    this.list = new List(listElement, {...RessourceList.options, ...options});
 | 
			
		||||
    this.list.list.innerHTML = `<tr class="show-if-only-child">
 | 
			
		||||
                                  <td colspan="3">
 | 
			
		||||
                                    <span class="card-title"><i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here...</span>
 | 
			
		||||
                                    <p>No ressource available (yet).</p>
 | 
			
		||||
                                  </td>
 | 
			
		||||
                                </tr>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  eventHandler(eventType, payload) {
 | 
			
		||||
    switch (eventType) {
 | 
			
		||||
      case 'init':
 | 
			
		||||
        this.init(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'patch':
 | 
			
		||||
        this.patch(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown event type: ${eventType}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(ressources) {
 | 
			
		||||
    this.list.clear();
 | 
			
		||||
    this.add(Object.values(ressources));
 | 
			
		||||
    this.list.sort('id', {order: 'desc'});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    /*
 | 
			
		||||
     * It's not possible to generalize a patch Handler for all type of
 | 
			
		||||
     * ressources. So this method is meant to be an interface.
 | 
			
		||||
     */
 | 
			
		||||
    console.error('patch method not implemented!');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  add(values) {
 | 
			
		||||
    let ressources = Array.isArray(values) ? values : [values];
 | 
			
		||||
    if (typeof this.preprocessRessource === 'function') {
 | 
			
		||||
      ressources = ressources.map(ressource => this.preprocessRessource(ressource));
 | 
			
		||||
    }
 | 
			
		||||
    // Set a callback function ('() => {return;}') to force List.js perform the
 | 
			
		||||
    // add method asynchronous: https://listjs.com/api/#add
 | 
			
		||||
    this.list.add(ressources, () => {return;});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  remove(id) {
 | 
			
		||||
    this.list.remove('id', id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  replace(id, valueName, newValue) {
 | 
			
		||||
      this.list.get('id', id)[0].values({[valueName]: newValue});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CorpusList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...CorpusList.options, ...options});
 | 
			
		||||
    this.corpora = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpora) {
 | 
			
		||||
    this.corpora = corpora;
 | 
			
		||||
    super.init(corpora);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let corpusId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    let action = (actionButtonElement === null) ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'analyse':
 | 
			
		||||
        window.location.href = this.corpora[corpusId].analysis_url;
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm corpus deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the corpus <b>${this.corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.corpora[corpusId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        // TODO: handle unprepared corpora
 | 
			
		||||
        window.location.href = this.corpora[corpusId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: ${action}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re, valueName;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{corpusId}
 | 
			
		||||
          re = /^\/corpora\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case 'add' ;)
 | 
			
		||||
          re = /^\/corpora\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{corpusId}/{status || description || title}
 | 
			
		||||
          re = /^\/corpora\/(\d+)\/(status|description|title)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id, valueName] = operation.path.match(re);
 | 
			
		||||
            this.replace(id, valueName, operation.value);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(corpus) {
 | 
			
		||||
    return {id: corpus.id,
 | 
			
		||||
            status: corpus.status,
 | 
			
		||||
            description: corpus.description,
 | 
			
		||||
            title: corpus.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
CorpusList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><a class="btn-floating disabled"><i class="material-icons">book</i></a></td>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i></td>
 | 
			
		||||
           <td><span class="badge new status" data-badge-caption=""></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title']
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CorpusFileList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...CorpusFileList.options, ...options});
 | 
			
		||||
    this.corpus = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.corpusId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpus) {
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
    super.init(this.corpus.files);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let corpusFileId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm corpus deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the corpus file <b>${this.corpus.files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.corpus.files[corpusFileId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.corpus.files[corpusFileId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        window.location.href = this.corpus.files[corpusFileId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{this.corpus.id}/files/{corpusFileId}
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/files/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case add ;)
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/files/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(corpusFile) {
 | 
			
		||||
    return {id: corpusFile.id, author: corpusFile.author, filename: corpusFile.filename, 'publishing-year': corpusFile.publishing_year, title: corpusFile.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
CorpusFileList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td><span class="author"></span></td>
 | 
			
		||||
           <td><span class="title"></span></td>
 | 
			
		||||
           <td><span class="publishing-year"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'author', 'filename', 'publishing-year', 'title']
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobList.options, ...options});
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm job deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the job <b>${this.user.data.jobs[jobId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.user.data.jobs[jobId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        window.location.href = this.user.data.jobs[jobId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re, valueName;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{jobId}
 | 
			
		||||
          re = /^\/jobs\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case add ;)
 | 
			
		||||
          re = /^\/jobs\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{jobId}/{service || status || description || title}
 | 
			
		||||
          re = /^\/jobs\/(\d+)\/(status|description|title)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id, valueName] = operation.path.match(re);
 | 
			
		||||
            this.replace(id, valueName, operation.value);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(job) {
 | 
			
		||||
    return {id: job.id,
 | 
			
		||||
            service: job.service,
 | 
			
		||||
            status: job.status,
 | 
			
		||||
            description: job.description,
 | 
			
		||||
            title: job.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><a class="btn-floating disabled"><i class="material-icons service"></i></a></td>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i></td>
 | 
			
		||||
           <td><span class="badge new status" data-badge-caption=""></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title']
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobInputList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobInputList.options, ...options});
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    super.init(this.job.inputs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobInputId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.job.inputs[jobInputId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(jobInput) {
 | 
			
		||||
    return {id: jobInput.id, filename: jobInput.filename};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobInputList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'filename']
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobResultList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobResultList.options, ...options});
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    super.init(this.job.results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobResultId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.job.results[jobResultId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{this.job.id}/results/{jobResultId}
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/results/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(jobResult) {
 | 
			
		||||
    let description;
 | 
			
		||||
    if (jobResult.filename.endsWith('.pdf.zip')) {
 | 
			
		||||
      description = 'PDF files with text layer';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.txt.zip')) {
 | 
			
		||||
      description = 'Raw text files';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.vrt.zip')) {
 | 
			
		||||
      description = 'VRT compliant files including the NLP data';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.xml.zip')) {
 | 
			
		||||
      description = 'TEI compliant files';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.poco.zip')) {
 | 
			
		||||
      description = 'HOCR and image files for post correction (PoCo)';
 | 
			
		||||
    } else {
 | 
			
		||||
      description = 'All result files created during this job';
 | 
			
		||||
    }
 | 
			
		||||
    return {id: jobResult.id, description: description, filename: jobResult.filename};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobResultList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="description"></span></td>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'description', 'filename']
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QueryResultList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...QueryResultList.options, ...options});
 | 
			
		||||
    this.user.eventListeners.queryResult.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
QueryResultList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i><br></td>
 | 
			
		||||
           <td><span class="corpus_title"></span><br><span class="query"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
 | 
			
		||||
};
 | 
			
		||||
@@ -1,215 +0,0 @@
 | 
			
		||||
class RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    if (displayElement.dataset.userId) {
 | 
			
		||||
      if (displayElement.dataset.userId in nopaque.appClient.users) {
 | 
			
		||||
        this.user = nopaque.appClient.users[displayElement.dataset.userId];
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`User not found: ${displayElement.dataset.userId}`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.user = nopaque.appClient.users.self;
 | 
			
		||||
    }
 | 
			
		||||
    this.displayElement = displayElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  eventHandler(eventType, payload) {
 | 
			
		||||
    switch (eventType) {
 | 
			
		||||
      case 'init':
 | 
			
		||||
        this.init(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'patch':
 | 
			
		||||
        this.patch(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.log(`Unknown event type: ${eventType}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {console.error('init method not implemented!');}
 | 
			
		||||
 | 
			
		||||
  patch() {console.error('patch method not implemented!');}
 | 
			
		||||
 | 
			
		||||
  setElement(element, value) {
 | 
			
		||||
    switch (element.tagName) {
 | 
			
		||||
      case 'INPUT':
 | 
			
		||||
        element.value = value;
 | 
			
		||||
        M.updateTextFields();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        element.innerText = value;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.corpus = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpus) {
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
    this.setCreationDate(this.corpus.creation_date);
 | 
			
		||||
    this.setDescription(this.corpus.description);
 | 
			
		||||
    this.setLastEditedDate(this.corpus.last_edited_date);
 | 
			
		||||
    this.setStatus(this.corpus.status);
 | 
			
		||||
    this.setTitle(this.corpus.title);
 | 
			
		||||
    this.setTokenRatio(this.corpus.current_nr_of_tokens, this.corpus.max_nr_of_tokens);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/last_edited_date');
 | 
			
		||||
          if (re.test(operation.path)) {this.setLastEditedDate(operation.value); break;}
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/status$');
 | 
			
		||||
          if (re.test(operation.path)) {this.setStatus(operation.value); break;}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTitle(title) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-title')) {this.setElement(element, title);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTokenRatio(currentNrOfTokens, maxNrOfTokens) {
 | 
			
		||||
    let tokenRatio = `${currentNrOfTokens}/${maxNrOfTokens}`;
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-token-ratio')) {this.setElement(element, tokenRatio);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDescription(description) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-description')) {this.setElement(element, description);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setStatus(status) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-status')) {this.setElement(element, status);}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
 | 
			
		||||
      if (['complete', 'failed', 'unprepared'].includes(status)) {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.build-corpus-trigger')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCreationDate(creationDateTimestamp) {
 | 
			
		||||
    let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-creation-date')) {this.setElement(element, creationDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setLastEditedDate(endDateTimestamp) {
 | 
			
		||||
    let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-end-date')) {this.setElement(element, endDate);}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobDisplay extends RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.jobId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    this.setCreationDate(this.job.creation_date);
 | 
			
		||||
    this.setEndDate(this.job.creation_date);
 | 
			
		||||
    this.setDescription(this.job.description);
 | 
			
		||||
    this.setService(this.job.service);
 | 
			
		||||
    this.setServiceArgs(this.job.service_args);
 | 
			
		||||
    this.setServiceVersion(this.job.service_version);
 | 
			
		||||
    this.setStatus(this.job.status);
 | 
			
		||||
    this.setTitle(this.job.title);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/end_date');
 | 
			
		||||
          if (re.test(operation.path)) {this.setEndDate(operation.value); break;}
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/status$');
 | 
			
		||||
          if (re.test(operation.path)) {this.setStatus(operation.value); break;}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTitle(title) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-title')) {this.setElement(element, title);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDescription(description) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-description')) {this.setElement(element, description);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setStatus(status) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-status')) {
 | 
			
		||||
      this.setElement(element, status);
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.restart-job-trigger')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCreationDate(creationDateTimestamp) {
 | 
			
		||||
    let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-creation-date')) {this.setElement(element, creationDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setEndDate(endDateTimestamp) {
 | 
			
		||||
    let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-end-date')) {this.setElement(element, endDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setService(service) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service')) {this.setElement(element, service);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setServiceArgs(serviceArgs) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service-args')) {this.setElement(element, serviceArgs);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setServiceVersion(serviceVersion) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service-version')) {this.setElement(element, serviceVersion);}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								web/app/static/js/nopaque/displays/CorpusDisplay.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								web/app/static/js/nopaque/displays/CorpusDisplay.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.corpus = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpus) {
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
    this.setCreationDate(this.corpus.creation_date);
 | 
			
		||||
    this.setDescription(this.corpus.description);
 | 
			
		||||
    this.setLastEditedDate(this.corpus.last_edited_date);
 | 
			
		||||
    this.setStatus(this.corpus.status);
 | 
			
		||||
    this.setTitle(this.corpus.title);
 | 
			
		||||
    this.setTokenRatio(this.corpus.current_nr_of_tokens, this.corpus.max_nr_of_tokens);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/last_edited_date');
 | 
			
		||||
          if (re.test(operation.path)) {this.setLastEditedDate(operation.value); break;}
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/status$');
 | 
			
		||||
          if (re.test(operation.path)) {this.setStatus(operation.value); break;}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTitle(title) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-title')) {this.setElement(element, title);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTokenRatio(currentNrOfTokens, maxNrOfTokens) {
 | 
			
		||||
    let tokenRatio = `${currentNrOfTokens}/${maxNrOfTokens}`;
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-token-ratio')) {this.setElement(element, tokenRatio);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDescription(description) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-description')) {this.setElement(element, description);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setStatus(status) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-status')) {this.setElement(element, status);}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
 | 
			
		||||
      if (['complete', 'failed', 'unprepared'].includes(status)) {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.build-corpus-trigger')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCreationDate(creationDateTimestamp) {
 | 
			
		||||
    let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-creation-date')) {this.setElement(element, creationDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setLastEditedDate(endDateTimestamp) {
 | 
			
		||||
    let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.corpus-end-date')) {this.setElement(element, endDate);}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								web/app/static/js/nopaque/displays/JobDisplay.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								web/app/static/js/nopaque/displays/JobDisplay.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
class JobDisplay extends RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), displayElement.dataset.jobId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    this.setCreationDate(this.job.creation_date);
 | 
			
		||||
    this.setEndDate(this.job.creation_date);
 | 
			
		||||
    this.setDescription(this.job.description);
 | 
			
		||||
    this.setService(this.job.service);
 | 
			
		||||
    this.setServiceArgs(this.job.service_args);
 | 
			
		||||
    this.setServiceVersion(this.job.service_version);
 | 
			
		||||
    this.setStatus(this.job.status);
 | 
			
		||||
    this.setTitle(this.job.title);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/end_date');
 | 
			
		||||
          if (re.test(operation.path)) {this.setEndDate(operation.value); break;}
 | 
			
		||||
          // Matches: /jobs/{this.job.id}/status
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/status$');
 | 
			
		||||
          if (re.test(operation.path)) {this.setStatus(operation.value); break;}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTitle(title) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-title')) {this.setElement(element, title);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDescription(description) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-description')) {this.setElement(element, description);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setStatus(status) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-status')) {
 | 
			
		||||
      this.setElement(element, status);
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status')) {element.dataset.status = status;}
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.status-spinner')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.restart-job-trigger')) {
 | 
			
		||||
      if (['complete', 'failed'].includes(status)) {
 | 
			
		||||
        element.classList.remove('hide');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.add('hide');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCreationDate(creationDateTimestamp) {
 | 
			
		||||
    let creationDate = new Date(creationDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-creation-date')) {this.setElement(element, creationDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setEndDate(endDateTimestamp) {
 | 
			
		||||
    let endDate = new Date(endDateTimestamp * 1000).toLocaleString("en-US");
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-end-date')) {this.setElement(element, endDate);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setService(service) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service')) {this.setElement(element, service);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setServiceArgs(serviceArgs) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service-args')) {this.setElement(element, serviceArgs);}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setServiceVersion(serviceVersion) {
 | 
			
		||||
    for (let element of this.displayElement.querySelectorAll('.job-service-version')) {this.setElement(element, serviceVersion);}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								web/app/static/js/nopaque/displays/RessourceDisplay.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								web/app/static/js/nopaque/displays/RessourceDisplay.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
class RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    if (displayElement.dataset.userId) {
 | 
			
		||||
      if (displayElement.dataset.userId in nopaque.appClient.users) {
 | 
			
		||||
        this.user = nopaque.appClient.users[displayElement.dataset.userId];
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`User not found: ${displayElement.dataset.userId}`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.user = nopaque.appClient.users.self;
 | 
			
		||||
    }
 | 
			
		||||
    this.displayElement = displayElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  eventHandler(eventType, payload) {
 | 
			
		||||
    switch (eventType) {
 | 
			
		||||
      case 'init':
 | 
			
		||||
        this.init(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'patch':
 | 
			
		||||
        this.patch(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.log(`Unknown event type: ${eventType}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {console.error('init method not implemented!');}
 | 
			
		||||
 | 
			
		||||
  patch() {console.error('patch method not implemented!');}
 | 
			
		||||
 | 
			
		||||
  setElement(element, value) {
 | 
			
		||||
    switch (element.tagName) {
 | 
			
		||||
      case 'INPUT':
 | 
			
		||||
        element.value = value;
 | 
			
		||||
        M.updateTextFields();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        element.innerText = value;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								web/app/static/js/nopaque/lists/CorpusFileList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								web/app/static/js/nopaque/lists/CorpusFileList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
class CorpusFileList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...CorpusFileList.options, ...options});
 | 
			
		||||
    this.corpus = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.corpusId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpus) {
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
    super.init(this.corpus.files);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let corpusFileId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm corpus deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the corpus file <b>${this.corpus.files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.corpus.files[corpusFileId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.corpus.files[corpusFileId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        window.location.href = this.corpus.files[corpusFileId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{this.corpus.id}/files/{corpusFileId}
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/files/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case add ;)
 | 
			
		||||
          re = new RegExp('^/corpora/' + this.corpus.id + '/files/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(corpusFile) {
 | 
			
		||||
    return {id: corpusFile.id, author: corpusFile.author, filename: corpusFile.filename, 'publishing-year': corpusFile.publishing_year, title: corpusFile.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
CorpusFileList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td><span class="author"></span></td>
 | 
			
		||||
           <td><span class="title"></span></td>
 | 
			
		||||
           <td><span class="publishing-year"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'author', 'filename', 'publishing-year', 'title']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										100
									
								
								web/app/static/js/nopaque/lists/CorpusList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								web/app/static/js/nopaque/lists/CorpusList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
class CorpusList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...CorpusList.options, ...options});
 | 
			
		||||
    this.corpora = undefined;
 | 
			
		||||
    this.user.eventListeners.corpus.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(corpora) {
 | 
			
		||||
    this.corpora = corpora;
 | 
			
		||||
    super.init(corpora);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let corpusId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    let action = (actionButtonElement === null) ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'analyse':
 | 
			
		||||
        window.location.href = this.corpora[corpusId].analysis_url;
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm corpus deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the corpus <b>${this.corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.corpora[corpusId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        // TODO: handle unprepared corpora
 | 
			
		||||
        window.location.href = this.corpora[corpusId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: ${action}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re, valueName;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{corpusId}
 | 
			
		||||
          re = /^\/corpora\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case 'add' ;)
 | 
			
		||||
          re = /^\/corpora\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches the only paths that should be handled here: /corpora/{corpusId}/{status || description || title}
 | 
			
		||||
          re = /^\/corpora\/(\d+)\/(status|description|title)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id, valueName] = operation.path.match(re);
 | 
			
		||||
            this.replace(id, valueName, operation.value);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(corpus) {
 | 
			
		||||
    return {id: corpus.id,
 | 
			
		||||
            status: corpus.status,
 | 
			
		||||
            description: corpus.description,
 | 
			
		||||
            title: corpus.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
CorpusList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><a class="btn-floating disabled"><i class="material-icons">book</i></a></td>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i></td>
 | 
			
		||||
           <td><span class="badge new status" data-badge-caption=""></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										43
									
								
								web/app/static/js/nopaque/lists/JobInputList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/app/static/js/nopaque/lists/JobInputList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
class JobInputList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobInputList.options, ...options});
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    super.init(this.job.inputs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobInputId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.job.inputs[jobInputId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(jobInput) {
 | 
			
		||||
    return {id: jobInput.id, filename: jobInput.filename};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobInputList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'filename']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										91
									
								
								web/app/static/js/nopaque/lists/JobList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								web/app/static/js/nopaque/lists/JobList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
class JobList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobList.options, ...options});
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'delete':
 | 
			
		||||
        let deleteModalHTML = `<div class="modal">
 | 
			
		||||
                                 <div class="modal-content">
 | 
			
		||||
                                   <h4>Confirm job deletion</h4>
 | 
			
		||||
                                   <p>Do you really want to delete the job <b>${this.user.data.jobs[jobId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                                 <div class="modal-footer">
 | 
			
		||||
                                   <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
                                   <a class="btn modal-close red waves-effect waves-light" href="${this.user.data.jobs[jobId].url}/delete"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
                                 </div>
 | 
			
		||||
                               </div>`;
 | 
			
		||||
        let deleteModalParentElement = document.querySelector('main');
 | 
			
		||||
        deleteModalParentElement.insertAdjacentHTML('beforeend', deleteModalHTML);
 | 
			
		||||
        let deleteModalElement = deleteModalParentElement.lastChild;
 | 
			
		||||
        let deleteModal = M.Modal.init(deleteModalElement, {onCloseEnd: () => {deleteModal.destroy(); deleteModalElement.remove();}});
 | 
			
		||||
        deleteModal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'view':
 | 
			
		||||
        window.location.href = this.user.data.jobs[jobId].url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let id, match, re, valueName;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{jobId}
 | 
			
		||||
          re = /^\/jobs\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        case 'remove':
 | 
			
		||||
          // See case add ;)
 | 
			
		||||
          re = /^\/jobs\/(\d+)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id] = operation.path.match(re);
 | 
			
		||||
            this.remove(id);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case 'replace':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{jobId}/{service || status || description || title}
 | 
			
		||||
          re = /^\/jobs\/(\d+)\/(status|description|title)$/;
 | 
			
		||||
          if (re.test(operation.path)) {
 | 
			
		||||
            [match, id, valueName] = operation.path.match(re);
 | 
			
		||||
            this.replace(id, valueName, operation.value);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(job) {
 | 
			
		||||
    return {id: job.id,
 | 
			
		||||
            service: job.service,
 | 
			
		||||
            status: job.status,
 | 
			
		||||
            description: job.description,
 | 
			
		||||
            title: job.title};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><a class="btn-floating disabled"><i class="material-icons service"></i></a></td>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i></td>
 | 
			
		||||
           <td><span class="badge new status" data-badge-caption=""></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										73
									
								
								web/app/static/js/nopaque/lists/JobResultList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								web/app/static/js/nopaque/lists/JobResultList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
class JobResultList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobResultList.options, ...options});
 | 
			
		||||
    this.job = undefined;
 | 
			
		||||
    this.user.eventListeners.job.addEventListener((eventType, payload) => this.eventHandler(eventType, payload), listElement.dataset.jobId);
 | 
			
		||||
    listElement.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(job) {
 | 
			
		||||
    this.job = job;
 | 
			
		||||
    super.init(this.job.results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onclick(event) {
 | 
			
		||||
    let ressourceElement = event.target.closest('tr');
 | 
			
		||||
    if (ressourceElement === null) {return;}
 | 
			
		||||
    let jobResultId = ressourceElement.dataset.id;
 | 
			
		||||
    let actionButtonElement = event.target.closest('.action-button');
 | 
			
		||||
    if (actionButtonElement === null) {return;}
 | 
			
		||||
    let action = actionButtonElement.dataset.action;
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case 'download':
 | 
			
		||||
        window.location.href = this.job.results[jobResultId].download_url;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown action: "${action}"`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    let re;
 | 
			
		||||
    for (let operation of patch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'add':
 | 
			
		||||
          // Matches the only paths that should be handled here: /jobs/{this.job.id}/results/{jobResultId}
 | 
			
		||||
          re = new RegExp('^/jobs/' + this.job.id + '/results/(\\d+)$');
 | 
			
		||||
          if (re.test(operation.path)) {this.add(operation.value);}
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preprocessRessource(jobResult) {
 | 
			
		||||
    let description;
 | 
			
		||||
    if (jobResult.filename.endsWith('.pdf.zip')) {
 | 
			
		||||
      description = 'PDF files with text layer';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.txt.zip')) {
 | 
			
		||||
      description = 'Raw text files';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.vrt.zip')) {
 | 
			
		||||
      description = 'VRT compliant files including the NLP data';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.xml.zip')) {
 | 
			
		||||
      description = 'TEI compliant files';
 | 
			
		||||
    } else if (jobResult.filename.endsWith('.poco.zip')) {
 | 
			
		||||
      description = 'HOCR and image files for post correction (PoCo)';
 | 
			
		||||
    } else {
 | 
			
		||||
      description = 'All result files created during this job';
 | 
			
		||||
    }
 | 
			
		||||
    return {id: jobResult.id, description: description, filename: jobResult.filename};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
JobResultList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><span class="description"></span></td>
 | 
			
		||||
           <td><span class="filename"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'description', 'filename']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										18
									
								
								web/app/static/js/nopaque/lists/QueryResultList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/app/static/js/nopaque/lists/QueryResultList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
class QueryResultList extends RessourceList {
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...QueryResultList.options, ...options});
 | 
			
		||||
    this.user.eventListeners.queryResult.addEventListener((eventType, payload) => this.eventHandler(eventType, payload));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
QueryResultList.options = {
 | 
			
		||||
  item: `<tr>
 | 
			
		||||
           <td><b class="title"></b><br><i class="description"></i><br></td>
 | 
			
		||||
           <td><span class="corpus_title"></span><br><span class="query"></span></td>
 | 
			
		||||
           <td class="right-align">
 | 
			
		||||
             <a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
 | 
			
		||||
             <a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="analyse" data-position="top" data-tooltip="Analyse"><i class="material-icons">search</i></a>
 | 
			
		||||
           </td>
 | 
			
		||||
         </tr>`,
 | 
			
		||||
  valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										72
									
								
								web/app/static/js/nopaque/lists/RessourceList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								web/app/static/js/nopaque/lists/RessourceList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
class RessourceList {
 | 
			
		||||
  /* A wrapper class for the list.js list.
 | 
			
		||||
   * This class is not meant to be used directly, instead it should be used as
 | 
			
		||||
   * a base class for concrete ressource list implementations.
 | 
			
		||||
   */
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    if (listElement.dataset.userId) {
 | 
			
		||||
      if (listElement.dataset.userId in nopaque.appClient.users) {
 | 
			
		||||
        this.user = nopaque.appClient.users[listElement.dataset.userId];
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`User not found: ${listElement.dataset.userId}`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.user = nopaque.appClient.users.self;
 | 
			
		||||
    }
 | 
			
		||||
    this.list = new List(listElement, {...RessourceList.options, ...options});
 | 
			
		||||
    this.list.list.innerHTML = `<tr class="show-if-only-child">
 | 
			
		||||
                                  <td colspan="3">
 | 
			
		||||
                                    <span class="card-title"><i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here...</span>
 | 
			
		||||
                                    <p>No ressource available (yet).</p>
 | 
			
		||||
                                  </td>
 | 
			
		||||
                                </tr>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  eventHandler(eventType, payload) {
 | 
			
		||||
    switch (eventType) {
 | 
			
		||||
      case 'init':
 | 
			
		||||
        this.init(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'patch':
 | 
			
		||||
        this.patch(payload);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error(`Unknown event type: ${eventType}`);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(ressources) {
 | 
			
		||||
    this.list.clear();
 | 
			
		||||
    this.add(Object.values(ressources));
 | 
			
		||||
    this.list.sort('id', {order: 'desc'});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patch(patch) {
 | 
			
		||||
    /*
 | 
			
		||||
     * It's not possible to generalize a patch Handler for all type of
 | 
			
		||||
     * ressources. So this method is meant to be an interface.
 | 
			
		||||
     */
 | 
			
		||||
    console.error('patch method not implemented!');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  add(values) {
 | 
			
		||||
    let ressources = Array.isArray(values) ? values : [values];
 | 
			
		||||
    if (typeof this.preprocessRessource === 'function') {
 | 
			
		||||
      ressources = ressources.map(ressource => this.preprocessRessource(ressource));
 | 
			
		||||
    }
 | 
			
		||||
    // Set a callback function ('() => {return;}') to force List.js perform the
 | 
			
		||||
    // add method asynchronous: https://listjs.com/api/#add
 | 
			
		||||
    this.list.add(ressources, () => {return;});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  remove(id) {
 | 
			
		||||
    this.list.remove('id', id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  replace(id, valueName, newValue) {
 | 
			
		||||
      this.list.get('id', id)[0].values({[valueName]: newValue});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
 | 
			
		||||
		Reference in New Issue
	
	Block a user