diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 5d1b1a413733035c24dfe18913f996f339fe5b05..8a3e5e66f950d079a6177cff80531d6a01aa02ff 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -16,6 +16,7 @@ from threading import Thread import os from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required from app import db, hashids +from app.decorators import content_negotiation from app.models import ( Corpus, CorpusFile, @@ -32,93 +33,6 @@ from .forms import ( ) -@bp.route('/<hashid:corpus_id>/is_public', methods=['POST']) -@login_required -@corpus_owner_or_admin_required -def update_corpus_is_public(corpus_id): - is_public = request.json - if not isinstance(is_public, bool): - response = jsonify('The request body must be a boolean') - response.status_code = 400 - abort(response) - corpus = Corpus.query.get_or_404(corpus_id) - corpus.is_public = is_public - db.session.commit() - return '', 204 - - -@bp.route('/<hashid:corpus_id>/followers/add', methods=['POST']) -@login_required -@corpus_owner_or_admin_required -def add_corpus_followers(corpus_id): - usernames = request.json - if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)): - response = jsonify('The request body must be a list of strings') - response.status_code = 400 - abort(response) - corpus = Corpus.query.get_or_404(corpus_id) - for username in usernames: - user = User.query.filter_by(username=username, is_public=True).first_or_404() - user.follow_corpus(corpus) - db.session.commit() - return '', 204 - - -@bp.route('/<hashid:corpus_id>/follow/<token>') -@login_required -def follow_corpus(corpus_id, token): - corpus = Corpus.query.get_or_404(corpus_id) - if current_user.follow_corpus_by_token(token): - db.session.commit() - flash(f'You are following {corpus.title} now', category='corpus') - return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) - abort(403) - - -@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/unfollow', methods=['POST']) -@login_required -def unfollow_corpus(corpus_id, follower_id): - corpus = Corpus.query.get_or_404(corpus_id) - follower = User.query.get_or_404(follower_id) - if not (corpus.user == current_user or follower == current_user or current_user.is_administrator()): - abort(403) - if not follower.is_following_corpus(corpus): - abort(409) # 'User is not following the corpus' - follower.unfollow_corpus(corpus) - db.session.commit() - flash(f'{follower.username} is not following {corpus.title} anymore', category='corpus') - return '', 204 - - -@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['POST']) -@corpus_follower_permission_required('UPDATE_FOLLOWER') -def add_permission(corpus_id, follower_id): - corpus_follower_association = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() - if not (corpus_follower_association.corpus.user == current_user or current_user.is_administrator()): - abort(403) - role_name = request.json.get('role') - if role_name is None: - abort(400) - corpus_follower_role = CorpusFollowerRole.query.filter_by(name=role_name).first_or_404() - corpus_follower_association.role = corpus_follower_role - db.session.commit() - return '', 204 - - -@bp.route('/public') -@login_required -def public_corpora(): - corpora = [ - c.to_json_serializeable() - for c in Corpus.query.filter(Corpus.is_public == True).all() - ] - return render_template( - 'corpora/public_corpora.html.j2', - corpora=corpora, - title='Corpora' - ) - - @bp.route('/create', methods=['GET', 'POST']) @login_required def create_corpus(): @@ -145,6 +59,24 @@ def create_corpus(): ) +@bp.route('/public') +@login_required +def public_corpora(): + corpora = [ + c.to_json_serializeable() + for c in Corpus.query.filter(Corpus.is_public == True).all() + ] + return render_template( + 'corpora/public_corpora.html.j2', + corpora=corpora, + title='Corpora' + ) + + +############################################################################## +# Corpus # +############################################################################## +#region corpus @bp.route('/<hashid:corpus_id>') @login_required def corpus(corpus_id): @@ -172,6 +104,18 @@ def corpus(corpus_id): abort(403) +@bp.route('/<hashid:corpus_id>/analyse') +@login_required +@corpus_follower_permission_required('VIEW') +def analyse_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + return render_template( + 'corpora/analyse_corpus.html.j2', + corpus=corpus, + title=f'Analyse Corpus {corpus.title}' + ) + + @bp.route('/<hashid:corpus_id>/generate-corpus-share-link', methods=['GET', 'POST']) @login_required @corpus_follower_permission_required('GENERATE_SHARE_LINK') @@ -186,9 +130,34 @@ def generate_corpus_share_link(corpus_id): return link +@bp.route('/<hashid:corpus_id>/follow/<token>') +@login_required +def follow_corpus(corpus_id, token): + corpus = Corpus.query.get_or_404(corpus_id) + if current_user.follow_corpus_by_token(token): + db.session.commit() + flash(f'You are following {corpus.title} now', category='corpus') + return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) + abort(403) + + +@bp.route('/import', methods=['GET', 'POST']) +@login_required +def import_corpus(): + abort(503) + + +@bp.route('/<hashid:corpus_id>/export') +@login_required +def export_corpus(corpus_id): + abort(503) + + +#region json-routes @bp.route('/<hashid:corpus_id>', methods=['DELETE']) @login_required @corpus_owner_or_admin_required +@content_negotiation(produces='application/json') def delete_corpus(corpus_id): def _delete_corpus(app, corpus_id): with app.app_context(): @@ -199,27 +168,22 @@ def delete_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) thread = Thread( target=_delete_corpus, - args=(current_app._get_current_object(), corpus_id) + args=(current_app._get_current_object(), corpus.id) ) thread.start() - return {}, 202 - - -@bp.route('/<hashid:corpus_id>/analyse') -@login_required -@corpus_follower_permission_required('VIEW') -def analyse_corpus(corpus_id): - corpus = Corpus.query.get_or_404(corpus_id) - return render_template( - 'corpora/analyse_corpus.html.j2', - corpus=corpus, - title=f'Analyse Corpus {corpus.title}' - ) + response_data = { + 'message': f'Corpus "{corpus.title}" marked for deletion', + 'category': 'corpus' + } + response = jsonify(response_data) + response.status_code = 200 + return response @bp.route('/<hashid:corpus_id>/build', methods=['POST']) @login_required @corpus_owner_or_admin_required +@content_negotiation(produces='application/json') def build_corpus(corpus_id): def _build_corpus(app, corpus_id): with app.app_context(): @@ -230,18 +194,51 @@ def build_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) if not (corpus.user == current_user or current_user.is_administrator()): abort(403) - # Check if the corpus has corpus files - if not corpus.files.all(): - response = {'errors': {'message': 'Corpus file(s) required'}} - return response, 409 + if len(corpus.files.all()) == 0: + abort(409) thread = Thread( target=_build_corpus, args=(current_app._get_current_object(), corpus_id) ) thread.start() - return {}, 202 + response_data = { + 'message': f'Corpus "{corpus.title}" marked for building', + 'category': 'corpus' + } + response = jsonify(response_data) + response.status_code = 202 + return response +@bp.route('/<hashid:corpus_id>/is_public', methods=['PUT']) +@login_required +@corpus_owner_or_admin_required +@content_negotiation(consumes='application/json', produces='application/json') +def update_corpus_is_public(corpus_id): + is_public = request.json + if not isinstance(is_public, bool): + abort(400) + corpus = Corpus.query.get_or_404(corpus_id) + corpus.is_public = is_public + db.session.commit() + response_data = { + 'message': ( + f'Corpus "{corpus.title}" is now' + f' {"public" if is_public else "private"}' + ), + 'category': 'corpus' + } + response = jsonify(response_data) + response.status_code = 200 + return response +#endregion json-routes +#endregion corpus + + +############################################################################## +# Corpus/Files # +############################################################################## +#region files @bp.route('/<hashid:corpus_id>/files/create', methods=['GET', 'POST']) @login_required @corpus_follower_permission_required('ADD_CORPUS_FILE') @@ -292,7 +289,7 @@ def create_corpus_file(corpus_id): @bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST']) @login_required -@corpus_follower_permission_required('ADD_CORPUS_FILE', 'UPDATE_CORPUS_FILE', 'REMOVE_CORPUS_FILE') +@corpus_follower_permission_required('UPDATE_CORPUS_FILE') def corpus_file(corpus_id, corpus_file_id): corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404() form = UpdateCorpusFileForm(data=corpus_file.to_json_serializeable()) @@ -313,9 +310,27 @@ def corpus_file(corpus_id, corpus_file_id): ) +@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download') +@login_required +@corpus_follower_permission_required('VIEW') +def download_corpus_file(corpus_id, corpus_file_id): + corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404() + if not (corpus_file.corpus.user == current_user or current_user.is_administrator()): + abort(403) + return send_from_directory( + os.path.dirname(corpus_file.path), + os.path.basename(corpus_file.path), + as_attachment=True, + attachment_filename=corpus_file.filename, + mimetype=corpus_file.mimetype + ) + + +#region json-routes @bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['DELETE']) @login_required @corpus_follower_permission_required('REMOVE_CORPUS_FILE') +@content_negotiation(produces='application/json') def delete_corpus_file(corpus_id, corpus_file_id): def _delete_corpus_file(app, corpus_file_id): with app.app_context(): @@ -323,40 +338,83 @@ def delete_corpus_file(corpus_id, corpus_file_id): corpus_file.delete() db.session.commit() - corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404() - if not (corpus_file.corpus.user == current_user or current_user.is_administrator()): - abort(403) + corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404() thread = Thread( target=_delete_corpus_file, - args=(current_app._get_current_object(), corpus_file_id) + args=(current_app._get_current_object(), corpus_file.id) ) thread.start() return {}, 202 - - -@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download') +#endregion json-routes +#endregion files + +############################################################################## +# Corpus/Followers # +############################################################################## +#region followers +#region json-routes +@bp.route('/<hashid:corpus_id>/followers', methods=['POST']) @login_required -@corpus_follower_permission_required('VIEW') -def download_corpus_file(corpus_id, corpus_file_id): - corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404() - if not (corpus_file.corpus.user == current_user or current_user.is_administrator()): - abort(403) - return send_from_directory( - os.path.dirname(corpus_file.path), - os.path.basename(corpus_file.path), - as_attachment=True, - attachment_filename=corpus_file.filename, - mimetype=corpus_file.mimetype - ) +@corpus_owner_or_admin_required +@content_negotiation(consumes='application/json', produces='application/json') +def add_corpus_followers(corpus_id): + usernames = request.json + if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)): + abort(400) + corpus = Corpus.query.get_or_404(corpus_id) + for username in usernames: + user = User.query.filter_by(username=username, is_public=True).first_or_404() + user.follow_corpus(corpus) + db.session.commit() + resonse_data = { + 'message': f'Users are now following "{corpus.title}"', + 'category': 'corpus' + } + response = jsonify(resonse_data) + response.status_code = 200 + return response -@bp.route('/import', methods=['GET', 'POST']) +@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE']) @login_required -def import_corpus(): - abort(503) +@content_negotiation(produces='application/json') +def unfollow_corpus(corpus_id, follower_id): + corpus = Corpus.query.get_or_404(corpus_id) + follower = User.query.get_or_404(follower_id) + if not (corpus.user == current_user or follower == current_user or current_user.is_administrator()): + abort(403) + if not follower.is_following_corpus(corpus): + abort(409) # 'User is not following the corpus' + follower.unfollow_corpus(corpus) + db.session.commit() + response_data = { + 'message': \ + f'"{follower.username}" is not following "{corpus.title}" anymore', + 'category': 'corpus' + } + response = jsonify(response_data) + response.status_code = 200 + return response -@bp.route('/<hashid:corpus_id>/export') +@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT']) @login_required -def export_corpus(corpus_id): - abort(503) +@corpus_owner_or_admin_required +@content_negotiation(consumes='application/json', produces='application/json') +def add_permission(corpus_id, follower_id): + role_name = request.json + if not isinstance(role_name, str): + abort(400) + cfr = CorpusFollowerRole.query.filter_by(name=role_name).first_or_404() + cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() + cfa.role = cfr + db.session.commit() + resonse_data = { + 'message': f'User "{cfa.follower.username}" is now {cfa.role.name}', + 'category': 'corpus' + } + response = jsonify(resonse_data) + response.status_code = 200 + return response +#endregion json-routes +#endregion followers diff --git a/app/static/js/Requests/Contributions.js b/app/static/js/Requests/Contributions.js deleted file mode 100644 index 306051359f2e7410d49be9479e19a4729d8acfe0..0000000000000000000000000000000000000000 --- a/app/static/js/Requests/Contributions.js +++ /dev/null @@ -1,51 +0,0 @@ -/***************************************************************************** -* Contributions * -* Fetch requests for /contributions routes * -*****************************************************************************/ -Requests.contributions = {}; - -Requests.contributions.spacy_nlp_pipeline_models = {}; - -Requests.contributions.spacy_nlp_pipeline_models.ent = {}; - -Requests.contributions.spacy_nlp_pipeline_models.ent.delete = (spacyNlpPipelineModelId) => { - let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}`; - let init = { - method: 'DELETE' - }; - return Requests.JSONfetch(input, init); -}; - -Requests.contributions.spacy_nlp_pipeline_models.ent.isPublic = {}; - -Requests.contributions.spacy_nlp_pipeline_models.ent.isPublic.update = (spacyNlpPipelineModelId, value) => { - let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}/is_public`; - let init = { - method: 'PUT', - body: JSON.stringify(value) - }; - return Requests.JSONfetch(input, init); -}; - -Requests.contributions.tesseract_ocr_pipeline_models = {}; - -Requests.contributions.tesseract_ocr_pipeline_models.ent = {}; - -Requests.contributions.tesseract_ocr_pipeline_models.ent.delete = (tesseractOcrPipelineModelId) => { - let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}`; - let init = { - method: 'DELETE' - }; - return Requests.JSONfetch(input, init); -}; - -Requests.contributions.tesseract_ocr_pipeline_models.ent.isPublic = {}; - -Requests.contributions.tesseract_ocr_pipeline_models.ent.isPublic.update = (tesseractOcrPipelineModelId, value) => { - let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}/is_public`; - let init = { - method: 'PUT', - body: JSON.stringify(value) - }; - return Requests.JSONfetch(input, init); -}; diff --git a/app/static/js/Requests/Corpora.js b/app/static/js/Requests/Corpora.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/static/js/Requests/contributions/contributions.js b/app/static/js/Requests/contributions/contributions.js new file mode 100644 index 0000000000000000000000000000000000000000..2d9cf26a1bcdf0cf4a1c574fead0c26201bf1b43 --- /dev/null +++ b/app/static/js/Requests/contributions/contributions.js @@ -0,0 +1,5 @@ +/***************************************************************************** +* Contributions * +* Fetch requests for /contributions routes * +*****************************************************************************/ +Requests.contributions = {}; diff --git a/app/static/js/Requests/contributions/spacy_nlp_pipeline_models.js b/app/static/js/Requests/contributions/spacy_nlp_pipeline_models.js new file mode 100644 index 0000000000000000000000000000000000000000..e1422c1e62ec3c624401cf636f24b7b64d16117a --- /dev/null +++ b/app/static/js/Requests/contributions/spacy_nlp_pipeline_models.js @@ -0,0 +1,26 @@ +/***************************************************************************** +* SpaCy NLP Pipeline Models * +* Fetch requests for /contributions/spacy-nlp-pipeline-models routes * +*****************************************************************************/ +Requests.contributions.spacy_nlp_pipeline_models = {}; + +Requests.contributions.spacy_nlp_pipeline_models.entity = {}; + +Requests.contributions.spacy_nlp_pipeline_models.entity.delete = (spacyNlpPipelineModelId) => { + let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}`; + let init = { + method: 'DELETE' + }; + return Requests.JSONfetch(input, init); +}; + +Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic = {}; + +Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update = (spacyNlpPipelineModelId, value) => { + let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}/is_public`; + let init = { + method: 'PUT', + body: JSON.stringify(value) + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/static/js/Requests/contributions/tesseract_ocr_pipeline_models.js b/app/static/js/Requests/contributions/tesseract_ocr_pipeline_models.js new file mode 100644 index 0000000000000000000000000000000000000000..13feb42a3d9ee50d1559299a96aed7ff967db738 --- /dev/null +++ b/app/static/js/Requests/contributions/tesseract_ocr_pipeline_models.js @@ -0,0 +1,26 @@ +/***************************************************************************** +* Tesseract OCR Pipeline Models * +* Fetch requests for /contributions/tesseract-ocr-pipeline-models routes * +*****************************************************************************/ +Requests.contributions.tesseract_ocr_pipeline_models = {}; + +Requests.contributions.tesseract_ocr_pipeline_models.entity = {}; + +Requests.contributions.tesseract_ocr_pipeline_models.entity.delete = (tesseractOcrPipelineModelId) => { + let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}`; + let init = { + method: 'DELETE' + }; + return Requests.JSONfetch(input, init); +}; + +Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic = {}; + +Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update = (tesseractOcrPipelineModelId, value) => { + let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}/is_public`; + let init = { + method: 'PUT', + body: JSON.stringify(value) + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/static/js/Requests/corpora/corpora.js b/app/static/js/Requests/corpora/corpora.js new file mode 100644 index 0000000000000000000000000000000000000000..051fb07f4c5a0923113f1025bc95050f55789579 --- /dev/null +++ b/app/static/js/Requests/corpora/corpora.js @@ -0,0 +1,78 @@ +/***************************************************************************** +* Corpora * +* Fetch requests for /corpora routes * +*****************************************************************************/ +Requests.corpora = {}; + +Requests.corpora.ent = {}; + +Requests.corpora.ent.delete = (corpusId) => { + let input = `/corpora/${corpusId}`; + let init = { + method: 'DELETE' + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.build = (corpusId) => { + let input = `/corpora/${corpusId}/build`; + let init = { + method: 'POST', + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.isPublic = {}; + +Requests.corpora.ent.isPublic.update = (corpusId, value) => { + let input = `/corpora/${corpusId}/is_public`; + let init = { + method: 'PUT', + body: JSON.stringify(value) + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.files = {}; + +Requests.corpora.ent.files.ent = {}; + +Requests.corpora.ent.files.ent.delete = (corpusId, corpusFileId) => { + let input = `/corpora/${corpusId}/files/${corpusFileId}`; + let init = { + method: 'DELETE', + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.followers = {}; + +Requests.corpora.ent.followers.add = (corpusId, usernames) => { + let input = `/corpora/${corpusId}/followers`; + let init = { + method: 'POST', + body: JSON.stringify(usernames) + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.followers.ent = {}; + +Requests.corpora.ent.followers.ent.delete = (corpusId, followerId) => { + let input = `/corpora/${corpusId}/followers/${followerId}`; + let init = { + method: 'DELETE', + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.ent.followers.ent.role = {}; + +Requests.corpora.ent.followers.ent.role.update = (corpusId, followerId, value) => { + let input = `/corpora/${corpusId}/followers/${followerId}/role`; + let init = { + method: 'PUT', + body: JSON.stringify(value) + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js index 5997fb0cfcfbfda50d3dca09ddb7a230bcd6686f..46d3739d940614cec029383884bed9c79b2b05be 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js @@ -120,7 +120,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { switch (listAction) { case 'toggle-is-public': { let newIsPublicValue = listActionElement.checked; - Requests.contributions.spacy_nlp_pipeline_models.ent.isPublic.update(itemId, newIsPublicValue) + Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue) .catch((response) => { listActionElement.checked = !newIsPublicValue; }); @@ -141,7 +141,37 @@ class SpaCyNLPPipelineModelList extends ResourceList { let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; switch (listAction) { case 'delete-request': { - Requests.contributions.spacy_nlp_pipeline_models.ent.delete(itemId); + let values = this.listjs.get('id', itemId)[0].values(); + let modalElement = Utils.HTMLToElement( + ` + <div class="modal"> + <div class="modal-content"> + <h4>Confirm SpaCy NLP Pipeline Model deletion</h4> + <p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${values.title}</b>? All files will be permanently deleted!</p> + </div> + <div class="modal-footer"> + <a class="btn modal-close waves-effect waves-light">Cancel</a> + <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a> + </div> + </div> + ` + ); + document.querySelector('#modals').appendChild(modalElement); + let modal = M.Modal.init( + modalElement, + { + dismissible: false, + onCloseEnd: () => { + modal.destroy(); + modalElement.remove(); + } + } + ); + let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + confirmElement.addEventListener('click', (event) => { + Requests.contributions.spacy_nlp_pipeline_models.entity.delete(itemId); + }); + modal.open(); break; } case 'view': { diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js index 29d48dbbd4e87522b1ac4ff5a772861a47e1ec04..9d632b29da88654565017f90338a32d2e643d5f0 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js @@ -129,7 +129,7 @@ class TesseractOCRPipelineModelList extends ResourceList { switch (listAction) { case 'toggle-is-public': { let newIsPublicValue = listActionElement.checked; - Requests.contributions.tesseract_ocr_pipeline_models.ent.isPublic.update(itemId, newIsPublicValue) + Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue) .catch((response) => { listActionElement.checked = !newIsPublicValue; }); @@ -155,7 +155,37 @@ class TesseractOCRPipelineModelList extends ResourceList { let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; switch (listAction) { case 'delete-request': { - Requests.contributions.tesseract_ocr_pipeline_models.ent.delete(itemId); + let values = this.listjs.get('id', itemId)[0].values(); + let modalElement = Utils.HTMLToElement( + ` + <div class="modal"> + <div class="modal-content"> + <h4>Confirm Tesseract OCR Pipeline Model deletion</h4> + <p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${values.title}</b>? All files will be permanently deleted!</p> + </div> + <div class="modal-footer"> + <a class="btn modal-close waves-effect waves-light">Cancel</a> + <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a> + </div> + </div> + ` + ); + document.querySelector('#modals').appendChild(modalElement); + let modal = M.Modal.init( + modalElement, + { + dismissible: false, + onCloseEnd: () => { + modal.destroy(); + modalElement.remove(); + } + } + ); + let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + confirmElement.addEventListener('click', (event) => { + Requests.contributions.tesseract_ocr_pipeline_models.entity.delete(itemId); + }); + modal.open(); break; } case 'view': { diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index a97f1d9731bd614f3b92d22d39b5b11d05cc8258..6fef8d1a0274f0bdbce79af8b09396536387efca 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -58,7 +58,10 @@ filters='rjsmin', output='gen/Requests.%(version)s.js', 'js/Requests/Requests.js', - 'js/Requests/Contributions.js' + 'js/Requests/contributions/contributions.js', + 'js/Requests/contributions/spacy_nlp_pipeline_models.js', + 'js/Requests/contributions/tesseract_ocr_pipeline_models.js', + 'js/Requests/Corpora.js' %} <script src="{{ ASSET_URL }}"></script> {%- endassets %} diff --git a/app/templates/contributions/contributions.html.j2 b/app/templates/contributions/contributions.html.j2 index 99147b56f247447160b2af8dae8ca2c02f886262..efefcbbb2385915d9d3e8f4de39f3d1debe46027 100644 --- a/app/templates/contributions/contributions.html.j2 +++ b/app/templates/contributions/contributions.html.j2 @@ -20,7 +20,7 @@ <div class="col s4"> <div class="card extension-selector hoverable service-color" data-service="spacy-nlp-pipeline"> - <a href="{{ url_for('.spacy_nlp_pipeline_models.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> + <a href="{{ url_for('.spacy_nlp_pipeline_models.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> <div class="card-content"> <span class="card-title"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>SpaCy NLP Pipeline Models</span> <p>Here you can see and edit the models that you have created. You can also create new models.</p> diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 172dac9020103fb85b1c95de5cacede5edfc8027..3174713f7abf61d6b79c4443c414684d1e4dc70f 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -68,7 +68,7 @@ <a class="btn waves-effect waves-light modal-trigger" href="#publishing-modal" style="width: 100%;"><i class="material-icons left">publish</i>Publishing</a> </div> <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;"> - <a class="action-button btn red waves-effect waves-light" data-action="delete-request" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a> + <a class="btn red waves-effect waves-light modal-trigger" href="#delete-modal" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a> </div> </div> <span class="card-title">Social</span> @@ -131,6 +131,17 @@ </div> </div> +<div class="modal" id="delete-modal"> + <div class="modal-content"> + <h4>Confirm Corpus deletion</h4> + <p>Do you really want to delete the Corpus <b>{{ corpus.title }}</b>? All files will be permanently deleted!</p> + </div> + <div class="modal-footer"> + <a class="btn modal-close waves-effect waves-light">Cancel</a> + <a class="btn modal-close red waves-effect waves-light" id="delete-modal-delete-button">Delete</a> + </div> +</div> + <div class="modal no-autoinit" id="invite-user-modal"> <div class="modal-content"> <h4>Invite a nopaque user by username</h4> @@ -215,13 +226,21 @@ let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch'); publishingModalIsPublicSwitchElement.addEventListener('change', (event) => { let newIsPublic = publishingModalIsPublicSwitchElement.checked; - Utils.updateCorpusIsPublicRequest(corpusId, newIsPublic) + Requests.corpora.corpus.isPublic.update(corpusId, newIsPublic) .catch((response) => { publishingModalIsPublicSwitchElement.checked = !newIsPublic; }); }); // #endregion publishing_modal_js + // #region delete_modal_js + let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button'); + deleteModalDeleteButtonElement.addEventListener('click', (event) => { + Requests.corpora.corpus.delete(corpusId) + .then((response) => {window.location.href = '/dashboard';}); + }); + // #endregion delete_modal_js + // #region invite_user_modal_js let inviteUserModalElement = document.querySelector('#invite-user-modal'); let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search');