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 template 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.valueNames = ['id']; for (let element of this.list.valueNames) { switch (typeof element) { case 'object': if (element.hasOwnProperty('name')) {this.valueNames.push(element.name);} break; case 'string': this.valueNames.push(element); break; default: console.error(`Unknown value name definition: ${element}`); } } } 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]; // Discard ressource values, that are not defined to be used in the list. ressources = ressources.map(ressource => { let cleanedRessource = {}; for (let [valueName, value] of Object.entries(ressource)) { if (this.valueNames.includes(valueName)) {cleanedRessource[valueName] = value;} } return cleanedRessource; }); // 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) { if (this.valueNames.includes(valueName)) { let item = this.list.get('id', id)[0]; item.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.user.addEventListener('corporaInit', corpora => this.init(corpora)); this.user.addEventListener('corporaPatch', patch => this.patch(patch)); listElement.addEventListener('click', (event) => {this.onclick(event)}); } onclick(event) { let corpusId = event.target.closest('tr').dataset.id; let actionButtonElement = event.target.closest('.action-button'); let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action; switch (action) { case 'analyse': window.location.href = nopaque.user.corpora[corpusId].analysis_url; case 'delete': let deleteModalHTML = ``; 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 = nopaque.user.corpora[corpusId].url; break; default: console.error(`Unknown action: ${action}`); break; } } patch(patch) { for (let operation of patch) { switch(operation.op) { case 'add': // Matches the only paths that should be handled here: /corpora/{corpusId} if (/^\/corpora\/(\d+)$/.test(operation.path)) {this.add(operation.value);} break; case 'remove': // See case 'add' ;) if (/^\/corpora\/(\d+)$/.test(operation.path)) { let [match, id] = operation.path.match(/^\/corpora\/(\d+)$/); this.remove(corpusId); } break; case 'replace': // Matches the only paths that should be handled here: /corpora/{corpusId}/{status || description || title} if (/^\/corpora\/(\d+)\/(status|description|title)$/.test(operation.path)) { let [match, id, valueName] = operation.path.match(/^\/corpora\/(\d+)\/(status|description|title)$/); this.replace(id, valueName, operation.value); } break; default: break; } } } } CorpusList.options = { item: ` book
delete search send `, valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title'] }; class JobList extends RessourceList { constructor(listElement, options = {}) { super(listElement, {...JobList.options, ...options}); this.user.addEventListener('jobsInit', jobs => this.init(jobs)); this.user.addEventListener('jobsPatch', patch => this.patch(patch)); listElement.addEventListener('click', (event) => {this.onclick(event)}); } onclick(event) { let jobId = event.target.closest('tr').dataset.id; let actionButtonElement = event.target.closest('.action-button'); let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action; switch (action) { case 'delete': let deleteModalHTML = ``; 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) { for (let operation of patch) { switch(operation.op) { case 'add': // Matches the only paths that should be handled here: /jobs/{jobId} if (/^\/jobs\/(\d+)$/.test(operation.path)) {this.add(operation.value);} break; case 'remove': // See case add ;) if (/^\/jobs\/(\d+)$/.test(operation.path)) { let [match, id] = operation.path.match(/^\/jobs\/(\d+)$/); this.remove(jobId); } break; case 'replace': // Matches the only paths that should be handled here: /jobs/{jobId}/{service || status || description || title} if (/^\/jobs\/(\d+)\/(service|status|description|title)$/.test(operation.path)) { let [match, id, valueName] = operation.path.match(/^\/jobs\/(\d+)\/(service|status|description|title)$/); this.replace(id, valueName, operation.value); } break; default: break; } } } } JobList.options = { item: `
delete send `, valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title'] }; class QueryResultList extends RessourceList { constructor(listElement, options = {}) { super(listElement, {...QueryResultList.options, ...options}); this.user.addEventListener('queryResultsInit', queryResults => this.init(queryResults)); this.user.addEventListener('queryResultsPatch', patch => this.init(patch)); } } QueryResultList.options = { item: `


delete send search `, valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title'] };