Merge branch 'public-corpus' into development

This commit is contained in:
Patrick Jentsch 2023-02-15 11:32:44 +01:00
commit 112d1ec020
21 changed files with 410 additions and 231 deletions

View File

@ -78,9 +78,6 @@ class UpdateCorpusFileForm(CorpusFileBaseForm):
kwargs['prefix'] = 'update-corpus-file-form' kwargs['prefix'] = 'update-corpus-file-form'
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class ChangeCorpusSettingsForm(FlaskForm):
is_public = BooleanField('Public Corpus')
submit = SubmitField()
class ImportCorpusForm(FlaskForm): class ImportCorpusForm(FlaskForm):
pass pass

View File

@ -1,3 +1,4 @@
from datetime import datetime
from flask import ( from flask import (
abort, abort,
current_app, current_app,
@ -11,21 +12,124 @@ from flask import (
) )
from flask_login import current_user, login_required from flask_login import current_user, login_required
from threading import Thread from threading import Thread
import jwt
import os import os
from app import db, hashids 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 . import bp
from .forms import ChangeCorpusSettingsForm, CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm from .forms import (
CreateCorpusFileForm,
CreateCorpusForm,
UpdateCorpusFileForm
)
@bp.route('') # @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 @login_required
def corpora(): def enable_corpus_is_public(corpus_id):
query = Corpus.query.filter( corpus = Corpus.query.get_or_404(corpus_id)
(Corpus.user_id == current_user.id) | (Corpus.is_public == True) 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('/<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 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']) @bp.route('/create', methods=['GET', 'POST'])
@ -58,48 +162,34 @@ def create_corpus():
@login_required @login_required
def corpus(corpus_id): def corpus(corpus_id):
corpus = Corpus.query.get_or_404(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(): 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( return render_template(
'corpora/corpus.html.j2', 'corpora/corpus.html.j2',
corpus_settings_form=corpus_settings_form,
corpus=corpus, corpus=corpus,
# token=token,
title='Corpus' title='Corpus'
) )
else: if current_user.is_following_corpus(corpus) or corpus.is_public:
print('public') corpus_files = [x.to_json_serializeable() for x in corpus.files]
return render_template( return render_template(
'corpora/corpus_public.html.j2', 'corpora/public_corpus.html.j2',
corpus=corpus, corpus=corpus,
corpus_files=corpus_files,
title='Corpus' title='Corpus'
) )
abort(403)
# @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'
# )
@bp.route('/<hashid:corpus_id>', methods=['DELETE']) @bp.route('/<hashid:corpus_id>', methods=['DELETE'])
@ -128,8 +218,7 @@ def analyse_corpus(corpus_id):
corpus = Corpus.query.get_or_404(corpus_id) corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.user == current_user if not (corpus.user == current_user
or current_user.is_administrator() or current_user.is_administrator()
or current_user.is_following_corpus(corpus) or current_user.is_following_corpus(corpus)):
or corpus.is_public):
abort(403) abort(403)
return render_template( return render_template(
'corpora/analyse_corpus.html.j2', 'corpora/analyse_corpus.html.j2',
@ -278,55 +367,3 @@ def import_corpus():
@login_required @login_required
def export_corpus(corpus_id): def export_corpus(corpus_id):
abort(503) 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'

View File

@ -27,15 +27,20 @@ def faq():
@bp.route('/dashboard') @bp.route('/dashboard')
@login_required @login_required
def dashboard(): def dashboard():
users = [ # users = [
u.to_json_serializeable(filter_by_privacy_settings=True) for u # 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() # in User.query.filter(User.is_public == True, User.id != current_user.id).all()
] # ]
corpora = [ # corpora = [
c.to_json_serializeable() for c # c.to_json_serializeable() for c
in Corpus.query.filter(Corpus.is_public == True).all() # 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) return render_template(
'main/dashboard.html.j2',
title='Dashboard',
# users=users,
# corpora=corpora
)
@bp.route('/dashboard2') @bp.route('/dashboard2')

View File

@ -11,6 +11,7 @@ class CorpusFileList extends ResourceList {
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.corpusId = listContainerElement.dataset.corpusId; this.corpusId = listContainerElement.dataset.corpusId;
if (this.userId === undefined || this.corpusId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -8,8 +8,9 @@ class CorpusList extends ResourceList {
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
this.isInitialized = false; this.isInitialized = false
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -11,6 +11,7 @@ class JobInputList extends ResourceList {
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId; this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -10,6 +10,7 @@ class JobList extends ResourceList {
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -11,6 +11,7 @@ class JobResultList extends ResourceList {
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId; this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -11,6 +11,7 @@ class SpaCyNLPPipelineModelList extends ResourceList {
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -11,6 +11,7 @@ class TesseractOCRPipelineModelList extends ResourceList {
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
this.isInitialized = false; this.isInitialized = false;
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => { app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}

View File

@ -12,6 +12,16 @@ class CorpusDisplay extends RessourceDisplay {
.addEventListener('click', (event) => { .addEventListener('click', (event) => {
Utils.deleteCorpusRequest(this.userId, this.corpusId); 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) { init(user) {

View File

@ -69,6 +69,88 @@ class Utils {
return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2)); 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) { static buildCorpusRequest(userId, corpusId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let corpus; let corpus;

View File

@ -20,7 +20,9 @@
'js/RessourceDisplays/JobDisplay.js', 'js/RessourceDisplays/JobDisplay.js',
'js/ResourceLists/ResourceList.js', 'js/ResourceLists/ResourceList.js',
'js/ResourceLists/CorpusFileList.js', 'js/ResourceLists/CorpusFileList.js',
'js/ResourceLists/PublicCorpusFileList.js',
'js/ResourceLists/CorpusList.js', 'js/ResourceLists/CorpusList.js',
'js/ResourceLists/PublicCorpusList.js',
'js/ResourceLists/JobList.js', 'js/ResourceLists/JobList.js',
'js/ResourceLists/JobInputList.js', 'js/ResourceLists/JobInputList.js',
'js/ResourceLists/JobResultList.js', 'js/ResourceLists/JobResultList.js',

View File

@ -10,15 +10,7 @@
<div class="col s12" data-corpus-id="{{ corpus.hashid }}" data-user-id="{{ corpus.user.hashid }}" id="corpus-display"> <div class="col s12" data-corpus-id="{{ corpus.hashid }}" data-user-id="{{ corpus.user.hashid }}" id="corpus-display">
<div class="row"> <div class="row">
<div class="col s8 m9 l10"> <div class="col s8 m9 l10">
{# <h1 id="title"><span class="corpus-title"></span></h1> #} <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 %}
</div> </div>
<div class="col s4 m3 l2 right-align"> <div class="col s4 m3 l2 right-align">
<p>&nbsp;</p> <p>&nbsp;</p>
@ -65,11 +57,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-action right-align"> <div class="card-action">
<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> <div class="right-align">
<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 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="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 disabled waves-effect waves-light" data-action="build-request"><i class="nopaque-icons left">K</i>Build</a>
<a class="action-button btn red waves-effect waves-light" data-action="delete-request"><i class="material-icons left">delete</i>Delete</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> </div>
</div> </div>
@ -85,25 +79,26 @@
</div> </div>
</div> </div>
</div> </div>
{% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %}
<div class="col s12"> {# <div class="col s12">
<form method="POST"> <div class="card">
{{ corpus_settings_form.hidden_tag() }} <div class="card-content">
<div class="card"> <div class="action-switch switch center-align" data-action="toggle-is-public">
<div class="card-content"> <span class="share"></span>
<span class="card-title" id="files">Corpus settings</span> <label>
<br> <input class="corpus-is-public" {% if corpus.is_public %}checked{% endif %} type="checkbox">
<p></p> <span class="lever"></span>
{{ wtf.render_field(corpus_settings_form.is_public) }} public
<br> </label>
</div>
<div class="card-action right-align">
{{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }}
</div> </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> </div>
</form> </div>
</div> </div>
{% endif %}
<div class="col s12"> <div class="col s12">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
@ -111,7 +106,7 @@
<div class="user-list no-autoinit"></div> <div class="user-list no-autoinit"></div>
</div> </div>
</div> </div>
</div> </div> #}
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}
@ -121,4 +116,25 @@
<script> <script>
let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display')); let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display'));
</script> </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 %} {% endblock scripts %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -42,7 +42,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col s12" id="social"> {# <div class="col s12" id="social">
<h3>Social</h3> <h3>Social</h3>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
@ -58,7 +58,7 @@
<div class="public-corpus-list no-autoinit"></div> <div class="public-corpus-list no-autoinit"></div>
</div> </div>
</div> </div>
</div> </div> #}
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}
@ -116,11 +116,11 @@
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> {# <script>
let userList = new UserList(document.querySelector('.user-list')); let userList = new UserList(document.querySelector('.user-list'));
userList.add({{ users|tojson }}); 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 }}); publicCorpusList.add({{ corpora|tojson }});
</script> </script> #}
{% endblock scripts %} {% endblock scripts %}

View File

@ -6,7 +6,7 @@ eventlet
Flask==2.1.3 Flask==2.1.3
Flask-APScheduler Flask-APScheduler
Flask-Assets Flask-Assets
Flask-Hashids==1.0.0 Flask-Hashids==1.0.1
Flask-HTTPAuth Flask-HTTPAuth
Flask-Login Flask-Login
Flask-Mail Flask-Mail
@ -24,6 +24,6 @@ pyScss
python-dotenv python-dotenv
pyyaml pyyaml
redis redis
SQLAlchemy==1.4.46 SQLAlchemy==1.4.45
tqdm tqdm
wtforms[email] wtforms[email]