diff --git a/app/models.py b/app/models.py index 38c44020..d16bcfae 100644 --- a/app/models.py +++ b/app/models.py @@ -1331,6 +1331,8 @@ class Corpus(HashidMixin, db.Model): @db.event.listens_for(Job, 'after_delete') @db.event.listens_for(JobInput, 'after_delete') @db.event.listens_for(JobResult, 'after_delete') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_delete') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_delete') def ressource_after_delete(mapper, connection, ressource): jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}] room = f'users.{ressource.user_hashid}' @@ -1344,6 +1346,8 @@ def ressource_after_delete(mapper, connection, ressource): @db.event.listens_for(Job, 'after_insert') @db.event.listens_for(JobInput, 'after_insert') @db.event.listens_for(JobResult, 'after_insert') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_insert') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_insert') def ressource_after_insert_handler(mapper, connection, ressource): value = ressource.to_json_serializeable() for attr in mapper.relationships: @@ -1360,6 +1364,8 @@ def ressource_after_insert_handler(mapper, connection, ressource): @db.event.listens_for(Job, 'after_update') @db.event.listens_for(JobInput, 'after_update') @db.event.listens_for(JobResult, 'after_update') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_update') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_update') def ressource_after_update_handler(mapper, connection, ressource): jsonpatch = [] for attr in db.inspect(ressource).attrs: diff --git a/app/static/js/ResourceLists/ResourceList.js b/app/static/js/ResourceLists/ResourceList.js index 7adc0e08..7fe7dec6 100644 --- a/app/static/js/ResourceLists/ResourceList.js +++ b/app/static/js/ResourceLists/ResourceList.js @@ -10,9 +10,9 @@ class ResourceList { JobList.autoInit(); JobInputList.autoInit(); JobResultList.autoInit(); - PublicUserList.autoInit(); SpaCyNLPPipelineModelList.autoInit(); TesseractOCRPipelineModelList.autoInit(); + UserList.autoInit(); AdminUserList.autoInit(); } diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js index f3959a79..078ec90c 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js @@ -18,39 +18,33 @@ class SpaCyNLPPipelineModelList extends ResourceList { }); app.getUser(this.userId).then((user) => { this.add(Object.values(user.spacy_nlp_pipeline_models)); - for (let uncheckedCheckbox of this.listjs.list.querySelectorAll('input[data-checked="True"]')) { - uncheckedCheckbox.setAttribute('checked', ''); - } - if (user.role.name !== ('Administrator' || 'Contributor')) { - for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) { - switchElement.setAttribute('disabled', ''); - } - } this.isInitialized = true; }); } get item() { - return ` - -
- ()
- -
- - -
- - - delete - send - - - `.trim(); + return (values) => { + return ` + +
+ ()
+ +
+ + +
+ + + delete + send + + + `.trim(); + }; } get valueNames() { @@ -65,8 +59,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { 'publishing-year', 'title', 'title-2', - 'version', - {name: 'is_public', attr: 'data-checked'} + 'version' ]; } @@ -108,7 +101,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { 'title': spaCyNLPPipelineModel.title, 'title-2': spaCyNLPPipelineModel.title, 'version': spaCyNLPPipelineModel.version, - 'is_public': spaCyNLPPipelineModel.is_public ? 'True' : 'False' + 'is-public': spaCyNLPPipelineModel.is_public }; } @@ -117,14 +110,15 @@ class SpaCyNLPPipelineModelList extends ResourceList { } onChange(event) { - let actionSwitchElement = event.target.closest('.list-action-trigger'); - let action = actionSwitchElement.dataset.listAction; - let spaCyNLPPipelineModelElement = event.target.closest('tr'); - let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; - switch (action) { + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + if (listActionElement === null) {return;} + let listAction = listActionElement.dataset.listAction; + switch (listAction) { case 'share-request': { - let is_public = actionSwitchElement.querySelector('input').checked; - Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, is_public); + Utils.spaCyNLPPipelineModelToggleIsPublicRequest(this.userId, itemId); break; } default: { @@ -134,24 +128,23 @@ class SpaCyNLPPipelineModelList extends ResourceList { } onClick(event) { - if (event.target.closest('.action-switch')) { - let userRole = app.data.users[this.userId].role.name; - if (userRole !== ('Administrator' || 'Contributor')) { - app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error'); - } - return; + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + // ignore switch clicks, handle them by the onChange method instead + if (listActionElement.classList.contains('switch')) { + event.preventDefault(); + this.onChange(event); } - let actionButtonElement = event.target.closest('.list-action-trigger'); - let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.listAction; - let spaCyNLPPipelineModelElement = event.target.closest('tr'); - let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; - switch (action) { + let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; + switch (listAction) { case 'delete-request': { - Utils.deleteSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId); + Utils.deleteSpaCyNLPPipelineModelRequest(this.userId, itemId); break; } case 'view': { - window.location.href = `/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`; + window.location.href = `/contributions/spacy-nlp-pipeline-models/${itemId}`; break; } default: { @@ -159,4 +152,41 @@ class SpaCyNLPPipelineModelList extends ResourceList { } } } + + onPatch(patch) { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)`); + let filteredPatch = patch.filter(operation => re.test(operation.path)); + for (let operation of filteredPatch) { + switch(operation.op) { + case 'add': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) {this.add(operation.value);} + break; + } + case 'remove': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) { + let [match, itemId] = operation.path.match(re); + this.remove(itemId); + } + break; + } + case 'replace': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)/(is_public)$`); + if (re.test(operation.path)) { + let [match, itemId, valueName] = operation.path.match(re); + if (valueName === 'is_public') { + this.listjs.list.querySelector(`.list-item[data-id="${itemId}"] .is-public`).checked = operation.value; + valueName = 'is-public'; + } + this.replace(itemId, valueName, operation.value); + } + break; + } + default: { + break; + } + } + } + } } diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js index c3bc6447..ad319041 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js @@ -31,26 +31,28 @@ class TesseractOCRPipelineModelList extends ResourceList { } get item() { - return ` - -
- ()
- -
- - -
- - - delete - send - - - `.trim(); + return (values) => { + return ` + +
+ ()
+ +
+ + +
+ + + delete + send + + + `.trim(); + }; } get valueNames() { @@ -108,7 +110,7 @@ class TesseractOCRPipelineModelList extends ResourceList { 'title': tesseractOCRPipelineModel.title, 'title-2': tesseractOCRPipelineModel.title, 'version': tesseractOCRPipelineModel.version, - 'is_public': tesseractOCRPipelineModel.is_public ? 'True' : 'False' + 'is-public': tesseractOCRPipelineModel.is_public }; } @@ -117,14 +119,15 @@ class TesseractOCRPipelineModelList extends ResourceList { } onChange(event) { - let actionSwitchElement = event.target.closest('.list-action-trigger'); - let action = actionSwitchElement.dataset.listAction; - let tesseractOCRPipelineModelElement = event.target.closest('tr'); - let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; - switch (action) { + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + if (listActionElement === null) {return;} + let listAction = listActionElement.dataset.listAction; + switch (listAction) { case 'share-request': { - let is_public = actionSwitchElement.querySelector('input').checked; - Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, is_public); + Utils.tesseractOCRPipelineModelToggleIsPublicRequest(this.userId, itemId); break; } default: { @@ -134,24 +137,23 @@ class TesseractOCRPipelineModelList extends ResourceList { } onClick(event) { - if (event.target.closest('.action-switch')) { - let userRole = app.data.users[this.userId].role.name; - if (userRole !== ('Administrator' || 'Contributor')) { - app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error'); - } - return; + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + // ignore switch clicks, handle them by the onChange method instead + if (listActionElement.classList.contains('switch')) { + event.preventDefault(); + this.onChange(event); } - let actionButtonElement = event.target.closest('.list-action-trigger'); - let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.listAction; - let tesseractOCRPipelineModelElement = event.target.closest('tr'); - let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; - switch (action) { + let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; + switch (listAction) { case 'delete-request': { - Utils.deleteTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId); + Utils.deleteTesseractOCRPipelineModelRequest(this.userId, itemId); break; } case 'view': { - window.location.href = `/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`; + window.location.href = `/contributions/tesseract-ocr-pipeline-models/${itemId}`; break; } default: { @@ -159,4 +161,41 @@ class TesseractOCRPipelineModelList extends ResourceList { } } } + + onPatch(patch) { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)`); + let filteredPatch = patch.filter(operation => re.test(operation.path)); + for (let operation of filteredPatch) { + switch(operation.op) { + case 'add': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) {this.add(operation.value);} + break; + } + case 'remove': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) { + let [match, itemId] = operation.path.match(re); + this.remove(itemId); + } + break; + } + case 'replace': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)/(is_public)$`); + if (re.test(operation.path)) { + let [match, itemId, valueName] = operation.path.match(re); + if (valueName === 'is_public') { + this.listjs.list.querySelector(`.list-item[data-id="${itemId}"] .is-public`).checked = operation.value; + valueName = 'is-public'; + } + this.replace(itemId, valueName, operation.value); + } + break; + } + default: { + break; + } + } + } + } } diff --git a/app/static/js/ResourceLists/PublicUserList.js b/app/static/js/ResourceLists/UserList.js similarity index 95% rename from app/static/js/ResourceLists/PublicUserList.js rename to app/static/js/ResourceLists/UserList.js index e6cfff34..d871ec31 100644 --- a/app/static/js/ResourceLists/PublicUserList.js +++ b/app/static/js/ResourceLists/UserList.js @@ -1,7 +1,7 @@ -class PublicUserList extends ResourceList { +class UserList extends ResourceList { static autoInit() { - for (let publicUserListElement of document.querySelectorAll('.public-user-list:not(.no-autoinit)')) { - new PublicUserList(publicUserListElement); + for (let publicUserListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) { + new UserList(publicUserListElement); } } diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index d00b613a..2e847477 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -563,7 +563,7 @@ class Utils { }); } - static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, is_public) { + static tesseractOCRPipelineModelToggleIsPublicRequest(userId, tesseractOCRPipelineModelId, is_public) { return new Promise((resolve, reject) => { let tesseractOCRPipelineModel; try { @@ -572,28 +572,24 @@ class Utils { tesseractOCRPipelineModel = {}; } - let msg = ''; - if (is_public) { - msg = `Model "${tesseractOCRPipelineModel?.title}" is now public`; - } else { - msg = `Model "${tesseractOCRPipelineModel?.title}" is now private`; - } fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - app.flash(msg); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); + .then( + (response) => { + if (response.status === 403) { + app.flash('Forbidden', 'error'); + reject(response); + } + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); }); } - static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, is_public) { + static spaCyNLPPipelineModelToggleIsPublicRequest(userId, spaCyNLPPipelineModelId) { return new Promise((resolve, reject) => { let spaCyNLPPipelineModel; try { @@ -602,24 +598,20 @@ class Utils { spaCyNLPPipelineModel = {}; } - let msg = ''; - if (is_public) { - msg = `Model "${spaCyNLPPipelineModel?.title}" is now public`; - } else { - msg = `Model "${spaCyNLPPipelineModel?.title}" is now private`; - } fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - app.flash(msg); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); + .then( + (response) => { + if (response.status === 403) { + app.flash('Forbidden', 'error'); + reject(response); + } + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); }); } } diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 17b0b7c6..95406079 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -24,9 +24,9 @@ 'js/ResourceLists/JobList.js', 'js/ResourceLists/JobInputList.js', 'js/ResourceLists/JobResultList.js', - 'js/ResourceLists/PublicUserList.js', 'js/ResourceLists/SpacyNLPPipelineModelList.js', 'js/ResourceLists/TesseractOCRPipelineModelList.js', + 'js/ResourceLists/UserList.js', 'js/ResourceLists/AdminUserList.js', 'js/XMLtoObject.js' %}