From 1003c4494d7fbb8e1e7b22af15c3edb8cba01ec4 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Mon, 7 Dec 2020 16:10:40 +0100 Subject: [PATCH] Progress on list rework --- web/app/models.py | 23 +- web/app/static/js/nopaque.js | 24 +- web/app/static/js/nopaque.lists.js | 506 +++++------------------ web/app/static/js/nopaque.lists.js.bak | 420 +++++++++++++++++++ web/app/static/js/nopaque.lists.new.js | 131 ++++-- web/app/templates/main/dashboard.html.j2 | 8 +- 6 files changed, 659 insertions(+), 453 deletions(-) create mode 100644 web/app/static/js/nopaque.lists.js.bak diff --git a/web/app/models.py b/web/app/models.py index f22042d5..6b7314ae 100644 --- a/web/app/models.py +++ b/web/app/models.py @@ -283,7 +283,7 @@ class JobInput(db.Model): @property def download_url(self): - return url_for('job.download_job_input', job_id=self.job_id, + return url_for('jobs.download_job_input', job_id=self.job_id, job_input_id=self.id) @property @@ -323,7 +323,7 @@ class JobResult(db.Model): @property def download_url(self): - return url_for('job.download_job_result', job_id=self.job_id, + return url_for('jobs.download_job_result', job_id=self.job_id, job_result_id=self.id) @property @@ -384,8 +384,8 @@ class Job(db.Model): return os.path.join(self.creator.path, 'jobs', str(self.id)) @property - def path(self): - return url_for('job.job', job_id=self.id) + def url(self): + return url_for('jobs.job', job_id=self.id) def __repr__(self): ''' @@ -430,9 +430,9 @@ class Job(db.Model): 'description': self.description, 'end_date': (self.end_date.timestamp() if self.end_date else None), - 'service': {'args': self.service_args, - 'name': self.service, - 'version': self.service_version}, + 'service': self.service, + 'service_args': self.service_args, + 'service_version': self.service_version, 'status': self.status, 'title': self.title, 'inputs': {input.id: input.to_dict() for input in self.inputs}, @@ -529,6 +529,10 @@ class Corpus(db.Model): files = db.relationship('CorpusFile', backref='corpus', lazy='dynamic', cascade='save-update, merge, delete') + @property + def analysis_url(self): + return url_for('corpora.analyse_corpus', corpus_id=self.id) + @property def path(self): return os.path.join(self.creator.path, 'corpora', str(self.id)) @@ -538,7 +542,8 @@ class Corpus(db.Model): return url_for('corpora.corpus', corpus_id=self.id) def to_dict(self): - return {'url': self.url, + return {'analysis_url': self.analysis_url, + 'url': self.url, 'id': self.id, 'user_id': self.user_id, 'creation_date': self.creation_date.timestamp(), @@ -628,8 +633,10 @@ class QueryResult(db.Model): 'url': self.url, 'id': self.id, 'user_id': self.user_id, + 'corpus_title': self.query_metadata['corpus_name'], 'description': self.description, 'filename': self.filename, + 'query': self.query_metadata['query'], 'query_metadata': self.query_metadata, 'title': self.title} diff --git a/web/app/static/js/nopaque.js b/web/app/static/js/nopaque.js index b006ec25..5e3ad92a 100644 --- a/web/app/static/js/nopaque.js +++ b/web/app/static/js/nopaque.js @@ -27,13 +27,13 @@ nopaque.socket = io({transports: ['websocket']}); nopaque.socket.on("user_data_stream_init", function(msg) { nopaque.user = JSON.parse(msg); for (let subscriber of nopaque.corporaSubscribers) { - subscriber._init(nopaque.user.corpora); + subscriber.init(nopaque.user.corpora); } for (let subscriber of nopaque.jobsSubscribers) { - subscriber._init(nopaque.user.jobs); + subscriber.init(nopaque.user.jobs); } for (let subscriber of nopaque.queryResultsSubscribers) { - subscriber._init(nopaque.user.query_results); + subscriber.init(nopaque.user.query_results); } }); @@ -46,13 +46,13 @@ nopaque.socket.on("user_data_stream_update", function(msg) { jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); for (let subscriber of nopaque.corporaSubscribers) { - subscriber._update(corpora_patch); + subscriber.update(corpora_patch); } for (let subscriber of nopaque.jobsSubscribers) { - subscriber._update(jobs_patch); + subscriber.update(jobs_patch); } for (let subscriber of nopaque.queryResultsSubscribers) { - subscriber._update(query_results_patch); + subscriber.update(query_results_patch); } if (["all", "end"].includes(nopaque.user.settings.job_status_site_notifications)) { for (operation of jobs_patch) { @@ -69,13 +69,13 @@ nopaque.socket.on("user_data_stream_update", function(msg) { nopaque.socket.on("foreign_user_data_stream_init", function(msg) { nopaque.foreignUser = JSON.parse(msg); for (let subscriber of nopaque.foreignCorporaSubscribers) { - subscriber._init(nopaque.foreignUser.corpora); + subscriber.init(nopaque.foreignUser.corpora); } for (let subscriber of nopaque.foreignJobsSubscribers) { - subscriber._init(nopaque.foreignUser.jobs); + subscriber.init(nopaque.foreignUser.jobs); } for (let subscriber of nopaque.foreignQueryResultsSubscribers) { - subscriber._init(nopaque.foreignUser.query_results); + subscriber.init(nopaque.foreignUser.query_results); } }); @@ -87,9 +87,9 @@ nopaque.socket.on("foreign_user_data_stream_update", function(msg) { corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); - for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(corpora_patch);} - for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(jobs_patch);} - for (let subscriber of nopaque.foreignQueryResultsSubscribers) {subscriber._update(query_results_patch);} + for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber.update(corpora_patch);} + for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber.update(jobs_patch);} + for (let subscriber of nopaque.foreignQueryResultsSubscribers) {subscriber.update(query_results_patch);} }); nopaque.Forms = {}; diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index c907f5be..0ce961ed 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -1,420 +1,140 @@ -class RessourceList extends List { - constructor(idOrElement, subscriberList, type, options) { - if (!type || !["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User"].includes(type)) { - throw "Unknown Type!"; - } - super(idOrElement, {...RessourceList.options['common'], - ...RessourceList.options[type], - ...(options ? options : {})}); - if (subscriberList) {subscriberList.push(this);} - this.type = type; +class RessourceList { + constructor(idOrElement, options = {}) { + this.list = new List(idOrElement, {...RessourceList.options, ...options}); + } + + init(ressources) { + this.list.clear(); + this.add(Object.values(ressources)); + this.list.sort('id', {order: 'desc'}); } - _init(ressources) { - this.clear(); - this._add(Object.values(ressources)); - this.sort("id", {order: "desc"}); - } - - - _update(patch) { + update(patch) { let item, pathArray; for (let operation of patch) { - /* "/{ressourceName}/{ressourceId}/..." -> ["{ressourceId}", "..."] */ - pathArray = operation.path.split("/").slice(2); + /* + * '/{ressourceName}/{ressourceId}/{valueName}' -> ['{ressourceId}', {valueName}] + * Example: '/jobs/1/status' -> ['1', 'status'] + */ + let [id, valueName] = operation.path.split("/").slice(2); switch(operation.op) { - case "add": - if (pathArray.includes("results")) {break;} - this._add([operation.value]); + case 'add': + this.add(operation.value); break; - case "remove": - this.remove("id", pathArray[0]); + case 'remove': + this.remove(id); + break; + case 'replace': + this.replace(id, valueName, operation.value); break; - case "replace": - item = this.get("id", pathArray[0])[0]; - switch(pathArray[1]) { - case "status": - item.values({status: operation.value, - "analyse-link": ["analysing", "prepared", "start analysis"].includes(operation.value) ? `/corpora/${pathArray[0]}/analyse` : ""}); - break; - default: - break; - } default: break; } } } - _add(values, callback) { - this.add(values.map(x => RessourceList.dataMappers[this.type](x)), callback); - // Initialize modal and tooltipped elements in list - M.AutoInit(this.listContainer); + add(values) { + /* WORKAROUND: Set a callback function ('() => {return;}') to force List.js + perform the add method asynchronous. + * https://listjs.com/api/#add + */ + this.list.add(values, () => {return;}); + } + + remove(id) { + this.list.remove('id', id); + } + + replace(id, valueName, newValue) { + if (!this.list.valuesNames.includes(valueName)) {return;} + let item = this.list.get('id', id); + item.values({[valueName]: newValue}); } } +RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]}; -RessourceList.dataMappers = { - // A data mapper describes entitys rendered per row. One key value pair holds - // the data to be rendered in the list.js table. Key has to correspond - // with the ValueNames defined below in RessourceList.options ValueNames. - // Links are declared with double ticks(") around them. The key for links - // have to correspond with the class of an element in the - // RessourceList.options item blueprint. +class CorpusList extends RessourceList { + constructor(listElementId, options = {}) { + let listElement = document.querySelector(`#${listElementId}`); + super(listElement, {...CorpusList.options, ...options}); + listElement.addEventListener('click', (event) => { + let actionButtonElement = event.target.closest('.action-button'); + if (actionButtonElement === null) {return;} + let corpusId = event.target.closest('tr').dataset.id; + let action = actionButtonElement.dataset.action; + switch (action) { + case 'analyse': + window.location.href = nopaque.user.corpora[corpusId].analysis_url; + } + }); + nopaque.corporaSubscribers.push(this); + } +} - /* ### Corpus mapper ### */ - Corpus: corpus => ({ - creation_date: corpus.creation_date, - description: corpus.description, - id: corpus.id, - link: `/corpora/${corpus.id}`, - status: corpus.status, - title: corpus.title, - title1: corpus.title, - "analyse-link": ["analysing", "prepared", "start analysis"].includes(corpus.status) ? `/corpora/${corpus.id}/analyse` : "", - "delete-link": `/corpora/${corpus.id}/delete`, - "delete-modal": `delete-corpus-${corpus.id}-modal`, - "delete-modal-trigger": `delete-corpus-${corpus.id}-modal`, - }), - /* ### CorpusFile mapper ### TODO: replace delete-modal with delete-onclick */ - CorpusFile: corpus_file => ({ - author: corpus_file.author, - filename: corpus_file.filename, - id: corpus_file.id, - link: `${corpus_file.corpus_id}/files/${corpus_file.id}`, - "publishing-year": corpus_file.publishing_year, - title: corpus_file.title, - title1: corpus_file.title, - "delete-link": `/corpora/${corpus_file.corpus_id}/files/${corpus_file.id}/delete`, - "delete-modal": `delete-corpus-file-${corpus_file.id}-modal`, - "delete-modal-trigger": `delete-corpus-file-${corpus_file.id}-modal`, - "download-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/download`, - }), - /* ### Job mapper ### */ - Job: job => ({ - creation_date: job.creation_date, - description: job.description, - id: job.id, - link: `/jobs/${job.id}`, - service: job.service.name, - status: job.status, - title: job.title, - title1: job.title, - "delete-link": `/jobs/${job.id}/delete`, - "delete-modal": `delete-job-${job.id}-modal`, - "delete-modal-trigger": `delete-job-${job.id}-modal`, - }), - /* ### JobInput mapper ### */ - JobInput: job_input => ({ - filename: job_input.filename, - id: job_input.job_id, - "download-link": `${job_input.job_id}/inputs/${job_input.id}/download` - }), - /* ### QueryResult mapper ### */ - QueryResult: query_result => ({ - corpus_name: query_result.query_metadata.corpus_name, - description: query_result.description, - id: query_result.id, - link: `/corpora/result/${query_result.id}`, - query: query_result.query_metadata.query, - title: query_result.title, - "delete-link": `/corpora/result/${query_result.id}/delete`, - "delete-modal": `delete-query-result-${query_result.id}-modal`, - "delete-modal-trigger": `delete-query-result-${query_result.id}-modal`, - "inspect-link": `/corpora/result/${query_result.id}/inspect`, - }), - /* ### User mapper ### */ - User: user => ({ - confirmed: user.confirmed, - email: user.email, - id: user.id, - link: `users/${user.id}`, - role: user.role.name, - username: user.username, - username2: user.username, - "delete-link": `/admin/users/${user.id}/delete`, - "delete-modal": `delete-user-${user.id}-modal`, - "delete-modal-trigger": `delete-user-${user.id}-modal`, - }), + +CorpusList.options = { + item: ` + book +
+ + + delete + edit + search + + `, + valueNames: [{data: ['id']}, {name: "status", attr: "data-status"}, 'description', 'title'] }; -RessourceList.options = { - // common list.js options for 5 rows per page etc. - common: {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]}, - // extended list.js options for 10 rows per page etc. - extended: { - page: 10, - pagination: [ - { - name: "paginationTop", - paginationClass: "paginationTop", - innerWindow: 8, - outerWindow: 1 - }, - { - paginationClass: "paginationBottom", - innerWindow: 8, - outerWindow: 1, - }, - ], - }, - /* Type specific List.js options. Usually only "item" and "valueNames" gets - * defined here but it is possible to define other List.js options. - * item: https://listjs.com/api/#item - * valueNames: https://listjs.com/api/#valueNames - */ - Corpus: { - item: ` - - - book - - - -
- - - - - - -
- - delete - - - edit - - - search - -
- - - `, - valueNames: [ - "creation_date", - "description", - "title", - "title1", - {data: ["id"]}, - {name: "analyse-link", attr: "href"}, - {name: "delete-link", attr: "href"}, - {name: "delete-modal-trigger", attr: "data-target"}, - {name: "delete-modal", attr: "id"}, - {name: "link", attr: "href"}, - {name: "status", attr: "data-status"}, - ] - }, - CorpusFile: { - item: ` - - - - - -
- - delete - - - file_download - - - edit - -
- - - `, - valueNames: [ - "author", - "filename", - "publishing-year", - "title", - "title1", - {data: ["id"]}, - {name: "delete-link", attr: "href"}, - {name: "delete-modal-trigger", attr: "data-target"}, - {name: "delete-modal", attr: "id"}, - {name: "download-link", attr: "href"}, - {name: "link", attr: "href"}, - ], - }, - Job: { - item: ` - - - - - - -
- - - - - - -
- - delete - - - send - -
- - - `, - valueNames: [ - "creation_date", - "description", - "title", - "title1", - {data: ["id"]}, - {name: "delete-link", attr: "href"}, - {name: "delete-modal-trigger", attr: "data-target"}, - {name: "delete-modal", attr: "id"}, - {name: "link", attr: "href"}, - {name: "service", attr: "data-service"}, - {name: "status", attr: "data-status"}, - ], - }, - JobInput: { - item : ` - - - - file_download - - - `, - valueNames: [ - "filename", - "id", - {name: "download-link", attr: "href"}, - ], - }, - QueryResult: { - item: ` - -
-
- - -
- - - -
- - delete - - - info - - - search - -
- - - `, - valueNames: [ - "corpus_name", - "description", - "query", - "title", - "title2", - {data: ["id"]}, - {name: "delete-link", attr: "href"}, - {name: "delete-modal-trigger", attr: "data-target"}, - {name: "delete-modal", attr: "id"}, - {name: "inspect-link", attr: "href"}, - {name: "link", attr: "href"}, - ], - }, - User: { - item: ` - - - - - -
- - delete - - - send - -
- - - `, - valueNames: [ - "username", - "username2", - "email", - "role", - "id", - {name: "link", attr: "href"}, - {name: "delete-link", attr: "href"}, - {name: "delete-modal-trigger", attr: "data-target"}, - {name: "delete-modal", attr: "id"}, - ], - }, +class JobList extends RessourceList { + constructor(listElementId, options = {}) { + let listElement = document.querySelector(`#${listElementId}`); + super(listElement, {...JobList.options, ...options}); + nopaque.jobsSubscribers.push(this); + } +} + + +JobList.options = { + item: ` + +
+ + + delete + send + + `, + valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: "status", attr: "data-status"}, 'description', 'title'] }; -export { RessourceList, }; + +class QueryResultList extends RessourceList { + constructor(listElementId, options = {}) { + let listElement = document.querySelector(`#${listElementId}`); + super(listElement, {...QueryResultList.options, ...options}); + nopaque.queryResultsSubscribers.push(this); + } +} + + +QueryResultList.options = { + item: ` +

+
+ + delete + send + search + + `, + valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title'] +}; + +export { CorpusList, JobList, QueryResultList }; diff --git a/web/app/static/js/nopaque.lists.js.bak b/web/app/static/js/nopaque.lists.js.bak new file mode 100644 index 00000000..c907f5be --- /dev/null +++ b/web/app/static/js/nopaque.lists.js.bak @@ -0,0 +1,420 @@ +class RessourceList extends List { + constructor(idOrElement, subscriberList, type, options) { + if (!type || !["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User"].includes(type)) { + throw "Unknown Type!"; + } + super(idOrElement, {...RessourceList.options['common'], + ...RessourceList.options[type], + ...(options ? options : {})}); + if (subscriberList) {subscriberList.push(this);} + this.type = type; + } + + + _init(ressources) { + this.clear(); + this._add(Object.values(ressources)); + this.sort("id", {order: "desc"}); + } + + + _update(patch) { + let item, pathArray; + + for (let operation of patch) { + /* "/{ressourceName}/{ressourceId}/..." -> ["{ressourceId}", "..."] */ + pathArray = operation.path.split("/").slice(2); + switch(operation.op) { + case "add": + if (pathArray.includes("results")) {break;} + this._add([operation.value]); + break; + case "remove": + this.remove("id", pathArray[0]); + break; + case "replace": + item = this.get("id", pathArray[0])[0]; + switch(pathArray[1]) { + case "status": + item.values({status: operation.value, + "analyse-link": ["analysing", "prepared", "start analysis"].includes(operation.value) ? `/corpora/${pathArray[0]}/analyse` : ""}); + break; + default: + break; + } + default: + break; + } + } + } + + _add(values, callback) { + this.add(values.map(x => RessourceList.dataMappers[this.type](x)), callback); + // Initialize modal and tooltipped elements in list + M.AutoInit(this.listContainer); + } +} + + + + +RessourceList.dataMappers = { + // A data mapper describes entitys rendered per row. One key value pair holds + // the data to be rendered in the list.js table. Key has to correspond + // with the ValueNames defined below in RessourceList.options ValueNames. + // Links are declared with double ticks(") around them. The key for links + // have to correspond with the class of an element in the + // RessourceList.options item blueprint. + + /* ### Corpus mapper ### */ + Corpus: corpus => ({ + creation_date: corpus.creation_date, + description: corpus.description, + id: corpus.id, + link: `/corpora/${corpus.id}`, + status: corpus.status, + title: corpus.title, + title1: corpus.title, + "analyse-link": ["analysing", "prepared", "start analysis"].includes(corpus.status) ? `/corpora/${corpus.id}/analyse` : "", + "delete-link": `/corpora/${corpus.id}/delete`, + "delete-modal": `delete-corpus-${corpus.id}-modal`, + "delete-modal-trigger": `delete-corpus-${corpus.id}-modal`, + }), + /* ### CorpusFile mapper ### TODO: replace delete-modal with delete-onclick */ + CorpusFile: corpus_file => ({ + author: corpus_file.author, + filename: corpus_file.filename, + id: corpus_file.id, + link: `${corpus_file.corpus_id}/files/${corpus_file.id}`, + "publishing-year": corpus_file.publishing_year, + title: corpus_file.title, + title1: corpus_file.title, + "delete-link": `/corpora/${corpus_file.corpus_id}/files/${corpus_file.id}/delete`, + "delete-modal": `delete-corpus-file-${corpus_file.id}-modal`, + "delete-modal-trigger": `delete-corpus-file-${corpus_file.id}-modal`, + "download-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/download`, + }), + /* ### Job mapper ### */ + Job: job => ({ + creation_date: job.creation_date, + description: job.description, + id: job.id, + link: `/jobs/${job.id}`, + service: job.service.name, + status: job.status, + title: job.title, + title1: job.title, + "delete-link": `/jobs/${job.id}/delete`, + "delete-modal": `delete-job-${job.id}-modal`, + "delete-modal-trigger": `delete-job-${job.id}-modal`, + }), + /* ### JobInput mapper ### */ + JobInput: job_input => ({ + filename: job_input.filename, + id: job_input.job_id, + "download-link": `${job_input.job_id}/inputs/${job_input.id}/download` + }), + /* ### QueryResult mapper ### */ + QueryResult: query_result => ({ + corpus_name: query_result.query_metadata.corpus_name, + description: query_result.description, + id: query_result.id, + link: `/corpora/result/${query_result.id}`, + query: query_result.query_metadata.query, + title: query_result.title, + "delete-link": `/corpora/result/${query_result.id}/delete`, + "delete-modal": `delete-query-result-${query_result.id}-modal`, + "delete-modal-trigger": `delete-query-result-${query_result.id}-modal`, + "inspect-link": `/corpora/result/${query_result.id}/inspect`, + }), + /* ### User mapper ### */ + User: user => ({ + confirmed: user.confirmed, + email: user.email, + id: user.id, + link: `users/${user.id}`, + role: user.role.name, + username: user.username, + username2: user.username, + "delete-link": `/admin/users/${user.id}/delete`, + "delete-modal": `delete-user-${user.id}-modal`, + "delete-modal-trigger": `delete-user-${user.id}-modal`, + }), +}; + + +RessourceList.options = { + // common list.js options for 5 rows per page etc. + common: {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]}, + // extended list.js options for 10 rows per page etc. + extended: { + page: 10, + pagination: [ + { + name: "paginationTop", + paginationClass: "paginationTop", + innerWindow: 8, + outerWindow: 1 + }, + { + paginationClass: "paginationBottom", + innerWindow: 8, + outerWindow: 1, + }, + ], + }, + /* Type specific List.js options. Usually only "item" and "valueNames" gets + * defined here but it is possible to define other List.js options. + * item: https://listjs.com/api/#item + * valueNames: https://listjs.com/api/#valueNames + */ + Corpus: { + item: ` + + + book + + + +
+ + + + + + +
+ + delete + + + edit + + + search + +
+ + + `, + valueNames: [ + "creation_date", + "description", + "title", + "title1", + {data: ["id"]}, + {name: "analyse-link", attr: "href"}, + {name: "delete-link", attr: "href"}, + {name: "delete-modal-trigger", attr: "data-target"}, + {name: "delete-modal", attr: "id"}, + {name: "link", attr: "href"}, + {name: "status", attr: "data-status"}, + ] + }, + CorpusFile: { + item: ` + + + + + +
+ + delete + + + file_download + + + edit + +
+ + + `, + valueNames: [ + "author", + "filename", + "publishing-year", + "title", + "title1", + {data: ["id"]}, + {name: "delete-link", attr: "href"}, + {name: "delete-modal-trigger", attr: "data-target"}, + {name: "delete-modal", attr: "id"}, + {name: "download-link", attr: "href"}, + {name: "link", attr: "href"}, + ], + }, + Job: { + item: ` + + + + + + +
+ + + + + + +
+ + delete + + + send + +
+ + + `, + valueNames: [ + "creation_date", + "description", + "title", + "title1", + {data: ["id"]}, + {name: "delete-link", attr: "href"}, + {name: "delete-modal-trigger", attr: "data-target"}, + {name: "delete-modal", attr: "id"}, + {name: "link", attr: "href"}, + {name: "service", attr: "data-service"}, + {name: "status", attr: "data-status"}, + ], + }, + JobInput: { + item : ` + + + + file_download + + + `, + valueNames: [ + "filename", + "id", + {name: "download-link", attr: "href"}, + ], + }, + QueryResult: { + item: ` + +
+
+ + +
+ + + +
+ + delete + + + info + + + search + +
+ + + `, + valueNames: [ + "corpus_name", + "description", + "query", + "title", + "title2", + {data: ["id"]}, + {name: "delete-link", attr: "href"}, + {name: "delete-modal-trigger", attr: "data-target"}, + {name: "delete-modal", attr: "id"}, + {name: "inspect-link", attr: "href"}, + {name: "link", attr: "href"}, + ], + }, + User: { + item: ` + + + + + +
+ + delete + + + send + +
+ + + `, + valueNames: [ + "username", + "username2", + "email", + "role", + "id", + {name: "link", attr: "href"}, + {name: "delete-link", attr: "href"}, + {name: "delete-modal-trigger", attr: "data-target"}, + {name: "delete-modal", attr: "id"}, + ], + }, +}; + +export { RessourceList, }; diff --git a/web/app/static/js/nopaque.lists.new.js b/web/app/static/js/nopaque.lists.new.js index 564bae45..8e6ddfce 100644 --- a/web/app/static/js/nopaque.lists.new.js +++ b/web/app/static/js/nopaque.lists.new.js @@ -1,33 +1,33 @@ -class RessourceList extends List { - constructor(idOrElement, options) { - super(idOrElement, {...RessourceList.options['default'], ...(options ? options : {})}); +class RessourceList { + constructor(idOrElement, options = {}) { + this.list = new List(idOrElement, {...RessourceList.options, ...options}); } - _init(ressources) { - this.clear(); - this._add(Object.values(ressources)); - this.sort("id", {order: "desc"}); + init(ressources) { + this.list.clear(); + this.add(Object.values(ressources)); + this.list.sort('id', {order: 'desc'}); } - _update(patch) { + update(patch) { let item, pathArray; for (let operation of patch) { /* - * '/{ressourceName}/{ressourceId}/...' -> ['{ressourceId}', ...] + * '/{ressourceName}/{ressourceId}/{valueName}' -> ['{ressourceId}', {valueName}] * Example: '/jobs/1/status' -> ['1', 'status'] */ - pathArray = operation.path.split("/").slice(2); + let [id, valueName] = operation.path.split("/").slice(2); switch(operation.op) { - case "add": - this.add_handler([operation.value]); + case 'add': + this.add(operation.value); break; - case "remove": - this.remove_handler(pathArray[0]); + case 'remove': + this.remove(id); break; - case "replace": - this.replace_handler(pathArray[0], pathArray[1], operation.value); + case 'replace': + this.replace(id, valueName, operation.value); break; default: break; @@ -35,34 +35,93 @@ class RessourceList extends List { } } - add_handler(values, callback) { - if (this.hasOwnProperty('add_')) { - this.add_(values, callback); - } else { - this.add(values, callback); - } + add(values) { + /* WORKAROUND: Set a callback function ('() => {return;}') to force List.js + perform the add method asynchronous. + * https://listjs.com/api/#add + */ + this.list.add(values, () => {return;}); } - remove_handler(id) { - if (this.hasOwnProperty('remove_')) { - this.remove_(id); - } else { - this.remove(id); - } + remove(id) { + this.list.remove('id', id); } - replace_handler(id, valueName, newValue) { - let item = this.get('id', id); - if (this.hasOwnProperty('add_')) - item.values({valueName: operation.value}); + replace(id, valueName, newValue) { + if (!this.list.valuesNames.includes(valueName)) {return;} + let item = this.list.get('id', id); + item.values({[valueName]: newValue}); } } -RessourceList.options = { - // default RessourceList options - default: {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]}, +RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]}; + + +class CorpusList extends RessourceList { + constructor(idOrElement, options = {}) { + super(idOrElement, {...CorpusList.options, ...options}); + nopaque.corporaSubscribers.push(this); + } +} + + +CorpusList.options = { + item: ` + book +
+ + + delete + edit + search + + `, + valueNames: [{data: ['id']}, 'description', 'status', 'title'] }; -export { RessourceList, }; +class JobList extends RessourceList { + constructor(idOrElement, options = {}) { + super(idOrElement, {...JobList.options, ...options}); + nopaque.jobsSubscribers.push(this); + } +} + + +JobList.options = { + item: ` + +
+ + + delete + send + + `, + valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, 'description', 'status', 'title'] +}; + + +class QueryResultList extends RessourceList { + constructor(idOrElement, options = {}) { + super(idOrElement, {...QueryResultList.options, ...options}); + nopaque.queryResultsSubscribers.push(this); + } +} + + +QueryResultList.options = { + item: ` +

+
+ + delete + send + search + + `, + valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title'] +}; + +export { CorpusList, JobList, QueryResultList }; diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2 index 56602f8c..e838f72a 100644 --- a/web/app/templates/main/dashboard.html.j2 +++ b/web/app/templates/main/dashboard.html.j2 @@ -176,9 +176,9 @@ {% block scripts %} {{ super() }} {% endblock scripts %}