diff --git a/.env.tpl b/.env.tpl index ca0657d59c8398055e0963816b59b757a010e07c..0d01b60daa070c0e0c6549c5250969c6b39bf22b 100644 --- a/.env.tpl +++ b/.env.tpl @@ -99,8 +99,9 @@ NOPAQUE_ADMIN_EMAIL_ADRESS= # CHOOSE ONE: development, production, testing # NOPAQUE_CONFIG= +# DEFAULT: None # EXAMPLE: contact.nopaque@example.com -NOPAQUE_CONTACT_EMAIL_ADRESS= +# NOPAQUE_CONTACT_EMAIL_ADRESS= # DEFAULT: /mnt/nopaque # NOTE: This must be a network share and it must be available on all Docker Swarm nodes diff --git a/docker-compose.yml b/docker-compose.yml index b6f7a9f0a3baf02d4b518ff29afba89ab2868795..fe90fada00a349e140708c838a1b03a993ec100d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - db - mq env_file: .env - image: nopaque/web + image: nopaque:development restart: unless-stopped volumes: - "${NOPAQUE_DATA_DIR:-/mnt/nopaque}:${NOPAQUE_DATA_DIR:-/mnt/nopaque}" @@ -27,7 +27,7 @@ services: - db - nopaque env_file: .env - image: nopaque/daemon + image: nopaqued:development restart: unless-stopped volumes: - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/web/app/__init__.py b/web/app/__init__.py index 7a5d8b6850c2f3e48dc30227e017801f5f5254ba..a39a51a55576aa65c4bcf433a98696f8a66e76a6 100644 --- a/web/app/__init__.py +++ b/web/app/__init__.py @@ -28,22 +28,23 @@ def create_app(config_name): socketio.init_app( app, message_queue=config[config_name].SOCKETIO_MESSAGE_QUEUE_URI) - from . import events - from .admin import admin as admin_blueprint + with app.app_context(): + from . import events + from .admin import admin as admin_blueprint + from .auth import auth as auth_blueprint + from .corpora import corpora as corpora_blueprint + from .errors import errors as errors_blueprint + from .jobs import jobs as jobs_blueprint + from .main import main as main_blueprint + from .services import services as services_blueprint + from .settings import settings as settings_blueprint app.register_blueprint(admin_blueprint, url_prefix='/admin') - from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') - from .corpora import corpora as corpora_blueprint app.register_blueprint(corpora_blueprint, url_prefix='/corpora') - from .errors import errors as errors_blueprint app.register_blueprint(errors_blueprint) - from .jobs import jobs as jobs_blueprint app.register_blueprint(jobs_blueprint, url_prefix='/jobs') - from .main import main as main_blueprint app.register_blueprint(main_blueprint) - from .profile import profile as profile_blueprint - app.register_blueprint(profile_blueprint, url_prefix='/profile') - from .services import services as services_blueprint app.register_blueprint(services_blueprint, url_prefix='/services') + app.register_blueprint(settings_blueprint, url_prefix='/settings') return app diff --git a/web/app/admin/forms.py b/web/app/admin/forms.py index fd41d01646e4904b556e610ea54dcb1dc4e03c16..42706baba3269e43a40eb4935c4264bb2e001bb6 100644 --- a/web/app/admin/forms.py +++ b/web/app/admin/forms.py @@ -1,36 +1,15 @@ -from flask_wtf import FlaskForm -from wtforms import (BooleanField, SelectField, StringField, SubmitField, - ValidationError) -from wtforms.validators import DataRequired, Email, Length, Regexp -from ..models import Role, User +from flask_login import current_user +from wtforms import BooleanField, SelectField +from ..models import Role +from ..settings.forms import EditGeneralSettingsForm -class EditUserForm(FlaskForm): - email = StringField('Email', - validators=[DataRequired(), Length(1, 64), Email()]) - username = StringField('Username', - validators=[DataRequired(), Length(1, 64), - Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, - 'Usernames must have only ' - 'letters, numbers, dots or ' - 'underscores')]) +class EditGeneralSettingsAdminForm(EditGeneralSettingsForm): confirmed = BooleanField('Confirmed') role = SelectField('Role', coerce=int) - name = StringField('Real name', validators=[Length(0, 64)]) - submit = SubmitField('Update Profile') - def __init__(self, user, *args, **kwargs): - super(EditUserForm, self).__init__(*args, **kwargs) + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, user=user, **kwargs) self.role.choices = [(role.id, role.name) for role in Role.query.order_by(Role.name).all()] self.user = user - - def validate_email(self, field): - if field.data != self.user.email and \ - User.query.filter_by(email=field.data).first(): - raise ValidationError('Email already registered.') - - def validate_username(self, field): - if field.data != self.user.username and \ - User.query.filter_by(username=field.data).first(): - raise ValidationError('Username already in use.') diff --git a/web/app/admin/views.py b/web/app/admin/views.py index e8d0d91691db6e86a4b283620c2780efe78df872..d06c856af88546bf0334674bd847796604341980 100644 --- a/web/app/admin/views.py +++ b/web/app/admin/views.py @@ -1,17 +1,17 @@ from flask import flash, redirect, render_template, url_for -from flask_login import login_required +from flask_login import current_user, login_required from . import admin -from .forms import EditUserForm +from .forms import EditGeneralSettingsAdminForm from .. import db from ..decorators import admin_required from ..models import Role, User -from ..profile import tasks as profile_tasks +from ..settings import tasks as settings_tasks -@admin.route('/') +@admin.route('/users') @login_required @admin_required -def index(): +def users(): users = User.query.all() users = [dict(username=u.username, email=u.email, @@ -19,48 +19,49 @@ def index(): confirmed=u.confirmed, id=u.id) for u in users] - return render_template('admin/index.html.j2', - title='Administration tools', - users=users) + return render_template('admin/users.html.j2', title='Users', users=users) -@admin.route('/user/<int:user_id>') +@admin.route('/users/<int:user_id>') @login_required @admin_required def user(user_id): user = User.query.get_or_404(user_id) - return render_template('admin/user.html.j2', title='Administration: User', - user=user) + return render_template('admin/user.html.j2', title='User', user=user) -@admin.route('/user/<int:user_id>/delete') +@admin.route('/users/<int:user_id>/delete') @login_required @admin_required def delete_user(user_id): - profile_tasks.delete_user(user_id) + settings_tasks.delete_user(user_id) flash('User has been deleted!') - return redirect(url_for('admin.index')) + return redirect(url_for('.users')) -@admin.route('/user/<int:user_id>/edit', methods=['GET', 'POST']) +@admin.route('/users/<int:user_id>/edit_general_settings', + methods=['GET', 'POST']) @login_required @admin_required -def edit_user(user_id): +def edit_general_settings(user_id): user = User.query.get_or_404(user_id) - edit_user_form = EditUserForm(user=user) - if edit_user_form.validate_on_submit(): - user.email = edit_user_form.email.data - user.username = edit_user_form.username.data - user.confirmed = edit_user_form.confirmed.data - user.role = Role.query.get(edit_user_form.role.data) + form = EditGeneralSettingsAdminForm(user=user) + if form.validate_on_submit(): + user.setting_dark_mode = form.dark_mode.data + user.email = form.email.data + user.username = form.username.data + user.confirmed = form.confirmed.data + user.role = Role.query.get(form.role.data) db.session.add(user) db.session.commit() flash('The profile has been updated.') - return redirect(url_for('admin.edit_user', user_id=user.id)) - edit_user_form.email.data = user.email - edit_user_form.username.data = user.username - edit_user_form.confirmed.data = user.confirmed - edit_user_form.role.data = user.role_id - return render_template('admin/edit_user.html.j2', - edit_user_form=edit_user_form, - title='Administration: Edit user', user=user) + return redirect(url_for('admin.edit_general_settings', user_id=user.id)) + form.confirmed.data = user.confirmed + form.dark_mode.data = user.setting_dark_mode + form.email.data = user.email + form.role.data = user.role_id + form.username.data = user.username + return render_template('admin/edit_general_settings.html.j2', + form=form, + title='General settings', + user=user) diff --git a/web/app/auth/forms.py b/web/app/auth/forms.py index e3f5c2766915db5f302aa634f8046d0fa29a1b4a..3344096b519da6a684170c2469f0a9bac09986d5 100644 --- a/web/app/auth/forms.py +++ b/web/app/auth/forms.py @@ -1,3 +1,4 @@ +from flask import current_app from ..models import User from flask_wtf import FlaskForm from wtforms import (BooleanField, PasswordField, StringField, SubmitField, @@ -17,9 +18,9 @@ class RegistrationForm(FlaskForm): username = StringField( 'Username', validators=[DataRequired(), Length(1, 64), - Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, - 'Usernames must have only letters, numbers, dots ' - 'or underscores')] + Regexp(current_app.config['ALLOWED_USERNAME_REGEX'], + message='Usernames must have only letters, numbers,' + ' dots or underscores')] ) password = PasswordField( 'Password', diff --git a/web/app/corpora/forms.py b/web/app/corpora/forms.py index f25c6b64e2b789821b2836c0733bf4408a307864..6eafbf6462de1e94d3f5ac9b4effcb182c93ac1b 100644 --- a/web/app/corpora/forms.py +++ b/web/app/corpora/forms.py @@ -9,21 +9,22 @@ class AddCorpusFileForm(FlaskForm): ''' Form to add a .vrt corpus file to the current corpus. ''' - address = StringField('Adress', validators=[Length(0, 255)]) + # Required fields author = StringField('Author', validators=[DataRequired(), Length(1, 255)]) + file = FileField('File', validators=[DataRequired()]) + title = StringField('Title', validators=[DataRequired(), Length(1, 255)]) + # Optional fields + address = StringField('Adress', validators=[Length(0, 255)]) booktitle = StringField('Booktitle', validators=[Length(0, 255)]) chapter = StringField('Chapter', validators=[Length(0, 255)]) editor = StringField('Editor', validators=[Length(0, 255)]) - file = FileField('File', validators=[DataRequired()]) institution = StringField('Institution', validators=[Length(0, 255)]) journal = StringField('Journal', validators=[Length(0, 255)]) pages = StringField('Pages', validators=[Length(0, 255)]) publisher = StringField('Publisher', validators=[Length(0, 255)]) - publishing_year = IntegerField('Publishing year', - validators=[DataRequired()]) + publishing_year = IntegerField('Publishing year') school = StringField('School', validators=[Length(0, 255)]) submit = SubmitField() - title = StringField('Title', validators=[DataRequired(), Length(1, 255)]) def __init__(self, corpus, *args, **kwargs): super(AddCorpusFileForm, self).__init__(*args, **kwargs) @@ -43,8 +44,11 @@ class EditCorpusFileForm(FlaskForm): ''' Form to edit meta data of one corpus file. ''' - address = StringField('Adress', validators=[Length(0, 255)]) + # Required fields author = StringField('Author', validators=[DataRequired(), Length(1, 255)]) + title = StringField('Title', validators=[DataRequired(), Length(1, 255)]) + # Optional fields + address = StringField('Adress', validators=[Length(0, 255)]) booktitle = StringField('Booktitle', validators=[Length(0, 255)]) chapter = StringField('Chapter', validators=[Length(0, 255)]) editor = StringField('Editor', validators=[Length(0, 255)]) @@ -52,11 +56,9 @@ class EditCorpusFileForm(FlaskForm): journal = StringField('Journal', validators=[Length(0, 255)]) pages = StringField('Pages', validators=[Length(0, 255)]) publisher = StringField('Publisher', validators=[Length(0, 255)]) - publishing_year = IntegerField('Publishing year', - validators=[DataRequired()]) + publishing_year = IntegerField('Publishing year') school = StringField('School', validators=[Length(0, 255)]) submit = SubmitField() - title = StringField('Title', validators=[DataRequired(), Length(1, 255)]) class AddCorpusForm(FlaskForm): diff --git a/web/app/corpora/views.py b/web/app/corpora/views.py index 1e98b679cf8a51126ae5012f52c3ab5633c0b49a..0bdc9413e357de7fd50f0836f800596dab3132f3 100644 --- a/web/app/corpora/views.py +++ b/web/app/corpora/views.py @@ -51,8 +51,7 @@ def corpus(corpus_id): title=corpus_file.title, publishing_year=corpus_file.publishing_year, corpus_id=corpus.id, - id=corpus_file.id - ) + id=corpus_file.id) for corpus_file in corpus.files] return render_template('corpora/corpus.html.j2', corpus=corpus, @@ -78,6 +77,7 @@ def analyse_corpus(corpus_id): prefix='inspect-display-options-form') return render_template( 'corpora/analyse_corpus.html.j2', + corpus=corpus, corpus_id=corpus_id, display_options_form=display_options_form, query_form=query_form, diff --git a/web/app/profile/forms.py b/web/app/profile/forms.py deleted file mode 100644 index 14517e293ed0d48ba89e1d75fe43eb1b9ac4d6de..0000000000000000000000000000000000000000 --- a/web/app/profile/forms.py +++ /dev/null @@ -1,52 +0,0 @@ -from flask_wtf import FlaskForm -from wtforms import (BooleanField, PasswordField, SelectField, StringField, - SubmitField, ValidationError) -from wtforms.validators import DataRequired, Email, EqualTo - - -class EditEmailForm(FlaskForm): - email = StringField('New email', validators=[Email(), DataRequired()]) - save_email = SubmitField('Save email') - - -class EditGeneralSettingsForm(FlaskForm): - dark_mode = BooleanField('Dark mode') - job_status_mail_notifications = SelectField( - 'Job status mail notifications', - choices=[('', 'Choose your option'), - ('all', 'Notify on all status changes'), - ('end', 'Notify only when a job ended'), - ('none', 'No status update notifications')], - validators=[DataRequired()]) - job_status_site_notifications = SelectField( - 'Job status site notifications', - choices=[('', 'Choose your option'), - ('all', 'Notify on all status changes'), - ('end', 'Notify only when a job ended'), - ('none', 'No status update notifications')], - validators=[DataRequired()]) - save_settings = SubmitField('Save settings') - - -class EditPasswordForm(FlaskForm): - current_password = PasswordField('Current password', - validators=[DataRequired()]) - password = PasswordField( - 'New password', - validators=[DataRequired(), EqualTo('password_confirmation', - message='Passwords must match.')] - ) - password_confirmation = PasswordField( - 'Password confirmation', - validators=[DataRequired(), - EqualTo('password', message='Passwords must match.')] - ) - save_password = SubmitField('Save password') - - def __init__(self, user, *args, **kwargs): - super(EditPasswordForm, self).__init__(*args, **kwargs) - self.user = user - - def validate_current_password(self, field): - if not self.user.verify_password(field.data): - raise ValidationError('Invalid password.') diff --git a/web/app/profile/views.py b/web/app/profile/views.py deleted file mode 100644 index f156715fbf824382c2d36a52a78482d12444f96c..0000000000000000000000000000000000000000 --- a/web/app/profile/views.py +++ /dev/null @@ -1,69 +0,0 @@ -from flask import flash, redirect, render_template, url_for -from flask_login import current_user, login_required, logout_user -from . import profile -from . import tasks -from .forms import EditEmailForm, EditGeneralSettingsForm, EditPasswordForm -from .. import db - - -@profile.route('/settings', methods=['GET', 'POST']) -@login_required -def settings(): - edit_email_form = EditEmailForm(prefix='edit-email-form') - edit_general_settings_form = EditGeneralSettingsForm( - prefix='edit-general-settings-form') - edit_password_form = EditPasswordForm(prefix='edit-password-form', - user=current_user) - # Check if edit_email_form is submitted and valid - if (edit_email_form.save_email.data - and edit_email_form.validate_on_submit()): - db.session.add(current_user) - db.session.commit() - flash('Your email address has been updated.') - return redirect(url_for('profile.settings')) - # Check if edit_settings_form is submitted and valid - if (edit_general_settings_form.save_settings.data - and edit_general_settings_form.validate_on_submit()): - current_user.setting_dark_mode = \ - edit_general_settings_form.dark_mode.data - current_user.setting_job_status_mail_notifications = \ - edit_general_settings_form.job_status_mail_notifications.data - current_user.setting_job_status_site_notifications = \ - edit_general_settings_form.job_status_site_notifications.data - db.session.add(current_user) - db.session.commit() - flash('Your settings have been updated.') - return redirect(url_for('profile.settings')) - # Check if edit_password_form is submitted and valid - if (edit_password_form.save_password.data - and edit_password_form.validate_on_submit()): - current_user.password = edit_password_form.password.data - db.session.add(current_user) - db.session.commit() - flash('Your password has been updated.') - return redirect(url_for('profile.settings')) - # If no form is submitted or valid, fill out fields with current values - edit_email_form.email.data = current_user.email - edit_general_settings_form.dark_mode.data = current_user.setting_dark_mode - edit_general_settings_form.job_status_site_notifications.data = \ - current_user.setting_job_status_site_notifications - edit_general_settings_form.job_status_mail_notifications.data = \ - current_user.setting_job_status_mail_notifications - return render_template( - 'profile/settings.html.j2', - edit_email_form=edit_email_form, - edit_password_form=edit_password_form, - edit_general_settings_form=edit_general_settings_form, - title='Settings') - - -@profile.route('/delete', methods=['GET', 'POST']) -@login_required -def delete(): - """ - View to delete yourslef and all associated data. - """ - tasks.delete_user(current_user.id) - logout_user() - flash('Your account has been deleted!') - return redirect(url_for('main.index')) diff --git a/web/app/profile/__init__.py b/web/app/settings/__init__.py similarity index 57% rename from web/app/profile/__init__.py rename to web/app/settings/__init__.py index 85b9f61cdd705ef41af827f47c015aa5bd817917..2329d880513181a2642551fd3fd1076d8a727217 100644 --- a/web/app/profile/__init__.py +++ b/web/app/settings/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -profile = Blueprint('profile', __name__) +settings = Blueprint('settings', __name__) from . import views # noqa diff --git a/web/app/settings/forms.py b/web/app/settings/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..6f7abeefbf254199c0f27896737af6d04f265091 --- /dev/null +++ b/web/app/settings/forms.py @@ -0,0 +1,78 @@ +from flask import current_app +from flask_login import current_user +from flask_wtf import FlaskForm +from wtforms import (BooleanField, PasswordField, SelectField, StringField, + SubmitField, ValidationError) +from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp +from ..models import User + + +class ChangePasswordForm(FlaskForm): + password = PasswordField('Old password', validators=[DataRequired()]) + new_password = PasswordField( + 'New password', + validators=[DataRequired(), EqualTo('password_confirmation', + message='Passwords must match.')] + ) + new_password2 = PasswordField( + 'Confirm new password', validators=[DataRequired()]) + submit = SubmitField('Change password') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + + def validate_current_password(self, field): + if not self.user.verify_password(field.data): + raise ValidationError('Invalid password.') + + +class EditGeneralSettingsForm(FlaskForm): + dark_mode = BooleanField('Dark mode') + email = StringField('E-Mail', + validators=[DataRequired(), Length(1, 254), Email()]) + username = StringField( + 'Benutzername', + validators=[DataRequired(), + Length(1, 64), + Regexp(current_app.config['ALLOWED_USERNAME_REGEX'], + message='Usernames must have only letters, numbers,' + ' dots or underscores')] + ) + submit = SubmitField('Submit') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + + def validate_email(self, field): + if (field.data != self.user.email + and User.query.filter_by(email=field.data).first()): + raise ValidationError('Email already registered.') + + def validate_username(self, field): + if (field.data != self.user.username + and User.query.filter_by(username=field.data).first()): + raise ValidationError('Username already in use.') + + +class EditNotificationSettingsForm(FlaskForm): + job_status_mail_notifications = SelectField( + 'Job status mail notifications', + choices=[('', 'Choose your option'), + ('all', 'Notify on all status changes'), + ('end', 'Notify only when a job ended'), + ('none', 'No status update notifications')], + validators=[DataRequired()]) + job_status_site_notifications = SelectField( + 'Job status site notifications', + choices=[('', 'Choose your option'), + ('all', 'Notify on all status changes'), + ('end', 'Notify only when a job ended'), + ('none', 'No status update notifications')], + validators=[DataRequired()]) + submit = SubmitField('Save settings') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user diff --git a/web/app/profile/tasks.py b/web/app/settings/tasks.py similarity index 100% rename from web/app/profile/tasks.py rename to web/app/settings/tasks.py diff --git a/web/app/settings/views.py b/web/app/settings/views.py new file mode 100644 index 0000000000000000000000000000000000000000..1bd4a07f1b614277761a422a83e7c7c5adc06d47 --- /dev/null +++ b/web/app/settings/views.py @@ -0,0 +1,80 @@ +from flask import current_app, flash, redirect, render_template, url_for +from flask_login import current_user, login_required, logout_user +from . import settings, tasks +from .forms import (ChangePasswordForm, EditGeneralSettingsForm, + EditNotificationSettingsForm) +from .. import db +from ..decorators import admin_required +from ..models import Role, User +import os +import uuid + + +@settings.route('/') +@login_required +def index(): + return redirect(url_for('.edit_general_settings')) + + +@settings.route('/change_password', methods=['GET', 'POST']) +@login_required +def change_password(): + form = ChangePasswordForm() + if form.validate_on_submit(): + current_user.password = form.new_password.data + db.session.commit() + flash('Your password has been updated.') + return redirect(url_for('.change_password')) + return render_template('settings/change_password.html.j2', + form=form, + title='Change password') + + +@settings.route('/edit_general_settings', methods=['GET', 'POST']) +@login_required +def edit_general_settings(): + form = EditGeneralSettingsForm() + if form.validate_on_submit(): + current_user.email = form.email.data + current_user.setting_dark_mode = form.dark_mode.data + current_user.username = form.username.data + db.session.commit() + flash('Your changes have been saved.') + form.dark_mode.data = current_user.setting_dark_mode + form.email.data = current_user.email + form.username.data = current_user.username + return render_template('settings/edit_general_settings.html.j2', + form=form, + title='General settings') + + +@settings.route('/edit_notification_settings', methods=['GET', 'POST']) +@login_required +def edit_notification_settings(): + form = EditNotificationSettingsForm() + if form.validate_on_submit(): + current_user.setting_job_status_mail_notifications = \ + form.job_status_mail_notifications.data + current_user.setting_job_status_site_notifications = \ + form.job_status_site_notifications.data + db.session.commit() + flash('Your changes have been saved.') + form.job_status_mail_notifications.data = \ + current_user.setting_job_status_mail_notifications + form.job_status_site_notifications.data = \ + current_user.setting_job_status_site_notifications + return render_template('settings/edit_notification_settings.html.j2', + form=form, + title='Notification settings') + + +@settings.route('/delete') +@login_required +def delete(): + """ + View to delete current_user and all associated data. + """ + tasks.delete_user(current_user.id) + logout_user() + flash('Your account has been deleted!') + return redirect(url_for('main.index')) diff --git a/web/app/static/css/material_design_icons.min.css b/web/app/static/css/material_design_icons.min.css deleted file mode 100644 index cc0058014e1a19becfa096ea03fd42796b1e543f..0000000000000000000000000000000000000000 --- a/web/app/static/css/material_design_icons.min.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(../fonts/material_design_icons/MaterialIcons-Regular.eot);src:local('Material Icons'),local('MaterialIcons-Regular'),url(../fonts/material_design_icons/MaterialIcons-Regular.ttf) format('truetype'),url(../fonts/material_design_icons/MaterialIconsOutlined-Regular.otf) format('opentype'),url(../fonts/material_design_icons/MaterialIconsRound-Regular.otf) format('opentype'),url(../fonts/material_design_icons/MaterialIconsSharp-Regular.otf) format('opentype'),url(../fonts/material_design_icons/MaterialIconsTwoTone-Regular.otf) format('opentype')}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'} diff --git a/web/app/static/css/materialize.sidenav-fixed.css b/web/app/static/css/materialize.sidenav-fixed.css new file mode 100644 index 0000000000000000000000000000000000000000..7cf789dab07d6de940ecbdf8320dbfe1746f87dd --- /dev/null +++ b/web/app/static/css/materialize.sidenav-fixed.css @@ -0,0 +1,12 @@ +/* + * The sidenav-fixed class is used which causes the sidenav to be fixed and open + * on large screens and hides to the regular functionality on smaller screens. + * In order to prevent the sidenav to overlap the content, the content (in our + * case header, main and footer) gets an offset equal to the width of the + * sidenav. + */ +@media only screen and (min-width : 993px) { + header, main, footer {padding-left: 300px;} + .modal:not(.bottom-sheet) {left: 300px;} + .navbar-fixed > nav {width: calc(100% - 300px)} +} diff --git a/web/app/static/css/materialize.sticky-footer.css b/web/app/static/css/materialize.sticky-footer.css new file mode 100644 index 0000000000000000000000000000000000000000..49b8f5b814be6fc2104be8bb9396ea00616cae0b --- /dev/null +++ b/web/app/static/css/materialize.sticky-footer.css @@ -0,0 +1,13 @@ +/* + * Force the footer to always stay on the bottom of the page regardless of how + * little content is on the page. + */ +body { + display: flex; + min-height: 100vh; + flex-direction: column; +} + +main { + flex: 1 0 auto; +} diff --git a/web/app/static/css/nopaque.css b/web/app/static/css/nopaque.css index d61138bc08dcf2b833bd70d221dacae2b24ba60c..821f8b034a2ce3e22500f63d8a4789bd7f38afaa 100644 --- a/web/app/static/css/nopaque.css +++ b/web/app/static/css/nopaque.css @@ -1,19 +1,7 @@ -/* - * ### Start sticky footer ### - * Force the footer to always stay on the bottom of the page regardless of how - * little content is on the page. -*/ -body { - display: flex; - min-height: 100vh; - flex-direction: column; +.tab .material-icons { + line-height: inherit; } -main { - flex: 1 0 auto; -} -/* ### End sticky footer ### */ - /* add custom bold class */ .bold { font-weight: bold; diff --git a/web/app/static/js/nopaque.js b/web/app/static/js/nopaque.js index 35ceac833ef0e45b0d3d24b055c9dfd5db344b06..b006ec25087d3b7c1e6d18f23a716a359fdc2c81 100644 --- a/web/app/static/js/nopaque.js +++ b/web/app/static/js/nopaque.js @@ -4,12 +4,8 @@ */ var nopaque = {}; -// nopaque ressources -nopaque.socket = undefined; - // User data nopaque.user = {}; -nopaque.user.isAuthenticated = undefined; nopaque.user.settings = {}; nopaque.user.settings.darkMode = undefined; nopaque.corporaSubscribers = []; @@ -25,81 +21,76 @@ nopaque.foreignCorporaSubscribers = []; nopaque.foreignJobsSubscribers = []; nopaque.foreignQueryResultsSubscribers = []; -nopaque.flashedMessages = undefined; - // nopaque functions -nopaque.socket = {}; -nopaque.socket.init = function() { - nopaque.socket = io({transports: ['websocket']}); - // Add event handlers - nopaque.socket.on("user_data_stream_init", function(msg) { - nopaque.user = JSON.parse(msg); - for (let subscriber of nopaque.corporaSubscribers) { - subscriber._init(nopaque.user.corpora); - } - for (let subscriber of nopaque.jobsSubscribers) { - subscriber._init(nopaque.user.jobs); - } - for (let subscriber of nopaque.queryResultsSubscribers) { - subscriber._init(nopaque.user.query_results); - } - }); - - nopaque.socket.on("user_data_stream_update", function(msg) { - var patch; - - patch = JSON.parse(msg); - nopaque.user = jsonpatch.apply_patch(nopaque.user, patch); - corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); - jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); - query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); - for (let subscriber of nopaque.corporaSubscribers) { - subscriber._update(corpora_patch); - } - for (let subscriber of nopaque.jobsSubscribers) { - subscriber._update(jobs_patch); - } - for (let subscriber of nopaque.queryResultsSubscribers) { - subscriber._update(query_results_patch); - } - if (["all", "end"].includes(nopaque.user.settings.job_status_site_notifications)) { - for (operation of jobs_patch) { - /* "/jobs/{jobId}/..." -> ["{jobId}", ...] */ - pathArray = operation.path.split("/").slice(2); - if (operation.op === "replace" && pathArray[1] === "status") { - if (nopaque.user.settings.job_status_site_notifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;} - nopaque.flash(`[<a href="/jobs/${pathArray[0]}">${nopaque.user.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`, "job"); - } +nopaque.socket = io({transports: ['websocket']}); +// Add event handlers +nopaque.socket.on("user_data_stream_init", function(msg) { + nopaque.user = JSON.parse(msg); + for (let subscriber of nopaque.corporaSubscribers) { + subscriber._init(nopaque.user.corpora); + } + for (let subscriber of nopaque.jobsSubscribers) { + subscriber._init(nopaque.user.jobs); + } + for (let subscriber of nopaque.queryResultsSubscribers) { + subscriber._init(nopaque.user.query_results); + } +}); + +nopaque.socket.on("user_data_stream_update", function(msg) { + var patch; + + patch = JSON.parse(msg); + nopaque.user = jsonpatch.apply_patch(nopaque.user, patch); + corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); + jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); + query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); + for (let subscriber of nopaque.corporaSubscribers) { + subscriber._update(corpora_patch); + } + for (let subscriber of nopaque.jobsSubscribers) { + subscriber._update(jobs_patch); + } + for (let subscriber of nopaque.queryResultsSubscribers) { + subscriber._update(query_results_patch); + } + if (["all", "end"].includes(nopaque.user.settings.job_status_site_notifications)) { + for (operation of jobs_patch) { + /* "/jobs/{jobId}/..." -> ["{jobId}", ...] */ + pathArray = operation.path.split("/").slice(2); + if (operation.op === "replace" && pathArray[1] === "status") { + if (nopaque.user.settings.job_status_site_notifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;} + nopaque.flash(`[<a href="/jobs/${pathArray[0]}">${nopaque.user.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`, "job"); } } - }); + } +}); - nopaque.socket.on("foreign_user_data_stream_init", function(msg) { - nopaque.foreignUser = JSON.parse(msg); - for (let subscriber of nopaque.foreignCorporaSubscribers) { - subscriber._init(nopaque.foreignUser.corpora); - } - for (let subscriber of nopaque.foreignJobsSubscribers) { - subscriber._init(nopaque.foreignUser.jobs); - } - for (let subscriber of nopaque.foreignQueryResultsSubscribers) { - subscriber._init(nopaque.foreignUser.query_results); - } - }); - - nopaque.socket.on("foreign_user_data_stream_update", function(msg) { - var patch; - - patch = JSON.parse(msg); - nopaque.foreignUser = jsonpatch.apply_patch(nopaque.foreignUser, patch); - corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); - jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); - query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); - for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(corpora_patch);} - for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(jobs_patch);} - for (let subscriber of nopaque.foreignQueryResultsSubscribers) {subscriber._update(query_results_patch);} - }); -} +nopaque.socket.on("foreign_user_data_stream_init", function(msg) { + nopaque.foreignUser = JSON.parse(msg); + for (let subscriber of nopaque.foreignCorporaSubscribers) { + subscriber._init(nopaque.foreignUser.corpora); + } + for (let subscriber of nopaque.foreignJobsSubscribers) { + subscriber._init(nopaque.foreignUser.jobs); + } + for (let subscriber of nopaque.foreignQueryResultsSubscribers) { + subscriber._init(nopaque.foreignUser.query_results); + } +}); + +nopaque.socket.on("foreign_user_data_stream_update", function(msg) { + var patch; + + patch = JSON.parse(msg); + nopaque.foreignUser = jsonpatch.apply_patch(nopaque.foreignUser, patch); + corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); + jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); + query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); + for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(corpora_patch);} + for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(jobs_patch);} + for (let subscriber of nopaque.foreignQueryResultsSubscribers) {subscriber._update(query_results_patch);} +}); nopaque.Forms = {}; nopaque.Forms.init = function() { @@ -173,32 +164,10 @@ nopaque.Forms.init = function() { } } -nopaque.Navigation = {}; -nopaque.Navigation.init = function() { - /* ### Initialize sidenav-main ### */ - for (let entry of document.querySelectorAll("#sidenav-main a")) { - if (entry.href === window.location.href) { - entry.parentNode.classList.add("active"); - } - } -} - -nopaque.flash = function() { - var classes, toast, toastActionElement; - - switch (arguments.length) { - case 1: - category = "message"; - message = arguments[0]; - break; - case 2: - message = arguments[0]; - category = arguments[1]; - break; - default: - console.error("Usage: nopaque.flash(message) or nopaque.flash(message, category)") - } +nopaque.flash = function(message, category) { + let toast; + let toastActionElement; switch (category) { case "corpus": @@ -219,27 +188,5 @@ nopaque.flash = function() { <i class="material-icons">close</i> </button>`}); toastActionElement = toast.el.querySelector('.toast-action[data-action="close"]'); - if (toastActionElement) { - toastActionElement.addEventListener('click', function() { - toast.dismiss(); - }); - } + toastActionElement.addEventListener('click', () => {toast.dismiss();}); } - - -document.addEventListener('DOMContentLoaded', () => { - // Disable all option elements with no value - for (let optionElement of document.querySelectorAll('option[value=""]')) { - optionElement.disabled = true; - } - M.AutoInit(); - M.CharacterCounter.init(document.querySelectorAll('input[data-length][type="text"]')); - M.Dropdown.init(document.querySelectorAll('#nav-notifications, #nav-account'), - {alignment: 'right', constrainWidth: false, coverTrigger: false}); - nopaque.Forms.init(); - nopaque.Navigation.init(); - while (nopaque.flashedMessages.length) { - flashedMessage = nopaque.flashedMessages.shift(); - nopaque.flash(flashedMessage[1], flashedMessage[0]); - } -}); diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index d91186dcc95b727e9ed58e6c1109a699674b46fa..28ae867941b7105e21bf2b926e47b568015d5c35 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -85,7 +85,6 @@ RessourceList.dataMappers = { author: corpus_file.author, filename: corpus_file.filename, link: `${corpus_file.corpus_id}/files/${corpus_file.id}`, - publishing_year: corpus_file.publishing_year, title: corpus_file.title, title1: corpus_file.title, "delete-link": `/corpora/${corpus_file.corpus_id}/files/${corpus_file.id}/delete`, @@ -131,11 +130,11 @@ RessourceList.dataMappers = { confirmed: user.confirmed, email: user.email, id: user.id, - link: `user/${user.id}`, + link: `users/${user.id}`, role_id: user.role_id, username: user.username, username2: user.username, - "delete-link": `/admin/user/${user.id}/delete`, + "delete-link": `/admin/users/${user.id}/delete`, "delete-modal": `delete-user-${user.id}-modal`, "delete-modal-trigger": `delete-user-${user.id}-modal`, }), @@ -239,7 +238,6 @@ RessourceList.options = { <td class="filename" style="word-break: break-word;"></td> <td class="author" style="word-break: break-word;"></td> <td class="title" style="word-break: break-word;"></td> - <td class="publishing_year" style="word-break: break-word;"></td> <td> <div class="right-align"> <a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal-trigger" data-position="top" data-tooltip="Delete"> @@ -267,7 +265,6 @@ RessourceList.options = { valueNames: [ "author", "filename", - "publishing_year", "title", "title1", {name: "delete-link", attr: "href"}, diff --git a/web/app/templates/_colors.html.j2 b/web/app/templates/_colors.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..be667210da85fe56d7f5a729f87d06a4bc36f5c3 --- /dev/null +++ b/web/app/templates/_colors.html.j2 @@ -0,0 +1,15 @@ +{% set colors = {'primary': '#00426f', + 'secondary': '#1A5C89', + 'footer': '#b1b3b4', + 'corpus_analysis': '#aa9cc9', + 'corpus_analysis_darken': '#6b3f89', + 'corpus_analysis_lighten': '#ebe8f6', + 'file_setup': '#d5dc95', + 'file_setup_darken': '#a1b300', + 'file_setup_lighten': '#f2f3e1', + 'nlp': '#98acd2', + 'nlp_darken': '#0064a3', + 'nlp_lighten': '#e5e8f5', + 'ocr': '#a9d8c8', + 'ocr_darken': '#00a58b', + 'ocr_lighten': '#e7f4f1'} %} diff --git a/web/app/templates/admin/edit_general_settings.html.j2 b/web/app/templates/admin/edit_general_settings.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..929a867c00a27abb81026cc4f902d61001266961 --- /dev/null +++ b/web/app/templates/admin/edit_general_settings.html.j2 @@ -0,0 +1,70 @@ +{% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">Edit user</h1> + </div> + + <div class="col s12 m4"> + <h2>{{ user.username }}</h2> + <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> + <a class="waves-effect waves-light btn" href="{{ url_for('.user', user_id=user.id) }}"><i class="material-icons left">arrow_back</i>Back to user administration</a> + </div> + + <div class="col s12 m8"> + <div class="card"> + <form method="POST"> + <div class="card-content"> + {{ form.hidden_tag() }} + {{ wtf.render_field(form.username, data_length='64', material_icon='account_circle') }} + {{ wtf.render_field(form.email, class_='validate', material_icon='email', type='email') }} + {{ wtf.render_field(form.role, material_icon='swap_vert') }} + <div class="row"> + <div class="col s12"><p> </p></div> + <div class="col s1"> + <p><i class="material-icons">brightness_3</i></p> + </div> + <div class="col s8"> + <p>{{ form.dark_mode.label.text }}</p> + <p class="light">Enable dark mode to ease your eyes.</p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + {{ form.dark_mode() }} + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + <div class="col s1"> + <p><i class="material-icons">check</i></p> + </div> + <div class="col s8"> + <p>{{ form.confirmed.label.text }}</p> + <p class="light">Change confirmation status manually.</p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + {{ form.confirmed() }} + <span class="lever"></span> + </label> + </div> + </div> + </div> + </div> + <div class="card-action right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </form> + </div> + </div> + </div> +</div> +{% endblock %} diff --git a/web/app/templates/admin/edit_user.html.j2 b/web/app/templates/admin/edit_user.html.j2 deleted file mode 100644 index 3a48adf8e16f8c20f87bb529a2b9c81e20e4a22b..0000000000000000000000000000000000000000 --- a/web/app/templates/admin/edit_user.html.j2 +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "nopaque.html.j2" %} - -{% block page_content %} -<div class="col s12 m4"> - <h3 id="title">{{ user.username }}</h3> - <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> - <a class="waves-effect waves-light btn" href="{{ url_for('admin.user', user_id=user.id) }}"><i class="material-icons left">arrow_back</i>Back to user administration</a> -</div> - -<div class="col s12 m8"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ edit_user_form.hidden_tag() }} - {{ M.render_field(edit_user_form.username, data_length='64', material_icon='account_circle') }} - {{ M.render_field(edit_user_form.email, class_='validate', material_icon='email', type='email') }} - {{ M.render_field(edit_user_form.role, material_icon='swap_vert') }} - {{ M.render_field(edit_user_form.confirmed, material_icon='check') }} - </div> - <div class="card-action right-align"> - {{ M.render_field(edit_user_form.submit, material_icon='send') }} - </div> - </form> - </div> -</div> - -{% endblock %} diff --git a/web/app/templates/admin/index.html.j2 b/web/app/templates/admin/index.html.j2 deleted file mode 100644 index 9fca5b583b8cf29374a0b0f43fae0096d680cc9b..0000000000000000000000000000000000000000 --- a/web/app/templates/admin/index.html.j2 +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "nopaque.html.j2" %} - -{% set full_width = True %} - -{% block page_content %} -<div class="col s12"> - <div class="card"> - <div class="card-content" id="users"> - <span class="card-title">User list</span> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-user" class="search" type="search"></input> - <label for="search-user">Search user</label> - </div> - <ul class="pagination paginationTop"></ul> - <table class="highlight responsive-table"> - <thead> - <tr> - <th class="sort" data-sort="username">Username</th> - <th class="sort" data-sort="email">Email</th> - <th class="sort" data-sort="role_id">Role</th> - <th class="sort" data-sort="confirmed">Confirmed Status</th> - <th class="sort" data-sort="id">Id</th> - <th>{# Actions #}</th> - </tr> - </thead> - <tbody class="list"> - </tbody> - </table> - <ul class="pagination paginationBottom"></ul> - </div> - </div> -</div> - -<script type="module"> -import {RessourceList} from '../../static/js/nopaque.lists.js'; -let userList = new RessourceList('users', null, "User", RessourceList.options.extended); -document.addEventListener("DOMContentLoaded", () => { - userList._add({{ users|tojson|safe }}); -}); -</script> -{% endblock %} diff --git a/web/app/templates/admin/user.html.j2 b/web/app/templates/admin/user.html.j2 index c1e53a182f9eb2a6b3a1c52c366afa0732b1b80f..570b6dd399809a5c5b5ea5acb89c90f3e061092a 100644 --- a/web/app/templates/admin/user.html.j2 +++ b/web/app/templates/admin/user.html.j2 @@ -1,34 +1,40 @@ {% extends "nopaque.html.j2" %} {% block page_content %} -<div class="col s12 m4"> - <h3 id="title">{{ user.username }}</h3> - <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> - <a class="waves-effect waves-light btn" href="{{ url_for('admin.index') }}"><i class="material-icons left">arrow_back</i>Back to admin board</a> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <div class="card"> - <div class="card-content"> - <span class="card-title">User information</span> - <ul> - <li>Username: {{ user.username }}</li> - <li>Email: {{ user.email }}</li> - <li>ID: {{ user.id }}</li> - <li>Member since: {{ user.member_since.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li> - <li>Confirmed status: {{ user.confirmed }}</li> - <li>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li> - <li>Role ID: {{ user.role_id }}</li> - <li>Permissions as Int: {{ user.role.permissions }}</li> - <li>Role name: {{ user.role.name }}</li> - </ul> + <div class="col s12 m4"> + <h2>{{ user.username }}</h2> + <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> + <a class="waves-effect waves-light btn" href="{{ url_for('.users') }}"><i class="material-icons left">arrow_back</i>Back to Users</a> </div> - <div class="card-action right-align"> - <a href="{{ url_for('admin.edit_user', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit</a> - <a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a> + + <div class="col s12 m8"> + <div class="card"> + <div class="card-content"> + <span class="card-title">User information</span> + <ul> + <li>Username: {{ user.username }}</li> + <li>Email: {{ user.email }}</li> + <li>ID: {{ user.id }}</li> + <li>Member since: {{ user.member_since.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li> + <li>Confirmed status: {{ user.confirmed }}</li> + <li>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li> + <li>Role ID: {{ user.role_id }}</li> + <li>Permissions as Int: {{ user.role.permissions }}</li> + <li>Role name: {{ user.role.name }}</li> + </ul> + </div> + <div class="card-action right-align"> + <a href="{{ url_for('.edit_general_settings', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit</a> + <a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a> + </div> + </div> </div> - </div> -</div> <div class="col s12 l6"> <h3>Corpora</h3> @@ -92,21 +98,23 @@ <!-- Modals --> <div id="delete-user-modal" class="modal"> <div class="modal-content"> - <h4>Confirm user deletion</h4> + <h3>Delete user</h3> <p>Do you really want to delete the user {{ user.username }}? All associated data will be permanently deleted!</p> </div> <div class="modal-footer"> <a href="#!" class="modal-close waves-effect waves-light btn">Cancel</a> - <a href="{{ url_for('admin.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-light btn red"><i class="material-icons left">delete</i>Delete</a> + <a href="{{ url_for('.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-light btn red"><i class="material-icons left">delete</i>Delete</a> </div> </div> +{% endblock %} + +{% block scripts %} +{{ super() }} <script type="module"> -import {RessourceList} from '../../static/js/nopaque.lists.js'; -let corpusList = new RessourceList("corpora", nopaque.foreignCorporaSubscribers, "Corpus"); -let jobList = new RessourceList("jobs", nopaque.foreignJobsSubscribers, "Job"); -document.addEventListener("DOMContentLoaded", () => { + import {RessourceList} from '{{ url_for('static', filename='js/nopaque.lists.js') }}'; + let corpusList = new RessourceList("corpora", nopaque.foreignCorporaSubscribers, "Corpus"); + let jobList = new RessourceList("jobs", nopaque.foreignJobsSubscribers, "Job"); nopaque.socket.emit("foreign_user_data_stream_init", {{ user.id }}); - }); </script> -{% endblock %} +{% endblock scripts %} diff --git a/web/app/templates/admin/users.html.j2 b/web/app/templates/admin/users.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..539ce0f13e2ea2e2c9a298a9ea4ac4081e1987a3 --- /dev/null +++ b/web/app/templates/admin/users.html.j2 @@ -0,0 +1,48 @@ +{% extends "nopaque.html.j2" %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> + + <div class="col s12"> + <div class="card"> + <div class="card-content" id="users"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-user" class="search" type="text"></input> + <label for="search-user">Search user</label> + </div> + <ul class="pagination paginationTop"></ul> + <table class="highlight responsive-table"> + <thead> + <tr> + <th class="sort" data-sort="username">Username</th> + <th class="sort" data-sort="email">Email</th> + <th class="sort" data-sort="role_id">Role</th> + <th class="sort" data-sort="confirmed">Confirmed Status</th> + <th class="sort" data-sort="id">Id</th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="list"> + </tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + </div> + </div> + </div> +</div> +{% endblock %} + +{% block scripts %} +{{ super() }} +<script type="module"> + import {RessourceList} from '{{ url_for('static', filename='js/nopaque.lists.js') }}'; + let userList = new RessourceList('users', null, "User", RessourceList.options.extended); + userList._add({{ users|tojson}}); +</script> +{% endblock scripts %} diff --git a/web/app/templates/auth/login.html.j2 b/web/app/templates/auth/login.html.j2 index 69ccbe57e785b5e4cc18759e1fc921341dda3dbe..657da25f602a21a6451a2998ba9832a70e9906a7 100644 --- a/web/app/templates/auth/login.html.j2 +++ b/web/app/templates/auth/login.html.j2 @@ -1,8 +1,8 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} -{% set headline = ' ' %} - -{% block page_content %} +{% block styles %} +{{ super() }} <style> main { background-image: url("{{ url_for('static', filename='images/parallax_lq/04_german_text_book_paper.jpg') }}"); @@ -10,39 +10,45 @@ background-size: cover; } </style> +{% endblock styles %} -<div class="col s12 m4"> - <div class="card medium"> - <div class="card-content"> - <h2>Log in</h2> - <p>Want to boost your research and get going? nopaque is free and no download is needed. Register now!</p> - </div> - <div class="card-action right-align"> - <a class="btn" href="{{ url_for('auth.register') }}"><i class="material-icons left">person_add</i>Register</a> +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12 m4"> + <div class="card medium"> + <div class="card-content"> + <h1 id="title">{{ title }}</h1> + <p>Want to boost your research and get going? nopaque is free and no download is needed. Register now!</p> + </div> + <div class="card-action right-align"> + <a class="btn" href="{{ url_for('.register') }}"><i class="material-icons left">person_add</i>Register</a> + </div> + </div> </div> - </div> -</div> -<div class="col s12 m8"> - <div class="card medium"> - <form method="POST"> - <div class="card-content"> - {{ login_form.hidden_tag() }} - {{ M.render_field(login_form.user, material_icon='person') }} - {{ M.render_field(login_form.password, material_icon='vpn_key') }} - <div class="row" style="margin-bottom: 0;"> - <div class="col s6 left-align"> - <a href="{{ url_for('auth.reset_password_request') }}">Forgot your password?</a> + <div class="col s12 m8"> + <div class="card medium"> + <form method="POST"> + <div class="card-content"> + {{ login_form.hidden_tag() }} + {{ wtf.render_field(login_form.user, material_icon='person') }} + {{ wtf.render_field(login_form.password, material_icon='vpn_key') }} + <div class="row" style="margin-bottom: 0;"> + <div class="col s6 left-align"> + <a href="{{ url_for('.reset_password_request') }}">Forgot your password?</a> + </div> + <div class="col s6 right-align"> + {{ wtf.render_field(login_form.remember_me) }} + </div> + </div> </div> - <div class="col s6 right-align"> - {{ M.render_field(login_form.remember_me) }} + <div class="card-action right-align"> + {{ wtf.render_field(login_form.submit, material_icon='send') }} </div> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(login_form.submit, material_icon='send') }} + </form> </div> - </form> + </div> </div> </div> {% endblock %} diff --git a/web/app/templates/auth/register.html.j2 b/web/app/templates/auth/register.html.j2 index af8a0b95f3e57f88804057df3ab8a094db7f1a40..9909cd608b1cb51d79ecb9026747d79d0adeb231 100644 --- a/web/app/templates/auth/register.html.j2 +++ b/web/app/templates/auth/register.html.j2 @@ -1,8 +1,8 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} -{% set headline = ' ' %} - -{% block page_content %} +{% block styles %} +{{ super() }} <style> main { background-image: url("{{ url_for('static', filename='images/parallax_lq/02_concept_document_focus_letter.jpg') }}"); @@ -10,32 +10,38 @@ background-size: cover; } </style> +{% endblock styles %} -<div class="col s12 m4"> - <div class="card medium"> - <div class="card-content"> - <h2>Register</h2> - <p>Simply enter a username and password to receive your registration email. After that you can start right away.</p> - <p>It goes without saying that the <a href="{{ url_for('main.privacy_policy') }}">General Data Protection Regulation</a> applies, only necessary data is stored.</p> - <p>Please also read our <a href="{{ url_for('main.terms_of_use') }}">terms of use</a> before signing up for nopaque!</p> +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12 m4"> + <div class="card medium"> + <div class="card-content"> + <h1 id="title">{{ title }}</h1> + <p>Simply enter a username and password to receive your registration email. After that you can start right away.</p> + <p>It goes without saying that the <a href="{{ url_for('main.privacy_policy') }}">General Data Protection Regulation</a> applies, only necessary data is stored.</p> + <p>Please also read our <a href="{{ url_for('main.terms_of_use') }}">terms of use</a> before signing up for nopaque!</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12 m8"> - <div class="card medium"> - <form method="POST"> - <div class="card-content"> - {{ registration_form.hidden_tag() }} - {{ M.render_field(registration_form.username, data_length='64', material_icon='person') }} - {{ M.render_field(registration_form.password, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(registration_form.password_confirmation, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(registration_form.email, class_='validate', material_icon='email', type='email') }} + <div class="col s12 m8"> + <div class="card medium"> + <form method="POST"> + <div class="card-content"> + {{ registration_form.hidden_tag() }} + {{ wtf.render_field(registration_form.username, data_length='64', material_icon='person') }} + {{ wtf.render_field(registration_form.password, data_length='128', material_icon='vpn_key') }} + {{ wtf.render_field(registration_form.password_confirmation, data_length='128', material_icon='vpn_key') }} + {{ wtf.render_field(registration_form.email, class_='validate', material_icon='email', type='email') }} + </div> + <div class="card-action right-align"> + {{ wtf.render_field(registration_form.submit, material_icon='send') }} + </div> + </form> </div> - <div class="card-action right-align"> - {{ M.render_field(registration_form.submit, material_icon='send') }} - </div> - </form> + </div> </div> </div> {% endblock %} diff --git a/web/app/templates/auth/reset_password.html.j2 b/web/app/templates/auth/reset_password.html.j2 index 6bd93f560bbb6159ed6f5b0978a25705f90f5e32..f3e4f26e71ec20db9f9da4b27ee5a44f33c7f265 100644 --- a/web/app/templates/auth/reset_password.html.j2 +++ b/web/app/templates/auth/reset_password.html.j2 @@ -1,23 +1,31 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} {% block page_content %} -<div class="col s12 m4"> - <h3>Lorem ipsum</h3> - <p>dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ reset_password_form.hidden_tag() }} - {{ M.render_field(reset_password_form.password, data_length='128') }} - {{ M.render_field(reset_password_form.password_confirmation, data_length='128') }} - </div> - <div class="card-action right-align"> - {{ M.render_field(reset_password_form.submit, material_icon='send') }} + <div class="col s12 m4"> + <p>Enter a new password and confirm it! After that, the entered password is your new one!</p> + </div> + + <div class="col s12 m8"> + <div class="card"> + <form method="POST"> + <div class="card-content"> + {{ reset_password_form.hidden_tag() }} + {{ wtf.render_field(reset_password_form.password, data_length='128') }} + {{ wtf.render_field(reset_password_form.password_confirmation, data_length='128') }} + </div> + <div class="card-action right-align"> + {{ wtf.render_field(reset_password_form.submit, material_icon='send') }} + </div> + </form> </div> - </form> + </div> </div> </div> {% endblock %} diff --git a/web/app/templates/auth/reset_password_request.html.j2 b/web/app/templates/auth/reset_password_request.html.j2 index cb8f23c8a72b486ea331ea8751c3b6251ddabb6c..403f1388230424cd657c47ce529c32edba90598f 100644 --- a/web/app/templates/auth/reset_password_request.html.j2 +++ b/web/app/templates/auth/reset_password_request.html.j2 @@ -1,21 +1,30 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} {% block page_content %} -<div class="col s12 m4"> - <p>After entering your email address you will receive instructions on how to reset your password.</p> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ reset_password_request_form.hidden_tag() }} - {{ M.render_field(reset_password_request_form.email, class_='validate', material_icon='email', type='email') }} - </div> - <div class="card-action right-align"> - {{ M.render_field(reset_password_request_form.submit, material_icon='send') }} + <div class="col s12 m4"> + <p>After entering your email address you will receive instructions on how to reset your password.</p> + </div> + + <div class="col s12 m8"> + <div class="card"> + <form method="POST"> + <div class="card-content"> + {{ reset_password_request_form.hidden_tag() }} + {{ wtf.render_field(reset_password_request_form.email, class_='validate', material_icon='email', type='email') }} + </div> + <div class="card-action right-align"> + {{ wtf.render_field(reset_password_request_form.submit, material_icon='send') }} + </div> + </form> </div> - </form> + </div> </div> </div> {% endblock %} diff --git a/web/app/templates/auth/unconfirmed.html.j2 b/web/app/templates/auth/unconfirmed.html.j2 index f3d26dc995ac949db2b1c74f25a43ad216a07f9a..613c51a402ca22ef462a48c24ff711fd065fcf2f 100644 --- a/web/app/templates/auth/unconfirmed.html.j2 +++ b/web/app/templates/auth/unconfirmed.html.j2 @@ -1,20 +1,25 @@ {% extends "nopaque.html.j2" %} -{% block title %}Opaque - Confirm your account{% endblock %} - {% block page_content %} -<div class="page-header"> - <h1> - Hello, {{ current_user.username }}! - </h1> - <h3>You have not confirmed your account yet.</h3> - <p> - Before you can access this site you need to confirm your account. - Check your inbox, you should have received an email with a confirmation link. - </p> - <p> - Need another confirmation email? - <a href="{{ url_for('auth.resend_confirmation') }}">Click here</a> - </p> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> + + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">Hello, {{ current_user.username }}!</span> + <p><b>You have not confirmed your account yet.</b></p> + <p>Before you can access this site you need to confirm your account. Check your inbox, you should have received an email with a confirmation link.</p> + <p>Need another confirmation email? Click the button below!</p> + </div> + <div class="card-action right-align"> + <a class="btn" href="{{ url_for('.resend_confirmation') }}">Resend confirmation mail</a> + </div> + </div> + </div> + </div> </div> {% endblock %} diff --git a/web/app/templates/corpora/add_corpus.html.j2 b/web/app/templates/corpora/add_corpus.html.j2 index 7205ab294fba0ff6a8b3405e3ff683eb262e73cb..a1970f0b1c73a13d5b34095af5d65d41de9fa275 100644 --- a/web/app/templates/corpora/add_corpus.html.j2 +++ b/web/app/templates/corpora/add_corpus.html.j2 @@ -1,29 +1,44 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} + +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -<div class="col s12 m4"> - <p>Fill out the following form to add a corpus to your corpora.</p> - <a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ add_corpus_form.hidden_tag() }} - <div class="row"> - <div class="col s12 m4"> - {{ M.render_field(add_corpus_form.title, data_length='32', material_icon='title') }} + <div class="col s12 m4"> + <p>Fill out the following form to add a corpus to your corpora.</p> + <a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a> + </div> + + <div class="col s12 m8"> + <div class="card"> + <form method="POST"> + <div class="card-content"> + {{ add_corpus_form.hidden_tag() }} + <div class="row"> + <div class="col s12 m4"> + {{ wtf.render_field(add_corpus_form.title, data_length='32', material_icon='title') }} + </div> + <div class="col s12 m8"> + {{ wtf.render_field(add_corpus_form.description, data_length='255', material_icon='description') }} + </div> + </div> </div> - <div class="col s12 m8"> - {{ M.render_field(add_corpus_form.description, data_length='255', material_icon='description') }} + <div class="card-action right-align"> + {{ wtf.render_field(add_corpus_form.submit, material_icon='send') }} </div> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_corpus_form.submit, material_icon='send') }} + </form> </div> - </form> + </div> </div> </div> {% endblock %} diff --git a/web/app/templates/corpora/add_corpus_file.html.j2 b/web/app/templates/corpora/add_corpus_file.html.j2 index 021ce59c17653823a8d6d22cb1860c4f998a200f..5f8f25b34e999757837ffe6e3a8b6d1453a3cef7 100644 --- a/web/app/templates/corpora/add_corpus_file.html.j2 +++ b/web/app/templates/corpora/add_corpus_file.html.j2 @@ -1,71 +1,75 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} + +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -<div class="col s12 m4"> - <h3>{{ corpus.title }}</h3> - <p> - Fill out the following form to add a corpus file in verticalized text - format (.vrt). - </p> - <p> - <strong>Do not use the .stand-off.vrt file!</strong> - </p> - <a class="waves-effect waves-light btn" href="{{ url_for('corpora.corpus', corpus_id=corpus.id) }}"><i class="material-icons left">arrow_back</i>Back to corpus</a> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <form class="nopaque-submit-form" data-progress-modal="progress-modal"> - <div class="card"> - <div class="card-content"> - <span class="card-title">Required metadata</span> - {{ add_corpus_file_form.hidden_tag() }} - <div class="row"> - <div class="col s12 m4"> - {{ M.render_field(add_corpus_file_form.author, data_length='255', material_icon='person') }} - </div> - <div class="col s12 m4"> - {{ M.render_field(add_corpus_file_form.title, data_length='255', material_icon='title') }} - </div> - <div class="col s12 m4"> - {{ M.render_field(add_corpus_file_form.publishing_year, material_icon='access_time') }} - </div> - <div class="col s12"> - {{ M.render_field(add_corpus_file_form.file, accept='.vrt', placeholder='Choose your .vrt file') }} - </div> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_corpus_file_form.submit, material_icon='send') }} - </div> + <div class="col s12 m4"> + <p>Fill out the following form to add a corpus file in verticalized text format (.vrt).</p> + <p><b>Do not use the .stand-off.vrt file!</b></p> </div> - <br> - <ul class="collapsible hoverable"> - <li> - <div class="collapsible-header"><i class="material-icons">add</i>Add additional metadata</div> - <div class="collapsible-body"> - {% for field in add_corpus_file_form - if field.short_name not in ['author', 'csrf_token', 'file', 'publishing_year', 'submit', 'title'] %} - {{ M.render_field(field, data_length='255', material_icon=field.label.text[0:1]) }} - {% endfor %} - </div> - </li> - </ul> - <br> - <ul class="collapsible hoverable"> - <li> - <div class="collapsible-header"><i class="material-icons">add</i>Add metadata with BibTex</div> - <div class="collapsible-body"> - <span> + + <div class="col s12 m8"> + <form class="nopaque-submit-form" data-progress-modal="progress-modal"> + <div class="card"> + <div class="card-content"> + {{ add_corpus_file_form.hidden_tag() }} <div class="row"> + <div class="col s12 m4"> + {{ wtf.render_field(add_corpus_file_form.author, data_length='255', material_icon='person') }} + </div> + <div class="col s12 m4"> + {{ wtf.render_field(add_corpus_file_form.title, data_length='255', material_icon='title') }} + </div> <div class="col s12"> - + {{ wtf.render_field(add_corpus_file_form.file, accept='.vrt', placeholder='Choose your .vrt file') }} </div> </div> - </span> + </div> + <div class="card-action right-align"> + {{ wtf.render_field(add_corpus_file_form.submit, material_icon='send') }} + </div> </div> - </li> - </ul> - </form> + <br> + <ul class="collapsible hoverable"> + <li> + <div class="collapsible-header"><i class="material-icons">add</i>Add additional metadata</div> + <div class="collapsible-body"> + {% for field in add_corpus_file_form + if field.short_name not in ['author', 'csrf_token', 'file', 'submit', 'title'] %} + {{ wtf.render_field(field, data_length='255', material_icon=field.label.text[0:1]) }} + {% endfor %} + </div> + </li> + </ul> + <br> + <ul class="collapsible hoverable"> + <li> + <div class="collapsible-header"><i class="material-icons">add</i>Add metadata with BibTex</div> + <div class="collapsible-body"> + <span> + <div class="row"> + <div class="col s12"> + + </div> + </div> + </span> + </div> + </li> + </ul> + </form> + </div> + </div> </div> <div id="progress-modal" class="modal"> diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index 0c61a08fb4a5305474a03069082ebd7f7f452abc..f4177a3f2adedbe5a56f7134b27a74028c2adfda 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -1,57 +1,61 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% import 'materialize/wtf.html.j2' as wtf %} -{% set headline = ' ' %} +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} -{% set full_width = True %} +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -{{ Macros.insert_color_scheme(corpus_analysis_color_darken) }} -<div class="col s12"> - <div class="card"> - <div class="card-content" style="padding-top: 5px; - padding-bottom: 0px;"> - <!-- Query form --> - <div class="row"> - <form class="col s12" id="query-form"> - <div class="row"> - <div class="input-field col s12 m10"> - <i class="material-icons prefix">search</i> - {{ query_form.query() }} - {{ query_form.query.label }} - <span class="helper-text"> - <a href="http://cwb.sourceforge.net/files/CQP_Tutorial/"> - <i class="material-icons" style="font-size: inherit;">help - </i> - CQP query language tutorial - </a> - </span> +<div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content" style="padding-top: 5px; + padding-bottom: 0px;"> + <!-- Query form --> + <div class="row"> + <form class="col s12" id="query-form"> + <div class="row"> + <div class="input-field col s12 m10"> + <i class="material-icons prefix">search</i> + {{ query_form.query() }} + {{ query_form.query.label }} + <span class="helper-text"> + <a href="http://cwb.sourceforge.net/files/CQP_Tutorial/"> + <i class="material-icons" style="font-size: inherit;">help + </i> + CQP query language tutorial + </a> + </span> + </div> + <div class="col s12 m2 center-align" style="margin-top: 1.75em;"> + {{ wtf.render_field(query_form.submit, material_icon='send', + style='width:100%;') }} + </div> </div> - <div class="col s12 m2 center-align" style="margin-top: 1.75em;"> - {{ M.render_field(query_form.submit, material_icon='send', - style='width:100%;') }} - </div> - </div> - </form> + </form> + </div> </div> </div> </div> -</div> -<!-- entire results div/card --> -<div class="col s12" id="query-display"> - <div class="card"> - <div class="card-content" id="result-list" style="overflow: hidden;"> - <div id="interactions-menu" class="row hide" - style="margin-bottom: 0px;"> - {# Importing menus for query settings, export etc. #} - {% include 'corpora/interactions/infos.html.j2' %} - {% include 'corpora/interactions/export.html.j2' %} - {% include 'corpora/interactions/create.html.j2' %} - {% include 'corpora/interactions/display.html.j2' %} - {% include 'corpora/interactions/analysis.html.j2' %} - {% include 'corpora/interactions/cite.html.j2' %} + <!-- entire results div/card --> + <div class="col s12" id="query-display"> + <div class="card"> + <div class="card-content" id="result-list" style="overflow: hidden;"> + <div id="interactions-menu" class="row hide" + style="margin-bottom: 0px;"> + {# Importing menus for query settings, export etc. #} + {% include 'corpora/interactions/infos.html.j2' %} + {% include 'corpora/interactions/export.html.j2' %} + {% include 'corpora/interactions/create.html.j2' %} + {% include 'corpora/interactions/display.html.j2' %} + {% include 'corpora/interactions/analysis.html.j2' %} + {% include 'corpora/interactions/cite.html.j2' %} + </div> + {% include 'tables/query_results.html.j2' %} </div> - {% include 'tables/query_results.html.j2' %} </div> </div> </div> @@ -65,7 +69,10 @@ {% include 'modals/export_query_results.html.j2' %} {% include 'modals/context_modal.html.j2' %} {% include 'modals/show_corpus_files.html.j2' %} +{% endblock page_content %} +{% block scripts %} +{{ super() }} <!-- import modules --> <script type="module"> /** diff --git a/web/app/templates/corpora/corpus.html.j2 b/web/app/templates/corpora/corpus.html.j2 index 8f3b7fc5c4c1275a8e098465be1120d86a12bb28..d2e7fd07e3fb8c18d42718c544129f312e0bd48b 100644 --- a/web/app/templates/corpora/corpus.html.j2 +++ b/web/app/templates/corpora/corpus.html.j2 @@ -1,101 +1,112 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} + +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} + +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -<div class="col s12 m4"> - <h3 id="title">{{ corpus.title }}</h3> - <p id="description">{{ corpus.description }}</p> - <div class="active preloader-wrapper small hide" id="progress-indicator"> - <div class="spinner-layer spinner-blue-only"> - <div class="circle-clipper left"> - <div class="circle"></div> - </div> - <div class="gap-patch"> - <div class="circle"></div> - </div> - <div class="circle-clipper right"> - <div class="circle"></div> - </div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ corpus.title }}</h1> + <p id="description">{{ corpus.description }}</p> </div> - </div> - <span class="chip status white-text hide" id="status"></span> -</div> -<div class="col s12 m8"> - <div class="card"> - <div class="card-content"> - <span class="card-title">Chronometrics</span> - <div class="row"> - <div class="col s12 m6"> - <div class="input-field"> - <input disabled value="{{ corpus.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="creation-date" type="text" class="validate"> - <label for="creation-date">Creation date</label> + <div class="col s12 m4"> + <div class="active preloader-wrapper small hide" id="progress-indicator"> + <div class="spinner-layer spinner-blue-only"> + <div class="circle-clipper left"> + <div class="circle"></div> </div> - </div> - <div class="col s12 m6"> - <div class="input-field"> - <input disabled value="{{ corpus.last_edited_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="last_edited_date" type="text" class="validate"> - <label for="creation-date">Last edited</label> + <div class="gap-patch"> + <div class="circle"></div> </div> - </div> - <div class="col s12 m6"> - <div class="input-field"> - <input disabled value="{{ corpus.current_nr_of_tokens }} / {{ corpus.max_nr_of_tokens }}" id="nr_of_tokens" type="text" class="validate"> - <label for="creation-date">Nr. of tokens used - <i class="material-icons tooltipped" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i> - </label> + <div class="circle-clipper right"> + <div class="circle"></div> </div> </div> </div> + <span class="chip status white-text hide" id="status"></span> </div> - <div class="card-action right-align"> - <a href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="analyze"><i class="material-icons left">search</i>Analyze</a> - <a href="{{ url_for('corpora.prepare_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="build"><i class="material-icons left">build</i>Build</a> - <a data-target="delete-corpus-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> + + <div class="col s12 m8"> + <div class="card"> + <div class="card-content"> + <span class="card-title">Chronometrics</span> + <div class="row"> + <div class="col s12 m6"> + <div class="input-field"> + <input disabled value="{{ corpus.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="creation-date" type="text" class="validate"> + <label for="creation-date">Creation date</label> + </div> + </div> + <div class="col s12 m6"> + <div class="input-field"> + <input disabled value="{{ corpus.last_edited_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="last_edited_date" type="text" class="validate"> + <label for="creation-date">Last edited</label> + </div> + </div> + <div class="col s12 m6"> + <div class="input-field"> + <input disabled value="{{ corpus.current_nr_of_tokens }} / {{ corpus.max_nr_of_tokens }}" id="nr_of_tokens" type="text" class="validate"> + <label for="creation-date">Nr. of tokens used + <i class="material-icons tooltipped" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i> + </label> + </div> + </div> + </div> + </div> + <div class="card-action right-align"> + <a href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="analyze"><i class="material-icons left">search</i>Analyze</a> + <a href="{{ url_for('corpora.prepare_corpus', corpus_id=corpus.id) }}" class="btn disabled hide waves-effect waves-light" id="build"><i class="material-icons left">build</i>Build</a> + <a data-target="delete-corpus-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> + </div> + </div> </div> - </div> -</div> -<div class="col s12"></div> + <div class="col s12"></div> -<div class="col s12"> - <div class="card"> - <div class="card-content" id="corpus-files" style="overflow: hidden;"> - <span class="card-title">Files</span> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-results" class="search" type="search"></input> - <label for="search-results">Search results</label> + <div class="col s12"> + <div class="card"> + <div class="card-content" id="corpus-files" style="overflow: hidden;"> + <span class="card-title" id="files">Corpus files</span> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-results" class="search" type="search"></input> + <label for="search-results">Search results</label> + </div> + <ul class="pagination paginationTop"></ul> + <table class="highlight responsive-table"> + <thead> + <tr> + <th class="sort" data-sort="filename">Filename</th> + <th class="sort" data-sort="author">Author</th> + <th class="sort" data-sort="title">Title</th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="list"> + {% if corpus_files|length == 0 %} + <tr class="show-if-only-child"> + <td colspan="5"> + <span class="card-title"><i class="material-icons left">book</i>Nothing here...</span> + <p>Corpus is empty. Add texts using the option below.</p> + </td> + </tr> + {% endif %} + </table> + <ul class="pagination paginationBottom"></ul> + </div> + <div class="card-action right-align"> + <a href="{{ url_for('corpora.add_corpus_file', corpus_id=corpus.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add corpus file</a> + </div> </div> - <ul class="pagination paginationTop"></ul> - <table class="highlight responsive-table"> - <thead> - <tr> - <th class="sort" data-sort="filename">Filename</th> - <th class="sort" data-sort="author">Author</th> - <th class="sort" data-sort="title">Title</th> - <th class="sort" data-sort="publishing_year">Publishing year</th> - <th>{# Actions #}</th> - </tr> - </thead> - <tbody class="list"> - {% if corpus_files|length == 0 %} - <tr class="show-if-only-child"> - <td colspan="5"> - <span class="card-title"><i class="material-icons left">book</i>Nothing here...</span> - <p>Corpus is empty. Add texts using the option below.</p> - </td> - </tr> - {% endif %} - </table> - <ul class="pagination paginationBottom"></ul> - </div> - <div class="card-action right-align"> - <a href="{{ url_for('corpora.add_corpus_file', corpus_id=corpus.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add corpus file</a> </div> </div> </div> - <!-- Modals --> <div id="delete-corpus-modal" class="modal"> <div class="modal-content"> @@ -107,7 +118,10 @@ <a href="{{ url_for('corpora.delete_corpus', corpus_id=corpus.id) }}" class="btn modal-close red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> </div> </div> +{% endblock page_content %} +{% block scripts %} +{{ super() }} <script type="module"> import {RessourceList} from '../../static/js/nopaque.lists.js'; class InformationUpdater { @@ -193,14 +207,10 @@ var informationUpdater = new InformationUpdater({{ corpus.id }}, false); {% else %} var informationUpdater = new InformationUpdater({{ corpus.id }}, true); - document.addEventListener("DOMContentLoaded", () => { - nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }}); - }); + nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }}); {% endif %} let corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile"); - document.addEventListener("DOMContentLoaded", () => { - corpusFilesList._add({{ corpus_files|tojson|safe }}); - }); + corpusFilesList._add({{ corpus_files|tojson|safe }}); </script> -{% endblock %} +{% endblock scripts %} diff --git a/web/app/templates/corpora/corpus_file.html.j2 b/web/app/templates/corpora/corpus_file.html.j2 index 8c30b14a784960ccc7aef85e62bb16e30318e64c..810816febcd380dde67d6afd4df7ab7e7eea8a46 100644 --- a/web/app/templates/corpora/corpus_file.html.j2 +++ b/web/app/templates/corpora/corpus_file.html.j2 @@ -1,45 +1,51 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} + +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -<div class="col s12 m4"> - <h3 id="title">...</h3> - <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et</p> - <a class="btn waves-effect waves-light" href="{{ url_for('corpora.corpus', corpus_id=corpus.id) }}"><i class="material-icons left">arrow_back</i>Back to corpus</a> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ corpus_file.author }}: {{ corpus_file.title }}</h1> + </div> -<div class="col s12 m8"> - <form method="POST"> - {{ edit_corpus_file_form.hidden_tag() }} - <div class="card"> - <div class="card-content"> - <div class="row"> - <div class="col s12 m4"> - {{ M.render_field(edit_corpus_file_form.author, data_length='255', material_icon='person') }} - </div> - <div class="col s12 m4"> - {{ M.render_field(edit_corpus_file_form.title, data_length='255', material_icon='title') }} + <div class="col s12"> + <form method="POST"> + {{ edit_corpus_file_form.hidden_tag() }} + <div class="card"> + <div class="card-content"> + <div class="row"> + <div class="col s12 m6"> + {{ wtf.render_field(edit_corpus_file_form.author, data_length='255', material_icon='person') }} + </div> + <div class="col s12 m6"> + {{ wtf.render_field(edit_corpus_file_form.title, data_length='255', material_icon='title') }} + </div> + </div> </div> - <div class="col s12 m4"> - {{ M.render_field(edit_corpus_file_form.publishing_year, material_icon='access_time') }} + <div class="card-action right-align"> + {{ wtf.render_field(edit_corpus_file_form.submit, material_icon='send') }} </div> </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(edit_corpus_file_form.submit, material_icon='send') }} - </div> + <br> + <ul class="collapsible hoverable"> + <li> + <div class="collapsible-header"><i class="material-icons">edit</i>Edit additional metadata</div> + <div class="collapsible-body"> + {% for field in edit_corpus_file_form + if field.short_name not in ['author', 'csrf_token', 'submit', 'title'] %} + {{ wtf.render_field(field, data_length='255', material_icon=field.label.text[0:1]) }} + {% endfor %} + </div> + </li> + </ul> + </form> </div> - <br> - <ul class="collapsible hoverable"> - <li> - <div class="collapsible-header"><i class="material-icons">edit</i>Edit additional metadata</div> - <div class="collapsible-body"> - {% for field in edit_corpus_file_form - if field.short_name not in ['author', 'csrf_token', 'publishing_year', 'submit', 'title'] %} - {{ M.render_field(field, data_length='255', material_icon=field.label.text[0:1]) }} - {% endfor %} - </div> - </li> - </ul> - </form> + </div> </div> {% endblock %} diff --git a/web/app/templates/corpora/interactions/display.html.j2 b/web/app/templates/corpora/interactions/display.html.j2 index 1f5c10c05758e50a0776031e9b566551964f8ac4..f6b7b4904ae0cf7d076ea25788f770109520e416 100644 --- a/web/app/templates/corpora/interactions/display.html.j2 +++ b/web/app/templates/corpora/interactions/display.html.j2 @@ -1,14 +1,15 @@ -<!-- HTML to allow the user to change how the results are being displayed.--> +{% import 'materialize/wtf.html.j2' as wtf %} +<!-- HTML to allow the user to change how the results are being displayed.--> <div class="col s12 m3 l2" id="display"> <h6 style="margin-top: 0px;">Display</h6> <div class="divider" style="margin-bottom: 10px;"></div> <div class="row"> <div class="col s12"> <form id="display-options-form"> - {{ M.render_field(display_options_form.results_per_page, + {{ wtf.render_field(display_options_form.results_per_page, material_icon='format_list_numbered') }} - {{ M.render_field(display_options_form.result_context, + {{ wtf.render_field(display_options_form.result_context, material_icon='short_text') }} <div class="col s12" style="line-height: 38px;"> <div class="col s8"> @@ -27,4 +28,3 @@ </div> </div> </div> - diff --git a/web/app/templates/corpora/query_results/add_query_result.html.j2 b/web/app/templates/corpora/query_results/add_query_result.html.j2 index e2f163feb353051213dca1e99fc6c368abc15dfb..8765b5a23345450b875a09d13a42a04073ee78d6 100644 --- a/web/app/templates/corpora/query_results/add_query_result.html.j2 +++ b/web/app/templates/corpora/query_results/add_query_result.html.j2 @@ -1,33 +1,48 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} + +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -<div class="col s12 m4"> - <p>Fill out the following form to upload and view your exported query data from the corpus analsis.</p> - <a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m8"> - <form class="nopaque-submit-form" data-progress-modal="progress-modal"> - <div class="card"> - <div class="card-content"> - {{ add_query_result_form.hidden_tag() }} - <div class="row"> - <div class="col s12 m4"> - {{ M.render_field(add_query_result_form.title, data_length='32', material_icon='title') }} - </div> - <div class="col s12 m8"> - {{ M.render_field(add_query_result_form.description, data_length='255', material_icon='description') }} + <div class="col s12 m4"> + <p>Fill out the following form to upload and view your exported query data from the corpus analsis.</p> + <a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a> + </div> + + <div class="col s12 m8"> + <form class="nopaque-submit-form" data-progress-modal="progress-modal"> + <div class="card"> + <div class="card-content"> + {{ add_query_result_form.hidden_tag() }} + <div class="row"> + <div class="col s12 m4"> + {{ wtf.render_field(add_query_result_form.title, data_length='32', material_icon='title') }} + </div> + <div class="col s12 m8"> + {{ wtf.render_field(add_query_result_form.description, data_length='255', material_icon='description') }} + </div> + <div class="col s12"> + {{ wtf.render_field(add_query_result_form.file, accept='.json', placeholder='Choose your .json file') }} + </div> + </div> </div> - <div class="col s12"> - {{ M.render_field(add_query_result_form.file, accept='.json', placeholder='Choose your .json file') }} + <div class="card-action right-align"> + {{ wtf.render_field(add_query_result_form.submit, material_icon='send') }} </div> </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_query_result_form.submit, material_icon='send') }} - </div> + </form> </div> - </form> + </div> </div> <div id="progress-modal" class="modal"> diff --git a/web/app/templates/corpora/query_results/inspect.html.j2 b/web/app/templates/corpora/query_results/inspect.html.j2 index 9de1a779e0b0ea17ba6ff69824d05de72224f6c0..4bcf078b28e1257621f0c804036a7ea0aa016a5f 100644 --- a/web/app/templates/corpora/query_results/inspect.html.j2 +++ b/web/app/templates/corpora/query_results/inspect.html.j2 @@ -1,64 +1,70 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} -{% set headline = ' ' %} +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} -{% set full_width = True %} +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} {% block page_content %} -{{ Macros.insert_color_scheme(corpus_analysis_color_darken) }} -<div class="col s12"> - <div class="card"> - <div class="card-content" style="padding-top: 5px; - padding-bottom: 0px;"> - <!-- Query form --> - <div class="row"> - <form id="query-form"> - <div class="col s12 m10"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input disabled value="{{ query_metadata.query|escape }}" id="disabled" type="text" class="validate"> - <label for="disabled">Query</label> +<div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content" style="padding-top: 5px; + padding-bottom: 0px;"> + <!-- Query form --> + <div class="row"> + <form id="query-form"> + <div class="col s12 m10"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input disabled value="{{ query_metadata.query|escape }}" id="disabled" type="text" class="validate"> + <label for="disabled">Query</label> + </div> </div> - </div> - <div class="col s12 m2 right-align"> - <br class="hide-on-small-only"> - </div> - </form> - </div> + <div class="col s12 m2 right-align"> + <br class="hide-on-small-only"> + </div> + </form> + </div> + </div> </div> </div> -</div> -<!-- entire results div/card --> -<div class="col s12" id="query-display"> - <div class="card"> - <div class="card-content" id="result-list" style="overflow: hidden;"> - <div class="row" id="interactions-menu"> - {% include 'corpora/interactions/infos.html.j2' %} - {% include 'corpora/interactions/display.html.j2' %} - {% include 'corpora/interactions/analysis.html.j2' %} - {% include 'corpora/interactions/cite.html.j2' %} - <div class="hide"> - {# Hide those because they are not needed when inspecting results. - But some of their elements are being asked for by the client. #} - {% include 'corpora/interactions/export.html.j2' %} - {% include 'corpora/interactions/create.html.j2' %} + <!-- entire results div/card --> + <div class="col s12" id="query-display"> + <div class="card"> + <div class="card-content" id="result-list" style="overflow: hidden;"> + <div class="row" id="interactions-menu"> + {% include 'corpora/interactions/infos.html.j2' %} + {% include 'corpora/interactions/display.html.j2' %} + {% include 'corpora/interactions/analysis.html.j2' %} + {% include 'corpora/interactions/cite.html.j2' %} + <div class="hide"> + {# Hide those because they are not needed when inspecting results. + But some of their elements are being asked for by the client. #} + {% include 'corpora/interactions/export.html.j2' %} + {% include 'corpora/interactions/create.html.j2' %} + </div> </div> + {% include 'tables/query_results.html.j2' %} </div> - {% include 'tables/query_results.html.j2' %} </div> </div> </div> -<!-- Scroll to top element --> -{% include 'corpora/interactions/scroll_to_top.html.j2' %} - -<!-- Modals --> +{# Import modals #} {% include 'modals/show_metadata.html.j2' %} {% include 'modals/show_corpus_files.html.j2' %} {% include 'modals/context_modal.html.j2' %} +<!-- Scroll to top element --> +{% include 'corpora/interactions/scroll_to_top.html.j2' %} +{% endblock page_content %} + +{% block scripts %} +{{ super() }} <script type="module"> /** * First Phase: @@ -117,125 +123,123 @@ import { * Second Phase: * Asynchronus and event driven code. */ -document.addEventListener("DOMContentLoaded", () => { +/** + * Initializing the results object as a model holding all the data of a + * query. Also holds the metadata of one query and results data. + * After that initialize the ResultsList object as the View handeling the + * representation of the data for the user. + */ +let results = new Results(); +let resultsList = new ResultsList('result-list', ResultsList.options); +// Import results data from json file. +const resultsJson = {{ query_result_file_content|tojson|safe }}; +// Import metadata from DB passed to this view +const metaDataJson = {{ query_metadata|tojson|safe }}; +// Initialize the client with dynamicMode set to false. +const client = new Client({'logging': true, + 'dynamicMode': false, + 'fullContext': metaDataJson.fullContext}); /** - * Initializing the results object as a model holding all the data of a - * query. Also holds the metadata of one query and results data. - * After that initialize the ResultsList object as the View handeling the - * representation of the data for the user. - */ - let results = new Results(); - let resultsList = new ResultsList('result-list', ResultsList.options); - // Import results data from json file. - const resultsJson = {{ query_result_file_content|tojson|safe }}; - // Import metadata from DB passed to this view - const metaDataJson = {{ query_metadata|tojson|safe }}; - // Initialize the client with dynamicMode set to false. - const client = new Client({'logging': true, - 'dynamicMode': false, - 'fullContext': metaDataJson.fullContext}); - /** - * Register needed listeners and their callbacks. But we will - * just call the attached callbacks manually. Because dynamicMode is false. - */ - const listenForQueryStatus = new ClientEventListener('corpus_analysis_query', - recieveQueryStatus); - const queryStatusCallback = new ListenerCallback('corpus_analysis_query', - prepareQueryData, - [client, results]); - listenForQueryStatus.setCallbacks([queryStatusCallback]); - const listenForQueryData = new ClientEventListener('corpus_analysis_query_results', - recieveQueryData); - const queryDataCallback = new ListenerCallback('corpus_analysis_query_results', - saveQueryData, + * Register needed listeners and their callbacks. But we will + * just call the attached callbacks manually. Because dynamicMode is false. + */ +const listenForQueryStatus = new ClientEventListener('corpus_analysis_query', + recieveQueryStatus); +const queryStatusCallback = new ListenerCallback('corpus_analysis_query', + prepareQueryData, [client, results]); - listenForQueryData.setCallbacks([queryDataCallback]); - // Set the event listeners - client.setSocketEventListeners([ - listenForQueryStatus, - listenForQueryData, - ]); - /** - * Register resultsList listeners listening to notification events emitted by - * the Client class. - */ - const listenForClientNotification = new ViewEventListener('notify-view', - recieveClientNotification); - /** - * Register vanilla Javascript events to the resultList listening for button - * clicks etc. done by the user. - * Get all needed HTMLElements for those event listeners before. - */ - resultsList.getHTMLElements([ - '.add-btn', - '.pagination', - '#display-options-form-expert_mode', - '#display-options-form-result_context', - '#display-options-form-results_per_page', - '#full-results-create', - '#full-results-export', - '#inspect-results-export', - '#meta-data-modal-content', - ['#meta-data-modal', { - 'preventScrolling': false, - 'opacity': 0.0, - 'dismissible': false, - 'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()}) - } - ], - '#query-results-table', - '#show-meta-data', - '#show-corpus-files', - '#show-corpus-files-modal-content', - ['#show-corpus-files-modal', { - 'preventScrolling': false, - 'opacity': 0.0, - 'dismissible': false, - 'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()}) - } - ], - '#sub-results-create', - '#sub-results-export', - ]); - let args = [resultsList, results, client]; - const listenForPageNavigation = new ViewEventListener('page-navigation', - pageNavigation, - args); - const listenForExpertModeSwitch = new ViewEventListener('expert-mode', - expertModeSwitch, - args); - const listenForActionButtons = new ViewEventListener('action-buttons', - actionButtons, +listenForQueryStatus.setCallbacks([queryStatusCallback]); +const listenForQueryData = new ClientEventListener('corpus_analysis_query_results', + recieveQueryData); +const queryDataCallback = new ListenerCallback('corpus_analysis_query_results', + saveQueryData, + [client, results]); +listenForQueryData.setCallbacks([queryDataCallback]); +// Set the event listeners +client.setSocketEventListeners([ + listenForQueryStatus, + listenForQueryData, +]); +/** + * Register resultsList listeners listening to notification events emitted by + * the Client class. + */ +const listenForClientNotification = new ViewEventListener('notify-view', + recieveClientNotification); +/** + * Register vanilla Javascript events to the resultList listening for button + * clicks etc. done by the user. + * Get all needed HTMLElements for those event listeners before. + */ +resultsList.getHTMLElements([ + '.add-btn', + '.pagination', + '#display-options-form-expert_mode', + '#display-options-form-result_context', + '#display-options-form-results_per_page', + '#full-results-create', + '#full-results-export', + '#inspect-results-export', + '#meta-data-modal-content', + ['#meta-data-modal', { + 'preventScrolling': false, + 'opacity': 0.0, + 'dismissible': false, + 'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()}) + } + ], + '#query-results-table', + '#show-meta-data', + '#show-corpus-files', + '#show-corpus-files-modal-content', + ['#show-corpus-files-modal', { + 'preventScrolling': false, + 'opacity': 0.0, + 'dismissible': false, + 'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()}) + } + ], + '#sub-results-create', + '#sub-results-export', +]); +let args = [resultsList, results, client]; +const listenForPageNavigation = new ViewEventListener('page-navigation', + pageNavigation, + args); +const listenForExpertModeSwitch = new ViewEventListener('expert-mode', + expertModeSwitch, + args); +const listenForActionButtons = new ViewEventListener('action-buttons', + actionButtons, + args); +const listenForDisplayOptions = new ViewEventListener('display-otions', + displayOptions, + args); +const listenForShowMetaData = new ViewEventListener('show-meta-data', + showMetaData, + args); +const listenForShowCorpusFiles = new ViewEventListener('show-corpus-files', + showCorpusFiles, args); - const listenForDisplayOptions = new ViewEventListener('display-otions', - displayOptions, - args); - const listenForShowMetaData = new ViewEventListener('show-meta-data', - showMetaData, - args); - const listenForShowCorpusFiles = new ViewEventListener('show-corpus-files', - showCorpusFiles, - args); - // Set and load defined listeners - resultsList.setViewEventListeners([ - listenForClientNotification, - listenForPageNavigation, - listenForExpertModeSwitch, - listenForActionButtons, - listenForDisplayOptions, - listenForShowMetaData, - listenForShowCorpusFiles, - ]); - resultsList.loadViewEventListeners(); - // Hide buttons which are not needed when just inspecting results - resultsList.inspectResultsExport.classList.add('hide'); - // Execute client event listener callbacks manually because dynamicMode is false - client.eventListeners['corpus_analysis_query'].executeCallbacks([resultsJson]); - // Save meta data to results after the init callback from line above - results.metaData = metaDataJson; - client.eventListeners['corpus_analysis_query_results'].executeCallbacks([resultsJson]); - // Enable scroll to Top functionality. - scrollToTop('#headline', '#menu-scroll-to-top-div'); -}); +// Set and load defined listeners +resultsList.setViewEventListeners([ + listenForClientNotification, + listenForPageNavigation, + listenForExpertModeSwitch, + listenForActionButtons, + listenForDisplayOptions, + listenForShowMetaData, + listenForShowCorpusFiles, +]); +resultsList.loadViewEventListeners(); +// Hide buttons which are not needed when just inspecting results +resultsList.inspectResultsExport.classList.add('hide'); +// Execute client event listener callbacks manually because dynamicMode is false +client.eventListeners['corpus_analysis_query'].executeCallbacks([resultsJson]); +// Save meta data to results after the init callback from line above +results.metaData = metaDataJson; +client.eventListeners['corpus_analysis_query_results'].executeCallbacks([resultsJson]); +// Enable scroll to Top functionality. +scrollToTop('#headline', '#menu-scroll-to-top-div'); </script> {% endblock %} diff --git a/web/app/templates/corpora/query_results/query_result.html.j2 b/web/app/templates/corpora/query_results/query_result.html.j2 index 95ba18caf6db876c17be74b9f11c44b7aa1d4357..5936d9a49b209e48a2f06bcc3ec290a7bbdd0a85 100644 --- a/web/app/templates/corpora/query_results/query_result.html.j2 +++ b/web/app/templates/corpora/query_results/query_result.html.j2 @@ -1,77 +1,88 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} -{% block page_content %} -{{ Macros.insert_color_scheme(corpus_analysis_color_darken) }} -<div class="col s12"> - <p>Below the metadata for the results from the Corpus - <i>{{ query_result.query_metadata.corpus_name }}</i> generated with the query - <i>{{ query_result.query_metadata.query }}</i> are shown. - </p> +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} -</div> +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} -<div class="col s12"> - <div class="card"> - <div class="card-action right-align"> - <a class="waves-effect waves-light btn left-align" href="{{ url_for('services.service', service='corpus_analysis') }}">Back To Overview<i class="material-icons right">arrow_back</i></a> - <a class="waves-effect waves-light btn" href="{{ url_for('corpora.inspect_query_result', query_result_id=query_result.id) }}">Inspect Results<i class="material-icons right">search</i></a> +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> </div> - <div class="card-content" id="results"> - <table class="responsive-table highlight"> - <thead> - <tr> - <th>Metadata Description</th> - <th>Value</th> - </tr> - </thead> - <tbody> - {% for pair in query_result.query_metadata|dictsort %} - <tr> - <td>{{ pair[0] }}</td> - {% if pair[0] == 'corpus_all_texts' - or pair[0] == 'text_lookup' %} - <td> - <table> - {% for key, value in pair[1].items() %} - <tr style="border-bottom: none;"> - <td> - <i>{{ value['title'] }}</i> written - by <i>{{ value['author'] }}</i> - in <i>{{ value['publishing_year'] }}</i> - <a class="waves-effect - waves-light - btn - right - more-text-detials" - data-metadata-key="{{ pair[0] }}" - data-text-key="{{ key }}">More - <i class="material-icons right" + + <div class="col s12"> + <p>Below the metadata for the results from the Corpus + <i>{{ query_result.query_metadata.corpus_name }}</i> generated with the query + <i>{{ query_result.query_metadata.query }}</i> are shown. + </p> + </div> + + <div class="col s12"> + <div class="card"> + <div class="card-action right-align"> + <a class="waves-effect waves-light btn left-align" href="{{ url_for('services.service', service='corpus_analysis') }}">Back To Overview<i class="material-icons right">arrow_back</i></a> + <a class="waves-effect waves-light btn" href="{{ url_for('corpora.inspect_query_result', query_result_id=query_result.id) }}">Inspect Results<i class="material-icons right">search</i></a> + </div> + <div class="card-content" id="results"> + <table class="responsive-table highlight"> + <thead> + <tr> + <th>Metadata Description</th> + <th>Value</th> + </tr> + </thead> + <tbody> + {% for pair in query_result.query_metadata|dictsort %} + <tr> + <td>{{ pair[0] }}</td> + {% if pair[0] == 'corpus_all_texts' + or pair[0] == 'text_lookup' %} + <td> + <table> + {% for key, value in pair[1].items() %} + <tr style="border-bottom: none;"> + <td> + <i>{{ value['title'] }}</i> written + by <i>{{ value['author'] }}</i> + in <i>{{ value['publishing_year'] }}</i> + <a class="waves-effect + waves-light + btn + right + more-text-detials" data-metadata-key="{{ pair[0] }}" - data-text-key="{{ key }}"> - info_outline - </i> - </a> - </td> - </tr> + data-text-key="{{ key }}">More + <i class="material-icons right" + data-metadata-key="{{ pair[0] }}" + data-text-key="{{ key }}"> + info_outline + </i> + </a> + </td> + </tr> + {% endfor %} + </table> + </td> + {% else %} + <td>{{ pair[1] }}</td> + {% endif %} + </tr> {% endfor %} - </table> - </td> - {% else %} - <td>{{ pair[1] }}</td> - {% endif %} - </tr> - {% endfor %} - </tbody> - </table> - </div> - <div class="card-action right-align"> - <a class="waves-effect waves-light btn left-align" href="{{ url_for('services.service', service='corpus_analysis') }}">Back To Overview<i class="material-icons right">arrow_back</i></a> - <a class="waves-effect waves-light btn" href="{{ url_for('corpora.inspect_query_result', query_result_id=query_result.id) }}">Inspect Results<i class="material-icons right">search</i></a> + </tbody> + </table> + </div> + <div class="card-action right-align"> + <a class="waves-effect waves-light btn left-align" href="{{ url_for('services.service', service='corpus_analysis') }}">Back To Overview<i class="material-icons right">arrow_back</i></a> + <a class="waves-effect waves-light btn" href="{{ url_for('corpora.inspect_query_result', query_result_id=query_result.id) }}">Inspect Results<i class="material-icons right">search</i></a> + </div> + </div> </div> </div> </div> -<!-- Modal Structure --> <div id="modal-text-details" class="modal modal-fixed-footer"> <div class="modal-content"> <h4>Bibliographic data</h4> @@ -81,7 +92,10 @@ <a href="#!" class="modal-close waves-effect waves-green red btn">Close</a> </div> </div> +{% endblock page_content %} +{% block scripts %} +{{ super() }} <script> var moreTextDetailsButtons; moreTextDetailsButtons = document.getElementsByClassName("more-text-detials"); @@ -117,5 +131,4 @@ for (var btn of moreTextDetailsButtons) { } } </script> - {% endblock %} diff --git a/web/app/templates/errors/403.html.j2 b/web/app/templates/errors/403.html.j2 index 7a4eca4e1eeb2a0f4e6e853aa9a9533dbed5b0d9..0ebbea86e4219589a193790d325abdaef1914fda 100644 --- a/web/app/templates/errors/403.html.j2 +++ b/web/app/templates/errors/403.html.j2 @@ -2,14 +2,14 @@ {% block page_content %} <div class="container"> - <h1>{{ title }}</h1> + <h1 id="title">{{ title }}</h1> <p class="light">{{ request.path }}</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> </div> <div class="modal" id="more-information-modal"> <div class="modal-content"> - <h4>{{ title }}</h4> + <h2>About the "{{ title }}" error</h2> <p>The request contained valid data and was understood by the server, but the server is refusing action. This may be due to the user not having the necessary permissions for a resource or needing an account of some sort, or attempting a prohibited action (e.g. creating a duplicate record where only one is allowed). This code is also typically used if the request provided authentication by answering the WWW-Authenticate header field challenge, but the server did not accept that authentication. The request should not be repeated.</p> </div> <div class="modal-footer"> diff --git a/web/app/templates/errors/404.html.j2 b/web/app/templates/errors/404.html.j2 index a6bf74926ea95260d36800818de209457fe607b7..07692692b1420e8d1845fdfcf11b18f848e622ac 100644 --- a/web/app/templates/errors/404.html.j2 +++ b/web/app/templates/errors/404.html.j2 @@ -2,14 +2,14 @@ {% block page_content %} <div class="container"> - <h1>{{ title }}</h1> + <h1 id="title">{{ title }}</h1> <p class="light">{{ request.path }}</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> </div> <div class="modal" id="more-information-modal"> <div class="modal-content"> - <h4>{{ title }}</h4> + <h2>About the "{{ title }}" error</h2> <p>The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.</p> </div> <div class="modal-footer"> diff --git a/web/app/templates/errors/413.html.j2 b/web/app/templates/errors/413.html.j2 index 1be86042bb4bc0cc901136d3024512fd9536769d..b711c42f0772f897b037550a7bc1b6d7b571cd3a 100644 --- a/web/app/templates/errors/413.html.j2 +++ b/web/app/templates/errors/413.html.j2 @@ -2,14 +2,14 @@ {% block page_content %} <div class="container"> - <h1>{{ title }}</h1> + <h1 id="title">{{ title }}</h1> <p class="light">{{ request.path }}</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> </div> <div class="modal" id="more-information-modal"> <div class="modal-content"> - <h4>{{ title }}</h4> + <h2>About the "{{ title }}" error</h2> <p>The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".</p> </div> <div class="modal-footer"> diff --git a/web/app/templates/errors/500.html.j2 b/web/app/templates/errors/500.html.j2 index 1faaa42d8fa50771ce0d924bde2ba439b81ee6c9..2db380d9c98b8279671180825cb4a41cfcbeeb5a 100644 --- a/web/app/templates/errors/500.html.j2 +++ b/web/app/templates/errors/500.html.j2 @@ -2,14 +2,14 @@ {% block page_content %} <div class="container"> - <h1>{{ title }}</h1> + <h1 id="title">{{ title }}</h1> <p class="light">{{ request.path }}</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> </div> <div class="modal" id="more-information-modal"> <div class="modal-content"> - <h4>{{ title }}</h4> + <h2>About the "{{ title }}" error</h2> <p>A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.</p> </div> <div class="modal-footer"> diff --git a/web/app/templates/jobs/job.html.j2 b/web/app/templates/jobs/job.html.j2 index 70cf47362c5a3d51ebe9715a7d66f4c491375535..20fda0f30d9fb293c0c5d4a77ae1df0fa5488da4 100644 --- a/web/app/templates/jobs/job.html.j2 +++ b/web/app/templates/jobs/job.html.j2 @@ -1,168 +1,171 @@ {% extends "nopaque.html.j2" %} - -{% set headline = '<i class="left material-icons service" data-service="{service}" style="font-size: inherit;"></i>Job ({service}) - {title}'.format(service=job.service, title=job.title) %} +{% from '_constants.html.j2' import COLORS %} {% if job.service == 'file-setup' %} + {% set border_color = COLORS.file_setup_darken %} {% set main_class = 'file-setup-color lighten' %} + {% set scheme_color = COLORS.file_setup_darken %} {% elif job.service == 'nlp' %} + {% set border_color = COLORS.nlp_darken %} {% set main_class = 'nlp-color lighten' %} + {% set scheme_color = COLORS.nlp_darken %} {% elif job.service == 'ocr' %} + {% set border_color = COLORS.ocr_darken %} {% set main_class = 'ocr-color lighten' %} + {% set scheme_color = COLORS.ocr_darken %} {% endif %} -{% block page_content %} -{% if job.service == 'file-setup' %} - {{ Macros.insert_color_scheme(file_setup_color_darken) }} - {% set border_color = file_setup_color_darken %} -{% elif job.service == 'nlp' %} - {{ Macros.insert_color_scheme(nlp_color_darken) }} - {% set border_color = nlp_color_darken %} -{% elif job.service == 'ocr' %} - {{ Macros.insert_color_scheme(ocr_color_darken) }} - {% set border_color = ocr_color_darken %} -{% endif %} +{% block main_attribs %} class="{{ main_class }}"{% endblock main_attribs %} -<div class="col s12"> - <div class="card" style="border-top: 10px solid {{border_color}}"> - <div class="card-content"> - <div class="row"> - <div class="col s8 m9 l10"> - <span class="card-title title">{{ job.title }}</span> - </div> +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1><i class="left material-icons service" data-service="{{ job.service }}" style="font-size: inherit;"></i>Job ({{ job.service }}) - {{ job.title }}</h1> + </div> - <div class="col s4 m3 l2 right-align"> - <span class="chip status white-text"></span> - <div class="active preloader-wrapper small" id="progress-indicator"> - <div class="spinner-layer spinner-blue-only"> - <div class="circle-clipper left"> - <div class="circle"></div> - </div> - <div class="gap-patch"> - <div class="circle"></div> - </div> - <div class="circle-clipper right"> - <div class="circle"></div> + <div class="col s12"> + <div class="card" style="border-top: 10px solid {{border_color}}"> + <div class="card-content"> + <div class="row"> + <div class="col s8 m9 l10"> + <span class="card-title title">{{ job.title }}</span> + </div> + + <div class="col s4 m3 l2 right-align"> + <span class="chip status white-text"></span> + <div class="active preloader-wrapper small" id="progress-indicator"> + <div class="spinner-layer spinner-blue-only"> + <div class="circle-clipper left"> + <div class="circle"></div> + </div> + <div class="gap-patch"> + <div class="circle"></div> + </div> + <div class="circle-clipper right"> + <div class="circle"></div> + </div> + </div> </div> </div> - </div> - </div> - <div class="col s12"> - <p class="description">{{ job.description }}</p> - </div> + <div class="col s12"> + <p class="description">{{ job.description }}</p> + </div> - <div class="col s12"> </div> + <div class="col s12"> </div> - <div class="col s12 m6"> - <div class="input-field"> - <input disabled id="creation-date" type="text" value="{{ job.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}"> - <label for="creation-date">Creation date</label> - </div> - </div> + <div class="col s12 m6"> + <div class="input-field"> + <input disabled id="creation-date" type="text" value="{{ job.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}"> + <label for="creation-date">Creation date</label> + </div> + </div> - <div class="col s12 m6"> - <div class="input-field"> - <input class="end-date" disabled id="end-date" type="text" value=""> - <label for="end-date">End date</label> - </div> - </div> + <div class="col s12 m6"> + <div class="input-field"> + <input class="end-date" disabled id="end-date" type="text" value=""> + <label for="end-date">End date</label> + </div> + </div> - <div class="col s12 m4"> - <div class="input-field"> - <input disabled id="service" type="text" value="{{ job.service }}"> - <label for="service">Service</label> - </div> - </div> + <div class="col s12 m4"> + <div class="input-field"> + <input disabled id="service" type="text" value="{{ job.service }}"> + <label for="service">Service</label> + </div> + </div> - <div class="col s12 m4"> - <div class="input-field"> - <input disabled id="service-args" type="text" value="{{ job.service_args|e }}"> - <label for="service-args">Service arguments</label> - </div> - </div> + <div class="col s12 m4"> + <div class="input-field"> + <input disabled id="service-args" type="text" value="{{ job.service_args|e }}"> + <label for="service-args">Service arguments</label> + </div> + </div> - <div class="col s12 m4"> - <div class="input-field"> - <input disabled id="service-version" type="text" value="{{ job.service_version }}"> - <label for="service-version">Service version</label> + <div class="col s12 m4"> + <div class="input-field"> + <input disabled id="service-version" type="text" value="{{ job.service_version }}"> + <label for="service-version">Service version</label> + </div> + </div> </div> </div> + <div class="card-action right-align"> + {% if current_user.is_administrator() and job.status == 'failed' %} + <a href="{{ url_for('jobs.restart', job_id=job.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">repeat</i>Restart</a> + {% endif %} + <!-- <a href="#" class="btn disabled waves-effect waves-light"><i class="material-icons left">settings</i>Export Parameters</a> --> + <a data-target="delete-job-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a> + </div> </div> </div> - <div class="card-action right-align"> - {% if current_user.is_administrator() and job.status == 'failed' %} - <a href="{{ url_for('jobs.restart', job_id=job.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">repeat</i>Restart</a> - {% endif %} - <!-- <a href="#" class="btn disabled waves-effect waves-light"><i class="material-icons left">settings</i>Export Parameters</a> --> - <a data-target="delete-job-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a> - </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content" id="inputs"> - <div class="row"> - <div class="col s12 m2"> - <span class="card-title"><i class="left material-icons" style="font-size: inherit;">input</i>Inputs</span> - <p>Original input files.</p> - </div> - <div class="col s12 m10"> - <ul class="pagination paginationTop"></ul> - <table class="highlight responsive-table"> - <thead> - <tr> - <th class="sort" data-sort="filename">Filename</th> - <th>{# Actions #}</th> - </tr> - </thead> - <tbody class="list"> - </tbody> - </table> - <ul class="pagination paginationBottom"></ul> + <div class="col s12"> + <div class="card"> + <div class="card-content" id="inputs"> + <div class="row"> + <div class="col s12 m2"> + <span class="card-title"><i class="left material-icons" style="font-size: inherit;">input</i>Inputs</span> + <p>Original input files.</p> + </div> + <div class="col s12 m10"> + <ul class="pagination paginationTop"></ul> + <table class="highlight responsive-table"> + <thead> + <tr> + <th class="sort" data-sort="filename">Filename</th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="list"> + </tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + </div> </div> </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <div class="row"> - <div class="col s12 m2"> - <span class="card-title"><i class="left material-icons" style="font-size: inherit;">done</i>Results</span> - <p>Processed result files.</p> - </div> - <div class="col s12 m10"> - <table class="highlight responsive-table"> - <thead> - <tr> - <th>Result Type</th> - <th>Archive Name</th> - <th>{# Actions #}</th> - </tr> - </thead> - <tbody class="results"> - <tr class="show-if-only-child"> - <td colspan="3"> - <span class="card-title"> - <i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here... - </span> - <p> - No results available (yet). Is the job already completed? - </p> - </td> - </tr> - </tbody> - </table> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <div class="row"> + <div class="col s12 m2"> + <span class="card-title"><i class="left material-icons" style="font-size: inherit;">done</i>Results</span> + <p>Processed result files.</p> + </div> + <div class="col s12 m10"> + <table class="highlight responsive-table"> + <thead> + <tr> + <th>Result Type</th> + <th>Archive Name</th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="results"> + <tr class="show-if-only-child"> + <td colspan="3"> + <span class="card-title"> + <i class="left material-icons" style="font-size: inherit;">file_download</i>Nothing here... + </span> + <p> + No results available (yet). Is the job already completed? + </p> + </td> + </tr> + </tbody> + </table> + </div> + </div> </div> </div> </div> </div> </div> - <!-- Modals --> <div id="delete-job-modal" class="modal"> <div class="modal-content"> @@ -174,8 +177,10 @@ <a class="btn modal-close red waves-effect waves-light" href="{{ url_for('jobs.delete_job', job_id=job.id) }}"><i class="material-icons left">delete</i>Delete</a> </div> </div> +{% endblock page_content %} - +{% block scripts %} +{{ super() }} <script type="module"> import {RessourceList} from '../../static/js/nopaque.lists.js'; class InformationUpdater { @@ -309,13 +314,9 @@ class InformationUpdater { var informationUpdater = new InformationUpdater({{ job.id }}, false); {% else %} var informationUpdater = new InformationUpdater({{ job.id }}, true); -document.addEventListener("DOMContentLoaded", () => { - nopaque.socket.emit("foreign_user_data_stream_init", {{ job.user_id }}); -}); +nopaque.socket.emit("foreign_user_data_stream_init", {{ job.user_id }}); {% endif %} let jobInputsList = new RessourceList("inputs", null, "JobInput"); -document.addEventListener("DOMContentLoaded", () => { - jobInputsList._add({{ job_inputs|tojson|safe }}); -}); +jobInputsList._add({{ job_inputs|tojson|safe }}); </script> -{% endblock %} +{% endblock scripts %} diff --git a/web/app/templates/main/about_and_faq.html.j2 b/web/app/templates/main/about_and_faq.html.j2 index c3b2ee7684ea463c681eb5228741220e213a3ebc..24e87d672c95719a8571ced7e6876bd7ec8ccbc9 100644 --- a/web/app/templates/main/about_and_faq.html.j2 +++ b/web/app/templates/main/about_and_faq.html.j2 @@ -1,259 +1,265 @@ {% extends "nopaque.html.j2" %} {% block page_content %} - -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">What is nopaque</span> - <p> - Our web application nopaque combines coordinated tools such as Optical - Character Recognition (OCR), Natrual Language Processing (NLP) and a - powerful Keyword In Context Search (KWIC) with the CQP query language. - </p> - <br> - <p> - nopaque offers the possibility to use all tools individually or as a - workflow. All work steps are coordinated in such a way that individual - services can be used on top of each other. The platform supports - researchers in converting their files into formats that can be further - processed, automatically enriching them with information and then - analyzing them, so that nopaque maps a large part of the research - processes in the humanities. With this toolbox we address researchers - in the humanities from all disciplines and levels of knowledge. - The data generated during the processes can be downloaded after each - step in order to evaluate or further process them with other (external) tools. - </p> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> </div> - </div> -</div> -<div class="col s12"> - <ul class="collapsible"> - <li> - <div class="collapsible-header">1. Who is developing nopaque?</div> - <div class="collapsible-body"> - <span> - <p> - <em>nopaque</em> is developed by a small, interdisciplinary Team at - <em>University Bielefeld</em> called <em>Data Infrastructure and - Digital Humanities (INF)</em>. We are part of the <em>SFB 1288 - - Practices of comparing. Ordering and changing the world</em>. - </p> - <p>For mor information <a href="https://www.uni-bielefeld.de/(en)/sfb1288/"> - visit the SFB1288 web site</a> or - <a href="https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html"> - our team page</a>. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">2. Is nopaque free to use for everyone?</div> - <div class="collapsible-body"> - <span> - <p> - Yes nopaque is free to use for everyone! It does not matter if you - are a researcher in the humanities, a student or just someone who - wants to learn something new. Just <a href="http://nopaque.localhost/auth/register"> - sign up for it</a> and try it out! - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">3. How much does it cost to use nopaque?</div> - <div class="collapsible-body"> - <span>nopaque and the services provided by it are free of charge.</span> - </div> - <li> - <li> - <div class="collapsible-header">4. Why is nopaque written in lower case?</div> - <div class="collapsible-body"> - <span>We just think that nopaque with a lower case first letter looks - better than Nopaque with an upper case first letter. Simple as that! - </span> - </div> - <li> - <li> - <div class="collapsible-header">5. Why the name nopaque?</div> - <div class="collapsible-body"> - <span> - <p> - When we started developing nopaque we wanted to have a cool name - like <a href="https://voyant-tools.org/">voyant</a> which can be translated - to light or seeing. So we choose opaque thinking that it means that - something is transparent. After a while we realized that we misunderstood - the meaning of the word (opaque means non-transparent) and simply - negated it ending up with nopaque. - </p> - <p> - We also think nopaque fits pretty nicley because we want you to be - able to make your texts transparent and see through them with our - analysis tool to gain knew knowledge about them. - </p> - </span> - </div> - <li> - <div class="collapsible-header">6. Is nopaque FOSS/Open Source?</div> - <div class="collapsible-body"> - <span>Yes nopaque only uses free and open source software (FOSS). You - can find the <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/opaque"> - source code in our gitlab repository</a>. - </span> - </div> - </li> - <li> - <div class="collapsible-header">7. What software/technologies is nopaque using/build with?</div> - <div class="collapsible-body"> - <span> - <p> - nopaques frontend (what the user sees) is written in HTML 5 - and Javascript. The backend (stuff that happens on our servers) - is realized with <a href="https://palletsprojects.com/p/flask/"> - Flask</a>, a python based lightweight WSGI web application - framework. We utilize <a href="https://www.docker.com/">Docker</a> - to easily deploy nopaque on our servers. - </p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">What is nopaque</span> <p> - The client server real time comminication is implemented using - <a href="https://flask-socketio.readthedocs.io/en/latest/">Flask-SocketIO</a>. + Our web application nopaque combines coordinated tools such as Optical + Character Recognition (OCR), Natrual Language Processing (NLP) and a + powerful Keyword In Context Search (KWIC) with the CQP query language. </p> - <p>Every service (e.g. OCR or NLP) provided by nopaque is using - established opensource software. Take a look at their related - questions to learn more about them.</p> + <br> <p> - For more details take a look at the - <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/opaque">source code</a>. + nopaque offers the possibility to use all tools individually or as a + workflow. All work steps are coordinated in such a way that individual + services can be used on top of each other. The platform supports + researchers in converting their files into formats that can be further + processed, automatically enriching them with information and then + analyzing them, so that nopaque maps a large part of the research + processes in the humanities. With this toolbox we address researchers + in the humanities from all disciplines and levels of knowledge. + The data generated during the processes can be downloaded after each + step in order to evaluate or further process them with other (external) tools. </p> - </span> + </div> </div> - </li> - <li> - <div class="collapsible-header">8. What software/technology is used for the File Setup service?</div> - <div class="collapsible-body"> - <span> - <p> - The File Setup service uses <a href="https://imagemagick.org/index.php">ImageMagick</a> - to merge your images into one file. - </p> - <p> - For more details take a look at the - <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/file-setup">source code</a>. - </p> - </span> - <li> - <div class="collapsible-header">9. What software/technology is used for the OCR service?</div> - <div class="collapsible-body"> - <span> - <p> - The OCR service uses <a href="https://github.com/tesseract-ocr/tesseract">Tesseract OCR.</a> - </p> - <p> - For more details take a look at the - <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/ocr">source code</a>. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">10. What software/technology is used for the NLP service?</div> - <div class="collapsible-body"> - <span> - <p> - The NLP service uses <a href="https://spacy.io/">spaCy</a>. - </p> - <p> - For more details take a look at the - <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nlp">source code</a>. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">11. What software/technology is used for the Analysis service?</div> - <div class="collapsible-body"> - <span> - <p> - The Corpus Analysis service uses the <a href="http://cwb.sourceforge.net/"> - IMS Open Corpus Workbench (CWB).</a> We developed a Python - library for the IMS Open Corpus Workbench (CWB) corpus query - interface (CQi) API to be able to request query results from the CWB - server using simple Python code. The library is - <a href="https://pypi.org/project/cqi/">avilable on PyPi.</a> - </p> - <p> - For more details take a look at the - <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/cqpweb">source code</a>. - </p> - </span> - </div> - <li> - <div class="collapsible-header">12. Can I download/export my results and processed files?</div> - <div class="collapsible-body"> - <span> - <p> - Yes. You can download everything that is the result of a service - and save it somwhere else. You can download your results depending - on the service in easily reusable formats like TXT, PDF, JSON, XML - and many more. This also empowers you to use your results in other - third party software to continue you research beyond the capabiltiys - of nopaque. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">13. Is my research data private?</div> - <div class="collapsible-body"> - <span> - <p> - Your uploaded research data cannot be accessed by any third party. - Take a look at our <a href="http://nopaque.localhost/privacy_policy">GDPR</a> - statement and <a href="http://nopaque.localhost/terms_of_use">terms of use</a> - if you want to learn more about how we handle your data. - <p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">14. Could I use nopaque as a permant storage for my research data?</div> - <div class="collapsible-body"> - <span> - <p> - nopaque saves your research data in theory as long as your account - exists. But nopaque is not a cloud storage solution! We encourage - you to permanently save your data somwhere else. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">15. What does OCR mean?</div> - <div class="collapsible-body"> - <span> - <p> - OCR stands for Optical Character Recognition. OCR is the automatical - conversion of images of handwritten or printed text into machine-encoded text. - </p> - </span> - </div> - </li> - <li> - <div class="collapsible-header">16. What does NLP mean?</div> - <div class="collapsible-body"> - <span> - <p> - NLP stands for natural language processing wich is a subfield of - linguistics, computer science and artificial intelligence concerned - with the interactions between computers and human language. - For example nopaque uses spaCy to automtically tag every word with - its part of speech tag which describes its grammatical property. - </p> - </span> - </div> - </li> - </ul> -</div> + </div> + <div class="col s12"> + <ul class="collapsible"> + <li> + <div class="collapsible-header">1. Who is developing nopaque?</div> + <div class="collapsible-body"> + <span> + <p> + <em>nopaque</em> is developed by a small, interdisciplinary Team at + <em>University Bielefeld</em> called <em>Data Infrastructure and + Digital Humanities (INF)</em>. We are part of the <em>SFB 1288 - + Practices of comparing. Ordering and changing the world</em>. + </p> + <p>For mor information <a href="https://www.uni-bielefeld.de/(en)/sfb1288/"> + visit the SFB1288 web site</a> or + <a href="https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html"> + our team page</a>. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">2. Is nopaque free to use for everyone?</div> + <div class="collapsible-body"> + <span> + <p> + Yes nopaque is free to use for everyone! It does not matter if you + are a researcher in the humanities, a student or just someone who + wants to learn something new. Just <a href="http://nopaque.localhost/auth/register"> + sign up for it</a> and try it out! + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">3. How much does it cost to use nopaque?</div> + <div class="collapsible-body"> + <span>nopaque and the services provided by it are free of charge.</span> + </div> + <li> + <li> + <div class="collapsible-header">4. Why is nopaque written in lower case?</div> + <div class="collapsible-body"> + <span>We just think that nopaque with a lower case first letter looks + better than Nopaque with an upper case first letter. Simple as that! + </span> + </div> + <li> + <li> + <div class="collapsible-header">5. Why the name nopaque?</div> + <div class="collapsible-body"> + <span> + <p> + When we started developing nopaque we wanted to have a cool name + like <a href="https://voyant-tools.org/">voyant</a> which can be translated + to light or seeing. So we choose opaque thinking that it means that + something is transparent. After a while we realized that we misunderstood + the meaning of the word (opaque means non-transparent) and simply + negated it ending up with nopaque. + </p> + <p> + We also think nopaque fits pretty nicley because we want you to be + able to make your texts transparent and see through them with our + analysis tool to gain knew knowledge about them. + </p> + </span> + </div> + <li> + <div class="collapsible-header">6. Is nopaque FOSS/Open Source?</div> + <div class="collapsible-body"> + <span>Yes nopaque only uses free and open source software (FOSS). You + can find the <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/opaque"> + source code in our gitlab repository</a>. + </span> + </div> + </li> + <li> + <div class="collapsible-header">7. What software/technologies is nopaque using/build with?</div> + <div class="collapsible-body"> + <span> + <p> + nopaques frontend (what the user sees) is written in HTML 5 + and Javascript. The backend (stuff that happens on our servers) + is realized with <a href="https://palletsprojects.com/p/flask/"> + Flask</a>, a python based lightweight WSGI web application + framework. We utilize <a href="https://www.docker.com/">Docker</a> + to easily deploy nopaque on our servers. + </p> + <p> + The client server real time comminication is implemented using + <a href="https://flask-socketio.readthedocs.io/en/latest/">Flask-SocketIO</a>. + </p> + <p>Every service (e.g. OCR or NLP) provided by nopaque is using + established opensource software. Take a look at their related + questions to learn more about them.</p> + <p> + For more details take a look at the + <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/opaque">source code</a>. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">8. What software/technology is used for the File Setup service?</div> + <div class="collapsible-body"> + <span> + <p> + The File Setup service uses <a href="https://imagemagick.org/index.php">ImageMagick</a> + to merge your images into one file. + </p> + <p> + For more details take a look at the + <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/file-setup">source code</a>. + </p> + </span> + <li> + <div class="collapsible-header">9. What software/technology is used for the OCR service?</div> + <div class="collapsible-body"> + <span> + <p> + The OCR service uses <a href="https://github.com/tesseract-ocr/tesseract">Tesseract OCR.</a> + </p> + <p> + For more details take a look at the + <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/ocr">source code</a>. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">10. What software/technology is used for the NLP service?</div> + <div class="collapsible-body"> + <span> + <p> + The NLP service uses <a href="https://spacy.io/">spaCy</a>. + </p> + <p> + For more details take a look at the + <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nlp">source code</a>. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">11. What software/technology is used for the Analysis service?</div> + <div class="collapsible-body"> + <span> + <p> + The Corpus Analysis service uses the <a href="http://cwb.sourceforge.net/"> + IMS Open Corpus Workbench (CWB).</a> We developed a Python + library for the IMS Open Corpus Workbench (CWB) corpus query + interface (CQi) API to be able to request query results from the CWB + server using simple Python code. The library is + <a href="https://pypi.org/project/cqi/">avilable on PyPi.</a> + </p> + <p> + For more details take a look at the + <a href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/cqpweb">source code</a>. + </p> + </span> + </div> + <li> + <div class="collapsible-header">12. Can I download/export my results and processed files?</div> + <div class="collapsible-body"> + <span> + <p> + Yes. You can download everything that is the result of a service + and save it somwhere else. You can download your results depending + on the service in easily reusable formats like TXT, PDF, JSON, XML + and many more. This also empowers you to use your results in other + third party software to continue you research beyond the capabiltiys + of nopaque. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">13. Is my research data private?</div> + <div class="collapsible-body"> + <span> + <p> + Your uploaded research data cannot be accessed by any third party. + Take a look at our <a href="http://nopaque.localhost/privacy_policy">GDPR</a> + statement and <a href="http://nopaque.localhost/terms_of_use">terms of use</a> + if you want to learn more about how we handle your data. + <p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">14. Could I use nopaque as a permant storage for my research data?</div> + <div class="collapsible-body"> + <span> + <p> + nopaque saves your research data in theory as long as your account + exists. But nopaque is not a cloud storage solution! We encourage + you to permanently save your data somwhere else. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">15. What does OCR mean?</div> + <div class="collapsible-body"> + <span> + <p> + OCR stands for Optical Character Recognition. OCR is the automatical + conversion of images of handwritten or printed text into machine-encoded text. + </p> + </span> + </div> + </li> + <li> + <div class="collapsible-header">16. What does NLP mean?</div> + <div class="collapsible-body"> + <span> + <p> + NLP stands for natural language processing wich is a subfield of + linguistics, computer science and artificial intelligence concerned + with the interactions between computers and human language. + For example nopaque uses spaCy to automtically tag every word with + its part of speech tag which describes its grammatical property. + </p> + </span> + </div> + </li> + </ul> + </div> + </div> +</div> {% endblock %} diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2 index 194bdecbcc3628e7e8b1052c333c9b690d5e9e48..71d4f480182ed9d0917904ab1a0e1111aede3435 100644 --- a/web/app/templates/main/dashboard.html.j2 +++ b/web/app/templates/main/dashboard.html.j2 @@ -1,171 +1,182 @@ {% extends "nopaque.html.j2" %} {% block page_content %} -<div class="col s12"> - <h3>My Corpora and Query results</h3> - <p>Create a corpus to interactively perform linguistic analysis or import query results to save interesting passages.</p> +<div class="container"> <div class="row"> <div class="col s12"> - <ul class="tabs"> - <li class="tab col s6"><a class="active" href="#corpora">Corpora</a></li> - <li class="tab col s6"><a href="#query-results">Query results</a></li> - </ul> + <h1 id="title">{{ title }}</h1> </div> - <div class="col s12" id="corpora"> - <div class="card"> - <div class="card-content"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-corpus" class="search" type="search"></input> - <label for="search-corpus">Search corpus</label> + + <div class="col s12"> + <h3>My Corpora and Query results</h3> + <p>Create a corpus to interactively perform linguistic analysis or import query results to save interesting passages.</p> + <div class="row"> + <div class="col s12"> + <ul class="tabs"> + <li class="tab col s6"><a class="active" href="#corpora">Corpora</a></li> + <li class="tab col s6"><a href="#query-results">Query results</a></li> + </ul> + </div> + <div class="col s12" id="corpora"> + <div class="card"> + <div class="card-content"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-corpus" class="search" type="search"></input> + <label for="search-corpus">Search corpus</label> + </div> + <ul class="pagination paginationTop"></ul> + <table class="highlight"> + <thead> + <tr> + <th></th> + <th> + <span class="sort" data-sort="title">Title</span> + <span class="sort" data-sort="description">Description</span> + </th> + <th><span class="sort" data-sort="status">Status</span></th> + <th></th> + </tr> + </thead> + <tbody class="list"></tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + <div class="card-action right-align"> + <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> + </div> </div> - <ul class="pagination paginationTop"></ul> - <table class="highlight"> - <thead> - <tr> - <th></th> - <th> - <span class="sort" data-sort="title">Title</span> - <span class="sort" data-sort="description">Description</span> - </th> - <th><span class="sort" data-sort="status">Status</span></th> - <th></th> - </tr> - </thead> - <tbody class="list"></tbody> - </table> - <ul class="pagination paginationBottom"></ul> </div> - <div class="card-action right-align"> - <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> + <div class="col s12" id="query-results"> + <div class="card"> + <div class="card-content"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-query-results" class="search" type="search"></input> + <label for="search-query-results">Search query result</label> + </div> + <ul class="pagination paginationTop"></ul> + <table class="highlight responsive-table"> + <thead> + <tr> + <th> + <span class="sort" data-sort="title">Title</span> and<br> + <span class="sort" data-sort="description">Description</span> + </th> + <th> + <span class="sort" data-sort="corpus">Corpus</span> and<br> + <span class="sort" data-sort="query">Query</span> + </th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="list"> + <tr class="show-if-only-child"> + <td colspan="5"> + <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> + <p>No query results yet imported.</p> + </td> + </tr> + </tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + <div class="card-action right-align"> + <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_query_result') }}">Add query result<i class="material-icons right">file_upload</i></a> + </div> + </div> </div> </div> </div> - <div class="col s12" id="query-results"> + + <div class="col s12" id="jobs"> + <h3>My Jobs</h3> + <p>A job is the execution of a service provided by nopaque. You can create any number of jobs and let them be processed simultaneously.</p> <div class="card"> <div class="card-content"> <div class="input-field"> <i class="material-icons prefix">search</i> - <input id="search-query-results" class="search" type="search"></input> - <label for="search-query-results">Search query result</label> + <input id="search-job" class="search" type="search"></input> + <label for="search-job">Search job</label> </div> <ul class="pagination paginationTop"></ul> - <table class="highlight responsive-table"> + <table class="highlight"> <thead> <tr> + <th><span class="sort" data-sort="service">Service</span></th> <th> - <span class="sort" data-sort="title">Title</span> and<br> + <span class="sort" data-sort="title">Title</span> <span class="sort" data-sort="description">Description</span> </th> - <th> - <span class="sort" data-sort="corpus">Corpus</span> and<br> - <span class="sort" data-sort="query">Query</span> - </th> - <th>{# Actions #}</th> + <th><span class="sort" data-sort="status">Status</span></th> + <th></th> </tr> </thead> - <tbody class="list"> - <tr class="show-if-only-child"> - <td colspan="5"> - <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> - <p>No query results yet imported.</p> - </td> - </tr> - </tbody> + <tbody class="list"></tbody> </table> <ul class="pagination paginationBottom"></ul> </div> <div class="card-action right-align"> - <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_query_result') }}">Add query result<i class="material-icons right">file_upload</i></a> + <p><a class="modal-trigger waves-effect waves-light btn" href="#" data-target="new-job-modal"><i class="material-icons left">add</i>New job</a></p> </div> </div> - </div> - </div> -</div> - -<div class="col s12" id="jobs"> - <h3>My Jobs</h3> - <p>A job is the execution of a service provided by nopaque. You can create any number of jobs and let them be processed simultaneously.</p> - <div class="card"> - <div class="card-content"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-job" class="search" type="search"></input> - <label for="search-job">Search job</label> - </div> - <ul class="pagination paginationTop"></ul> - <table class="highlight"> - <thead> - <tr> - <th><span class="sort" data-sort="service">Service</span></th> - <th> - <span class="sort" data-sort="title">Title</span> - <span class="sort" data-sort="description">Description</span> - </th> - <th><span class="sort" data-sort="status">Status</span></th> - <th></th> - </tr> - </thead> - <tbody class="list"></tbody> - </table> - <ul class="pagination paginationBottom"></ul> - </div> - <div class="card-action right-align"> - <p><a class="modal-trigger waves-effect waves-light btn" href="#" data-target="new-job-modal"><i class="material-icons left">add</i>New job</a></p> - </div> - </div> - <div id="new-job-modal" class="modal"> - <div class="modal-content"> - <h4>Select a service</h4> - <div class="row"> - <div class="col s12 m4"> - <div class="card-panel center-align hoverable"> - <br> - <a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large file-setup-color darken waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="file-setup"></i> - </a> - <p> </p> - <p class="file-setup-color-text"><b>File setup</b></p> - <p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing and the application of other services.</p> - <a href="{{ url_for('services.service', service='file-setup') }}" class="waves-effect waves-light btn file-setup-color darken">Create Job</a> + <div id="new-job-modal" class="modal"> + <div class="modal-content"> + <h4>Select a service</h4> + <div class="row"> + <div class="col s12 m4"> + <div class="card-panel center-align hoverable"> + <br> + <a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large file-setup-color darken waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="file-setup"></i> + </a> + <p> </p> + <p class="file-setup-color-text"><b>File setup</b></p> + <p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing and the application of other services.</p> + <a href="{{ url_for('services.service', service='file-setup') }}" class="waves-effect waves-light btn file-setup-color darken">Create Job</a> + </div> </div> - </div> - <div class="col s12 m4"> - <div class="card-panel center-align hoverable"> - <br> - <a href="{{ url_for('services.service', service='ocr') }}" class="btn-floating btn-large ocr-color darken waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="ocr"></i> - </a> - <p> </p> - <p class="ocr-color-text"><b>Optical Character Recognition</b></p> - <p class="light">nopaque converts your image data – like photos or scans – into text data through a process called OCR. This step enables you to proceed with further computational analysis of your documents.</p> - <a href="{{ url_for('services.service', service='ocr') }}" class="waves-effect waves-light btn ocr-color darken">Create Job</a> + <div class="col s12 m4"> + <div class="card-panel center-align hoverable"> + <br> + <a href="{{ url_for('services.service', service='ocr') }}" class="btn-floating btn-large ocr-color darken waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="ocr"></i> + </a> + <p> </p> + <p class="ocr-color-text"><b>Optical Character Recognition</b></p> + <p class="light">nopaque converts your image data – like photos or scans – into text data through a process called OCR. This step enables you to proceed with further computational analysis of your documents.</p> + <a href="{{ url_for('services.service', service='ocr') }}" class="waves-effect waves-light btn ocr-color darken">Create Job</a> + </div> </div> - </div> - <div class="col s12 m4"> - <div class="card-panel center-align hoverable"> - <br> - <a href="{{ url_for('services.service', service='nlp') }}" class="btn-floating btn-large nlp-color darken waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="nlp"></i> - </a> - <p> </p> - <p class="nlp-color-text"><b>Natural Language Processing</b></p> - <p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p> - <a href="{{ url_for('services.service', service='nlp') }}" class="waves-effect waves-light btn nlp-color darken">Create Job</a> + <div class="col s12 m4"> + <div class="card-panel center-align hoverable"> + <br> + <a href="{{ url_for('services.service', service='nlp') }}" class="btn-floating btn-large nlp-color darken waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="nlp"></i> + </a> + <p> </p> + <p class="nlp-color-text"><b>Natural Language Processing</b></p> + <p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p> + <a href="{{ url_for('services.service', service='nlp') }}" class="waves-effect waves-light btn nlp-color darken">Create Job</a> + </div> </div> + </div> + </div> + <div class="modal-footer"> + <a href="#!" class="modal-close waves-effect waves-light btn-flat">Close</a> </div> </div> </div> - <div class="modal-footer"> - <a href="#!" class="modal-close waves-effect waves-light btn-flat">Close</a> - </div> </div> </div> +{% endblock %} + +{% block scripts %} +{{ super() }} <script type="module"> import {RessourceList} from '../../static/js/nopaque.lists.js'; let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus"); let jobList = new RessourceList("jobs", nopaque.jobsSubscribers, "Job"); let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult"); </script> - -{% endblock %} +{% endblock scripts %} diff --git a/web/app/templates/main/index.html.j2 b/web/app/templates/main/index.html.j2 index 59817520d5054644d6af23c98752bd24f8477022..dbc32c3e95b510d8abd1ff775b9ed1d00e224644 100644 --- a/web/app/templates/main/index.html.j2 +++ b/web/app/templates/main/index.html.j2 @@ -1,12 +1,11 @@ {% extends "nopaque.html.j2" %} - -{% set parallax = True %} +{% import 'materialize/wtf.html.j2' as wtf %} {% block page_content %} <div class="section white"> <div class="row container"> <div class="col s12"> - <h2>nopaque</h2> + <h1 id="title">{{ title }}</h1> <p>From text to data to analysis</p> </div> </div> @@ -157,19 +156,19 @@ <div class="card-content"> <span class="card-title">Log in</span> {{ login_form.hidden_tag() }} - {{ M.render_field(login_form.user, material_icon='person') }} - {{ M.render_field(login_form.password, material_icon='vpn_key') }} + {{ wtf.render_field(login_form.user, material_icon='person') }} + {{ wtf.render_field(login_form.password, material_icon='vpn_key') }} <div class="row" style="margin-bottom: 0;"> <div class="col s6 left-align"> <a href="{{ url_for('auth.reset_password_request') }}">Forgot your password?</a> </div> <div class="col s6 right-align"> - {{ M.render_field(login_form.remember_me) }} + {{ wtf.render_field(login_form.remember_me) }} </div> </div> </div> <div class="card-action right-align"> - {{ M.render_field(login_form.submit, material_icon='send') }} + {{ wtf.render_field(login_form.submit, material_icon='send') }} </div> </form> </div> diff --git a/web/app/templates/main/news.html.j2 b/web/app/templates/main/news.html.j2 index f6ed5ba468d3df78ebbc51e68c48ae2918297341..c7947530590bfa498f6cd5f755c49eef4a899fd0 100644 --- a/web/app/templates/main/news.html.j2 +++ b/web/app/templates/main/news.html.j2 @@ -1,14 +1,22 @@ {% extends "nopaque.html.j2" %} {% block page_content %} -<div class="col s12"> - <div class="card" id="beta-launch"> - <div class="card-content"> - <span class="card-title">nopaque's beta launch</span> - <p>Dear users</p> - <br> - <p>A few days ago we went live with nopaque. Right now nopaque is still in its Beta phase. So some bugs are to be expected. If you encounter any bugs or some feature is not working as expected please send as an email using the feedback button at the botton of the page in the footer!</p> - <p>We are happy to help you with any issues and will use the feedback to fix all mentioned bugs!</p> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> + + <div class="col s12"> + <div class="card" id="beta-launch"> + <div class="card-content"> + <span class="card-title">nopaque's beta launch</span> + <p>Dear users</p> + <br> + <p>A few days ago we went live with nopaque. Right now nopaque is still in its Beta phase. So some bugs are to be expected. If you encounter any bugs or some feature is not working as expected please send as an email using the feedback button at the botton of the page in the footer!</p> + <p>We are happy to help you with any issues and will use the feedback to fix all mentioned bugs!</p> + </div> + </div> </div> </div> </div> diff --git a/web/app/templates/main/privacy_policy.html.j2 b/web/app/templates/main/privacy_policy.html.j2 index 22c23421179dea0a028e83031a8ab372bef2165b..71c469bf6cc8bc0f0de28d1ded1c964ec129b507 100644 --- a/web/app/templates/main/privacy_policy.html.j2 +++ b/web/app/templates/main/privacy_policy.html.j2 @@ -1,147 +1,154 @@ {% extends "nopaque.html.j2" %} {% block page_content %} -<div class="col s12"> - <p>With these data protection notices, Bielefeld University fulfils its obligation to provide information in accordance with Articles 13 & 14 of the EU General Data Protection Regulation (GDPR) on the above-mentioned processing of personal data. Terms such as "personal data", "processing", "data controller", "third party", etc. are used as defined in Article 4 GDPR.</p> -</div> +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 1 Contact Details</span> - <p>Bielefeld University, a legal entity under public law established by the state of North Rhine-Westphalia (NRW), is responsible for processing the data. It is represented by its rector, Prof. Dr. Ing. Gerhard Sagerer.</p> - <h6>§ 1.1. Contact details of the data controller</h6> - <ul class="browser-default"> - <li>Data protection officer of the Faculty of History</li> - <li>Prof. Dr. Stefan Gorißen</li> - <li>Universitätsstraße 25</li> - <li>D-33615 Bielefeld</li> - <li>Phone: +49 521 / 106-3152</li> - <li>Email: - <a href="mailto:stefan.gorissen@uni-bielefeld.de">stefan.gorissen@uni-bielefeld.de</a> - </li> - <li>Web: - <a href="https://www.uni-bielefeld.de">https://www.uni-bielefeld.de</a> - </li> - </ul> - <h6>§ 1.2. Technical contact person</h6> - <ul class="browser-default"> - <li>Dr. Johanna Vompras</li> - <li>Email: - <a href="mailto:johanna.vompras@uni-bielefeld.de">johanna.vompras@uni-bielefeld.de</a> - </li> - <li>Web.: - <a href="https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html">https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html</a> - </li> - </ul> - <h6>§ 1.2. Contact details of the data protection officer</h6> - <ul class="browser-default"> - <li>The data protection officer responsible is:</li> - <li>Phone: +49 521 106-5225</li> - <li> - Email: <a href="mailto:datenschutzbeauftragte@uni-bielefeld.de">datenschutzbeauftragte@uni-bielefeld.de</a> - </li> - </ul> + <div class="col s12"> + <p>With these data protection notices, Bielefeld University fulfils its obligation to provide information in accordance with Articles 13 & 14 of the EU General Data Protection Regulation (GDPR) on the above-mentioned processing of personal data. Terms such as "personal data", "processing", "data controller", "third party", etc. are used as defined in Article 4 GDPR.</p> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 2 General information on data processing and its purpose</span> - <p>We process the personal data of our users only to the extent necessary to provide a functioning website and its functionalities.</p> - <p>The following personal data is collected and stored within the system:</p> - <h6>Master Data</h6> - <p>Within the scope of user authentication the following personal data is collected and processed:</p> - <ul class="browser-default"> - <li>User name</li> - <li>E-Mail</li> - </ul> - <p>Registration of the user is required for the provision of certain content and services within nopaque.</p> - <h6>Protocol Data</h6> - <p>In general, when a website is visited, for technical reasons information is automatically sent from the browser to the server and stored there in access protocols. When using a web application, additional protocol data is also generated, which is necessary for tracking technical errors. This information includes:</p> - <ul class="browser-default"> - <li>IP address</li> - <li>User account</li> - <li>Complete HTTP request URL</li> - <li>HTTP action (e.g. GET: call up a page, POST: send form data)</li> - <li>Access status (HTTP status code)</li> - <li>data volume retrieved</li> - <li>Date and time of the action</li> - <li>User-Agent string</li> - </ul> - <p>Locally logged data will be used by the development team in order to debug and improve tools. This data can only be viewed by the technical administration and by the employees responsible for the nopaque platform. Data is stored for seven days to ensure proper technical operation and to find the cause of errors and is deleted <u>afterwards</u>.</p> - <p>Logged data may be used to understand how researchers are using the nopaque platform. To be able to use the data for research purposes, we reserve the right to store it in an anonymous and aggregated form for a longer period of time (up to two years after completion of the SFB 1288 INF project).</p> - <h6>Cookies</h6> - <p>Browsers store so-called cookies. Cookies are files that can be stored by the provider of a website in the directory of the browser program on the user's computer. These files contain text information and can be read again by the provider when the page is called up again. The provider can use these cookies, for example, to always deliver pages in the theme selected by the user.</p> - <p>The storage of cookies can be switched off in the browser settings or provided with an expiry time. By deactivating cookies, however, some functions that are controlled by cookies can then only be used to a limited extent or not at all.</p> - <p>NOPAQUE uses cookies for the following purposes:</p> - <ul class="browser-default"> - <li>Recognition of a user during a session in order to assign personal content and other user-defined settings.</li> - <li>Login Script with ‘Remember Me’ feature allows the user to preserve their logged in status. When the user checks the Remember Me option, then the logged in status is serialized in the session and stored in cookies in an encrypted way.</li> - </ul> - <h6>Content Data</h6> - <p>The content data includes all data that is entered or created by users themselves in the system. This data is listed here because it is assigned to individual authors and may contain personal data. This may include: uploaded files, images, texts or other media files. Please note that files and scans submitted to NOPAQUE are stored in order to allow persistent access during a work session and between work sessions.</p> - <p>According to § 4 paragraph 2 of the General Terms of Use for the use of NOPAQUE at Bielefeld University, the users themselves are responsible for the content they post and must comply with the legal provisions of data protection. This includes in particular the deletion of personal data that may no longer be processed.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 1 Contact Details</span> + <p>Bielefeld University, a legal entity under public law established by the state of North Rhine-Westphalia (NRW), is responsible for processing the data. It is represented by its rector, Prof. Dr. Ing. Gerhard Sagerer.</p> + <h6>§ 1.1. Contact details of the data controller</h6> + <ul class="browser-default"> + <li>Data protection officer of the Faculty of History</li> + <li>Prof. Dr. Stefan Gorißen</li> + <li>Universitätsstraße 25</li> + <li>D-33615 Bielefeld</li> + <li>Phone: +49 521 / 106-3152</li> + <li>Email: + <a href="mailto:stefan.gorissen@uni-bielefeld.de">stefan.gorissen@uni-bielefeld.de</a> + </li> + <li>Web: + <a href="https://www.uni-bielefeld.de">https://www.uni-bielefeld.de</a> + </li> + </ul> + <h6>§ 1.2. Technical contact person</h6> + <ul class="browser-default"> + <li>Dr. Johanna Vompras</li> + <li>Email: + <a href="mailto:johanna.vompras@uni-bielefeld.de">johanna.vompras@uni-bielefeld.de</a> + </li> + <li>Web.: + <a href="https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html">https://www.uni-bielefeld.de/(en)/sfb1288/projekte/inf.html</a> + </li> + </ul> + <h6>§ 1.2. Contact details of the data protection officer</h6> + <ul class="browser-default"> + <li>The data protection officer responsible is:</li> + <li>Phone: +49 521 106-5225</li> + <li> + Email: <a href="mailto:datenschutzbeauftragte@uni-bielefeld.de">datenschutzbeauftragte@uni-bielefeld.de</a> + </li> + </ul> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 3 Legal basis of the data processing</span> - <p>The legal basis for the processing of personal data for user authentication is <b>Article 6 (1) letter e GDPR</b>. The processing is carried out within the framework of the fulfilment of the tasks of Bielefeld University in accordance with HG NRW (NRW Higher Education Act), if necessary in connection with an order of the university to be named or by a special law, e.g. University Statistics Act, State Civil Servants Act, Staff Representation Act, Equal Opportunities Act.</p> - <p>The collection of personal data for user authentication is based on the consent of the data subjects as stated in <b>Article 6 (1) letter a GDPR</b>. The legal basis for the transmission of personal data is <b>Article 6 (1) letter c GDPR</b>.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 2 General information on data processing and its purpose</span> + <p>We process the personal data of our users only to the extent necessary to provide a functioning website and its functionalities.</p> + <p>The following personal data is collected and stored within the system:</p> + <h6>Master Data</h6> + <p>Within the scope of user authentication the following personal data is collected and processed:</p> + <ul class="browser-default"> + <li>User name</li> + <li>E-Mail</li> + </ul> + <p>Registration of the user is required for the provision of certain content and services within nopaque.</p> + <h6>Protocol Data</h6> + <p>In general, when a website is visited, for technical reasons information is automatically sent from the browser to the server and stored there in access protocols. When using a web application, additional protocol data is also generated, which is necessary for tracking technical errors. This information includes:</p> + <ul class="browser-default"> + <li>IP address</li> + <li>User account</li> + <li>Complete HTTP request URL</li> + <li>HTTP action (e.g. GET: call up a page, POST: send form data)</li> + <li>Access status (HTTP status code)</li> + <li>data volume retrieved</li> + <li>Date and time of the action</li> + <li>User-Agent string</li> + </ul> + <p>Locally logged data will be used by the development team in order to debug and improve tools. This data can only be viewed by the technical administration and by the employees responsible for the nopaque platform. Data is stored for seven days to ensure proper technical operation and to find the cause of errors and is deleted <u>afterwards</u>.</p> + <p>Logged data may be used to understand how researchers are using the nopaque platform. To be able to use the data for research purposes, we reserve the right to store it in an anonymous and aggregated form for a longer period of time (up to two years after completion of the SFB 1288 INF project).</p> + <h6>Cookies</h6> + <p>Browsers store so-called cookies. Cookies are files that can be stored by the provider of a website in the directory of the browser program on the user's computer. These files contain text information and can be read again by the provider when the page is called up again. The provider can use these cookies, for example, to always deliver pages in the theme selected by the user.</p> + <p>The storage of cookies can be switched off in the browser settings or provided with an expiry time. By deactivating cookies, however, some functions that are controlled by cookies can then only be used to a limited extent or not at all.</p> + <p>NOPAQUE uses cookies for the following purposes:</p> + <ul class="browser-default"> + <li>Recognition of a user during a session in order to assign personal content and other user-defined settings.</li> + <li>Login Script with ‘Remember Me’ feature allows the user to preserve their logged in status. When the user checks the Remember Me option, then the logged in status is serialized in the session and stored in cookies in an encrypted way.</li> + </ul> + <h6>Content Data</h6> + <p>The content data includes all data that is entered or created by users themselves in the system. This data is listed here because it is assigned to individual authors and may contain personal data. This may include: uploaded files, images, texts or other media files. Please note that files and scans submitted to NOPAQUE are stored in order to allow persistent access during a work session and between work sessions.</p> + <p>According to § 4 paragraph 2 of the General Terms of Use for the use of NOPAQUE at Bielefeld University, the users themselves are responsible for the content they post and must comply with the legal provisions of data protection. This includes in particular the deletion of personal data that may no longer be processed.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 4 Data transmissions</span> - <p>Your personal data, which are processed by Bielefeld University for the purposes mentioned under 2. will not be transferred to third parties.</p> - <p>In individual cases, data may also be legally transmitted to third parties, for example, to law enforcement authorities for the investigation of criminal offences within the framework of the <b>Code of Criminal Procedure (StPO)</b>. If technical service providers are given access to personal data, this is done on the basis of a contract in accordance with <b>Article 28 GDPR</b>.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 3 Legal basis of the data processing</span> + <p>The legal basis for the processing of personal data for user authentication is <b>Article 6 (1) letter e GDPR</b>. The processing is carried out within the framework of the fulfilment of the tasks of Bielefeld University in accordance with HG NRW (NRW Higher Education Act), if necessary in connection with an order of the university to be named or by a special law, e.g. University Statistics Act, State Civil Servants Act, Staff Representation Act, Equal Opportunities Act.</p> + <p>The collection of personal data for user authentication is based on the consent of the data subjects as stated in <b>Article 6 (1) letter a GDPR</b>. The legal basis for the transmission of personal data is <b>Article 6 (1) letter c GDPR</b>.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 5 Duration of processing / data deletion</span> - <p>Data processed for user authentication are deleted immediately after account deletion.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 4 Data transmissions</span> + <p>Your personal data, which are processed by Bielefeld University for the purposes mentioned under 2. will not be transferred to third parties.</p> + <p>In individual cases, data may also be legally transmitted to third parties, for example, to law enforcement authorities for the investigation of criminal offences within the framework of the <b>Code of Criminal Procedure (StPO)</b>. If technical service providers are given access to personal data, this is done on the basis of a contract in accordance with <b>Article 28 GDPR</b>.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 6 Your rights as a data subject</span> - <p>As a data subject, you have certain rights under <b>GDPR</b> that you may assert at any time:</p> - <ul class="browser-default"> - <li>the right to access information about whether or not personal data concerning you is processed, and if so, what categories of data are being processed (<b>Article 15 GDPR</b>),</li> - <li>the right to demand the rectification or completion of data concerning you (<b>Article 16 GDPR</b>),</li> - <li>the right to erasure of your personal data in accordance with <b>Article 17 GDPR</b>,</li> - <li>the right to demand the restriction of the processing of your data per <b>Article 18 GDPR</b>,</li> - <li>the right to withdraw your consent at any time. The withdrawal of consent does not affect the lawfulness of the processing based on consent before its withdrawal (<b>Article 7 (3) GDPR</b>),</li> - <li>the right to object to the future processing of your data in accordance of <b>Article 21 GDPR</b>,</li> - <li>the right to receive personal data concerning you and your account in a structured, common and machine-readable format in accordance of <b>Article 20 GDPR</b>.</li> - </ul> - <p>In addition to the aforementioned rights, you have the right to lodge a complaint with the data protection supervisory authority (<b>Article 77 GDPR</b>); for example, the university is under the supervision of the</p> - <ul> - <li>North Rhine-Westphalia State Commissioner</li> - <li>for Data Protection and Freedom of Information</li> - <li>(Landesbeauftragte für Datenschutz und</li> - <li>Informationsfreiheit Nordrhein-Westfalen)</li> - <li>Kavalleriestraße 2-4</li> - <li>40213 Düsseldorf, German</li> - </ul> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 5 Duration of processing / data deletion</span> + <p>Data processed for user authentication are deleted immediately after account deletion.</p> + </div> + </div> + </div> + + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 6 Your rights as a data subject</span> + <p>As a data subject, you have certain rights under <b>GDPR</b> that you may assert at any time:</p> + <ul class="browser-default"> + <li>the right to access information about whether or not personal data concerning you is processed, and if so, what categories of data are being processed (<b>Article 15 GDPR</b>),</li> + <li>the right to demand the rectification or completion of data concerning you (<b>Article 16 GDPR</b>),</li> + <li>the right to erasure of your personal data in accordance with <b>Article 17 GDPR</b>,</li> + <li>the right to demand the restriction of the processing of your data per <b>Article 18 GDPR</b>,</li> + <li>the right to withdraw your consent at any time. The withdrawal of consent does not affect the lawfulness of the processing based on consent before its withdrawal (<b>Article 7 (3) GDPR</b>),</li> + <li>the right to object to the future processing of your data in accordance of <b>Article 21 GDPR</b>,</li> + <li>the right to receive personal data concerning you and your account in a structured, common and machine-readable format in accordance of <b>Article 20 GDPR</b>.</li> + </ul> + <p>In addition to the aforementioned rights, you have the right to lodge a complaint with the data protection supervisory authority (<b>Article 77 GDPR</b>); for example, the university is under the supervision of the</p> + <ul> + <li>North Rhine-Westphalia State Commissioner</li> + <li>for Data Protection and Freedom of Information</li> + <li>(Landesbeauftragte für Datenschutz und</li> + <li>Informationsfreiheit Nordrhein-Westfalen)</li> + <li>Kavalleriestraße 2-4</li> + <li>40213 Düsseldorf, German</li> + </ul> + </div> + </div> </div> </div> </div> - {% endblock %} diff --git a/web/app/templates/main/terms_of_use.html.j2 b/web/app/templates/main/terms_of_use.html.j2 index 9e3593f00e8aa10ab05325fb2a6242b0f258ec89..dbe39dc93b3e014a2d3cceb5410188cde8d2f312 100644 --- a/web/app/templates/main/terms_of_use.html.j2 +++ b/web/app/templates/main/terms_of_use.html.j2 @@ -1,105 +1,111 @@ {% extends "nopaque.html.j2" %} {% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12"> - <p>With the usage of the nopaque platform you declare your acceptance of the General Terms of Use and that you have taken note of the legal framework and the data protection declaration.</p> -</div> + <div class="col s12"> + <p>With the usage of the nopaque platform you declare your acceptance of the General Terms of Use and that you have taken note of the legal framework and the data protection declaration.</p> + </div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 1 Scope</span> - <p>The General Terms of Use for the nopaque platform apply to everyone who uses the system as an authorised user in the sense of <b>§ 2</b> (1) of the General Terms of Use. By using the system and with your consent you accept these terms of use.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 1 Scope</span> + <p>The General Terms of Use for the nopaque platform apply to everyone who uses the system as an authorised user in the sense of <b>§ 2</b> (1) of the General Terms of Use. By using the system and with your consent you accept these terms of use.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 2 Right of use</span> - <p>(1) The nopaque platform is available to users exclusively for the purposes of teaching and research. Any other use, especially for business, commercial is not permitted. The following groups shall be entitled to use the nopaque platform:</p> - <ul class="browser-default"> - <li>students, teaching staff and employees at Bielefeld University</li> - <li>external researchers from outside the University Bielefeld</li> - </ul> - <p> </p> - <p>(2) The use of the system is free of charge.</p> - <p> </p> - <p>(3) The duration of the right of use ends with the deletion of the user account by the user (see <b>§ 7</b>)</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 2 Right of use</span> + <p>(1) The nopaque platform is available to users exclusively for the purposes of teaching and research. Any other use, especially for business, commercial is not permitted. The following groups shall be entitled to use the nopaque platform:</p> + <ul class="browser-default"> + <li>students, teaching staff and employees at Bielefeld University</li> + <li>external researchers from outside the University Bielefeld</li> + </ul> + <p> </p> + <p>(2) The use of the system is free of charge.</p> + <p> </p> + <p>(3) The duration of the right of use ends with the deletion of the user account by the user (see <b>§ 7</b>)</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 3 Purpose of the Services</span> - <p>nopaque custom-built web application which serves as a platform for preprocessing and analysing digital copies of various text based research data (books, letters, etc.) in different files and formats. nopaque converts image data – like photos or scans – into text data through OCR making it machine readable. This step enables to proceed with further computational analysis of the documents. By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from texts.</p> - <p> </p> - <p>(1) Change of service</p> - <p>The provider of the nopaque platform is entitled to change and supplement the scope of functions of nopaque without prior notice. This could result from a thematic and scientific reorientation of the project.</p> - <p> </p> - <p>(2) Support</p> - <p>On nopaque, a contact form is available. As far as possible the SFB 1288 INF staff will try to provide user support.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 3 Purpose of the Services</span> + <p>nopaque custom-built web application which serves as a platform for preprocessing and analysing digital copies of various text based research data (books, letters, etc.) in different files and formats. nopaque converts image data – like photos or scans – into text data through OCR making it machine readable. This step enables to proceed with further computational analysis of the documents. By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from texts.</p> + <p> </p> + <p>(1) Change of service</p> + <p>The provider of the nopaque platform is entitled to change and supplement the scope of functions of nopaque without prior notice. This could result from a thematic and scientific reorientation of the project.</p> + <p> </p> + <p>(2) Support</p> + <p>On nopaque, a contact form is available. As far as possible the SFB 1288 INF staff will try to provide user support.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 4 Obligations of the User</span> - <p>(1) The system is suitable for normal security requirements. Data with a high need for protection (e.g. health data) may not be stored or processed in the nopaque platform.</p> - <p> </p> - <p>(2) Users of nopaque are responsible for their own entered contents. The uploading of illegal content, especially content that violates criminal, personal, data protection or copyright regulations (including § 60a) is not permitted.</p> - <p> </p> - <p>(3) Users undertake to indemnify Bielefeld University from all claims by third parties based on the data they use and to reimburse Bielefeld University for any costs incurred by the latter due to possible infringements of rights. This also includes the costs incurred by Bielefeld University in defending itself against such claims in and out of court.</p> - <p> </p> - <p>(4) Exclusion from use</p> - <p>Bielefeld University is entitled to immediately block access to the service if there are reasonable grounds to suspect that the stored data is unlawful (e.g upload harmful files via file upload) and/or violates the rights of third parties. Other infringements of the provisions of these Terms of Use, in particular the obligations under §6 also entitle Bielefeld University to block the user. Bielefeld University shall immediately notify the user of the block and the reason for the block. The block must be lifted as soon as the suspicion is invalidated.</p> - <p> </p> - <p>(5) Usage of Data</p> - <p>The data stored by the user on the storage space intended for him may be legally protected, the responsibility for the processing of the data from these points of view lies solely with the user. By using nopaque, the user grants Bielefeld the right to process the data with the corresponding tools. At all times during processing in nopaque, data remains in the user's private storage location and will not passed on to third parties.</p> - <p> </p> - <p>(6) Release of Bielefeld University from Third-Party Claims</p> - <p>The user is responsible for the data stored by him/her in nopaque. Furthermore he/she is responsible for entering and maintaining the data and information required to use nopaque.</p> - <p> </p> - <p>The user is obliged to indemnify Bielefeld University against all claims by third parties based on the data stored by him/her and to reimburse Bielefeld University for any costs incurred as a result of possible legal infringements. This also includes the costs incurred by Bielefeld University for extrajudicial and judicial defense against these claims.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 4 Obligations of the User</span> + <p>(1) The system is suitable for normal security requirements. Data with a high need for protection (e.g. health data) may not be stored or processed in the nopaque platform.</p> + <p> </p> + <p>(2) Users of nopaque are responsible for their own entered contents. The uploading of illegal content, especially content that violates criminal, personal, data protection or copyright regulations (including § 60a) is not permitted.</p> + <p> </p> + <p>(3) Users undertake to indemnify Bielefeld University from all claims by third parties based on the data they use and to reimburse Bielefeld University for any costs incurred by the latter due to possible infringements of rights. This also includes the costs incurred by Bielefeld University in defending itself against such claims in and out of court.</p> + <p> </p> + <p>(4) Exclusion from use</p> + <p>Bielefeld University is entitled to immediately block access to the service if there are reasonable grounds to suspect that the stored data is unlawful (e.g upload harmful files via file upload) and/or violates the rights of third parties. Other infringements of the provisions of these Terms of Use, in particular the obligations under §6 also entitle Bielefeld University to block the user. Bielefeld University shall immediately notify the user of the block and the reason for the block. The block must be lifted as soon as the suspicion is invalidated.</p> + <p> </p> + <p>(5) Usage of Data</p> + <p>The data stored by the user on the storage space intended for him may be legally protected, the responsibility for the processing of the data from these points of view lies solely with the user. By using nopaque, the user grants Bielefeld the right to process the data with the corresponding tools. At all times during processing in nopaque, data remains in the user's private storage location and will not passed on to third parties.</p> + <p> </p> + <p>(6) Release of Bielefeld University from Third-Party Claims</p> + <p>The user is responsible for the data stored by him/her in nopaque. Furthermore he/she is responsible for entering and maintaining the data and information required to use nopaque.</p> + <p> </p> + <p>The user is obliged to indemnify Bielefeld University against all claims by third parties based on the data stored by him/her and to reimburse Bielefeld University for any costs incurred as a result of possible legal infringements. This also includes the costs incurred by Bielefeld University for extrajudicial and judicial defense against these claims.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 5 Liability of Bielefeld University</span> - <p>Claims for damages against Bielefeld University are excluded irrespective of the legal grounds. Bielefeld University shall not be liable for loss of data and information or other „indirect“ damages, e.g. loss of profit, loss of production, or other indirect damages. Bielefeld University shall not be liable for the loss of data to the extent that the damage is due to the fact that the user has failed to back up the data and thereby ensure that lost data can be restored with justifiable effort.</p> - <p> </p> - <p>nopaque is available in accordance with normal operational care based on the "Best Effort" practice. No liability is assumed for the consequences of failures or errors of the nopaque platform. Bielefeld University does not guarantee that the systems will run error-free and without interruption at all times. Bielefeld University accepts no responsibility for technical quality. Nor is it liable for the content, in particular for the accuracy, completeness, and timeliness of information to which it merely provides access for use.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 5 Liability of Bielefeld University</span> + <p>Claims for damages against Bielefeld University are excluded irrespective of the legal grounds. Bielefeld University shall not be liable for loss of data and information or other „indirect“ damages, e.g. loss of profit, loss of production, or other indirect damages. Bielefeld University shall not be liable for the loss of data to the extent that the damage is due to the fact that the user has failed to back up the data and thereby ensure that lost data can be restored with justifiable effort.</p> + <p> </p> + <p>nopaque is available in accordance with normal operational care based on the "Best Effort" practice. No liability is assumed for the consequences of failures or errors of the nopaque platform. Bielefeld University does not guarantee that the systems will run error-free and without interruption at all times. Bielefeld University accepts no responsibility for technical quality. Nor is it liable for the content, in particular for the accuracy, completeness, and timeliness of information to which it merely provides access for use.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 6 Data Protection</span> - <p>Information on the handling of personal data during the operation of the service can be found in the separate data protection policy.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 6 Data Protection</span> + <p>Information on the handling of personal data during the operation of the service can be found in the separate data protection policy.</p> + </div> + </div> </div> - </div> -</div> -<div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">§ 7 Duration and Termination</span> - <p>The user may terminate the use nopaque by deleting his/her account at any time without giving reasons. After deletion of the account, all users‘ data will be automatically deleted and access to the service blocked. This does not affect the user's right to delete data under data protection law.</p> - <p> </p> - <p>Bielefeld University may exclude the user from using the service without notice for an important reason. Important reasons include, in particular, repeated violations of the provisions of these Terms of Use or of applicable laws.</p> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title">§ 7 Duration and Termination</span> + <p>The user may terminate the use nopaque by deleting his/her account at any time without giving reasons. After deletion of the account, all users‘ data will be automatically deleted and access to the service blocked. This does not affect the user's right to delete data under data protection law.</p> + <p> </p> + <p>Bielefeld University may exclude the user from using the service without notice for an important reason. Important reasons include, in particular, repeated violations of the provisions of these Terms of Use or of applicable laws.</p> + </div> + </div> </div> </div> </div> - {% endblock %} diff --git a/web/app/templates/materialize/base.html.j2 b/web/app/templates/materialize/base.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..e7d585e7f43d9654524158c1d4d5a2e97f57eb01 --- /dev/null +++ b/web/app/templates/materialize/base.html.j2 @@ -0,0 +1,45 @@ +{% block doc %} +<!DOCTYPE html> +<html{% block html_attribs %}{% endblock html_attribs %}> +{% block html %} + <head> + {% block head %} + <title>{% block title %}{{title|default}}{% endblock title %}</title> + + {% block metas %} + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + {% endblock metas %} + + {% block styles %} + <link href="{{ url_for('static', filename='css/material_design_icons.css') }}" rel="stylesheet"> + <link href="{{ url_for('static', filename='css/materialize.min.css') }}" media="screen,projection" rel="stylesheet"> + {% endblock styles %} + {% endblock head %} + </head> + <body{% block body_attribs %}{% endblock body_attribs %}> + {% block body %} + <header{% block header_attribs %}{% endblock header_attribs %}> + {% block header %} + {% block navbar %} + {% endblock navbar %} + {% block sidenav %} + {% endblock sidenav %} + {% endblock header %} + </header> + + <main{% block main_attribs %}{% endblock main_attribs %}> + {% block main %}{% endblock main %} + </main> + + <footer{% block footer_attribs %}{% endblock footer_attribs %}> + {% block footer %}{% endblock footer %} + </footer> + + {% block scripts %} + <script src="{{ url_for('static', filename='js/materialize.min.js') }}"></script> + {% endblock scripts %} + {% endblock body %} + </body> +{% endblock html %} +</html> +{% endblock doc %} diff --git a/web/app/templates/utils/materialize.html.j2 b/web/app/templates/materialize/wtf.html.j2 similarity index 75% rename from web/app/templates/utils/materialize.html.j2 rename to web/app/templates/materialize/wtf.html.j2 index fe5153b306b1eb66e5f9844e01e26bc4ff1e3a6c..428a4ee8ffd1da8120a0397067d2ae1cc47ce3e3 100644 --- a/web/app/templates/utils/materialize.html.j2 +++ b/web/app/templates/materialize/wtf.html.j2 @@ -1,30 +1,19 @@ {% macro render_field(field) %} - {% if field.flags.required and field.type not in ['FileField', 'MultipleFileField'] %} - {% if 'class_' in kwargs and 'validate' not in kwargs['class_'] %} - {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' validate'}) %} - {% else %} - {% set tmp = kwargs.update({'class_': 'validate'}) %} - {% endif %} - {% endif %} - {% if field.type == 'BooleanField' %} {{ render_boolean_field(field, *args, **kwargs) }} {% elif field.type == 'DecimalRangeField' %} {{ render_decimal_range_field(field, *args, **kwargs) }} - {% elif field.type == 'IntegerField' %} - {% set tmp = kwargs.update({'type': 'number'}) %} + {% elif field.type == 'SubmitField' %} + {{ render_submit_field(field, *args, **kwargs) }} + {% elif field.type in ['FileField', 'MultipleFileField'] %} + {{ render_file_field(field, *args, **kwargs) }} + {% else %} {% if 'class_' in kwargs and 'validate' not in kwargs['class_'] %} {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' validate'}) %} {% else %} {% set tmp = kwargs.update({'class_': 'validate'}) %} {% endif %} {{ render_generic_field(field, *args, **kwargs) }} - {% elif field.type == 'SubmitField' %} - {{ render_submit_field(field, *args, **kwargs) }} - {% elif field.type in ['FileField', 'MultipleFileField'] %} - {{ render_file_field(field, *args, **kwargs) }} - {% elif field.type in ['PasswordField', 'SelectField', 'StringField'] %} - {{ render_generic_field(field, *args, **kwargs) }} {% endif %} {% endmacro %} @@ -35,11 +24,11 @@ <i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i> {% endif %} <label> + {{ field(*args, **kwargs) }} + <span class="lever"></span> {% if label %} {{ field.label.text }} {% endif %} - {{ field(*args, **kwargs) }} - <span class="lever"></span> </label> {% for error in field.errors %} <span class="helper-text red-text">{{ error }}</span> @@ -57,10 +46,18 @@ <div class="file-path-wrapper"> <input class="file-path validate" type="text" placeholder="{{ placeholder }}"> </div> + {% for error in field.errors %} + <span class="helper-text red-text">{{ error }}</span> + {% endfor %} </div> {% endmacro %} {% macro render_generic_field(field) %} + {% if field.type == 'TextAreaField' and 'materialize-textarea' not in kwargs['class_'] %} + {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' materialize-textarea'}) %} + {% elif field.type == 'IntegerField' %} + {% set tmp = kwargs.update({'type': 'number'}) %} + {% endif %} {% set label = kwargs.pop('label', True) %} <div class="input-field"> {% if 'material_icon' in kwargs %} @@ -77,13 +74,24 @@ {% endmacro %} {% macro render_submit_field(field) %} - <button class="btn waves-effect waves-light" + {% if 'class_' in kwargs and 'btn' not in kwargs['class_'] %} + {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' btn'}) %} + {% else %} + {% set tmp = kwargs.update({'class_': 'btn'}) %} + {% endif %} + {% if 'waves-effect' not in kwargs['class_'] %} + {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' waves-effect'}) %} + {% endif %} + {% if 'waves-light' not in kwargs['class_'] %} + {% set tmp = kwargs.update({'class_': kwargs['class_'] + ' waves-light'}) %} + {% endif %} + <button class="{{ kwargs['class_'] }}" id="{{ field.id }}" name="{{ field.name }}" type="submit" value="{{ field.label.text }}" {% if 'style' in kwargs %} - style={{ kwargs.pop('style') }} + style="{{ kwargs.pop('style') }}" {% endif %}> {{ field.label.text }} {% if 'material_icon' in kwargs %} diff --git a/web/app/templates/modals/analysis_init.html.j2 b/web/app/templates/modals/analysis_init.html.j2 index b7e540260202b4c17815d771c13d9e005130e52c..b4f189cf31270f567f5e8d1a68433192b0079054 100644 --- a/web/app/templates/modals/analysis_init.html.j2 +++ b/web/app/templates/modals/analysis_init.html.j2 @@ -1,6 +1,4 @@ -<!-- Analysis init modal. User feedback showing that the analysis session is -loading. --> - +<!-- Analysis init modal. User feedback showing that the analysis session is loading. --> <div class="modal no-autoinit" id="analysis-init-modal"> <div class="modal-content"> <h4>Initializing your corpus analysis session...</h4> @@ -14,4 +12,4 @@ loading. --> </div> <p id="analysis-init-error" class="hide red-text"></p> </div> -</div> \ No newline at end of file +</div> diff --git a/web/app/templates/modals/context_modal.html.j2 b/web/app/templates/modals/context_modal.html.j2 index 636c907e42f0a9483d1ca5c304dbb7ff1345dd45..185a767a2b5758d4943520a9732ea7a9aa9ba41e 100644 --- a/web/app/templates/modals/context_modal.html.j2 +++ b/web/app/templates/modals/context_modal.html.j2 @@ -1,5 +1,4 @@ <!-- Modal showing detailed context info for one match. --> - <div id="context-modal" class="modal"> <div class="modal-content"> <form> @@ -73,4 +72,4 @@ </a> <a href="#!" class="modal-close waves-effect waves-light red btn">Close</a> </div> -</div> \ No newline at end of file +</div> diff --git a/web/app/templates/modals/export_query_results.html.j2 b/web/app/templates/modals/export_query_results.html.j2 index e49edb93e9183b632bbb0b7e599a1220f5b56d2e..cf544d5e8ea77e85ac7b064ad43065bf1697263f 100644 --- a/web/app/templates/modals/export_query_results.html.j2 +++ b/web/app/templates/modals/export_query_results.html.j2 @@ -1,6 +1,4 @@ -<!-- Export query results modal. Allos the user to download the results in -different file formats. WIP --> - +<!-- Export query results modal. Allos the user to download the results in different file formats. WIP --> <div id="query-results-download-modal" class="modal modal-fixed-footer no-autoinit"> <div class="modal-content"> @@ -51,4 +49,4 @@ different file formats. WIP --> <div class="modal-footer"> <a href="#!" class="modal-close waves-effect waves-light red btn">Close</a> </div> -</div> \ No newline at end of file +</div> diff --git a/web/app/templates/modals/show_corpus_files.html.j2 b/web/app/templates/modals/show_corpus_files.html.j2 index 6b960fda430c6aadb86585eab3d8f04396fbfe10..942c8130126068d9e91a73a49b9beec45ccf5186 100644 --- a/web/app/templates/modals/show_corpus_files.html.j2 +++ b/web/app/templates/modals/show_corpus_files.html.j2 @@ -1,6 +1,4 @@ -<!-- Modal showing the corpus files for the current query results including -title ant match count per corpus file. --> - +<!-- Modal showing the corpus files for the current query results including title ant match count per corpus file. --> <div id="show-corpus-files-modal" class="modal bottom-sheet"> <div class="container"> <div class="row"> diff --git a/web/app/templates/modals/show_metadata.html.j2 b/web/app/templates/modals/show_metadata.html.j2 index 863204b77357612c38d2f3acd8241b703e963810..e07d15fb579ff9572aa68ddb91bb4da2581cea1a 100644 --- a/web/app/templates/modals/show_metadata.html.j2 +++ b/web/app/templates/modals/show_metadata.html.j2 @@ -1,6 +1,4 @@ -<!-- Modal showing the meta data for the current query results or the imported -results --> - +<!-- Modal showing the meta data for the current query results or the imported results --> <div id="meta-data-modal" class="modal bottom-sheet"> <div class="container"> <div class="row"> diff --git a/web/app/templates/modals/show_text_details.html.j2 b/web/app/templates/modals/show_text_details.html.j2 index e3a7f18a0dfbacc5b2b51c1162ae264f511efe46..f246da538aca09eb09867f93823d11eeb272cb4c 100644 --- a/web/app/templates/modals/show_text_details.html.j2 +++ b/web/app/templates/modals/show_text_details.html.j2 @@ -1,6 +1,4 @@ -<!-- Modal to show all metadata of one text/corpus file. Used in conjunction -with the show_meta_data.html.j2 template.--> - +<!-- Modal to show all metadata of one text/corpus file. Used in conjunction with the show_meta_data.html.j2 template. --> <div id="modal-text-details" class="modal modal-fixed-footer"> <div class="modal-content"> <h4>Bibliographic data</h4> @@ -9,4 +7,4 @@ with the show_meta_data.html.j2 template.--> <div class="modal-footer"> <a href="#!" class="modal-close waves-effect waves-green red btn">Close</a> </div> -</div> \ No newline at end of file +</div> diff --git a/web/app/templates/nopaque.html.j2 b/web/app/templates/nopaque.html.j2 index 65853474dba563d28ddcae17c546bfb482494b84..b2e85dbe71894bb29b88599b4705d8b1edd88edc 100644 --- a/web/app/templates/nopaque.html.j2 +++ b/web/app/templates/nopaque.html.j2 @@ -1,300 +1,257 @@ -{% import "utils/macros.html.j2" as Macros %} -{% import "utils/materialize.html.j2" as M %} +{% extends "materialize/base.html.j2" %} +{% from '_colors.html.j2' import colors %} +{% block html_attribs %} lang="en"{% endblock html_attribs %} -{% if title is not defined %} - {% set title = None %} -{% endif %} +{% block head %} +{{ super() }} +<link href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}" rel="icon"> +{% endblock head %} -{% if headline is not defined %} - {% set headline = title %} -{% endif %} +{% block metas %} +<meta charset="UTF-8"> +{{ super() }} +{% endblock metas %} -{% if parallax is not defined %} - {% set parallax = False %} -{% endif %} +{% block title %}{{title}}{% endblock title %} -{% if main_class is not defined %} - {% set main_class = 'grey lighten-5' %} +{% block styles %} +{{ super() }} +{% if current_user.is_authenticated %} +<link href="{{ url_for('static', filename='css/materialize.sidenav-fixed.css') }}" media="screen,projection" rel="stylesheet"> {% endif %} +<link href="{{ url_for('static', filename='css/materialize.sticky-footer.css') }}" media="screen,projection" rel="stylesheet"> +<link href="{{ url_for('static', filename='css/nopaque.css') }}" media="screen,projection" rel="stylesheet"> +<style> + .primary-color {background-color: {{ colors.primary }} !important;} + .primary-color-text {color: {{ colors.primary }} !important;} + .secondary-color {background-color: {{ colors.secondary }} !important;} + .secondary-color-text {color: {{ colors.secondary }} !important;} + .footer-color {background-color: {{ colors.footer }} !important;} + .footer-color-text {color: {{ colors.footer }} !important;} -{% set primary_color = '#00426f' %} -{% set secondary_color = '#b1b3b4' %} - -{% set corpus_analysis_color = '#aa9cc9' %} -{% set corpus_analysis_color_darken = '#6b3f89' %} -{% set corpus_analysis_color_lighten = '#ebe8f6' %} - -{% set file_setup_color = '#d5dc95' %} -{% set file_setup_color_darken = '#a1b300' %} -{% set file_setup_color_lighten = '#f2f3e1' %} - -{% set nlp_color = '#98acd2' %} -{% set nlp_color_darken = '#0064a3' %} -{% set nlp_color_lighten = '#e5e8f5' %} - -{% set ocr_color = '#a9d8c8' %} -{% set ocr_color_darken = '#00a58b' %} -{% set ocr_color_lighten = '#e7f4f1' %} - -{%- macro insert_content() -%} - {% block page_content %}{% endblock %} -{%- endmacro -%} - + .corpus-analysis-color {background-color: {{ colors.corpus_analysis }} !important;} + .corpus-analysis-color-text {color: {{ colors.corpus_analysis }} !important;} + .corpus-analysis-color.darken {background-color: {{ colors.corpus_analysis_darken }} !important;} + .corpus-analysis-color-text.text-darken {color: {{ colors.corpus_analysis_darken }} !important;} + .corpus-analysis-color.lighten {background-color: {{ colors.corpus_analysis_lighten }} !important;} + .corpus-analysis-color-text.text-lighten {color: {{ colors.corpus_analysis_lighten }} !important;} -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <meta name="theme-color" content="#ee6e73"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title> - nopaque - {% if request.path != url_for('main.index') %} – {{ title }}{% endif %} - </title> - <link rel="icon" href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}"> - <!--Import Google Icon Font--> - <link rel="stylesheet" href="{{ url_for('static', filename='css/material_design_icons.min.css') }}"> - <!--Import materialize.css--> - <link rel="stylesheet" href="{{ url_for('static', filename='css/materialize.min.css') }}"> - <link rel="stylesheet" href="{{ url_for('static', filename='css/nopaque.css') }}"> - <style> - .primary-color {background-color: {{ primary_color }} !important;} - .primary-color-text {color: {{ primary_color }} !important;} - .secondary-color {background-color: {{ secondary_color }} !important;} - .secondary-color-text {color: {{ secondary_color }} !important;} + .file-setup-color {background-color: {{ colors.file_setup }} !important;} + .file-setup-color-text {color: {{ colors.file_setup }} !important;} + .file-setup-color.darken {background-color: {{ colors.file_setup_darken }} !important;} + .file-setup-color-text.text-darken {color: {{ colors.file_setup_darken }} !important;} + .file-setup-color.lighten {background-color: {{ colors.file_setup_lighten }} !important;} + .file-setup-color-text.text-lighten {color: {{ colors.file_setup_lighten }} !important;} - .corpus-analysis-color {background-color: {{ corpus_analysis_color }} !important;} - .corpus-analysis-color-text {color: {{ corpus_analysis_color }} !important;} - .corpus-analysis-color.darken {background-color: {{ corpus_analysis_color_darken }} !important;} - .corpus-analysis-color-text.text-darken {color: {{ corpus_analysis_color_darken }} !important;} - .corpus-analysis-color.lighten {background-color: {{ corpus_analysis_color_lighten }} !important;} - .corpus-analysis-color-text.text-lighten {color: {{ corpus_analysis_color_lighten }} !important;} + .ocr-color {background-color: {{ colors.ocr }} !important;} + .ocr-color-text {color: {{ colors.ocr }} !important;} + .ocr-color.darken {background-color: {{ colors.ocr_darken }} !important;} + .ocr-color-text.text-darken {color: {{ colors.ocr_darken }} !important;} + .ocr-color.lighten {background-color: {{ colors.ocr_lighten }} !important;} + .ocr-color-text.text-lighten {color: {{ colors.ocr_lighten }} !important;} - .file-setup-color {background-color: {{ file_setup_color }} !important;} - .file-setup-color-text {color: {{ file_setup_color }} !important;} - .file-setup-color.darken {background-color: {{ file_setup_color_darken }} !important;} - .file-setup-color-text.text-darken {color: {{ file_setup_color_darken }} !important;} - .file-setup-color.lighten {background-color: {{ file_setup_color_lighten }} !important;} - .file-setup-color-text.text-lighten {color: {{ file_setup_color_lighten }} !important;} + .nlp-color {background-color: {{ colors.nlp }} !important;} + .nlp-color-text {color: {{ colors.nlp }} !important;} + .nlp-color.darken {background-color: {{ colors.nlp_darken }} !important;} + .nlp-color-text.text-darken {color: {{ colors.nlp_darken }} !important;} + .nlp-color.lighten {background-color: {{ colors.nlp_lighten }} !important;} + .nlp-color-text.text-lighten {color: {{ colors.nlp_lighten }} !important;} - .ocr-color {background-color: {{ ocr_color }} !important;} - .ocr-color-text {color: {{ ocr_color }} !important;} - .ocr-color.darken {background-color: {{ ocr_color_darken }} !important;} - .ocr-color-text.text-darken {color: {{ ocr_color_darken }} !important;} - .ocr-color.lighten {background-color: {{ ocr_color_lighten }} !important;} - .ocr-color-text.text-lighten {color: {{ ocr_color_lighten }} !important;} + {% if scheme_primary_color is not defined %} + {% set scheme_primary_color = colors.primary %} + {% endif %} + {% if scheme_secondary_color is not defined %} + {% set scheme_secondary_color = colors.secondary %} + {% endif %} + main .btn, main .btn-small, main .btn-large, main .btn-floating {background-color: {{ scheme_primary_color }};} + main .btn:hover, main .btn-large:hover, main .btn-small:hover, main .btn-floating:hover {background-color: {{ scheme_secondary_color }};} + main .pagination li.active {background-color: {{ scheme_primary_color }};} + main .table-of-contents a.active {border-color: {{ scheme_primary_color }};} + main .tabs .tab a {color: inherit;} + main .tabs .tab a:hover {color: {{ scheme_primary_color }};} + main .tabs .tab a.active, .tabs .tab a:focus.active { + color: {{ scheme_primary_color }}; + background-color: {{ scheme_primary_color }}28; + } + main .tabs .indicator {background-color: {{ scheme_primary_color }};} +</style> +{% endblock styles %} - .nlp-color {background-color: {{ nlp_color }} !important;} - .nlp-color-text {color: {{ nlp_color }} !important;} - .nlp-color.darken {background-color: {{ nlp_color_darken }} !important;} - .nlp-color-text.text-darken {color: {{ nlp_color_darken }} !important;} - .nlp-color.lighten {background-color: {{ nlp_color_lighten }} !important;} - .nlp-color-text.text-lighten {color: {{ nlp_color_lighten }} !important;} - - .pagination li.active {background-color: {{ primary_color }};} - .table-of-contents a.active {border-color: {{ primary_color }};} - .tabs .tab a {color: inherit; /* Custom Text Color */} - .tabs .tab a:hover {color: {{ primary_color }}; /* Custom Color On Hover */} - .tabs .tab a.active, .tabs .tab a:focus.active { - color: {{ primary_color }}; /* Custom Text Color While Active */ - background-color: {{ primary_color }}28; /* Custom Background Color While Active */ - } - .tabs .indicator {background-color: {{ primary_color }}; /* Custom Color Of Indicator */} +{% block navbar %} +<div class="navbar-fixed"> + <nav class="nav-extended"> + <div class="nav-wrapper primary-color"> {% if current_user.is_authenticated %} - /* - * ### Start sidenav-fixed offset ### - * The sidenav-fixed class is used which causes the sidenav to be fixed and open - * on large screens and hides to the regular functionality on smaller screens. - * In order to prevent the sidenav to overlap the content, the content (in our - * case header, main and footer) gets an offset equal to the width of the - * sidenav. - */ - @media only screen and (min-width : 993px) { - header, main, footer {padding-left: 300px;} - .modal:not(.bottom-sheet) {left: 300px;} - .navbar-fixed > nav {width: calc(100% - 300px)} - } - /* ### End sidenav-fixed offset ### */ + <a href="#" data-target="sidenav" class="sidenav-trigger"><i class="material-icons">menu</i></a> {% endif %} - </style> - </head> - <body> - <header> - <div class="navbar-fixed"> - <nav class="nav-extended primary-color white-text"> - <div class="nav-wrapper"> - <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo_name_slogan.svg') }}" style="height: 128px; margin-top: -32px;"></a> - <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-large-only" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo.svg') }}" style="height: 128px; margin-top: -32px;"></a> - {% if current_user.is_authenticated %} - <a href="#" data-target="sidenav-main" class="sidenav-trigger"><i class="material-icons">menu</i></a> - {% endif %} - <ul class="right"> - {% if current_user.is_authenticated %} - <li> - <a id="nav-notifications" class="dropdown-trigger no-autoinit" href="{{ url_for('main.news') }}" data-target="nav-notifications-dropdown"> - <span class="hide-on-small-only">News</span> - <i class="material-icons right">notifications</i> - </a> - </li> - {% endif %} - <li> - <a id="nav-account" class="dropdown-trigger no-autoinit" href="#!" data-target="nav-account-dropdown"> - {% if current_user.is_authenticated %} - <span class="hide-on-small-only">{{ current_user.username }}</span><i class="material-icons right">account_circle</i> - {% else %} - <i class="material-icons">account_circle</i> - {% endif %} - </a> - </li> - </ul> - </div> - <div class="nav-content"> - <noscript> - <div class="card z-depth-0" style="background-color: inherit;"> - <div class="card-content"> - <span class="card-title">JavaScript is disabled</span> - <p> - You have JavaScript disabled. Nopaque uses javascript and - sockets to send data in realtime to you. For example showing - you the status of your jobs and your corpora. - Please activate JavaScript to make full use of nopaque.</p> - <p>Some services are still useable without Javascript.</p> - </div> - <div class="card-action"> - <a href="#">What services can I still use?</a> - <a href="#">What services and functions are not available?</a> - </div> - </div> - </noscript> - </div> - </nav> - </div> - - <!-- Dropdown menus for the navbar --> - <div id="nav-notifications-dropdown" class="dropdown-content"> - <li> - <a href="{{ url_for('main.news', _anchor='beta-launch') }}"><i class="material-icons">error_outline</i>nopaque's beta launch</a> - </li> - </div> - <ul id="nav-account-dropdown" class="dropdown-content"> + <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo_name_slogan.svg') }}" style="height: 128px; margin-top: -32px; margin-left: -32px;"></a> + <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-large-only" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo.svg') }}" style="height: 128px; margin-top: -32px; margin-left: -32px;"></a> + <ul class="right"> + <li class="hide-on-med-and-down{% if request.path == url_for('main.news') %} active{% endif %}"><a href="{{ url_for('main.news') }}"><i class="material-icons left">notifications</i>News</a></li> {% if current_user.is_authenticated %} - <li><a href="{{ url_for('profile.settings') }}"><i class="material-icons">settings</i>Settings</a></li> - <li><a href="{{ url_for('auth.logout') }}"><i class="material-icons">power_settings_new</i>Log out</a></li> + <li class="hide-on-med-and-down{% if request.path == url_for('main.dashboard') %} active{% endif %}"><a href="{{ url_for('main.dashboard') }}"><i class="material-icons left">dashboard</i>Dashboard</a></li> + <li class="hide-on-med-and-down"><a class="dropdown-trigger no-autoinit" data-target="nav-more-dropdown" href="#!" id="nav-more-dropdown-trigger"><i class="material-icons">more_vert</i></a></li> {% else %} - <li><a href="{{ url_for('auth.login') }}"><i class="material-icons">login</i>Log in</a></li> - <li><a href="{{ url_for('auth.register') }}"><i class="material-icons">assignment</i>Register</a></li> + <li{% if request.path == url_for('auth.register') %} class="active"{% endif %}><a href="{{ url_for('auth.register') }}"><i class="material-icons left">assignment</i>Register</a></li> + <li{% if request.path == url_for('auth.login') %} class="active"{% endif %}><a href="{{ url_for('auth.login') }}"><i class="material-icons left">login</i>Log in</a></li> {% endif %} </ul> - - <ul id="sidenav-main" class="sidenav sidenav-fixed{% if not current_user.is_authenticated %} hide{% endif %}"> - <li><a href="{{ url_for('main.index') }}"><i class="material-icons">opacity</i>nopaque</a></li> - <li><a href="#"><i class="material-icons">linear_scale</i>Workflow</a></li> - <li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li> - <li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="material-icons">book</i>My Corpora</a></li> - <li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="material-icons">work</i>My Jobs</a></li> - <li><div class="divider"></div></li> - <li><a class="subheader">Processes & Services</a></li> - <li style="background-color: {{ file_setup_color }}; border-left: 10px solid {{ file_setup_color_darken }};"><a href="{{ url_for('services.service', service='file-setup') }}"><i class="material-icons">burst_mode</i>File setup</a></li> - <li style="background-color: {{ ocr_color }}; border-left: 10px solid {{ ocr_color_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li> - <li style="background-color: {{ nlp_color }}; border-left: 10px solid {{ nlp_color_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li> - <li style="background-color: {{ corpus_analysis_color }}; border-left: 10px solid {{ corpus_analysis_color_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus_analysis') }}"><i class="material-icons">search</i>Corpus analysis</a></li> - <li><div class="divider"></div></li> - <li><a class="subheader">Account</a></li> - {% if current_user.is_authenticated %} - <li><a href="{{ url_for('profile.settings') }}"><i class="material-icons">settings</i>Settings</a></li> - <li><a href="{{ url_for('auth.logout') }}"><i class="material-icons">power_settings_new</i>Log out</a></li> + </div> + <div class="nav-content secondary-color"> + {% block nav_content %} + {% if current_user.is_authenticated %} + <ul class="tabs tabs-transparent"> + <li class="tab disabled"><a><i class="material-icons left">explore</i>Roadmap</a></li> + <li class="tab"><a{%if request.path == url_for('services.service', service='file-setup') %} class="active"{% endif %} href="{{ url_for('services.service', service='file-setup') }}" target="_self">File setup</a></li> + <li class="tab disabled"><i class="material-icons">navigate_next</i></li> + <li class="tab"><a{%if request.path == url_for('services.service', service='ocr') %} class="active"{% endif %} href="{{ url_for('services.service', service='ocr') }}" target="_self">OCR</a></li> + <li class="tab disabled"><i class="material-icons">navigate_next</i></li> + <li class="tab"><a{%if request.path == url_for('services.service', service='nlp') %} class="active"{% endif %} href="{{ url_for('services.service', service='nlp') }}" target="_self">NLP</a></li> + <li class="tab disabled"><i class="material-icons">navigate_next</i></li> + <li class="tab"><a{%if request.path == url_for('corpora.add_corpus') %} class="active"{% endif %} href="{{ url_for('corpora.add_corpus') }}" target="_self">Add corpus</a></li> + <li class="tab disabled"><i class="material-icons">navigate_next</i></li> + {% if corpus %} + <li class="tab"><a{%if request.path == url_for('corpora.add_corpus_file', corpus_id=corpus.id) %} class="active"{% endif %} href="{{ url_for('corpora.add_corpus_file', corpus_id=corpus.id) }}" target="_self">Add corpus file(s)</a></li> {% else %} - <li><a href="{{ url_for('auth.login') }}"><i class="material-icons">person</i>Log in</a></li> - <li><a href="{{ url_for('auth.register') }}"><i class="material-icons">person_add</i>Register</a></li> + <li class="tab disabled tooltipped" data-tooltip="Select a corpus first"><a>Add corpus file(s)</a></li> {% endif %} - {% if current_user.is_administrator() %} - <li><div class="divider"></div></li> - <li><a class="subheader">Administration</a></li> - <li><a href="{{ url_for('admin.index') }}"><i class="material-icons">build</i>Administration tools</a></li> + <li class="tab disabled"><i class="material-icons">navigate_next</i></li> + {% if corpus %} + {% if corpus.files.all() %} + <li class="tab"><a{%if request.path == url_for('corpora.analyse_corpus', corpus_id=corpus.id) %} class="active"{% endif %} href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}" target="_self">Corpus analysis</a></li> + {% else %} + <li class="tab disabled tooltipped" data-tooltip="Add at least one corpus file first"><a>Corpus analysis</a></li> + {% endif %} + {% else %} + <li class="tab disabled tooltipped" data-tooltip="Select a corpus first"><a>Corpus analysis</a></li> {% endif %} </ul> - </header> + {% endif %} + {% endblock nav_content %} + </div> + </nav> +</div> - {% if parallax %} - <main> - {{ insert_content() }} - </main> - {% else %} - <main class="{{ main_class }}"> - {% if not full_width %} - <div class="container"> - {% endif %} - <div class="row"> - <div class="col s12" id="headline"> - <h2>{{ headline }}</h2> - </div> - {{ insert_content() }} - </div> - {% if not full_width %} - </div> - {% endif %} - </div> - {% endif %} - </main> +{% if current_user.is_authenticated %} +<ul class="dropdown-content" id="nav-more-dropdown"> + <li><a href="{{ url_for('settings.index') }}"><i class="material-icons left">settings</i>Settings</a></li> + <li class="divider" tabindex="-1"></li> + <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> +</ul> +{% endif %} +{% endblock navbar %} + +{% block sidenav %} +<ul class="sidenav sidenav-fixed{% if not current_user.is_authenticated %} hide{% endif %}"> + {% if current_user.is_authenticated %} + <li> + <div class="user-view"> + <div class="background primary-color"></div> + <span class="white-text name">{{ current_user.username }}</span> + <span class="white-text email">{{ current_user.email }}</span> + </div> + </li> + <li><a href="{{ url_for('main.index') }}">nopaque</a></li> + <li><a href="#"><i class="material-icons">linear_scale</i>Workflow</a></li> + <li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li> + <li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="material-icons">book</i>My Corpora</a></li> + <li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="material-icons">work</i>My Jobs</a></li> + <li><div class="divider"></div></li> + <li><a class="subheader">Processes & Services</a></li> + <li style="background-color: {{ colors.file_setup }}; border-left: 10px solid {{ colors.file_setup_darken }};"><a href="{{ url_for('services.service', service='file-setup') }}"><i class="material-icons">burst_mode</i>File setup</a></li> + <li style="background-color: {{ colors.ocr }}; border-left: 10px solid {{ colors.ocr_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li> + <li style="background-color: {{ colors.nlp }}; border-left: 10px solid {{ colors.nlp_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li> + <li style="background-color: {{ colors.corpus_analysis }}; border-left: 10px solid {{ colors.corpus_analysis_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus_analysis') }}"><i class="material-icons">search</i>Corpus analysis</a></li> + <li><div class="divider"></div></li> + <li><a class="subheader">Account</a></li> + <li><a href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a></li> + <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> + {% else %} + <li><a href="{{ url_for('auth.register') }}"><i class="material-icons">assignment</i>Register</a></li> + <li><a href="{{ url_for('auth.login') }}"><i class="material-icons">login</i>Log in</a></li> + {% endif %} + {% if current_user.is_administrator() %} + <li><div class="divider"></div></li> + <li><a class="subheader">Administration</a></li> + <li><a href="{{ url_for('admin.users') }}"><i class="material-icons">build</i>Administration tools</a></li> + {% endif %} +</ul> +{% endblock sidenav %} - <footer class="page-footer secondary-color white-text"> - <div class="container"> - <div class="row"> - <div class="col s6 m3"> - <a href="https://www.dfg.de/"> - <img class="responsive-img" src="{{ url_for('static', filename='images/logo_-_dfg.gif') }}"> - </a> - </div> - <div class="col s6 m3 offset-m1 center-align"> - <a href="https://www.uni-bielefeld.de/sfb1288/"> - <img class="responsive-img" src="{{ url_for('static', filename='images/logo_-_sfb_1288.png') }}"> - </a> - </div> - <div class="col s12 m3 offset-m1"> - <h5 class="white-text">Legal Notice</h5> - <ul> - <li><a class="grey-text text-lighten-3" href="https://www.uni-bielefeld.de/(en)/impressum/">Legal Notice</a></li> - <li><a class="grey-text text-lighten-3" href="{{ url_for('main.privacy_policy') }}">Privacy statement (GDPR)</a></li> - <li><a class="grey-text text-lighten-3" href="{{ url_for('main.terms_of_use') }}">Terms of use</a></li> - <li></li> - </ul> - </div> - </div> +{% block main %} + {% block page_content %}{% endblock page_content %} +{% endblock main %} + +{% block footer_attribs %} class="page-footer footer-color"{% endblock footer_attribs %} + +{% block footer %} +<div class="container"> + <div class="row"> + <div class="col s6 m3"> + <a href="https://www.dfg.de/"> + <img class="responsive-img" src="{{ url_for('static', filename='images/logo_-_dfg.gif') }}"> + </a> + </div> + <div class="col s6 m3 offset-m1 center-align"> + <a href="https://www.uni-bielefeld.de/sfb1288/"> + <img class="responsive-img" src="{{ url_for('static', filename='images/logo_-_sfb_1288.png') }}"> + </a> + </div> + <div class="col s12 m3 offset-m1"> + <h5 class="white-text">Legal Notice</h5> + <ul> + <li><a class="grey-text text-lighten-3" href="https://www.uni-bielefeld.de/(en)/impressum/">Legal Notice</a></li> + <li><a class="grey-text text-lighten-3" href="{{ url_for('main.privacy_policy') }}">Privacy statement (GDPR)</a></li> + <li><a class="grey-text text-lighten-3" href="{{ url_for('main.terms_of_use') }}">Terms of use</a></li> + <li></li> + </ul> + </div> + </div> +</div> +<div class="footer-copyright primary-color"> + <div class="container"> + <div class="row" style="margin-bottom: 0;"> + <div class="col s12 m3"> + <span>© 2020 Bielefeld University</span> </div> - <div class="footer-copyright primary-color white-text"> - <div class="container"> - <div class="row" style="margin-bottom: 0;"> - <div class="col s12 m3"> - <span>© 2020 Bielefeld University</span> - </div> - <div class="col s12 m9 right-align"> - <a class="btn-small blue waves-effect waves-light" href="{{ url_for('main.about_and_faq') }}"><i class="left material-icons">info_outline</i>About and faq</a> - <a class="btn-small pink waves-effect waves-light" href="mailto:{{ config.CONTACT_EMAIL_ADRESS }}?subject={{ config.NOPAQUE_MAIL_SUBJECT_PREFIX }} Contact"><i class="left material-icons">rate_review</i>Contact</a> - <a class="btn-small green waves-effect waves-light" href="mailto:{{ config.CONTACT_EMAIL_ADRESS }}?subject={{ config.NOPAQUE_MAIL_SUBJECT_PREFIX }} Feedback"><i class="left material-icons">feedback</i>Feedback</a> - <a class="btn-small orange waves-effect waves-light" href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/opaque"><i class="left material-icons">code</i>GitLab</a> - </div> - </div> - </div> + <div class="col s12 m9 right-align"> + <a class="btn-small blue waves-effect waves-light" href="{{ url_for('main.about_and_faq') }}"><i class="left material-icons">info_outline</i>About and faq</a> + {% if config.CONTACT_EMAIL_ADRESS %} + <a class="btn-small pink waves-effect waves-light" href="mailto:{{ config.CONTACT_EMAIL_ADRESS }}?subject=[nopaque] Contact"><i class="left material-icons">rate_review</i>Contact</a> + <a class="btn-small green waves-effect waves-light" href="mailto:{{ config.CONTACT_EMAIL_ADRESS }}?subject=[nopaque] Feedback"><i class="left material-icons">feedback</i>Feedback</a> + {% endif %} + <a class="btn-small orange waves-effect waves-light" href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque"><i class="left material-icons">code</i>GitLab</a> </div> - </footer> - <script src="{{ url_for('static', filename='js/darkreader.js') }}"></script> - <script src="{{ url_for('static', filename='js/jsonpatch.min.js') }}"></script> - <script src="{{ url_for('static', filename='js/list.min.js') }}"></script> - <script src="{{ url_for('static', filename='js/materialize.min.js') }}"></script> - <script src="{{ url_for('static', filename='js/socket.io.slim.js') }}"></script> - <script src="{{ url_for('static', filename='js/nopaque.js') }}"></script> - <script> - {% if current_user.is_authenticated %} - {% if current_user.setting_dark_mode %} - DarkReader.enable({brightness: 150, contrast: 100, sepia: 0}); - {% endif %} - document.addEventListener('DOMContentLoaded', () => { - nopaque.socket.init(); - nopaque.socket.emit('user_data_stream_init'); - }); - {% endif %} - nopaque.flashedMessages = {{ get_flashed_messages(with_categories=True)|tojson }}; - </script> - </body> -</html> + </div> + </div> +</div> +{% endblock footer %} + +{% block scripts %} +{{ super() }} +<script src="{{ url_for('static', filename='js/darkreader.js') }}"></script> +<script src="{{ url_for('static', filename='js/jsonpatch.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/list.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/socket.io.slim.js') }}"></script> +<script src="{{ url_for('static', filename='js/nopaque.js') }}"></script> +<script> + {% if current_user.setting_dark_mode %} + DarkReader.enable({brightness: 150, contrast: 100, sepia: 0}); + {% endif %} + // Disable all option elements with no value + for (let optionElement of document.querySelectorAll('option[value=""]')) { + optionElement.disabled = true; + } + M.AutoInit(); + M.CharacterCounter.init(document.querySelectorAll('input[data-length][type="email"], input[data-length][type="password"], input[data-length][type="text"], textarea[data-length]')); + M.Dropdown.init(document.querySelectorAll('#nav-more-dropdown-trigger'), {alignment: 'right', constrainWidth: false, coverTrigger: false}); + nopaque.Forms.init(); + {% if current_user.is_authenticated %} + nopaque.socket.emit('user_data_stream_init'); + {% endif %} + for (let flashedMessage of {{ get_flashed_messages(with_categories=True)|tojson }}) { + nopaque.flash(flashedMessage[1], flashedMessage[0]); + } +</script> +{% endblock scripts %} diff --git a/web/app/templates/profile/settings.html.j2 b/web/app/templates/profile/settings.html.j2 deleted file mode 100644 index 64e5152b3e729b7af10056fb4d567d809cfe430e..0000000000000000000000000000000000000000 --- a/web/app/templates/profile/settings.html.j2 +++ /dev/null @@ -1,136 +0,0 @@ -{% extends "nopaque.html.j2" %} - -{% block page_content %} -<div class="col s12 m4"> - <h3>General settings</h3> -</div> -<div class="col s12 m8"> - <br class="hide-on-small-only"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ edit_general_settings_form.hidden_tag() }} - <div class="row"> - <div class="col s9"> - <p><i class="material-icons left">brightness_3</i>{{ edit_general_settings_form.dark_mode.label.text }}</p> - <p class="light">Activate dark mode to ease your eyes.</p> - </div> - <div class="col s3 right-align"> - {{ M.render_field(edit_general_settings_form.dark_mode, label=False) }} - </div> - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - <div class="col s12 m8"> - <p><i class="material-icons left">notifications</i>Job status site notifications</p> - <p class="light">Receive site notifications about job status changes.</p> - </div> - <div class="col s12 m4 right-align" style="margin-top: -1rem;"> - {{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }} - </div> - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - <div class="col s12 m8"> - <p><i class="material-icons left">notifications</i>Job status mail notifications</p> - <p class="light">Receive mail notifications about job status changes.</p> - </div> - <div class="col s12 m4 right-align" style="margin-top: -1rem;"> - {{ M.render_field(edit_general_settings_form.job_status_mail_notifications, label=False) }} - </div> - <!-- - Seperate each setting with the following two elements - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - --> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(edit_general_settings_form.save_settings, material_icon='send') }} - </div> - </form> - </div> -</div> - - -<div class="col s12"></div> - - -<div class="col s12 m4"> - <h3>Change password</h3> -</div> -<div class="col s12 m8"> - <br class="hide-on-small-only"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ edit_password_form.hidden_tag() }} - {{ M.render_field(edit_password_form.current_password, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(edit_password_form.password, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(edit_password_form.password_confirmation, data_length='128', material_icon='vpn_key') }} - </div> - <div class="card-action right-align"> - {{ M.render_field(edit_password_form.save_password, material_icon='send') }} - </div> - </form> - </div> -</div> - - -<div class="col s12"></div> - - -<div class="col s12 m4"> - <h3>Change email</h3> -</div> -<div class="col s12 m8"> - <br class="hide-on-small-only"> - <div class="card"> - <form method="POST"> - <div class="card-content"> - {{ edit_email_form.hidden_tag() }} - {{ M.render_field(edit_email_form.email, class_='validate', material_icon='email', type='email') }} - </div> - <div class="card-action right-align"> - {{ M.render_field(edit_email_form.save_email, material_icon='send') }} - </div> - </form> - </div> -</div> - - -<div class="col s12"></div> - - -<div class="col s12 m4"> - <h3>Delete account</h3> -</div> -<div class="col s12 m8"> - <br class="hide-on-small-only"> - <div class="card"> - <div class="card-content"> - <p>Deleting an account has the following effects:</p> - <ul> - <li>All data associated with your corpora and jobs will be permanently deleted.</li> - <li>All settings will be permanently deleted.</li> - </ul> - </div> - <div class="card-action right-align"> - <a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> - </div> - </div> -</div> - - -<!-- Modals --> -<div class="modal" id="delete-account-modal"> - <div class="modal-content"> - <h4>Confirm deletion</h4> - <p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p> - </div> - <div class="modal-footer"> - <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a> - <a href="{{ url_for('profile.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> - </div> -</div> -{% endblock %} diff --git a/web/app/templates/services/corpus_analysis.html.j2 b/web/app/templates/services/corpus_analysis.html.j2 index e50cab777d15b85c8fbda47d881a43a46c1be4e0..906e25863a715dd40683840c8beeb2c38bf6a7a6 100644 --- a/web/app/templates/services/corpus_analysis.html.j2 +++ b/web/app/templates/services/corpus_analysis.html.j2 @@ -1,99 +1,111 @@ {% extends "nopaque.html.j2" %} +{% from '_colors.html.j2' import colors %} -{% set main_class = 'corpus-analysis-color lighten' %} +{% set scheme_primary_color = colors.corpus_analysis_darken %} +{% set scheme_secondary_color = colors.corpus_analysis %} -{% block page_content %} -{{ Macros.insert_color_scheme(corpus_analysis_color_darken) }} - -<div class="col s12 m3 push-m9"> - <div class="center-align"> - <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="corpus-analysis"></i> - </a> - </div> -</div> +{% block main_attribs %} class="corpus-analysis-color lighten"{% endblock main_attribs %} -<div class="col s12 m9 pull-m3"> - <p>{{ rgb }}</p> - <p>Nopaque lets you create and upload as many text corpora as you want. It makes use of CQP Query Language, which allows for complex search requests with the aid of metadata and NLP tags. The results can either be displayed as text or abstract visualizations.</p> -</div> +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12"> - <h3>My Corpora</h3> - <div class="card"> - <div class="card-content" id="corpora"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-corpus" class="search" type="search"></input> - <label for="search-corpus">Search corpus</label> + <div class="col s12 m3 push-m9"> + <div class="center-align"> + <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="corpus-analysis"></i> + </a> </div> - <ul class="pagination paginationTop"></ul> - <table> - <thead> - <tr> - <th></th> - <th> - <span class="sort" data-sort="title">Title</span> - <span class="sort" data-sort="description">Description</span> - </th> - <th><span class="sort" data-sort="status">Status</span></th> - <th></th> - </tr> - </thead> - <tbody class="list"></tbody> - </table> - <ul class="pagination paginationBottom"></ul> </div> - <div class="card-action right-align"> - <a class="btn corpus-analysis-color darken waves-effect waves-light" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> + + <div class="col s12 m9 pull-m3"> + <p>Nopaque lets you create and upload as many text corpora as you want. It makes use of CQP Query Language, which allows for complex search requests with the aid of metadata and NLP tags. The results can either be displayed as text or abstract visualizations.</p> </div> - </div> -</div> -<div class="col s12"> - <h3>My query results</h3> - <div class="card"> - <div class="card-content" id="query-results"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="search-query-results" class="search" type="search"></input> - <label for="search-query-results">Search query result</label> + <div class="col s12"> + <h2>My Corpora</h2> + <div class="card"> + <div class="card-content" id="corpora"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-corpus" class="search" type="search"></input> + <label for="search-corpus">Search corpus</label> + </div> + <ul class="pagination paginationTop"></ul> + <table> + <thead> + <tr> + <th></th> + <th> + <span class="sort" data-sort="title">Title</span> + <span class="sort" data-sort="description">Description</span> + </th> + <th><span class="sort" data-sort="status">Status</span></th> + <th></th> + </tr> + </thead> + <tbody class="list"></tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + <div class="card-action right-align"> + <a class="btn waves-effect waves-light" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> + </div> </div> - <ul class="pagination paginationTop"></ul> - <table class="highlight responsive-table"> - <thead> - <tr> - <th> - <span class="sort" data-sort="title">Title</span> and<br> - <span class="sort" data-sort="description">Description</span> - </th> - <th> - <span class="sort" data-sort="corpus">Corpus</span> and<br> - <span class="sort" data-sort="query">Query</span> - </th> - <th>{# Actions #}</th> - </tr> - </thead> - <tbody class="list"> - <tr class="show-if-only-child"> - <td colspan="5"> - <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> - <p>No query results yet imported.</p> - </td> - </tr> - </tbody> - </table> - <ul class="pagination paginationBottom"></ul> </div> - <div class="card-action right-align"> - <a class="btn corpus-analysis-color darken waves-effect waves-light" href="{{ url_for('corpora.add_query_result') }}">Add query result<i class="material-icons right">file_upload</i></a> + + <div class="col s12"> + <h2>My query results</h2> + <div class="card"> + <div class="card-content" id="query-results"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-query-results" class="search" type="search"></input> + <label for="search-query-results">Search query result</label> + </div> + <ul class="pagination paginationTop"></ul> + <table class="highlight responsive-table"> + <thead> + <tr> + <th> + <span class="sort" data-sort="title">Title</span> and<br> + <span class="sort" data-sort="description">Description</span> + </th> + <th> + <span class="sort" data-sort="corpus">Corpus</span> and<br> + <span class="sort" data-sort="query">Query</span> + </th> + <th>{# Actions #}</th> + </tr> + </thead> + <tbody class="list"> + <tr class="show-if-only-child"> + <td colspan="5"> + <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> + <p>No query results yet imported.</p> + </td> + </tr> + </tbody> + </table> + <ul class="pagination paginationBottom"></ul> + </div> + <div class="card-action right-align"> + <a class="btn waves-effect waves-light" href="{{ url_for('corpora.add_query_result') }}">Add query result<i class="material-icons right">file_upload</i></a> + </div> + </div> </div> </div> </div> +{% endblock %} +{% block scripts %} +{{ super() }} <script type="module"> import {RessourceList} from '../../static/js/nopaque.lists.js'; let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus"); let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult"); </script> -{% endblock %} +{% endblock scripts %} diff --git a/web/app/templates/services/file-setup.html.j2 b/web/app/templates/services/file-setup.html.j2 index 475a00d6a95b85b301d2feedd1c486bf2d97ca5f..48ac3817df9b6faa1ab1475ec2706303299259f1 100644 --- a/web/app/templates/services/file-setup.html.j2 +++ b/web/app/templates/services/file-setup.html.j2 @@ -1,60 +1,71 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} +{% from '_colors.html.j2' import colors %} -{% set main_class = 'file-setup-color lighten' %} +{% set scheme_primary_color = colors.file_setup_darken %} +{% set scheme_secondary_color = colors.file_setup %} + +{% block main_attribs %} class="file-setup-color lighten"{% endblock main_attribs %} {% block page_content %} -{{ Macros.insert_color_scheme(file_setup_color_darken) }} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m3 push-m9"> - <div class="center-align"> - <p class="hide-on-small-only"> </p> - <p class="hide-on-small-only"> </p> - <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="file-setup"></i> - </a> - </div> -</div> + <div class="col s12 m3 push-m9"> + <div class="center-align"> + <p class="hide-on-small-only"> </p> + <p class="hide-on-small-only"> </p> + <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="file-setup"></i> + </a> + </div> + </div> -<div class="col s12 m9 pull-m3"> - <div class="card" style="border-top: 10px solid {{ file_setup_color_darken }};"> - <div class="card-content"> - <div class="row"> - <div class="col s12"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>Merge process</span> - <p>nopaque converts and merges your files in its cloud infrastructure to facilitate further processing and the application of other services. You only have to number your files in the correct order to receive a PDF file.</p> + <div class="col s12 m9 pull-m3"> + <div class="card" style="border-top: 10px solid {{ colors.file_setup_darken }};"> + <div class="card-content"> + <div class="row"> + <div class="col s12"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>Merge process</span> + <p>nopaque converts and merges your files in its cloud infrastructure to facilitate further processing and the application of other services. You only have to number your files in the correct order to receive a PDF file.</p> + </div> + </div> </div> </div> </div> </div> - </div> -</div> -<div class="col s12"> - <h3>Submit a job</h3> - <div class="card"> - <form class="nopaque-submit-form" data-progress-modal="progress-modal"> - <div class="card-content"> - {{ add_job_form.hidden_tag() }} - <div class="row"> - <div class="col s12 l4"> - {{ M.render_field(add_job_form.title, data_length='32', material_icon='title') }} - </div> - <div class="col s12 l8"> - {{ M.render_field(add_job_form.description, data_length='255', material_icon='description') }} - </div> - <div class="col s12"> - {{ M.render_field(add_job_form.files, accept='image/jpeg, image/png, image/tiff', placeholder='Choose your .jpeg, .png or .tiff files') }} + <div class="col s12"> + <h2>Submit a job</h2> + <div class="card"> + <form class="nopaque-submit-form" data-progress-modal="progress-modal"> + <div class="card-content"> + {{ add_job_form.hidden_tag() }} + <div class="row"> + <div class="col s12 l4"> + {{ wtf.render_field(add_job_form.title, data_length='32', material_icon='title') }} + </div> + <div class="col s12 l8"> + {{ wtf.render_field(add_job_form.description, data_length='255', material_icon='description') }} + </div> + <div class="col s12"> + {{ wtf.render_field(add_job_form.files, accept='image/jpeg, image/png, image/tiff', placeholder='Choose your .jpeg, .png or .tiff files') }} + </div> + <div class="col s12 hide"> + {{ wtf.render_field(add_job_form.version, material_icon='apps') }} + </div> + </div> </div> - <div class="col s12 hide"> - {{ M.render_field(add_job_form.version, material_icon='apps') }} + <div class="card-action right-align"> + {{ wtf.render_field(add_job_form.submit, material_icon='send') }} </div> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_job_form.submit, material_icon='send') }} + </form> </div> - </form> + </div> </div> </div> diff --git a/web/app/templates/services/nlp.html.j2 b/web/app/templates/services/nlp.html.j2 index de1380ce3e9ed34e78985bf3b98a53643765ba7c..8cb70d2882015c737b97862b8abf9cd15ed2a1d4 100644 --- a/web/app/templates/services/nlp.html.j2 +++ b/web/app/templates/services/nlp.html.j2 @@ -1,102 +1,113 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} +{% from '_colors.html.j2' import colors %} -{% set main_class = 'nlp-color lighten' %} +{% set scheme_primary_color = colors.nlp_darken %} +{% set scheme_secondary_color = colors.nlp %} + +{% block main_attribs %} class="nlp-color lighten"{% endblock main_attribs %} {% block page_content %} -{{ Macros.insert_color_scheme(nlp_color_darken) }} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m3 push-m9"> - <div class="center-align"> - <p class="hide-on-small-only"> </p> - <p class="hide-on-small-only"> </p> - <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="nlp"></i> - </a> - </div> -</div> + <div class="col s12 m3 push-m9"> + <div class="center-align"> + <p class="hide-on-small-only"> </p> + <p class="hide-on-small-only"> </p> + <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="nlp"></i> + </a> + </div> + </div> -<div class="col s12 m9 pull-m3"> - <div class="card" style="border-top: 10px solid {{ nlp_color_darken }};"> - <div class="card-content"> - <div class="row"> - <div class="col s12 m6"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>Tokenization</span> - <p>Your text is split up into sentences and words, so called tokens, which can then be analyzed.</p> - </div> - </div> - <div class="col s12 m6"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>Lemmatization</span> - <p>All inflected forms of a word are grouped together so that it can be analyzed as a single item.</p> - </div> - </div> - <div class="col s12 m6"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>Part-of-speech Tagging</span> - <p>In accordance with its definition and context, each word is marked up as corresponding to a particular part of speech.</p> - </div> - </div> - <div class="col s12 m6"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>Named-Entity Recognition</span> - <p>Named entities are located and classified into specific categories like persons or locations.</p> + <div class="col s12 m9 pull-m3"> + <div class="card" style="border-top: 10px solid {{ colors.nlp_darken }};"> + <div class="card-content"> + <div class="row"> + <div class="col s12 m6"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>Tokenization</span> + <p>Your text is split up into sentences and words, so called tokens, which can then be analyzed.</p> + </div> + </div> + <div class="col s12 m6"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>Lemmatization</span> + <p>All inflected forms of a word are grouped together so that it can be analyzed as a single item.</p> + </div> + </div> + <div class="col s12 m6"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>Part-of-speech Tagging</span> + <p>In accordance with its definition and context, each word is marked up as corresponding to a particular part of speech.</p> + </div> + </div> + <div class="col s12 m6"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>Named-Entity Recognition</span> + <p>Named entities are located and classified into specific categories like persons or locations.</p> + </div> + </div> </div> </div> </div> </div> - </div> -</div> -<div class="col s12"> - <h3>Submit a job</h3> - <div class="card"> - <form class="nopaque-submit-form" data-progress-modal="progress-modal"> - <div class="card-content"> - {{ add_job_form.hidden_tag() }} - <div class="row"> - <div class="col s12 l4"> - {{ M.render_field(add_job_form.title, data_length='32', material_icon='title') }} - </div> - <div class="col s12 l8"> - {{ M.render_field(add_job_form.description, data_length='255', material_icon='description') }} - </div> - <div class="col s12 l5"> - {{ M.render_field(add_job_form.files, accept='text/plain', placeholder='Choose your .txt files') }} - </div> - <div class="col s12 l4"> - {{ M.render_field(add_job_form.language, material_icon='language') }} - </div> - <div class="col s12 l3"> - {{ M.render_field(add_job_form.version, material_icon='apps') }} - </div> - <div class="col s12"> - <span class="card-title">Preprocessing</span> - </div> - <div class="col s9"> - <p>{{ add_job_form.check_encoding.label.text }}</p> - <p class="light">If the input files are not created with the nopaque OCR service or you do not know if your text files are UTF-8 encoded, check this switch. We will try to automatically determine the right encoding for your texts to process them.</p> - </div> - <div class="col s3 right-align"> - <div class="switch"> - <label> - {{ add_job_form.check_encoding() }} - <span class="lever"></span> - </label> + <div class="col s12"> + <h2>Submit a job</h2> + <div class="card"> + <form class="nopaque-submit-form" data-progress-modal="progress-modal"> + <div class="card-content"> + {{ add_job_form.hidden_tag() }} + <div class="row"> + <div class="col s12 l4"> + {{ wtf.render_field(add_job_form.title, data_length='32', material_icon='title') }} + </div> + <div class="col s12 l8"> + {{ wtf.render_field(add_job_form.description, data_length='255', material_icon='description') }} + </div> + <div class="col s12 l5"> + {{ wtf.render_field(add_job_form.files, accept='text/plain', placeholder='Choose your .txt files') }} + </div> + <div class="col s12 l4"> + {{ wtf.render_field(add_job_form.language, material_icon='language') }} + </div> + <div class="col s12 l3"> + {{ wtf.render_field(add_job_form.version, material_icon='apps') }} + </div> + <div class="col s12"> + <span class="card-title">Preprocessing</span> + </div> + <div class="col s9"> + <p>{{ add_job_form.check_encoding.label.text }}</p> + <p class="light">If the input files are not created with the nopaque OCR service or you do not know if your text files are UTF-8 encoded, check this switch. We will try to automatically determine the right encoding for your texts to process them.</p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + {{ add_job_form.check_encoding() }} + <span class="lever"></span> + </label> + </div> + </div> + <!-- + Seperate each setting with the following + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + --> </div> </div> - <!-- - Seperate each setting with the following - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - --> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_job_form.submit, material_icon='send') }} + <div class="card-action right-align"> + {{ wtf.render_field(add_job_form.submit, material_icon='send') }} + </div> + </form> </div> - </form> + </div> </div> </div> diff --git a/web/app/templates/services/ocr.html.j2 b/web/app/templates/services/ocr.html.j2 index 8b167b2f5ffd3de2f047a28c08c9827039d1a67b..150778c3062a20c662cc65c36a81f25e15f12f1c 100644 --- a/web/app/templates/services/ocr.html.j2 +++ b/web/app/templates/services/ocr.html.j2 @@ -1,129 +1,140 @@ {% extends "nopaque.html.j2" %} +{% import 'materialize/wtf.html.j2' as wtf %} +{% from '_colors.html.j2' import colors %} -{% set main_class = 'ocr-color lighten' %} +{% set scheme_primary_color = colors.ocr_darken %} +{% set scheme_secondary_color = colors.ocr %} + +{% block main_attribs %} class="ocr-color lighten"{% endblock main_attribs %} {% block page_content %} -{{ Macros.insert_color_scheme(ocr_color_darken) }} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> -<div class="col s12 m3 push-m9"> - <div class="center-align"> - <p class="hide-on-small-only"> </p> - <p class="hide-on-small-only"> </p> - <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> - <i class="material-icons service" data-service="ocr"></i> - </a> - </div> -</div> + <div class="col s12 m3 push-m9"> + <div class="center-align"> + <p class="hide-on-small-only"> </p> + <p class="hide-on-small-only"> </p> + <a class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);"> + <i class="material-icons service" data-service="ocr"></i> + </a> + </div> + </div> -<div class="col s12 m9 pull-m3"> - <div class="card" style="border-top: 10px solid {{ ocr_color_darken }};"> - <div class="card-content"> - <div class="row"> - <div class="col s12"> - <div class="card-panel z-depth-0"> - <span class="card-title"><i class="left material-icons">layers</i>OCR</span> - <p>In this process, nopaque converts your image data – like photos or scans – into text data. This step enables you to proceed with the computational analysis of your documents.</p> + <div class="col s12 m9 pull-m3"> + <div class="card" style="border-top: 10px solid {{ colors.ocr_darken }};"> + <div class="card-content"> + <div class="row"> + <div class="col s12"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>OCR</span> + <p>In this process, nopaque converts your image data – like photos or scans – into text data. This step enables you to proceed with the computational analysis of your documents.</p> + </div> + </div> </div> </div> </div> </div> - </div> -</div> -<div class="col s12"> - <h3>Submit a job</h3> - <div class="card"> - <form class="nopaque-submit-form" data-progress-modal="progress-modal"> - <div class="card-content"> - {{ add_job_form.hidden_tag() }} - <div class="row"> - <div class="col s12 l4"> - {{ M.render_field(add_job_form.title, data_length='32', material_icon='title') }} - </div> - <div class="col s12 l8"> - {{ M.render_field(add_job_form.description, data_length='255', material_icon='description') }} - </div> - <div class="col s12 l5"> - {{ M.render_field(add_job_form.files, accept='application/pdf', color=ocr_color_darken, placeholder='Choose your .pdf files') }} - </div> - <div class="col s12 l4"> - {{ M.render_field(add_job_form.language, material_icon='language') }} - </div> - <div class="col s12 l3"> - {{ M.render_field(add_job_form.version, material_icon='apps') }} - </div> - <div class="col s12"> - <span class="card-title">Preprocessing</span> - </div> - <div class="col s9"> - <p>{{ add_job_form.binarization.label.text }}</p> - <p class="light">Based on a brightness threshold pixels are converted into either black or white. It is useful to reduce noise in images. (<b>longer duration</b>)</p> - </div> - <div class="col s3 right-align"> - <div class="switch"> - <label> - {{ add_job_form.binarization() }} - <span class="lever"></span> - </label> + <div class="col s12"> + <h2>Submit a job</h2> + <div class="card"> + <form class="nopaque-submit-form" data-progress-modal="progress-modal"> + <div class="card-content"> + {{ add_job_form.hidden_tag() }} + <div class="row"> + <div class="col s12 l4"> + {{ wtf.render_field(add_job_form.title, data_length='32', material_icon='title') }} + </div> + <div class="col s12 l8"> + {{ wtf.render_field(add_job_form.description, data_length='255', material_icon='description') }} + </div> + <div class="col s12 l5"> + {{ wtf.render_field(add_job_form.files, accept='application/pdf', color=ocr_color_darken, placeholder='Choose your .pdf files') }} + </div> + <div class="col s12 l4"> + {{ wtf.render_field(add_job_form.language, material_icon='language') }} + </div> + <div class="col s12 l3"> + {{ wtf.render_field(add_job_form.version, material_icon='apps') }} + </div> + <div class="col s12"> + <span class="card-title">Preprocessing</span> + </div> + <div class="col s9"> + <p>{{ add_job_form.binarization.label.text }}</p> + <p class="light">Based on a brightness threshold pixels are converted into either black or white. It is useful to reduce noise in images. (<b>longer duration</b>)</p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + {{ add_job_form.binarization() }} + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + <div class="col s9"> + <p>Page range</p> + <p class="light"></p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + <input disabled type="checkbox"> + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + <div class="col s9"> + <p>Page rotation</p> + <p class="light"></p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + <input disabled type="checkbox"> + <span class="lever"></span> + </label> + </div> + </div> + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + <div class="col s9"> + <p>Page split</p> + <p class="light"></p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + <input disabled type="checkbox"> + <span class="lever"></span> + </label> + </div> + </div> + <!-- + Seperate each setting with the following + <div class="col s12"><p> </p></div> + <div class="col s12 divider"></div> + <div class="col s12"><p> </p></div> + --> </div> </div> - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - <div class="col s9"> - <p>Page range</p> - <p class="light"></p> - </div> - <div class="col s3 right-align"> - <div class="switch"> - <label> - <input disabled type="checkbox"> - <span class="lever"></span> - </label> - </div> - </div> - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - <div class="col s9"> - <p>Page rotation</p> - <p class="light"></p> - </div> - <div class="col s3 right-align"> - <div class="switch"> - <label> - <input disabled type="checkbox"> - <span class="lever"></span> - </label> - </div> + <div class="card-action right-align"> + {{ wtf.render_field(add_job_form.submit, color=ocr_color_darken, material_icon='send') }} </div> - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - <div class="col s9"> - <p>Page split</p> - <p class="light"></p> - </div> - <div class="col s3 right-align"> - <div class="switch"> - <label> - <input disabled type="checkbox"> - <span class="lever"></span> - </label> - </div> - </div> - <!-- - Seperate each setting with the following - <div class="col s12"><p> </p></div> - <div class="col s12 divider"></div> - <div class="col s12"><p> </p></div> - --> - </div> - </div> - <div class="card-action right-align"> - {{ M.render_field(add_job_form.submit, color=ocr_color_darken, material_icon='send') }} + </form> </div> - </form> + </div> </div> </div> diff --git a/web/app/templates/services/roadmap.html.j2 b/web/app/templates/services/roadmap.html.j2 deleted file mode 100644 index 05b703febbb29baba374ef47ebf64c558f04737f..0000000000000000000000000000000000000000 --- a/web/app/templates/services/roadmap.html.j2 +++ /dev/null @@ -1,20 +0,0 @@ -<ul class="table-of-contents" id="roadmap"> - <li><b>Roadmap</b></li> - <li> - <a href="{{ url_for('services.service', service='file-setup') }}">File setup</a> - </li> - <li> - <a href="{{ url_for('services.service', service='ocr') }}">Optical Character Recognition</a> - </li> - <li> - <a href="{{ url_for('services.service', service='nlp') }}">Natural Language Processing</a> - </li> - <li> - <a href="{{ url_for('services.service', service='corpus_analysis') }}">Corpus analysis</a> - </li> -</ul> -<script> -for (let entry of document.querySelectorAll(`#roadmap a`)) { - if (entry.href === window.location.href) {entry.classList.add("active");} -} -</script> diff --git a/web/app/templates/settings/_menu.html.j2 b/web/app/templates/settings/_menu.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..554a3f67eb49b1bcaabffda54ada154afa959bad --- /dev/null +++ b/web/app/templates/settings/_menu.html.j2 @@ -0,0 +1,5 @@ +<div class="collection"> + <a href="{{ url_for('.edit_general_settings') }}" class="collection-item{%if request.path == url_for('.edit_general_settings') %} active{% endif %}">Edit general settings</a> + <a href="{{ url_for('.change_password') }}" class="collection-item{%if request.path == url_for('.change_password') %} active{% endif %}">Change password</a> + <a href="{{ url_for('.edit_notification_settings') }}" class="collection-item{%if request.path == url_for('.edit_notification_settings') %} active{% endif %}">Edit notification settings</a> +</div> diff --git a/web/app/templates/settings/change_password.html.j2 b/web/app/templates/settings/change_password.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..771886bf7d8f17fd7d0b0e39ac3ccb42704e25fe --- /dev/null +++ b/web/app/templates/settings/change_password.html.j2 @@ -0,0 +1,35 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">Settings</h1> + </div> + + <div class="col s12 m4"> + {% include 'settings/_menu.html.j2' %} + </div> + + <div class="col s12 m8"> + <div class="card"> + <form enctype="multipart/form-data" method="POST"> + <div class="card-content"> + <span class="card-title">{{ title }}</span> + {{ form.hidden_tag() }} + {{ wtf.render_field(form.password, material_icon='vpn_key') }} + {{ wtf.render_field(form.new_password, material_icon='vpn_key') }} + {{ wtf.render_field(form.new_password2, material_icon='vpn_key') }} + </div> + <div class="card-action"> + <div class="right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </div> + </form> + </div> + </div> + </div> +</div> +{% endblock page_content %} diff --git a/web/app/templates/settings/edit_general_settings.html.j2 b/web/app/templates/settings/edit_general_settings.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..18eff4d7b34822bd0be54eda7ac9395ed7917eb2 --- /dev/null +++ b/web/app/templates/settings/edit_general_settings.html.j2 @@ -0,0 +1,79 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">Settings</h1> + </div> + + <div class="col s12 m4"> + {% include 'settings/_menu.html.j2' %} + </div> + + <div class="col s12 m8"> + <div class="card"> + <form enctype="multipart/form-data" method="POST"> + <div class="card-content"> + <span class="card-title">{{ title }}</span> + {{ form.hidden_tag() }} + {{ wtf.render_field(form.username, data_length='64', material_icon='person') }} + {{ wtf.render_field(form.email, data_length='254', material_icon='email') }} + <div class="row"> + <div class="col s12"><p> </p></div> + <div class="col s1"> + <p><i class="material-icons">brightness_3</i></p> + </div> + <div class="col s8"> + <p>{{ form.dark_mode.label.text }}</p> + <p class="light">Enable dark mode to ease your eyes.</p> + </div> + <div class="col s3 right-align"> + <div class="switch"> + <label> + {{ form.dark_mode() }} + <span class="lever"></span> + </label> + </div> + </div> + </div> + </div> + <div class="card-action"> + <div class="right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </div> + </form> + </div> + + <div class="card"> + <div class="card-content"> + <span class="card-title">Delete account</span> + <p>Deleting an account has the following effects:</p> + <ul> + <li>All data associated with your corpora and jobs will be permanently deleted.</li> + <li>All settings will be permanently deleted.</li> + </ul> + </div> + <div class="card-action right-align"> + <a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> + </div> + </div> + </div> + </div> +</div> + + +<!-- Modals --> +<div class="modal" id="delete-account-modal"> + <div class="modal-content"> + <h4>Confirm deletion</h4> + <p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p> + </div> + <div class="modal-footer"> + <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a> + <a href="{{ url_for('.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a> + </div> +</div> +{% endblock page_content %} diff --git a/web/app/templates/settings/edit_notification_settings.html.j2 b/web/app/templates/settings/edit_notification_settings.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..2a8e7b24f02bcedb87a784fe21e5a6f70cb70cfa --- /dev/null +++ b/web/app/templates/settings/edit_notification_settings.html.j2 @@ -0,0 +1,34 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">Settings</h1> + </div> + + <div class="col s12 m4"> + {% include 'settings/_menu.html.j2' %} + </div> + + <div class="col s12 m8"> + <div class="card"> + <form enctype="multipart/form-data" method="POST"> + <div class="card-content"> + <span class="card-title">{{ title }}</span> + {{ form.hidden_tag() }} + {{ wtf.render_field(form.job_status_mail_notifications, material_icon='notifications') }} + {{ wtf.render_field(form.job_status_site_notifications, material_icon='feedback') }} + </div> + <div class="card-action"> + <div class="right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </div> + </form> + </div> + </div> + </div> +</div> +{% endblock page_content %} diff --git a/web/app/templates/utils/macros.html.j2 b/web/app/templates/utils/macros.html.j2 deleted file mode 100644 index 54fa671d26f2063c91b0add80df88c7f66682463..0000000000000000000000000000000000000000 --- a/web/app/templates/utils/macros.html.j2 +++ /dev/null @@ -1,37 +0,0 @@ -{%- macro insert_page_content() -%} -<noscript> - <div class="container"> - <div class="row"> - <div class="col s12"> - <div class="card red darken-1"> - <div class="card-content white-text"> - <span class="card-title">JavaScript is disabled</span> - <p>You have JavaScript disabled. Nopaque uses javascript and sockets to send data in realtime to you. For example showing you the status of your jobs and your corpora. Please activate JavaScript to make full use of nopaque.</p> - <p>Some services are still useable without Javascript.</p> - </div> - <div class="card-action"> - <a href="#">What services can I still use?</a> - <a href="#">What services and functions are not available?</a> - </div> - </div> - </div> - </div> - </div> -</noscript> -{% block page_content %}{% endblock %} -{%- endmacro -%} - -{%- macro insert_color_scheme(hex_color) -%} -<style> - main button, main .btn, main .btn-floating {background-color: {{ hex_color }};} - main .pagination li.active {background-color: {{ hex_color }};} - main .table-of-contents a.active {border-color: {{ hex_color }};} - main .tabs .tab a {color: inherit;} - main .tabs .tab a:hover {color: {{ hex_color }};} - main .tabs .tab a.active, .tabs .tab a:focus.active { - color: {{ hex_color }}; - background-color: {{ hex_color }}28; - } - main .tabs .indicator {background-color: {{ hex_color }};} -</style> -{%- endmacro -%} diff --git a/web/boot.sh b/web/boot.sh index a390d4c4f5507bde4f27c06b583ac99f5d11ff42..0d088ac24f892d21d1d759f306d1fb60c7637eb2 100755 --- a/web/boot.sh +++ b/web/boot.sh @@ -1,5 +1,6 @@ #!/bin/bash source venv/bin/activate +export FLASK_APP=nopaque.py if [[ "$#" -eq 0 ]]; then while true; do flask deploy diff --git a/web/config.py b/web/config.py index 0f78e6adf6d7642a1eec3a4561f20f5032a950cc..4ca3704fa524c804bc26e5f172945703fe8fbbe0 100644 --- a/web/config.py +++ b/web/config.py @@ -31,6 +31,7 @@ class Config: ''' # General # ''' ADMIN_EMAIL_ADRESS = os.environ.get('NOPAQUE_ADMIN_EMAIL_ADRESS') + ALLOWED_USERNAME_REGEX = '^[A-Za-zÄÖÜäöüß0-9_.]*$' CONTACT_EMAIL_ADRESS = os.environ.get('NOPAQUE_CONTACT_EMAIL_ADRESS') DATA_DIR = os.environ.get('NOPAQUE_DATA_DIR', '/mnt/nopaque') SECRET_KEY = os.environ.get('NOPAQUE_SECRET_KEY', 'hard to guess string')