diff --git a/app/corpora/forms.py b/app/corpora/forms.py index 8950622d1800a4cf48bc558264dbc5a2db9fb8f7..8403e621932deac8779a6f1b4fc27f9322984a29 100644 --- a/app/corpora/forms.py +++ b/app/corpora/forms.py @@ -78,9 +78,6 @@ class UpdateCorpusFileForm(CorpusFileBaseForm): kwargs['prefix'] = 'update-corpus-file-form' super().__init__(*args, **kwargs) -class ChangeCorpusSettingsForm(FlaskForm): - is_public = BooleanField('Public Corpus') - submit = SubmitField() class ImportCorpusForm(FlaskForm): pass diff --git a/app/corpora/routes.py b/app/corpora/routes.py index e1c67f401045fe0cafde4f67877231e5c0e244ae..4b10fa2cc3bbc6020ed203134dfdbf4e2b78ac5d 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -1,3 +1,4 @@ +from datetime import datetime from flask import ( abort, current_app, @@ -11,21 +12,124 @@ from flask import ( ) from flask_login import current_user, login_required from threading import Thread +import jwt import os from app import db, hashids -from app.models import Corpus, CorpusFile, CorpusStatus, CorpusFollowerAssociation, User +from app.models import Corpus, CorpusFile, CorpusStatus, User from . import bp -from .forms import ChangeCorpusSettingsForm, CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm +from .forms import ( + CreateCorpusFileForm, + CreateCorpusForm, + UpdateCorpusFileForm +) + + +# @bp.route('/share/<token>', methods=['GET', 'POST']) +# def share_corpus(token): +# try: +# payload = jwt.decode( +# token, +# current_app.config['SECRET_KEY'], +# algorithms=['HS256'], +# issuer=current_app.config['SERVER_NAME'], +# options={'require': ['iat', 'iss', 'sub']} +# ) +# except jwt.PyJWTError: +# return False +# corpus_hashid = payload.get('sub') +# corpus_id = hashids.decode(corpus_hashid) +# return redirect(url_for('.corpus', corpus_id=corpus_id)) + + +@bp.route('/<hashid:corpus_id>/enable_is_public', methods=['POST']) +@login_required +def enable_corpus_is_public(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + if not (corpus.user == current_user or current_user.is_administrator()): + abort(403) + corpus.is_public = True + db.session.commit() + return '', 204 + + +@bp.route('/<hashid:corpus_id>/disable_is_public', methods=['POST']) +@login_required +def disable_corpus_is_public(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + if not (corpus.user == current_user or current_user.is_administrator()): + abort(403) + corpus.is_public = False + db.session.commit() + return '', 204 -@bp.route('') +# @bp.route('/<hashid:corpus_id>/follow', methods=['GET', 'POST']) +# @login_required +# def follow_corpus(corpus_id): +# corpus = Corpus.query.get_or_404(corpus_id) +# user_hashid = request.args.get('user_id') +# if user_hashid is None: +# user = current_user +# else: +# if not current_user.is_administrator(): +# abort(403) +# else: +# user_id = hashids.decode(user_hashid) +# user = User.query.get_or_404(user_id) +# if not user.is_following_corpus(corpus): +# user.follow_corpus(corpus) +# db.session.commit() +# flash(f'You are following {corpus.title} now', category='corpus') +# return {}, 202 + + +@bp.route('/<hashid:corpus_id>/unfollow', methods=['GET', 'POST']) @login_required -def corpora(): - query = Corpus.query.filter( - (Corpus.user_id == current_user.id) | (Corpus.is_public == True) +def unfollow_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + user_hashid = request.args.get('user_id') + if user_hashid is None: + user = current_user + elif current_user.is_administrator(): + user_id = hashids.decode(user_hashid) + user = User.query.get_or_404(user_id) + else: + abort(403) + if user.is_following_corpus(corpus): + user.unfollow_corpus(corpus) + db.session.commit() + flash(f'You are not following {corpus.title} anymore', category='corpus') + return {}, 202 + + +# @bp.route('/add_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>') +# def add_permission(corpus_id, user_id, permission): +# a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() +# a.add_permission(permission) +# db.session.commit() +# return 'ok' + + +# @bp.route('/remove_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>') +# def remove_permission(corpus_id, user_id, permission): +# a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() +# a.remove_permission(permission) +# db.session.commit() +# return 'ok' + + +@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' ) - corpora = [c.to_json_serializeable() for c in query.all()] - return render_template('corpora/corpora.html.j2', corpora=corpora, title='Corpora') @bp.route('/create', methods=['GET', 'POST']) @@ -58,48 +162,34 @@ def create_corpus(): @login_required def corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) - if not (corpus.user == current_user - or current_user.is_administrator() - or current_user.is_following_corpus(corpus) - or corpus.is_public): - abort(403) - corpus_settings_form = ChangeCorpusSettingsForm( - data=corpus.to_json_serializeable(), - prefix='corpus-settings-form' - ) - if corpus_settings_form.validate_on_submit(): - corpus.is_public = corpus_settings_form.is_public.data - db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.corpus', corpus_id=corpus.id)) if corpus.user == current_user or current_user.is_administrator(): + # now = datetime.utcnow() + # payload = { + # 'exp': now + timedelta(weeks=1), + # 'iat': now, + # 'iss': current_app.config['SERVER_NAME'], + # 'sub': corpus.hashid + # } + # token = jwt.encode( + # payload, + # current_app.config['SECRET_KEY'], + # algorithm='HS256' + # ) return render_template( 'corpora/corpus.html.j2', - corpus_settings_form=corpus_settings_form, corpus=corpus, + # token=token, title='Corpus' ) - else: - print('public') + if current_user.is_following_corpus(corpus) or corpus.is_public: + corpus_files = [x.to_json_serializeable() for x in corpus.files] return render_template( - 'corpora/corpus_public.html.j2', + 'corpora/public_corpus.html.j2', corpus=corpus, + corpus_files=corpus_files, title='Corpus' ) - - - -# @bp.route('/<hashid:corpus_id>/update') -# @login_required -# def update_corpus(corpus_id): -# corpus = Corpus.query.get_or_404(corpus_id) -# if not (corpus.user == current_user or current_user.is_administrator()): -# abort(403) -# return render_template( -# 'corpora/update_corpus.html.j2', -# corpus=corpus, -# title='Corpus' -# ) + abort(403) @bp.route('/<hashid:corpus_id>', methods=['DELETE']) @@ -128,8 +218,7 @@ def analyse_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) if not (corpus.user == current_user or current_user.is_administrator() - or current_user.is_following_corpus(corpus) - or corpus.is_public): + or current_user.is_following_corpus(corpus)): abort(403) return render_template( 'corpora/analyse_corpus.html.j2', @@ -278,55 +367,3 @@ def import_corpus(): @login_required def export_corpus(corpus_id): abort(503) - -@bp.route('/<hashid:corpus_id>/follow', methods=['GET', 'POST']) -@login_required -def follow_corpus(corpus_id): - corpus = Corpus.query.get_or_404(corpus_id) - user_hashid = request.args.get('user_id') - if user_hashid is None: - user = current_user - else: - if not current_user.is_administrator(): - abort(403) - else: - user_id = hashids.decode(user_hashid) - user = User.query.get_or_404(user_id) - if not user.is_following_corpus(corpus): - user.follow_corpus(corpus) - db.session.commit() - return {}, 202 - -@bp.route('/<hashid:corpus_id>/unfollow', methods=['GET', 'POST']) -@login_required -def unfollow_corpus(corpus_id): - corpus = Corpus.query.get_or_404(corpus_id) - user_hashid = request.args.get('user_id') - if user_hashid is None: - user = current_user - else: - if not current_user.is_administrator(): - abort(403) - else: - user_id = hashids.decode(user_hashid) - user = User.query.get_or_404(user_id) - if user.is_following_corpus(corpus): - user.unfollow_corpus(corpus) - db.session.commit() - return {}, 202 - -@bp.route('/add_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>') -def add_permission(corpus_id, user_id, permission): - a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() - a.add_permission(permission) - db.session.commit() - return 'ok' - - -@bp.route('/remove_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>') -def remove_permission(corpus_id, user_id, permission): - a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() - a.remove_permission(permission) - db.session.commit() - return 'ok' - diff --git a/app/main/routes.py b/app/main/routes.py index aed63853844745d68cd308af7178debcc21b5482..eff0dadd60d30a877fc75bae8d2dd6a34a2f18a4 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -27,15 +27,20 @@ def faq(): @bp.route('/dashboard') @login_required def dashboard(): - users = [ - u.to_json_serializeable(filter_by_privacy_settings=True) for u - in User.query.filter(User.is_public == True, User.id != current_user.id).all() - ] - corpora = [ - c.to_json_serializeable() for c - in Corpus.query.filter(Corpus.is_public == True).all() - ] - return render_template('main/dashboard.html.j2', title='Dashboard', users=users, corpora=corpora) + # users = [ + # u.to_json_serializeable(filter_by_privacy_settings=True) for u + # in User.query.filter(User.is_public == True, User.id != current_user.id).all() + # ] + # corpora = [ + # c.to_json_serializeable() for c + # in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() + # ] + return render_template( + 'main/dashboard.html.j2', + title='Dashboard', + # users=users, + # corpora=corpora + ) @bp.route('/dashboard2') diff --git a/app/static/js/ResourceLists/CorpusFileList.js b/app/static/js/ResourceLists/CorpusFileList.js index ca27fe27e10505a8ffffc84bd0d828172879c2a7..c052b2eaf529467ac659e3f83815cd99fbe23260 100644 --- a/app/static/js/ResourceLists/CorpusFileList.js +++ b/app/static/js/ResourceLists/CorpusFileList.js @@ -11,6 +11,7 @@ class CorpusFileList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.corpusId = listContainerElement.dataset.corpusId; + if (this.userId === undefined || this.corpusId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/CorpusList.js b/app/static/js/ResourceLists/CorpusList.js index e4af0b12caff89cd00f7dc42111d295d09105a98..bf62ebed1b69e1b67f3cc327609d2e6cee30b736 100644 --- a/app/static/js/ResourceLists/CorpusList.js +++ b/app/static/js/ResourceLists/CorpusList.js @@ -8,8 +8,9 @@ class CorpusList extends ResourceList { constructor(listContainerElement, options = {}) { super(listContainerElement, options); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); - this.isInitialized = false; + this.isInitialized = false this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobInputList.js b/app/static/js/ResourceLists/JobInputList.js index 36cb47f787432ee04a54739036ddb85b6228fabe..7f1a51058d01cdb9674ef51d95c6491cb4f1a769 100644 --- a/app/static/js/ResourceLists/JobInputList.js +++ b/app/static/js/ResourceLists/JobInputList.js @@ -11,6 +11,7 @@ class JobInputList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; + if (this.userId === undefined || this.jobId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobList.js b/app/static/js/ResourceLists/JobList.js index 1fa14ea2ae4e4348cff0ff51d9cdcf8de986b7bd..ff7f82b2a54fbcdb0c127d25f4ff86d2cca65e9e 100644 --- a/app/static/js/ResourceLists/JobList.js +++ b/app/static/js/ResourceLists/JobList.js @@ -10,6 +10,7 @@ class JobList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobResultList.js b/app/static/js/ResourceLists/JobResultList.js index 71c430afe58fdbed30e8f71f8a61263734921466..b0cbc08897f5bcebca97b48dcbf3e28f98d2d3b1 100644 --- a/app/static/js/ResourceLists/JobResultList.js +++ b/app/static/js/ResourceLists/JobResultList.js @@ -11,6 +11,7 @@ class JobResultList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; + if (this.userId === undefined || this.jobId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/PublicCorpusFileList.js b/app/static/js/ResourceLists/PublicCorpusFileList.js new file mode 100644 index 0000000000000000000000000000000000000000..37a284cf117ee285a9d3252f38872e280ceb1a3e --- /dev/null +++ b/app/static/js/ResourceLists/PublicCorpusFileList.js @@ -0,0 +1,15 @@ +class PublicCorpusFileList extends CorpusFileList { + get item() { + return ` + <tr class="list-item clickable hoverable"> + <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="list-action-trigger btn-floating service-color darken waves-effect waves-light" data-list-action="view" data-service="corpus-analysis"><i class="material-icons">send</i></a> + </td> + </tr> + `.trim(); + } +} diff --git a/app/static/js/ResourceLists/PublicCorpusList.js b/app/static/js/ResourceLists/PublicCorpusList.js new file mode 100644 index 0000000000000000000000000000000000000000..fdb2e9edaaf4627222c61a5404fb2b81849940ea --- /dev/null +++ b/app/static/js/ResourceLists/PublicCorpusList.js @@ -0,0 +1,14 @@ +class PublicCorpusList extends CorpusList { + get item() { + return ` + <tr class="list-item clickable hoverable"> + <td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td> + <td><b class="title"></b><br><i class="description"></i></td> + <td><span class="status badge new corpus-status-color corpus-status-text" data-badge-caption=""></span></td> + <td class="right-align"> + <a class="list-action-trigger btn-floating service-color darken waves-effect waves-light" data-list-action="view" data-service="corpus-analysis"><i class="material-icons">send</i></a> + </td> + </tr> + `.trim(); + } +} diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js index 078ec90c59b7a0a3f853203816fbaa6c772d3e95..b90fb06bf09545dbdc462eecf7533dd8e6cfc921 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js @@ -11,6 +11,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js index ad319041e82a3992436aae2ae2f5c64407940f35..4ad3f5b143327f29b827f9a6936ce0204f33e85f 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js @@ -11,6 +11,7 @@ class TesseractOCRPipelineModelList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/RessourceDisplays/CorpusDisplay.js b/app/static/js/RessourceDisplays/CorpusDisplay.js index af4da56a3f4ef53de0d4c86a33138dedc01536e6..fd342dd4bcbedb51450edeef8e38a67e82e3cbc3 100644 --- a/app/static/js/RessourceDisplays/CorpusDisplay.js +++ b/app/static/js/RessourceDisplays/CorpusDisplay.js @@ -12,6 +12,16 @@ class CorpusDisplay extends RessourceDisplay { .addEventListener('click', (event) => { Utils.deleteCorpusRequest(this.userId, this.corpusId); }); + this.displayElement + .querySelector('.action-switch[data-action="toggle-is-public"]') + .addEventListener('click', (event) => { + if (event.target.tagName !== 'INPUT') {return;} + if (event.target.checked) { + Utils.enableCorpusIsPublicRequest(this.userId, this.corpusId); + } else { + Utils.disableCorpusIsPublicRequest(this.userId, this.corpusId); + } + }); } init(user) { diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index d63be025b41ee4676ccbad03bfd7d97164839629..aee382a0c46c219cf928f6bf7f6b6908ba7ef211 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -69,6 +69,88 @@ class Utils { return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2)); } + static enableCorpusIsPublicRequest(userId, corpusId) { + return new Promise((resolve, reject) => { + let corpus; + try { + corpus = app.data.users[userId].corpora[corpusId]; + } catch (error) { + corpus = {}; + } + + let modalElement = Utils.HTMLToElement( + ` + <div class="modal"> + <div class="modal-content"> + <h4>Hier könnte eine Warnung stehen</h4> + <p></p> + </div> + <div class="modal-footer"> + <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a> + <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Confirm</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) => { + let corpusTitle = corpus?.title; + fetch(`/corpora/${corpusId}/enable_is_public`, {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + app.flash(`Corpus "${corpusTitle}" is public now`, 'corpus'); + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + modal.open(); + }); + } + + static disableCorpusIsPublicRequest(userId, corpusId) { + return new Promise((resolve, reject) => { + let corpus; + try { + corpus = app.data.users[userId].corpora[corpusId]; + } catch (error) { + corpus = {}; + } + + let corpusTitle = corpus?.title; + fetch(`/corpora/${corpusId}/disable_is_public`, {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + app.flash(`Corpus "${corpusTitle}" is private now`, 'corpus'); + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + } + static buildCorpusRequest(userId, corpusId) { return new Promise((resolve, reject) => { let corpus; diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 9540607921b5addf0566695a84ab490a751e2129..53a0469c245863507c04000d2dc186dba7a16646 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -20,7 +20,9 @@ 'js/RessourceDisplays/JobDisplay.js', 'js/ResourceLists/ResourceList.js', 'js/ResourceLists/CorpusFileList.js', + 'js/ResourceLists/PublicCorpusFileList.js', 'js/ResourceLists/CorpusList.js', + 'js/ResourceLists/PublicCorpusList.js', 'js/ResourceLists/JobList.js', 'js/ResourceLists/JobInputList.js', 'js/ResourceLists/JobResultList.js', diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index c29da972c7e86ac67a02b8aea5f36caacade75d6..34228347187cc93a25d29c617e3d7a7566ec2418 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -10,15 +10,7 @@ <div class="col s12" data-corpus-id="{{ corpus.hashid }}" data-user-id="{{ corpus.user.hashid }}" id="corpus-display"> <div class="row"> <div class="col s8 m9 l10"> - {# <h1 id="title"><span class="corpus-title"></span></h1> #} - <h1 id="title">{{ corpus.title }}</h1> - {% if not corpus.user == current_user %} - {% if current_user.is_following_corpus(corpus) %} - <a class="btn waves-effect waves-light" id="follow-corpus-request"><i class="material-icons left">add</i>Unfollow Corpus</a> - {% elif not current_user.is_following_corpus(corpus) %} - <a class="btn waves-effect waves-light" id="follow-corpus-request"><i class="material-icons left">add</i>Follow Corpus</a> - {% endif %} - {% endif %} + <h1 id="title"><span class="corpus-title"></span></h1> </div> <div class="col s4 m3 l2 right-align"> <p> </p> @@ -65,11 +57,13 @@ </div> </div> </div> - <div class="card-action right-align"> - <a class="btn corpus-analyse-trigger disabled waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">search</i>Analyze</a> - <a class="action-button btn disabled waves-effect waves-light" data-action="build-request"><i class="nopaque-icons left">K</i>Build</a> - <a class="btn disabled export-corpus-trigger waves-effect waves-light" href="{{ url_for('corpora.export_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">import_export</i>Export</a> - <a class="action-button btn red waves-effect waves-light" data-action="delete-request"><i class="material-icons left">delete</i>Delete</a> + <div class="card-action"> + <div class="right-align"> + <a class="btn corpus-analyse-trigger disabled waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">search</i>Analyze</a> + <a class="action-button btn disabled waves-effect waves-light" data-action="build-request"><i class="nopaque-icons left">K</i>Build</a> + <a class="btn disabled export-corpus-trigger waves-effect waves-light" href="{{ url_for('corpora.export_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">import_export</i>Export</a> + <a class="action-button btn red waves-effect waves-light" data-action="delete-request"><i class="material-icons left">delete</i>Delete</a> + </div> </div> </div> </div> @@ -85,25 +79,26 @@ </div> </div> </div> - {% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %} - <div class="col s12"> - <form method="POST"> - {{ corpus_settings_form.hidden_tag() }} - <div class="card"> - <div class="card-content"> - <span class="card-title" id="files">Corpus settings</span> - <br> - <p></p> - {{ wtf.render_field(corpus_settings_form.is_public) }} - <br> - </div> - <div class="card-action right-align"> - {{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }} + + {# <div class="col s12"> + <div class="card"> + <div class="card-content"> + <div class="action-switch switch center-align" data-action="toggle-is-public"> + <span class="share"></span> + <label> + <input class="corpus-is-public" {% if corpus.is_public %}checked{% endif %} type="checkbox"> + <span class="lever"></span> + public + </label> </div> + + <a class="action-button btn waves-effect waves-light" id="generate-share-link-button">Generate Share Link</a> + <div id="share-link"></div> + <a class="action-button btn-small waves-effect waves-light hide" id="copy-share-link-button">Copy</a> </div> - </form> + </div> </div> - {% endif %} + <div class="col s12"> <div class="card"> <div class="card-content"> @@ -111,7 +106,7 @@ <div class="user-list no-autoinit"></div> </div> </div> - </div> + </div> #} </div> </div> {% endblock page_content %} @@ -121,4 +116,25 @@ <script> let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display')); </script> +{# <script> + let generateShareLinkButton = document.querySelector('#generate-share-link-button'); + let copyShareLinkButton = document.querySelector('#copy-share-link-button'); + let shareLink = document.querySelector('#share-link'); + let linkValue = '{{ url_for('corpora.share_corpus', token=token, _external=True) }}'; + + generateShareLinkButton.addEventListener('click', () => { + let shareLinkElement = document.createElement('input'); + shareLinkElement.value = linkValue; + shareLinkElement.setAttribute('readonly', ''); + shareLink.appendChild(shareLinkElement); + copyShareLinkButton.classList.remove('hide'); + }); + + copyShareLinkButton.addEventListener('click', () => { + let shareLinkElement = document.querySelector('#share-link input'); + shareLinkElement.select(); + document.execCommand('copy'); + app.flash(`Copied!`, 'success'); + }); +</script> #} {% endblock scripts %} diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 deleted file mode 100644 index 7ca143789e84b4f54fc65f960909f476c2ae0300..0000000000000000000000000000000000000000 --- a/app/templates/corpora/corpus_public.html.j2 +++ /dev/null @@ -1,86 +0,0 @@ -{% extends "base.html.j2" %} -{% import "materialize/wtf.html.j2" as wtf %} - -{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} - -{% block page_content %} -<div class="container"> - <div class="row"> - <div class="col s12"> - <h1>{{ title }} </h1> - <div class="card service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid"> - <div class="card-content"> - <div class="row"> - <div class="col s12"> - <p><b>Description:</b> {{ corpus.description }}</p> - <br> - <p></p> - </div> - <div class="col s6"> - <p><b>Creation date:</b> {{ corpus.creation_date }}</p> - </div> - <div class="col s6"> - <p><b>Number of tokens used:</b> {{ corpus.num_tokens }}</p> - </div> - </div> - </div> - </div> - </div> - </div> -</div> -{% endblock page_content %} - -{% block scripts %} -{{ super() }} -<script> - let corpusFollowingRequest = document.querySelector('#follow-corpus-request'); - - {# let followingUserList = new UserList(document.querySelector('.user-list')); - followingUserList.add({{ following_users|tojson }}); #} - - corpusFollowingRequest.addEventListener('click', () => { - corpusFollowingRequest.innerHTML = '<i class="material-icons left">add</i>Unfollow Corpus'; - if ("{{ current_user.is_following_corpus(corpus) }}" === "False") { - corpusFollowingRequest.lastChild.textContent = 'Unfollow Corpus'; - return new Promise((resolve, reject) => { - fetch(`/corpora/{{ corpus.hashid }}/follow`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);} - app.flash(`You follow "{{ corpus.title }}" now`, 'corpus'); - window.location.href = '{{ url_for("corpora.corpus", corpus_id=corpus.id) }}' - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - - } else { - corpusFollowingRequest.innerHTML = '<i class="material-icons left">add</i>Unfollow Corpus'; - return new Promise((resolve, reject) => { - fetch(`/corpora/{{ corpus.hashid }}/unfollow`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);} - app.flash(`You are not following "{{ corpus.title }}" anymore`, 'corpus'); - resolve(response); - window.location.href = '{{ url_for("corpora.corpus", corpus_id=corpus.id) }}' - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - - } - }); -</script> -{% endblock scripts %} diff --git a/app/templates/corpora/corpora.html.j2 b/app/templates/corpora/public_corpora.html.j2 similarity index 100% rename from app/templates/corpora/corpora.html.j2 rename to app/templates/corpora/public_corpora.html.j2 diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..94b3524cee745f0e591636003d62d43e504c3d8a --- /dev/null +++ b/app/templates/corpora/public_corpus.html.j2 @@ -0,0 +1,80 @@ +{% extends "base.html.j2" %} +{% import "materialize/wtf.html.j2" as wtf %} + +{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1>{{ corpus.title }} </h1> + <div class="row"> + <div class="col s8 m9 l10"> + {% if current_user.is_following_corpus(corpus) %} + <a class="action-button btn waves-effect waves-light" data-action="unfollow-request"><i class="material-icons left">add</i>Unfollow Corpus</a> + {% endif %} + {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} + <a class="btn waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}">Analyze</a> + {% endif %} + </div> + </div> + <div class="card service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid"> + <div class="card-content"> + <div class="row"> + <div class="col s12"> + <p><b>Status:</b> <span class="chip corpus-status-text corpus-status-color white-text" data-status="{{ corpus.status.name }}"></span></p> + <p></p> + <br> + </div> + <div class="col s12"> + <p><b>Description:</b> {{ corpus.description }}</p> + <br> + <p></p> + </div> + <div class="col s6"> + <p><b>Creation date:</b> {{ corpus.creation_date }}</p> + </div> + <div class="col s6"> + <p><b>Number of tokens used:</b> {{ corpus.num_tokens }}</p> + </div> + </div> + </div> + </div> + <div class="card"> + <div class="card-content"> + <span class="card-title" id="files">Corpus files</span> + <div class="corpus-file-list no-autoinit" data-user-id="{{ corpus.user.hashid }}" data-corpus-id="{{ corpus.hashid }}"></div> + </div> + </div> + </div> + </div> + </div> +</div> +{% endblock page_content %} + +{% block scripts %} +{{ super() }} +<script> + let corpusFileList = new PublicCorpusFileList(document.querySelector('.corpus-file-list')); + corpusFileList.add({{ corpus_files|tojson }}); + + let unfollowRequestElement = document.querySelector('.action-button[data-action="unfollow-request"]'); + unfollowRequestElement.addEventListener('click', () => { + return new Promise((resolve, reject) => { + fetch('{{ url_for("corpora.unfollow_corpus", corpus_id=corpus.id) }}', {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + resolve(response); + window.location.href = '{{ url_for("corpora.corpus", corpus_id=corpus.id) }}'; + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + }); +</script> +{% endblock scripts %} diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2 index 5391a8af466b982bc8d1c4fc31cd1ecb64ee5149..ca5422df41903820e7055e9c723d8467ddff73e6 100644 --- a/app/templates/main/dashboard.html.j2 +++ b/app/templates/main/dashboard.html.j2 @@ -42,7 +42,7 @@ </div> </div> </div> - <div class="col s12" id="social"> + {# <div class="col s12" id="social"> <h3>Social</h3> <div class="card"> <div class="card-content"> @@ -58,7 +58,7 @@ <div class="public-corpus-list no-autoinit"></div> </div> </div> - </div> + </div> #} </div> </div> {% endblock page_content %} @@ -116,11 +116,11 @@ {% block scripts %} {{ super() }} -<script> +{# <script> let userList = new UserList(document.querySelector('.user-list')); userList.add({{ users|tojson }}); - let publicCorpusList = new CorpusList(document.querySelector('.public-corpus-list')); + let publicCorpusList = new PublicCorpusList(document.querySelector('.public-corpus-list')); publicCorpusList.add({{ corpora|tojson }}); -</script> +</script> #} {% endblock scripts %} diff --git a/requirements.txt b/requirements.txt index 1c6a94f55afed62df975b38592a5dffd78d5b173..838cee8fe7e067fa51462010d6b50f507849c1dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ eventlet Flask==2.1.3 Flask-APScheduler Flask-Assets -Flask-Hashids==1.0.0 +Flask-Hashids==1.0.1 Flask-HTTPAuth Flask-Login Flask-Mail @@ -24,6 +24,6 @@ pyScss python-dotenv pyyaml redis -SQLAlchemy==1.4.46 +SQLAlchemy==1.4.45 tqdm wtforms[email]