mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2025-07-03 03:10:33 +00:00
Compare commits
24 Commits
460257294d
...
1.1.0
Author | SHA1 | Date | |
---|---|---|---|
713a7645db | |||
0c64c07925 | |||
a6ddf4c980 | |||
cab5f7ea05 | |||
07f09cdbd9 | |||
c97b2a886e | |||
df2bffe0fd | |||
aafb3ca3ec | |||
12a3ac1d5d | |||
a2904caea2 | |||
e325552100 | |||
e269156925 | |||
9c9de242ca | |||
ec54fdc3bb | |||
2263a8d27d | |||
143cdd91f9 | |||
b5f7478e14 | |||
a95b8d979d | |||
18d5ab160e | |||
7439edacef | |||
99d7a8bdfc | |||
54c4295bf7 | |||
1e5c26b8e3 | |||
9f56647cf7 |
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@ -1,10 +1,24 @@
|
||||
{
|
||||
"editor.rulers": [79],
|
||||
"editor.tabSize": 4,
|
||||
"emmet.includeLanguages": {
|
||||
"jinja-html": "html"
|
||||
},
|
||||
"files.associations": {
|
||||
".flaskenv": "env",
|
||||
"*.env.tpl": "env",
|
||||
"*.txt.j2": "jinja"
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[html]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[jinja-html]": {
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ def socketio_login_required(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
if current_user.is_authenticated:
|
||||
return f(*args, **kwargs)
|
||||
return {'code': 401, 'body': 'Unauthorized'}
|
||||
return {'status': 401, 'statusText': 'Unauthorized'}
|
||||
return wrapper
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ def socketio_permission_required(permission):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not current_user.can(permission):
|
||||
return {'code': 403, 'body': 'Forbidden'}
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
@ -50,7 +50,7 @@ def _create_build_corpus_service(corpus: Corpus):
|
||||
''' ## Constraints ## '''
|
||||
constraints = ['node.role==worker']
|
||||
''' ## Image ## '''
|
||||
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1879'
|
||||
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1887'
|
||||
''' ## Labels ## '''
|
||||
labels = {
|
||||
'nopaque.server_name': current_app.config['SERVER_NAME']
|
||||
@ -141,7 +141,7 @@ def _create_cqpserver_container(corpus: Corpus):
|
||||
''' ## Entrypoint ## '''
|
||||
entrypoint = ['bash', '-c']
|
||||
''' ## Image ## '''
|
||||
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1879'
|
||||
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1887'
|
||||
''' ## Name ## '''
|
||||
name = f'nopaque-cqpserver-{corpus.id}'
|
||||
''' ## Network ## '''
|
||||
|
@ -42,8 +42,9 @@ def resource_after_delete(mapper, connection, resource):
|
||||
'path': resource.jsonpatch_path
|
||||
}
|
||||
]
|
||||
namespace = '/users'
|
||||
room = f'/users/{resource.user_hashid}'
|
||||
socketio.emit('patch_user', jsonpatch, namespace='/users', room=room)
|
||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||
|
||||
|
||||
def cfa_after_delete(mapper, connection, cfa):
|
||||
@ -54,8 +55,9 @@ def cfa_after_delete(mapper, connection, cfa):
|
||||
'path': jsonpatch_path
|
||||
}
|
||||
]
|
||||
namespace = '/users'
|
||||
room = f'/users/{cfa.corpus.user.hashid}'
|
||||
socketio.emit('patch_user', jsonpatch, namespace='/users', room=room)
|
||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||
|
||||
|
||||
def resource_after_insert(mapper, connection, resource):
|
||||
@ -69,8 +71,9 @@ def resource_after_insert(mapper, connection, resource):
|
||||
'value': jsonpatch_value
|
||||
}
|
||||
]
|
||||
namespace = '/users'
|
||||
room = f'/users/{resource.user_hashid}'
|
||||
socketio.emit('patch_user', jsonpatch, namespace='/users', room=room)
|
||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||
|
||||
|
||||
def cfa_after_insert(mapper, connection, cfa):
|
||||
@ -83,8 +86,9 @@ def cfa_after_insert(mapper, connection, cfa):
|
||||
'value': jsonpatch_value
|
||||
}
|
||||
]
|
||||
namespace = '/users'
|
||||
room = f'/users/{cfa.corpus.user.hashid}'
|
||||
socketio.emit('patch_user', jsonpatch, namespace='/users', room=room)
|
||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||
|
||||
|
||||
def resource_after_update(mapper, connection, resource):
|
||||
@ -109,8 +113,9 @@ def resource_after_update(mapper, connection, resource):
|
||||
}
|
||||
)
|
||||
if jsonpatch:
|
||||
namespace = '/users'
|
||||
room = f'/users/{resource.user_hashid}'
|
||||
socketio.emit('patch_user', jsonpatch, namespace='/users', room=room)
|
||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||
|
||||
|
||||
def job_after_update(mapper, connection, job):
|
||||
|
@ -9,8 +9,8 @@ from threading import Lock
|
||||
from app import db, docker_client, hashids, socketio
|
||||
from app.decorators import socketio_login_required
|
||||
from app.models import Corpus, CorpusStatus
|
||||
from . import cqi_extensions
|
||||
from .utils import CQiOverSocketIOSessionManager
|
||||
from . import cqi_extension_functions
|
||||
from .utils import SessionManager
|
||||
|
||||
|
||||
'''
|
||||
@ -85,6 +85,16 @@ CQI_API_FUNCTION_NAMES = [
|
||||
]
|
||||
|
||||
|
||||
CQI_EXTENSION_FUNCTION_NAMES = [
|
||||
'ext_corpus_update_db',
|
||||
'ext_corpus_static_data',
|
||||
'ext_corpus_paginate_corpus',
|
||||
'ext_cqp_paginate_subcorpus',
|
||||
'ext_cqp_partial_export_subcorpus',
|
||||
'ext_cqp_export_subcorpus',
|
||||
]
|
||||
|
||||
|
||||
class CQiOverSocketIONamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_connect(self):
|
||||
@ -135,25 +145,25 @@ class CQiOverSocketIONamespace(Namespace):
|
||||
cqi_client = CQiClient(cqpserver_ip_address)
|
||||
cqi_client_lock = Lock()
|
||||
|
||||
CQiOverSocketIOSessionManager.setup()
|
||||
CQiOverSocketIOSessionManager.set_corpus_id(corpus_id)
|
||||
CQiOverSocketIOSessionManager.set_cqi_client(cqi_client)
|
||||
CQiOverSocketIOSessionManager.set_cqi_client_lock(cqi_client_lock)
|
||||
SessionManager.setup()
|
||||
SessionManager.set_corpus_id(corpus_id)
|
||||
SessionManager.set_cqi_client(cqi_client)
|
||||
SessionManager.set_cqi_client_lock(cqi_client_lock)
|
||||
|
||||
return {'code': 200, 'msg': 'OK'}
|
||||
|
||||
@socketio_login_required
|
||||
def on_exec(self, fn_name: str, fn_args: dict = {}) -> dict:
|
||||
try:
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_client_lock = SessionManager.get_cqi_client_lock()
|
||||
except KeyError:
|
||||
return {'code': 424, 'msg': 'Failed Dependency'}
|
||||
|
||||
if fn_name in CQI_API_FUNCTION_NAMES:
|
||||
fn = getattr(cqi_client.api, fn_name)
|
||||
elif fn_name in cqi_extensions.CQI_EXTENSION_FUNCTION_NAMES:
|
||||
fn = getattr(cqi_extensions, fn_name)
|
||||
elif fn_name in CQI_EXTENSION_FUNCTION_NAMES:
|
||||
fn = getattr(cqi_extension_functions, fn_name)
|
||||
else:
|
||||
return {'code': 400, 'msg': 'Bad Request'}
|
||||
|
||||
@ -198,10 +208,10 @@ class CQiOverSocketIONamespace(Namespace):
|
||||
|
||||
def on_disconnect(self):
|
||||
try:
|
||||
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
||||
CQiOverSocketIOSessionManager.teardown()
|
||||
corpus_id = SessionManager.get_corpus_id()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_client_lock = SessionManager.get_cqi_client_lock()
|
||||
SessionManager.teardown()
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
|
@ -8,22 +8,12 @@ import json
|
||||
import math
|
||||
from app import db
|
||||
from app.models import Corpus
|
||||
from .utils import CQiOverSocketIOSessionManager
|
||||
|
||||
|
||||
CQI_EXTENSION_FUNCTION_NAMES = [
|
||||
'ext_corpus_update_db',
|
||||
'ext_corpus_static_data',
|
||||
'ext_corpus_paginate_corpus',
|
||||
'ext_cqp_paginate_subcorpus',
|
||||
'ext_cqp_partial_export_subcorpus',
|
||||
'ext_cqp_export_subcorpus',
|
||||
]
|
||||
from .utils import SessionManager
|
||||
|
||||
|
||||
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
||||
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
corpus_id = SessionManager.get_corpus_id()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
db_corpus = Corpus.query.get(corpus_id)
|
||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||
db_corpus.num_tokens = cqi_corpus.size
|
||||
@ -32,7 +22,7 @@ def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
||||
|
||||
|
||||
def ext_corpus_static_data(corpus: str) -> dict:
|
||||
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||
corpus_id = SessionManager.get_corpus_id()
|
||||
db_corpus = Corpus.query.get(corpus_id)
|
||||
|
||||
static_data_file_path = db_corpus.path / 'cwb' / 'static.json.gz'
|
||||
@ -40,7 +30,7 @@ def ext_corpus_static_data(corpus: str) -> dict:
|
||||
with static_data_file_path.open('rb') as f:
|
||||
return f.read()
|
||||
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||
cqi_p_attrs = cqi_corpus.positional_attributes.list()
|
||||
cqi_s_attrs = cqi_corpus.structural_attributes.list()
|
||||
@ -168,7 +158,7 @@ def ext_corpus_paginate_corpus(
|
||||
page: int = 1,
|
||||
per_page: int = 20
|
||||
) -> dict:
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||
# Sanity checks
|
||||
if (
|
||||
@ -215,7 +205,7 @@ def ext_cqp_paginate_subcorpus(
|
||||
per_page: int = 20
|
||||
) -> dict:
|
||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||
# Sanity checks
|
||||
@ -262,7 +252,7 @@ def ext_cqp_partial_export_subcorpus(
|
||||
context: int = 50
|
||||
) -> dict:
|
||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||
cqi_subcorpus_partial_export = _partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
|
||||
@ -271,7 +261,7 @@ def ext_cqp_partial_export_subcorpus(
|
||||
|
||||
def ext_cqp_export_subcorpus(subcorpus: str, context: int = 50) -> dict:
|
||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||
cqi_client = SessionManager.get_cqi_client()
|
||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||
cqi_subcorpus_export = _export_subcorpus(cqi_subcorpus, context=context)
|
@ -3,7 +3,7 @@ from threading import Lock
|
||||
from flask import session
|
||||
|
||||
|
||||
class CQiOverSocketIOSessionManager:
|
||||
class SessionManager:
|
||||
@staticmethod
|
||||
def setup():
|
||||
session['cqi_over_sio'] = {}
|
||||
|
109
app/namespaces/jobs.py
Normal file
109
app/namespaces/jobs.py
Normal file
@ -0,0 +1,109 @@
|
||||
from flask import current_app, Flask
|
||||
from flask_login import current_user
|
||||
from flask_socketio import Namespace
|
||||
from app import db, hashids, socketio
|
||||
from app.decorators import socketio_admin_required, socketio_login_required
|
||||
from app.models import Job, JobStatus
|
||||
|
||||
|
||||
def _delete_job(app: Flask, job_id: int):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _restart_job(app, job_id):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.restart()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class UsersNamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_delete(self, job_hashid: str) -> dict:
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if not (
|
||||
job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_delete_job,
|
||||
current_app._get_current_object(),
|
||||
job_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Job "{job.title}" marked for deletion',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
||||
|
||||
@socketio_admin_required
|
||||
def on_log(self, job_hashid: str):
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||
return {'status': 409, 'statusText': 'Conflict'}
|
||||
|
||||
with open(job.path / 'pipeline_data' / 'logs' / 'pyflow_log.txt') as log_file:
|
||||
log = log_file.read()
|
||||
|
||||
return {
|
||||
'body': log,
|
||||
'status': 200,
|
||||
'statusText': 'Forbidden'
|
||||
}
|
||||
|
||||
socketio_login_required
|
||||
def on_restart(self, job_hashid: str):
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if not (
|
||||
job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
if job.status == JobStatus.FAILED:
|
||||
return {'status': 409, 'statusText': 'Conflict'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_restart_job,
|
||||
current_app._get_current_object(),
|
||||
job_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Job "{job.title}" marked for restarting',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
@ -1,13 +1,21 @@
|
||||
from flask import current_app, Flask
|
||||
from flask_login import current_user
|
||||
from flask_socketio import join_room, leave_room, Namespace
|
||||
from app import hashids
|
||||
from app import db, hashids, socketio
|
||||
from app.decorators import socketio_login_required
|
||||
from app.models import User
|
||||
|
||||
|
||||
def _delete_user(app: Flask, user_id: int):
|
||||
with app.app_context():
|
||||
user = User.query.get(user_id)
|
||||
user.delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class UsersNamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_get_user(self, user_hashid: str) -> dict:
|
||||
def on_get(self, user_hashid: str) -> dict:
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -34,7 +42,7 @@ class UsersNamespace(Namespace):
|
||||
}
|
||||
|
||||
@socketio_login_required
|
||||
def on_subscribe_user(self, user_hashid: str) -> dict:
|
||||
def on_subscribe(self, user_hashid: str) -> dict:
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -56,7 +64,7 @@ class UsersNamespace(Namespace):
|
||||
return {'status': 200, 'statusText': 'OK'}
|
||||
|
||||
@socketio_login_required
|
||||
def on_unsubscribe_user(self, user_hashid: str) -> dict:
|
||||
def on_unsubscribe(self, user_hashid: str) -> dict:
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -76,3 +84,33 @@ class UsersNamespace(Namespace):
|
||||
leave_room(f'/users/{user.hashid}')
|
||||
|
||||
return {'status': 200, 'statusText': 'OK'}
|
||||
|
||||
@socketio_login_required
|
||||
def on_delete(self, user_hashid: str) -> dict:
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
user = User.query.get(user_id)
|
||||
|
||||
if user is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if not (
|
||||
user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_delete_user,
|
||||
current_app._get_current_object(),
|
||||
user.id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'User "{user.username}" marked for deletion',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
--corpus-status-content: "unprepared";
|
||||
}
|
||||
|
||||
[data-corpus-status="SUBMITTED"] {
|
||||
--corpus-status-content: "submitted";
|
||||
}
|
||||
|
||||
[data-corpus-status="QUEUED"] {
|
||||
--corpus-status-content: "queued";
|
||||
}
|
||||
|
47
app/static/css/height.css
Normal file
47
app/static/css/height.css
Normal file
@ -0,0 +1,47 @@
|
||||
.h-10 {
|
||||
height: 10% !important;
|
||||
}
|
||||
|
||||
.h-20 {
|
||||
height: 20% !important;
|
||||
}
|
||||
|
||||
.h-25 {
|
||||
height: 25% !important;
|
||||
}
|
||||
|
||||
.h-30 {
|
||||
height: 30% !important;
|
||||
}
|
||||
|
||||
.h-40 {
|
||||
height: 40% !important;
|
||||
}
|
||||
|
||||
.h-50 {
|
||||
height: 50% !important;
|
||||
}
|
||||
|
||||
.h-60 {
|
||||
height: 60% !important;
|
||||
}
|
||||
|
||||
.h-70 {
|
||||
height: 70% !important;
|
||||
}
|
||||
|
||||
.h-75 {
|
||||
height: 75% !important;
|
||||
}
|
||||
|
||||
.h-80 {
|
||||
height: 80% !important;
|
||||
}
|
||||
|
||||
.h-90 {
|
||||
height: 90% !important;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100% !important;
|
||||
}
|
@ -1,25 +1,3 @@
|
||||
/* #region sidenav-fixed */
|
||||
/*
|
||||
* 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 (header, main and footer)
|
||||
* gets an offset equal to the width of the sidenav.
|
||||
*
|
||||
* Read more: https://materializecss.com/sidenav.html#variations
|
||||
*/
|
||||
@media only screen and (min-width: 993px) {
|
||||
body[data-sidenav-fixed="true" i] header,
|
||||
body[data-sidenav-fixed="true" i] main,
|
||||
body[data-sidenav-fixed="true" i] footer {
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
body[data-sidenav-fixed="true" i] .navbar-fixed > nav {
|
||||
width: calc(100% - 300px);
|
||||
}
|
||||
}
|
||||
/* #endregion sidenav-fixed */
|
||||
|
||||
/* #region sticky-footer */
|
||||
/*
|
||||
* Sticky Footer:
|
||||
@ -32,13 +10,13 @@
|
||||
*
|
||||
* Read more: https://materializecss.com/footer.html#sticky-footer
|
||||
*/
|
||||
body[data-sticky-footer="true" i] {
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body[data-sticky-footer="true" i] main {
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
/* #endregion sticky-footer */
|
||||
|
47
app/static/css/width.css
Normal file
47
app/static/css/width.css
Normal file
@ -0,0 +1,47 @@
|
||||
.w-10 {
|
||||
width: 10% !important;
|
||||
}
|
||||
|
||||
.w-20 {
|
||||
width: 20% !important;
|
||||
}
|
||||
|
||||
.w-25 {
|
||||
width: 25% !important;
|
||||
}
|
||||
|
||||
.w-30 {
|
||||
width: 30% !important;
|
||||
}
|
||||
|
||||
.w-40 {
|
||||
width: 40% !important;
|
||||
}
|
||||
|
||||
.w-50 {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.w-60 {
|
||||
width: 60% !important;
|
||||
}
|
||||
|
||||
.w-70 {
|
||||
width: 70% !important;
|
||||
}
|
||||
|
||||
.w-75 {
|
||||
width: 75% !important;
|
||||
}
|
||||
|
||||
.w-80 {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
.w-90 {
|
||||
width: 90% !important;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100% !important;
|
||||
}
|
@ -1,205 +1,20 @@
|
||||
nopaque.App = class App {
|
||||
#promises;
|
||||
|
||||
constructor() {
|
||||
this.data = {
|
||||
users: {}
|
||||
};
|
||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||
|
||||
this.#promises = {
|
||||
getUser: {},
|
||||
subscribeUser: {}
|
||||
};
|
||||
// Endpoints
|
||||
this.users = new nopaque.app.endpoints.Users(this);
|
||||
|
||||
this.sockets = {
|
||||
users: io('/users', {transports: ['websocket'], upgrade: false})
|
||||
};
|
||||
|
||||
this.sockets.users.on('patch_user', (patch) => {this.onPatch(patch);});
|
||||
}
|
||||
|
||||
getUser(userId) {
|
||||
if (userId in this.#promises.getUser) {
|
||||
return this.#promises.getUser[userId];
|
||||
}
|
||||
|
||||
let socket = this.sockets.users;
|
||||
|
||||
this.#promises.getUser[userId] = new Promise((resolve, reject) => {
|
||||
socket.emit('get_user', userId, (response) => {
|
||||
if (response.status === 200) {
|
||||
this.data.users[userId] = response.body;
|
||||
resolve(this.data.users[userId]);
|
||||
} else {
|
||||
reject(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.#promises.getUser[userId];
|
||||
}
|
||||
|
||||
subscribeUser(userId) {
|
||||
if (userId in this.#promises.subscribeUser) {
|
||||
return this.#promises.subscribeUser[userId];
|
||||
}
|
||||
|
||||
let socket = this.sockets.users;
|
||||
|
||||
this.#promises.subscribeUser[userId] = new Promise((resolve, reject) => {
|
||||
socket.emit('subscribe_user', userId, (response) => {
|
||||
if (response.status === 200) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.#promises.subscribeUser[userId];
|
||||
}
|
||||
|
||||
flash(message, category) {
|
||||
let iconPrefix = '';
|
||||
switch (category) {
|
||||
case 'corpus': {
|
||||
iconPrefix = '<i class="left material-icons">book</i>';
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
iconPrefix = '<i class="error-color-text left material-icons">error</i>';
|
||||
break;
|
||||
}
|
||||
case 'job': {
|
||||
iconPrefix = '<i class="left nopaque-icons">J</i>';
|
||||
break;
|
||||
}
|
||||
case 'settings': {
|
||||
iconPrefix = '<i class="left material-icons">settings</i>';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
iconPrefix = '<i class="left material-icons">notifications</i>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
let toast = M.toast(
|
||||
{
|
||||
html: `
|
||||
<span>${iconPrefix}${message}</span>
|
||||
<button class="action-button btn-flat toast-action white-text" data-action="close">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
`.trim()
|
||||
}
|
||||
);
|
||||
let toastCloseActionElement = toast.el.querySelector('.action-button[data-action="close"]');
|
||||
toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
|
||||
}
|
||||
|
||||
onPatch(patch) {
|
||||
// Filter Patch to only include operations on users that are initialized
|
||||
let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
|
||||
let filteredPatch = patch.filter(operation => regExp.test(operation.path));
|
||||
|
||||
// Handle job status updates
|
||||
let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`);
|
||||
let subFilteredPatch = filteredPatch
|
||||
.filter((operation) => {return operation.op === 'replace';})
|
||||
.filter((operation) => {return subRegExp.test(operation.path);});
|
||||
for (let operation of subFilteredPatch) {
|
||||
let [match, userId, jobId] = operation.path.match(subRegExp);
|
||||
this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
|
||||
}
|
||||
|
||||
// Apply Patch
|
||||
jsonpatch.applyPatch(this.data, filteredPatch);
|
||||
// Extensions
|
||||
this.toaster = new nopaque.app.extensions.Toaster(this);
|
||||
this.ui = new nopaque.app.extensions.UI(this);
|
||||
this.userHub = new nopaque.app.extensions.UserHub(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
/* Pre-Initialization fixes */
|
||||
// #region
|
||||
|
||||
// Flask-WTF sets the standard HTML maxlength Attribute on input/textarea
|
||||
// elements to specify their maximum length (in characters). Unfortunatly
|
||||
// Materialize won't recognize the maxlength Attribute, instead it uses
|
||||
// the data-length Attribute. It's conversion time :)
|
||||
for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) {
|
||||
elem.dataset.length = elem.getAttribute('maxlength');
|
||||
elem.removeAttribute('maxlength');
|
||||
}
|
||||
|
||||
// To work around some limitations with the Form setup of Flask-WTF.
|
||||
// HTML option elements with an empty value are considered as placeholder
|
||||
// elements. The user should not be able to actively select these options.
|
||||
// So they get the disabled attribute.
|
||||
for (let optionElement of document.querySelectorAll('option[value=""]')) {
|
||||
optionElement.disabled = true;
|
||||
}
|
||||
|
||||
// TODO: Check why we are doing this.
|
||||
for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) {
|
||||
for (let c of optgroupElement.children) {
|
||||
optgroupElement.parentElement.insertAdjacentElement('afterbegin', c);
|
||||
}
|
||||
optgroupElement.remove();
|
||||
}
|
||||
// #endregion
|
||||
|
||||
|
||||
/* Initialize Materialize Components */
|
||||
// #region
|
||||
|
||||
// Automatically initialize Materialize Components that do not require
|
||||
// additional configuration.
|
||||
M.AutoInit();
|
||||
|
||||
// CharacterCounters
|
||||
// Materialize didn't include the CharacterCounter plugin within the
|
||||
// AutoInit method (maybe they forgot it?). Anyway... We do it here. :)
|
||||
M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)'));
|
||||
|
||||
// Header navigation processes and services Dropdown.
|
||||
M.Dropdown.init(
|
||||
document.querySelector('#nav-processes-and-services-dropdown-trigger'),
|
||||
{
|
||||
constrainWidth: false,
|
||||
coverTrigger: false
|
||||
}
|
||||
);
|
||||
|
||||
// Header navigation account Dropdown.
|
||||
M.Dropdown.init(
|
||||
document.querySelector('#nav-account-dropdown-trigger'),
|
||||
{
|
||||
alignment: 'right',
|
||||
constrainWidth: false,
|
||||
coverTrigger: false
|
||||
}
|
||||
);
|
||||
|
||||
// Terms of use modal
|
||||
M.Modal.init(
|
||||
document.querySelector('#terms-of-use-modal'),
|
||||
{
|
||||
dismissible: false,
|
||||
onCloseEnd: (modalElement) => {
|
||||
nopaque.requests.users.entity.acceptTermsOfUse();
|
||||
}
|
||||
}
|
||||
);
|
||||
// #endregion
|
||||
|
||||
|
||||
/* Initialize nopaque Components */
|
||||
// #region
|
||||
nopaque.resource_displays.AutoInit();
|
||||
nopaque.resource_lists.AutoInit();
|
||||
nopaque.forms.AutoInit();
|
||||
// #endregion
|
||||
// Initialize extensions
|
||||
this.toaster.init();
|
||||
this.ui.init();
|
||||
this.userHub.init();
|
||||
}
|
||||
};
|
||||
|
1
app/static/js/app/endpoints/index.js
Normal file
1
app/static/js/app/endpoints/index.js
Normal file
@ -0,0 +1 @@
|
||||
nopaque.app.endpoints = {};
|
41
app/static/js/app/endpoints/users.js
Normal file
41
app/static/js/app/endpoints/users.js
Normal file
@ -0,0 +1,41 @@
|
||||
nopaque.app.endpoints.Users = class Users {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
this.socket = io('/users', {transports: ['websocket'], upgrade: false});
|
||||
}
|
||||
|
||||
async get(userId) {
|
||||
const response = await this.socket.emitWithAck('get', userId);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async subscribe(userId) {
|
||||
const response = await this.socket.emitWithAck('subscribe', userId);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async unsubscribe(userId) {
|
||||
const response = await this.socket.emitWithAck('unsubscribe', userId);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(userId) {
|
||||
const response = await this.socket.emitWithAck('delete', userId);
|
||||
|
||||
if (response.status != 202) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
}
|
1
app/static/js/app/extensions/index.js
Normal file
1
app/static/js/app/extensions/index.js
Normal file
@ -0,0 +1 @@
|
||||
nopaque.app.extensions = {};
|
56
app/static/js/app/extensions/toaster.js
Normal file
56
app/static/js/app/extensions/toaster.js
Normal file
@ -0,0 +1,56 @@
|
||||
nopaque.app.extensions.Toaster = class Toaster {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.app.userHub.addEventListener('patch', (event) => {this.#onPatch(event.detail);});
|
||||
}
|
||||
|
||||
async #onPatch(patch) {
|
||||
// Handle corpus updates
|
||||
const corpusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)`);
|
||||
const corpusPatch = patch.filter((operation) => {return corpusRegExp.test(operation.path);});
|
||||
|
||||
this.#onCorpusPatch(corpusPatch);
|
||||
|
||||
// Handle job updates
|
||||
const jobRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)`);
|
||||
const jobPatch = patch.filter((operation) => {return jobRegExp.test(operation.path);});
|
||||
|
||||
this.#onJobPatch(jobPatch);
|
||||
}
|
||||
|
||||
async #onCorpusPatch(patch) {
|
||||
return;
|
||||
// Handle corpus status updates
|
||||
const corpusStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/corpora/([A-Za-z0-9]+)/status$`);
|
||||
const corpusStatusPatch = patch
|
||||
.filter((operation) => {return corpusStatusRegExp.test(operation.path);})
|
||||
.filter((operation) => {return operation.op === 'replace';});
|
||||
|
||||
for (let operation of corpusStatusPatch) {
|
||||
const [match, userId, corpusId] = operation.path.match(corpusStatusRegExp);
|
||||
const user = await this.app.userHub.get(userId);
|
||||
const corpus = user.corpora[corpusId];
|
||||
|
||||
this.app.ui.flash(`[<a href="/corpora/${corpusId}">${corpus.title}</a>] New status: <span class="corpus-status-text" data-corpus-status="${operation.value}"></span>`, 'corpus');
|
||||
}
|
||||
}
|
||||
|
||||
async #onJobPatch(patch) {
|
||||
// Handle job status updates
|
||||
const jobStatusRegExp = new RegExp(`^/users/([A-Za-z0-9]+)/jobs/([A-Za-z0-9]+)/status$`);
|
||||
const jobStatusPatch = patch
|
||||
.filter((operation) => {return jobStatusRegExp.test(operation.path);})
|
||||
.filter((operation) => {return operation.op === 'replace';});
|
||||
|
||||
for (let operation of jobStatusPatch) {
|
||||
const [match, userId, jobId] = operation.path.match(jobStatusRegExp);
|
||||
const user = await this.app.userHub.get(userId);
|
||||
const job = user.jobs[jobId];
|
||||
|
||||
this.app.ui.flash(`[<a href="/jobs/${jobId}">${job.title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
|
||||
}
|
||||
}
|
||||
}
|
126
app/static/js/app/extensions/ui.js
Normal file
126
app/static/js/app/extensions/ui.js
Normal file
@ -0,0 +1,126 @@
|
||||
nopaque.app.extensions.UI = class UI {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
init() {
|
||||
/* Pre-Initialization fixes */
|
||||
// #region
|
||||
|
||||
// Flask-WTF sets the standard HTML maxlength Attribute on input/textarea
|
||||
// elements to specify their maximum length (in characters). Unfortunatly
|
||||
// Materialize won't recognize the maxlength Attribute, instead it uses
|
||||
// the data-length Attribute. It's conversion time :)
|
||||
for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) {
|
||||
elem.dataset.length = elem.getAttribute('maxlength');
|
||||
elem.removeAttribute('maxlength');
|
||||
}
|
||||
|
||||
// To work around some limitations with the Form setup of Flask-WTF.
|
||||
// HTML option elements with an empty value are considered as placeholder
|
||||
// elements. The user should not be able to actively select these options.
|
||||
// So they get the disabled attribute.
|
||||
for (let optionElement of document.querySelectorAll('option[value=""]')) {
|
||||
optionElement.disabled = true;
|
||||
}
|
||||
|
||||
// TODO: Check why we are doing this.
|
||||
for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) {
|
||||
for (let c of optgroupElement.children) {
|
||||
optgroupElement.parentElement.insertAdjacentElement('afterbegin', c);
|
||||
}
|
||||
optgroupElement.remove();
|
||||
}
|
||||
// #endregion
|
||||
|
||||
|
||||
/* Initialize Materialize Components */
|
||||
// #region
|
||||
|
||||
// Automatically initialize Materialize Components that do not require
|
||||
// additional configuration.
|
||||
M.AutoInit();
|
||||
|
||||
// CharacterCounters
|
||||
// Materialize didn't include the CharacterCounter plugin within the
|
||||
// AutoInit method (maybe they forgot it?). Anyway... We do it here. :)
|
||||
M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)'));
|
||||
|
||||
// Header navigation processes and services Dropdown.
|
||||
M.Dropdown.init(
|
||||
document.querySelector('#navbar-data-processing-and-analysis-dropdown-trigger'),
|
||||
{
|
||||
constrainWidth: false,
|
||||
container: document.querySelector('#dropdowns'),
|
||||
coverTrigger: false
|
||||
}
|
||||
);
|
||||
|
||||
// Header navigation account Dropdown.
|
||||
M.Dropdown.init(
|
||||
document.querySelector('#navbar-account-dropdown-trigger'),
|
||||
{
|
||||
alignment: 'right',
|
||||
constrainWidth: false,
|
||||
container: document.querySelector('#dropdowns'),
|
||||
coverTrigger: false
|
||||
}
|
||||
);
|
||||
|
||||
// Terms of use modal
|
||||
M.Modal.init(
|
||||
document.querySelector('#terms-of-use-modal'),
|
||||
{
|
||||
dismissible: false,
|
||||
onCloseEnd: (modalElement) => {
|
||||
nopaque.requests.users.entity.acceptTermsOfUse();
|
||||
}
|
||||
}
|
||||
);
|
||||
// #endregion
|
||||
|
||||
|
||||
/* Initialize nopaque Components */
|
||||
// #region
|
||||
nopaque.resource_displays.AutoInit();
|
||||
nopaque.resource_lists.AutoInit();
|
||||
nopaque.forms.AutoInit();
|
||||
// #endregion
|
||||
}
|
||||
|
||||
flash(message, category) {
|
||||
let iconPrefix;
|
||||
|
||||
switch (category) {
|
||||
case 'corpus': {
|
||||
iconPrefix = '<i class="material-icons left">book</i>';
|
||||
break;
|
||||
}
|
||||
case 'job': {
|
||||
iconPrefix = '<i class="nopaque-icons left">J</i>';
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
iconPrefix = '<i class="material-icons left error-color-text">error</i>';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
iconPrefix = '<i class="material-icons left">notifications</i>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let toast = M.toast(
|
||||
{
|
||||
html: `
|
||||
<span>${iconPrefix}${message}</span>
|
||||
<button class="btn-flat toast-action white-text" data-toast-action="dismiss">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
`.trim()
|
||||
}
|
||||
);
|
||||
let dismissToastElement = toast.el.querySelector('.toast-action[data-toast-action="dismiss"]');
|
||||
dismissToastElement.addEventListener('click', () => {toast.dismiss();});
|
||||
}
|
||||
}
|
68
app/static/js/app/extensions/user-hub.js
Normal file
68
app/static/js/app/extensions/user-hub.js
Normal file
@ -0,0 +1,68 @@
|
||||
nopaque.app.extensions.UserHub = class UserHub extends EventTarget {
|
||||
#data;
|
||||
|
||||
constructor(app) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
|
||||
this.#data = {
|
||||
users: {},
|
||||
promises: {}
|
||||
};
|
||||
}
|
||||
|
||||
init() {
|
||||
this.app.users.socket.on('patch', (patch) => {this.#onPatch(patch)});
|
||||
}
|
||||
|
||||
add(userId) {
|
||||
if (!(userId in this.#data.promises)) {
|
||||
this.#data.promises[userId] = this.#add(userId);
|
||||
}
|
||||
|
||||
return this.#data.promises[userId];
|
||||
}
|
||||
|
||||
async #add(userId) {
|
||||
await this.app.users.subscribe(userId);
|
||||
this.#data.users[userId] = await this.app.users.get(userId);
|
||||
}
|
||||
|
||||
async get(userId) {
|
||||
await this.add(userId);
|
||||
return this.#data.users[userId];
|
||||
}
|
||||
|
||||
#onPatch(patch) {
|
||||
// Filter patch to only include operations on users that are initialized
|
||||
const filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`);
|
||||
const filteredPatch = patch.filter(operation => filterRegExp.test(operation.path));
|
||||
|
||||
// Apply patch
|
||||
jsonpatch.applyPatch(this.#data, filteredPatch);
|
||||
|
||||
// Notify event listeners
|
||||
const patchEventa = new CustomEvent('patch', {detail: filteredPatch});
|
||||
this.dispatchEvent(patchEventa);
|
||||
|
||||
// Notify event listeners. Event type: "patch *"
|
||||
const patchEvent = new CustomEvent('patch *', {detail: filteredPatch});
|
||||
this.dispatchEvent(patchEvent);
|
||||
|
||||
// Group patches by user id: {<user-id>: [op, ...], ...}
|
||||
const patches = {};
|
||||
const matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`);
|
||||
for (let operation of filteredPatch) {
|
||||
const [match, userId] = operation.path.match(matchRegExp);
|
||||
if (!(userId in patches)) {patches[userId] = [];}
|
||||
patches[userId].push(operation);
|
||||
}
|
||||
|
||||
// Notify event listeners. Event type: "patch <user-id>"
|
||||
for (let [userId, patch] of Object.entries(patches)) {
|
||||
const userPatchEvent = new CustomEvent(`patch ${userId}`, {detail: patch});
|
||||
this.dispatchEvent(userPatchEvent);
|
||||
}
|
||||
}
|
||||
}
|
1
app/static/js/app/index.js
Normal file
1
app/static/js/app/index.js
Normal file
@ -0,0 +1 @@
|
||||
nopaque.app = {};
|
@ -66,7 +66,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
|
||||
errorString += `${error.constructor.name}`;
|
||||
this.elements.error.innerText = errorString;
|
||||
this.elements.error.classList.remove('hide');
|
||||
app.flash(errorString, 'error');
|
||||
app.ui.flash(errorString, 'error');
|
||||
this.elements.progress.classList.add('hide');
|
||||
}
|
||||
this.app.enableActionElements();
|
||||
@ -239,7 +239,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
|
||||
if (subcorpus.selectedItems.size === 0) {
|
||||
this.elements.progress.classList.add('hide');
|
||||
this.app.enableActionElements();
|
||||
app.flash('No matches selected', 'error');
|
||||
app.ui.flash('No matches selected', 'error');
|
||||
return;
|
||||
}
|
||||
promise = subcorpus.o.partialExport([...subcorpus.selectedItems], 50);
|
||||
@ -298,7 +298,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
|
||||
let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
|
||||
subcorpus.o.drop().then(
|
||||
(cQiStatus) => {
|
||||
app.flash(`${subcorpus.o.name} deleted`, 'corpus');
|
||||
app.ui.flash(`${subcorpus.o.name} deleted`, 'corpus');
|
||||
delete this.data.subcorpora[subcorpus.o.name];
|
||||
this.settings.selectedSubcorpus = undefined;
|
||||
for (let subcorpusName in this.data.subcorpora) {
|
||||
@ -320,7 +320,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
|
||||
},
|
||||
(cqiError) => {
|
||||
let errorString = `${cqiError.code}: ${cqiError.constructor.name}`;
|
||||
app.flash(errorString, 'error');
|
||||
app.ui.flash(errorString, 'error');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -46,7 +46,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
|
||||
if ('description' in error) {errorString += `: ${error.description}`;}
|
||||
this.elements.error.innerText = errorString;
|
||||
this.elements.error.classList.remove('hide');
|
||||
app.flash(errorString, 'error');
|
||||
app.ui.flash(errorString, 'error');
|
||||
this.elements.progress.classList.add('hide');
|
||||
}
|
||||
this.app.enableActionElements();
|
||||
|
@ -101,7 +101,7 @@ nopaque.forms.BaseForm = class BaseForm {
|
||||
}
|
||||
}
|
||||
if (request.status === 500) {
|
||||
app.flash('Internal Server Error', 'error');
|
||||
app.ui.flash('Internal Server Error', 'error');
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
|
@ -25,16 +25,16 @@ nopaque.requests.JSONfetch = (input, init={}) => {
|
||||
let message = json.message;
|
||||
let category = json.category || 'message';
|
||||
if (message) {
|
||||
app.flash(message, category);
|
||||
app.ui.flash(message, category);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
app.flash(`[${response.status}]: ${response.statusText}`, 'error');
|
||||
app.ui.flash(`[${response.status}]: ${response.statusText}`, 'error');
|
||||
}
|
||||
);
|
||||
},
|
||||
(response) => {
|
||||
app.flash('Something went wrong', 'error');
|
||||
app.ui.flash('Something went wrong', 'error');
|
||||
reject(response);
|
||||
}
|
||||
);
|
||||
|
@ -52,22 +52,23 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
this.setElements(this.displayElement.querySelectorAll('.corpus-title'), title);
|
||||
async setTitle(title) {
|
||||
const corpusTitleElements = this.displayElement.querySelectorAll('.corpus-title');
|
||||
this.setElements(corpusTitleElements, title);
|
||||
}
|
||||
|
||||
setNumTokens(numTokens) {
|
||||
this.setElements(
|
||||
this.displayElement.querySelectorAll('.corpus-token-ratio'),
|
||||
`${numTokens}/${app.data.users[this.userId].corpora[this.corpusId].max_num_tokens}`
|
||||
);
|
||||
const corpusTokenRatioElements = this.displayElement.querySelectorAll('.corpus-token-ratio');
|
||||
const maxNumTokens = 2147483647;
|
||||
|
||||
this.setElements(corpusTokenRatioElements, `${numTokens}/${maxNumTokens}`);
|
||||
}
|
||||
|
||||
setDescription(description) {
|
||||
this.setElements(this.displayElement.querySelectorAll('.corpus-description'), description);
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
async setStatus(status) {
|
||||
let elements = this.displayElement.querySelectorAll('.action-button[data-action="analyze"]');
|
||||
for (let element of elements) {
|
||||
if (['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'].includes(status)) {
|
||||
@ -77,8 +78,10 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re
|
||||
}
|
||||
}
|
||||
elements = this.displayElement.querySelectorAll('.action-button[data-action="build-request"]');
|
||||
const user = await app.userHub.get(this.userId);
|
||||
const corpusFiles = user.corpora[this.corpusId].files;
|
||||
for (let element of elements) {
|
||||
if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(app.data.users[this.userId].corpora[this.corpusId].files).length > 0) {
|
||||
if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(corpusFiles.length > 0)) {
|
||||
element.classList.remove('disabled');
|
||||
} else {
|
||||
element.classList.add('disabled');
|
||||
|
@ -5,19 +5,14 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
|
||||
this.displayElement = displayElement;
|
||||
this.userId = this.displayElement.dataset.userId;
|
||||
this.isInitialized = false;
|
||||
if (this.userId) {
|
||||
app.subscribeUser(this.userId)
|
||||
.then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
});
|
||||
app.getUser(this.userId)
|
||||
.then((user) => {
|
||||
this.init(user);
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
if (this.userId === undefined) {return;}
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.init(user);
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
init(user) {throw 'Not implemented';}
|
||||
|
@ -14,12 +14,11 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
|
||||
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
|
||||
this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false;
|
||||
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
// TODO: Make this better understandable
|
||||
this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
|
@ -12,15 +12,16 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
this.corpusId = listContainerElement.dataset.corpusId;
|
||||
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
// TODO: Check if the following is better
|
||||
// let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations);
|
||||
// let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId);
|
||||
// this.add(filteredList);
|
||||
|
||||
// TODO: Make this better understandable
|
||||
this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
|
@ -11,12 +11,10 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
this.selectedItemIds = new Set();
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
if (this.userId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(this.aggregateData(user));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
@ -69,6 +67,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
<span class="disable-on-click"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td><a class="btn-floating service-color darken" data-service="corpus-analysis"><i class="material-icons">book</i></a></td>
|
||||
<td>
|
||||
<b class="title"></b><br>
|
||||
<i class="description"></i>
|
||||
@ -80,7 +79,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
<td>${values['current-user-is-following'] ? '<span><i class="left material-icons">visibility</i>Following</span>' : ''}</td>
|
||||
<td class="right-align">
|
||||
<a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a>
|
||||
<a class="list-action-trigger btn-floating darken waves-effect waves-light" data-list-action="view"><i class="material-icons">send</i></a>
|
||||
<a class="list-action-trigger btn-floating waves-effect waves-light" data-list-action="view"><i class="material-icons">send</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim();
|
||||
@ -119,6 +118,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
<span></span>
|
||||
</label>
|
||||
</th>
|
||||
<th></th>
|
||||
<th>Title and Description</th>
|
||||
<th>Owner</th>
|
||||
<th>Status</th>
|
||||
|
@ -8,8 +8,10 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
this.jobId = listContainerElement.dataset.jobId;
|
||||
if (this.userId === undefined || this.jobId === undefined) {return;}
|
||||
app.subscribeUser(this.userId);
|
||||
app.getUser(this.userId).then((user) => {
|
||||
// app.userHub.addEventListener('patch', (event) => {
|
||||
// if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
// });
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(Object.values(user.jobs[this.jobId].inputs));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
|
@ -12,12 +12,10 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
|
||||
this.selectedItemIds = new Set();
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
if (this.userId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(Object.values(user.jobs));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
@ -25,19 +23,19 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
|
||||
|
||||
get item() {
|
||||
return `
|
||||
<tr class="list-item clickable hoverable">
|
||||
<tr class="list-item clickable hoverable service-color lighten">
|
||||
<td>
|
||||
<label class="list-action-trigger" data-list-action="select">
|
||||
<input class="select-checkbox" type="checkbox">
|
||||
<span class="disable-on-click"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td><a class="btn-floating service-color darken" data-service="inherit"><i class="nopaque-icons service-icons" data-service="inherit"></i></a></td>
|
||||
<td><a class="btn-floating service-color darken"><i class="nopaque-icons service-icons"></i></a></td>
|
||||
<td><b class="title"></b><br><i class="description"></i></td>
|
||||
<td><span class="status badge new job-status-color job-status-text" data-badge-caption=""></span></td>
|
||||
<td class="right-align">
|
||||
<a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a>
|
||||
<a class="list-action-trigger btn-floating darken waves-effect waves-light" data-list-action="view"><i class="material-icons">send</i></a>
|
||||
<a class="list-action-trigger btn-floating service-color darken waves-effect waves-light" data-list-action="view"><i class="material-icons">send</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trim();
|
||||
|
@ -8,12 +8,10 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
this.jobId = listContainerElement.dataset.jobId;
|
||||
if (this.userId === undefined || this.jobId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(Object.values(user.jobs[this.jobId].results));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
|
@ -8,12 +8,10 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
|
||||
this.isInitialized = false;
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
if (this.userId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(Object.values(user.spacy_nlp_pipeline_models));
|
||||
this.isInitialized = true;
|
||||
});
|
||||
|
@ -8,21 +8,11 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
|
||||
this.isInitialized = false;
|
||||
this.userId = listContainerElement.dataset.userId;
|
||||
if (this.userId === undefined) {return;}
|
||||
app.subscribeUser(this.userId).then((response) => {
|
||||
app.sockets.users.on('patch_user', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
app.userHub.addEventListener('patch', (event) => {
|
||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||
});
|
||||
app.getUser(this.userId).then((user) => {
|
||||
app.userHub.get(this.userId).then((user) => {
|
||||
this.add(Object.values(user.tesseract_ocr_pipeline_models));
|
||||
for (let uncheckedCheckbox of this.listjs.list.querySelectorAll('input[data-checked="True"]')) {
|
||||
uncheckedCheckbox.setAttribute('checked', '');
|
||||
}
|
||||
if (user.role.name !== ('Administrator' || 'Contributor')) {
|
||||
for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) {
|
||||
switchElement.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
<div id="data-processing-and-analysis-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="card-panel primary-color white-text">
|
||||
<h4 class="m-3"><i class="material-icons left" style="font-size: inherit; line-height: inherit;">miscellaneous_services</i>Data Processing & Analysis</h4>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m6 l3 center-align hoverable" style="position: relative;">
|
||||
<a href="{{ url_for('services.file_setup_pipeline') }}" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"></a>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="file-setup-pipeline"></i>
|
||||
<br>
|
||||
<b class="service-color-text text-darken" data-service="file-setup-pipeline">File Setup</b>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col s12 m6 l3 center-align center-align hoverable" style="position: relative;">
|
||||
<a href="{{ url_for('services.tesseract_ocr_pipeline') }}" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"></a>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="tesseract-ocr-pipeline"></i>
|
||||
<br>
|
||||
<b class="service-color-text text-darken" data-service="tesseract-ocr-pipeline">Optical Character Recognition</b>
|
||||
<p class="light">nopaque converts your image data – like photos or scans – into text data through OCR making it machine readable. This step enables you to proceed with further computational analysis of your documents.</p>
|
||||
</div>
|
||||
{% if config.NOPAQUE_TRANSKRIBUS_ENABLED %}
|
||||
<div class="col s12 m6 l3 center-align center-align hoverable" style="position: relative;">
|
||||
<a href="{{ url_for('services.transkribus_htr_pipeline') }}" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"></a>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="transkribus-htr-pipeline"></i>
|
||||
<br>
|
||||
<b class="service-color-text text-darken" data-service="transkribus-htr-pipeline">Transkribus HTR Pipeline</b>
|
||||
<p class="light">nopaque converts your image data – like photos or scans – into text data through HTR making it machine readable. This step enables you to proceed with further computational analysis of your documents.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col s12 m6 l3 center-align center-align hoverable" style="position: relative;">
|
||||
<a href="{{ url_for('services.spacy_nlp_pipeline') }}" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"></a>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="spacy-nlp-pipeline"></i>
|
||||
<br>
|
||||
<b class="service-color-text text-darken" data-service="spacy-nlp-pipeline">Natural Language Processing</b>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col s12 m6 l3 center-align center-align hoverable" style="position: relative;">
|
||||
<a href="{{ url_for('services.corpus_analysis') }}" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"></a>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="corpus-analysis"></i>
|
||||
<br>
|
||||
<b class="service-color-text text-darken" data-service="corpus-analysis">Corpus analysis</b>
|
||||
<p class="light">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn-flat modal-close">Close</a>
|
||||
</div>
|
||||
</div>
|
@ -1,32 +0,0 @@
|
||||
<div id="terms-of-use-modal" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1 id="title">Terms of use</h1>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<div class="switch">
|
||||
<label>
|
||||
DE
|
||||
<input type="checkbox" id="terms-of-use-modal-switch">
|
||||
<span class="lever"></span>
|
||||
EN
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="terms-of-use-modal-content hide">
|
||||
{% include "main/terms_of_use_en.html.j2" %}
|
||||
</div>
|
||||
<div class="terms-of-use-modal-content">
|
||||
{% include "main/terms_of_use_de.html.j2" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span style="margin-right:20px;">I have taken note of the new GTC and agree to their validity in the context of my further use.</span>
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn">Yes</a>
|
||||
</div>
|
||||
</div>
|
@ -1,9 +1,60 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
<ul class="dropdown-content" id="nav-account-dropdown-content">
|
||||
<li><a href="#!">Logged in as {{ current_user.username }}<br><{{ current_user.email }}></a></li>
|
||||
<ul class="dropdown-content" id="navbar-data-processing-and-analysis-dropdown-content">
|
||||
<li {% if request.path == url_for('services.file_setup_pipeline') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('services.file_setup_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="file-setup-pipeline"></i>
|
||||
File Setup Pipeline
|
||||
</a>
|
||||
</li>
|
||||
<li {% if request.path == url_for('services.tesseract_ocr_pipeline') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('services.tesseract_ocr_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="tesseract-ocr-pipeline"></i>
|
||||
Tesseract OCR Pipeline
|
||||
</a>
|
||||
</li>
|
||||
{% if config.NOPAQUE_TRANSKRIBUS_ENABLED %}
|
||||
<li {% if request.path == url_for('services.transkribus_htr_pipeline') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('services.transkribus_htr_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="transkribus-htr-pipeline"></i>
|
||||
Transkribus HTR Pipeline
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li {% if request.path == url_for('services.spacy_nlp_pipeline') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('services.spacy_nlp_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="spacy-nlp-pipeline"></i>
|
||||
SpaCy NLP Pipeline
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" tabindex="-1"></li>
|
||||
<li><a href="{{ url_for('users.user', user_id=current_user.id) }}"><i class="material-icons">person</i>Your profile</a></li>
|
||||
<li><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>Settings</a></li>
|
||||
<li><a href="{{ url_for('auth.logout') }}"><i class="material-icons left">logout</i>Log out</a></li>
|
||||
<li {% if request.path == url_for('services.corpus_analysis') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('services.corpus_analysis') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="corpus-analysis"></i>
|
||||
Corpus Analyis
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<ul class="dropdown-content" id="navbar-account-dropdown-content">
|
||||
<li {% if request.path == url_for('users.user', user_id=current_user.id) %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('users.user', user_id=current_user.id) }}">
|
||||
<i class="material-icons">person</i>
|
||||
Your profile
|
||||
</a>
|
||||
</li>
|
||||
<li {% if request.path == url_for('settings.settings') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('settings.settings') }}">
|
||||
<i class="material-icons">settings</i>
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('auth.logout') }}">
|
||||
<i class="material-icons">logout</i>
|
||||
Log out
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -1,40 +1,45 @@
|
||||
<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">
|
||||
<div class="col s12 l3">
|
||||
<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">
|
||||
<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 m2">
|
||||
<span class="right"><b>Version {{ config.NOPAQUE_VERSION }}</b></span>
|
||||
</div>
|
||||
<div class="col s12 m7 right-align">
|
||||
<a class="btn-small waves-effect waves-light" href="{{ url_for('main.faq') }}"><i class="left material-icons">info_outline</i>Frequently Asked Questions</a>
|
||||
<a class="btn-small waves-effect waves-light" href="mailto:{{ config.NOPAQUE_SERVICE_DESK }}"><i class="left material-icons">mail</i>Report an issue</a>
|
||||
<a class="btn-small waves-effect waves-light" href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque" target="_blank"><i class="left material-icons">code</i>GitLab</a>
|
||||
</div>
|
||||
|
||||
<div class="col s12 l3">
|
||||
<h5 class="white-text">More Resources</h5>
|
||||
<ul>
|
||||
<li><a class="grey-text text-lighten-3" href="{{ url_for('main.faq') }}">Frequently asked questions</a></li>
|
||||
<li><a class="grey-text text-lighten-3" href="mailto:{{ config.NOPAQUE_SERVICE_DESK }}">Report an issue</a></li>
|
||||
<li><a class="grey-text text-lighten-3" href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque">GitLab (source code)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col s12 l4">
|
||||
<h5 class="white-text">Who made this?</h5>
|
||||
<p class="grey-text text-lighten-4">
|
||||
This software is developed by the SFB 1288 INF project at Bielefeld University.
|
||||
Thanks to all the people who made nopaque possible.
|
||||
<span class="red-text">♥</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col s12 l2">
|
||||
<br class="hide-on-med-and-down">
|
||||
<br class="hide-on-med-and-down">
|
||||
<a href="https://www.dfg.de/">
|
||||
<img class="responsive-img" src="{{ url_for('static', filename='images/logo_-_dfg.gif') }}">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-copyright">
|
||||
<div class="container">
|
||||
© 2024 Bielefeld University
|
||||
<a class="grey-text text-lighten-4 right" href="https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque/-/releases/{{ config.NOPAQUE_VERSION }}">Version {{ config.NOPAQUE_VERSION }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
1
app/templates/_base/icons.html.j2
Normal file
1
app/templates/_base/icons.html.j2
Normal file
@ -0,0 +1 @@
|
||||
<link href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}" rel="icon">
|
@ -1,5 +1,34 @@
|
||||
{% if current_user.is_authenticated and not current_user.terms_of_use_accepted %}
|
||||
{% include "_base/_modals/terms_of_use.html.j2" %}
|
||||
<div id="terms-of-use-modal" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1 id="title">Terms of use</h1>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<div class="switch">
|
||||
<label>
|
||||
DE
|
||||
<input type="checkbox" id="terms-of-use-modal-switch">
|
||||
<span class="lever"></span>
|
||||
EN
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="terms-of-use-modal-content hide">
|
||||
{% include "main/terms_of_use_en.html.j2" %}
|
||||
</div>
|
||||
<div class="terms-of-use-modal-content">
|
||||
{% include "main/terms_of_use_de.html.j2" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span style="margin-right:20px;">I have taken note of the new GTC and agree to their validity in the context of my further use.</span>
|
||||
<a href="#!" class="modal-close waves-effect waves-green btn">Yes</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "_base/_modals/data-processing-and-analysis.html.j2" %}
|
||||
|
105
app/templates/_base/navbar.html.j2
Normal file
105
app/templates/_base/navbar.html.j2
Normal file
@ -0,0 +1,105 @@
|
||||
<div class="navbar-fixed">
|
||||
<nav>
|
||||
<div class="nav-wrapper">
|
||||
{# menu icon #}
|
||||
{# small/medium devices #}
|
||||
<a href="#!" class="sidenav-trigger" data-target="sidenav">
|
||||
<i class="material-icons">menu</i>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{# nopaque logo #}
|
||||
{# large devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down h-100">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo.png') }}" alt="" class="mx-3 py-3 h-100">
|
||||
</a>
|
||||
|
||||
{# left aligned navigation items #}
|
||||
{# large devices #}
|
||||
<ul class="hide-on-med-and-down" style="margin-left: calc(57px + 1.5rem);">
|
||||
{# dashboard #}
|
||||
<li {% if request.path == url_for('main.dashboard') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('main.dashboard') }}">
|
||||
<i class="material-icons left">dashboard</i>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# data processing & analysis #}
|
||||
<li>
|
||||
<a href="#!" class="dropdown-trigger no-autoinit" data-target="navbar-data-processing-and-analysis-dropdown-content" id="navbar-data-processing-and-analysis-dropdown-trigger">
|
||||
<i class="material-icons left">miscellaneous_services</i>
|
||||
Data Processing & Analysis
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# contributions #}
|
||||
<li {% if request.path == url_for('contributions.index') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('contributions.index') }}">
|
||||
<i class="material-icons left">new_label</i>
|
||||
Contributions
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# social #}
|
||||
<li {% if request.path == url_for('main.social') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('main.social') }}">
|
||||
<i class="material-icons left">groups</i>
|
||||
Social
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
{# nopaque logo+wordmark+slogan #}
|
||||
{# large devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down h-100">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark+slogan.png') }}" alt="" class="mx-3 py-3 h-100">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# nopaque logo+wordmark #}
|
||||
{# small/medium devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo center hide-on-large-only h-100">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark.png') }}" alt="" class="py-3 h-100">
|
||||
</a>
|
||||
|
||||
{# right aligned navigation items #}
|
||||
{# large devices #}
|
||||
<ul class="right hide-on-med-and-down h-100">
|
||||
{# manual #}
|
||||
<li class="tooltipped {% if request.path == url_for('main.manual') %}active{% endif %}" data-position="bottom" data-tooltip="Manual">
|
||||
<a href="{{ url_for('main.manual') }}">
|
||||
<i class="material-icons">help_outline</i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# news #}
|
||||
<li class="tooltipped {% if request.path == url_for('main.news') %}active{% endif %}" data-position="bottom" data-tooltip="News">
|
||||
<a href="{{ url_for('main.news') }}">
|
||||
<i class="material-icons">newspaper</i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{# avatar #}
|
||||
<li class="h-100">
|
||||
<a href="#!" class="dropdown-trigger no-autoinit h-100" data-target="navbar-account-dropdown-content" id="navbar-account-dropdown-trigger">
|
||||
<span class="mr-3">{{ current_user.username }}</span>
|
||||
<img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="" class="circle py-3 h-100 right">
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{# log in #}
|
||||
<li {% if request.path == url_for('auth.login') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.login') }}">Log in</a>
|
||||
</li>
|
||||
|
||||
{# register #}
|
||||
<li {% if request.path == url_for('auth.register') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.register') }}" class="btn waves-effect waves-light primary-color lighten">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
@ -1,95 +0,0 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
{# menu icon #}
|
||||
{# shown for small/medium devices #}
|
||||
<a href="#!" class="sidenav-trigger" data-target="sidenav"><i class="material-icons">menu</i></a>
|
||||
|
||||
{# nopaque logo #}
|
||||
{# shown for large devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%;">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo.png') }}" alt="" class="mx-3 py-3" style="height: 100%;">
|
||||
</a>
|
||||
|
||||
{# left aligned navigation items #}
|
||||
{# shown for large devices #}
|
||||
<ul class="hide-on-med-and-down" style="margin-left: calc(57px + 1.5rem);">
|
||||
{# dashboard #}
|
||||
<li>
|
||||
<a href="{{ url_for('main.dashboard') }}">
|
||||
<i class="material-icons left">dashboard</i>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# processes & services #}
|
||||
<li>
|
||||
<a href="#data-processing-and-analysis-modal" class="modal-trigger">
|
||||
<i class="material-icons left">miscellaneous_services</i>
|
||||
Data Processing & Analysis
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# contributions #}
|
||||
<li>
|
||||
<a href="{{ url_for('contributions.index') }}">
|
||||
<i class="material-icons left">new_label</i>
|
||||
Contributions
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# social #}
|
||||
<li>
|
||||
<a href="{{ url_for('main.social') }}">
|
||||
<i class="material-icons left">groups</i>
|
||||
Social
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
{# nopaque logo+wordmark+slogan #}
|
||||
{# shown for large devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%;">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark+slogan.png') }}" alt="" class="mx-3 py-3" style="height: 100%;">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# nopaque logo+wordmark #}
|
||||
{# small/medium devices #}
|
||||
<a href="{{ url_for('main.index') }}" class="brand-logo center hide-on-large-only" style="height: 100%;">
|
||||
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark.png') }}" alt="" class="py-3" style="height: 100%;">
|
||||
</a>
|
||||
|
||||
{# right aligned navigation items #}
|
||||
{# large devices #}
|
||||
<ul class="right hide-on-med-and-down" style="height: 64px;">
|
||||
{# manual #}
|
||||
<li class="tooltipped" data-position="bottom" data-tooltip="Manual">
|
||||
<a href="{{ url_for('main.manual') }}">
|
||||
<i class="material-icons">help_outline</i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# news #}
|
||||
<li class="tooltipped" data-position="bottom" data-tooltip="News">
|
||||
<a href="{{ url_for('main.news') }}">
|
||||
<i class="material-icons">email</i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{# avatar #}
|
||||
<li style="height: 100%;">
|
||||
<a href="#!" class="dropdown-trigger no-autoinit" data-target="nav-account-dropdown-content" id="nav-account-dropdown-trigger" style="height: 100%;">
|
||||
<img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="" class="circle py-3" style="height: 100%;">
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{# log in #}
|
||||
<li {% if request.path == url_for('auth.login') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.login') }}">Log in</a>
|
||||
</li>
|
||||
{# register #}
|
||||
<li {% if request.path == url_for('auth.register') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.register') }}" class="waves-effect waves-light btn primary-color lighten">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
@ -5,69 +5,76 @@
|
||||
<script src="{{ url_for('static', filename='external/plotly.js/js/plotly.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='external/socket.io/js/socket.io.min.js') }}"></script>
|
||||
{% assets
|
||||
filters='rjsmin',
|
||||
output='gen/nopaque.%(version)s.js',
|
||||
'js/index.js',
|
||||
'js/app.js',
|
||||
'js/utils.js',
|
||||
filters='rjsmin',
|
||||
output='gen/nopaque.%(version)s.js',
|
||||
'js/index.js',
|
||||
'js/app.js',
|
||||
'js/app/index.js',
|
||||
'js/app/endpoints/index.js',
|
||||
'js/app/endpoints/users.js',
|
||||
'js/app/extensions/index.js',
|
||||
'js/app/extensions/toaster.js',
|
||||
'js/app/extensions/ui.js',
|
||||
'js/app/extensions/user-hub.js',
|
||||
'js/utils.js',
|
||||
|
||||
'js/forms/index.js',
|
||||
'js/forms/base-form.js',
|
||||
'js/forms/create-contribution-form.js',
|
||||
'js/forms/create-corpus-file-form.js',
|
||||
'js/forms/create-job-form.js',
|
||||
'js/forms/index.js',
|
||||
'js/forms/base-form.js',
|
||||
'js/forms/create-contribution-form.js',
|
||||
'js/forms/create-corpus-file-form.js',
|
||||
'js/forms/create-job-form.js',
|
||||
|
||||
'js/resource-displays/index.js',
|
||||
'js/resource-displays/resource-display.js',
|
||||
'js/resource-displays/corpus-display.js',
|
||||
'js/resource-displays/job-display.js',
|
||||
'js/resource-displays/index.js',
|
||||
'js/resource-displays/resource-display.js',
|
||||
'js/resource-displays/corpus-display.js',
|
||||
'js/resource-displays/job-display.js',
|
||||
|
||||
'js/resource-lists/index.js',
|
||||
'js/resource-lists/resource-list.js',
|
||||
'js/resource-lists/admin-user-list.js',
|
||||
'js/resource-lists/corpus-file-list.js',
|
||||
'js/resource-lists/corpus-follower-list.js',
|
||||
'js/resource-lists/corpus-list.js',
|
||||
'js/resource-lists/corpus-text-info-list.js',
|
||||
'js/resource-lists/corpus-token-list.js',
|
||||
'js/resource-lists/detailed-public-corpus-list.js',
|
||||
'js/resource-lists/job-input-list.js',
|
||||
'js/resource-lists/job-list.js',
|
||||
'js/resource-lists/job-result-list.js',
|
||||
'js/resource-lists/public-corpus-list.js',
|
||||
'js/resource-lists/public-user-list.js',
|
||||
'js/resource-lists/spacy-nlp-pipeline-model-list.js',
|
||||
'js/resource-lists/tesseract-ocr-pipeline-model-list.js',
|
||||
'js/resource-lists/index.js',
|
||||
'js/resource-lists/resource-list.js',
|
||||
'js/resource-lists/admin-user-list.js',
|
||||
'js/resource-lists/corpus-file-list.js',
|
||||
'js/resource-lists/corpus-follower-list.js',
|
||||
'js/resource-lists/corpus-list.js',
|
||||
'js/resource-lists/corpus-text-info-list.js',
|
||||
'js/resource-lists/corpus-token-list.js',
|
||||
'js/resource-lists/detailed-public-corpus-list.js',
|
||||
'js/resource-lists/job-input-list.js',
|
||||
'js/resource-lists/job-list.js',
|
||||
'js/resource-lists/job-result-list.js',
|
||||
'js/resource-lists/public-corpus-list.js',
|
||||
'js/resource-lists/public-user-list.js',
|
||||
'js/resource-lists/spacy-nlp-pipeline-model-list.js',
|
||||
'js/resource-lists/tesseract-ocr-pipeline-model-list.js',
|
||||
|
||||
'js/requests/index.js',
|
||||
'js/requests/admin.js',
|
||||
'js/requests/contributions.js',
|
||||
'js/requests/corpora.js',
|
||||
'js/requests/jobs.js',
|
||||
'js/requests/users.js',
|
||||
'js/requests/index.js',
|
||||
'js/requests/admin.js',
|
||||
'js/requests/contributions.js',
|
||||
'js/requests/corpora.js',
|
||||
'js/requests/jobs.js',
|
||||
'js/requests/users.js',
|
||||
|
||||
'js/corpus-analysis/index.js',
|
||||
'js/corpus-analysis/cqi/index.js',
|
||||
'js/corpus-analysis/cqi/constants.js',
|
||||
'js/corpus-analysis/cqi/errors.js',
|
||||
'js/corpus-analysis/cqi/status.js',
|
||||
'js/corpus-analysis/cqi/api/index.js',
|
||||
'js/corpus-analysis/cqi/api/client.js',
|
||||
'js/corpus-analysis/cqi/models/index.js',
|
||||
'js/corpus-analysis/cqi/models/resource.js',
|
||||
'js/corpus-analysis/cqi/models/attributes.js',
|
||||
'js/corpus-analysis/cqi/models/subcorpora.js',
|
||||
'js/corpus-analysis/cqi/models/corpora.js',
|
||||
'js/corpus-analysis/cqi/client.js',
|
||||
'js/corpus-analysis/query-builder/index.js',
|
||||
'js/corpus-analysis/query-builder/element-references.js',
|
||||
'js/corpus-analysis/query-builder/query-builder.js',
|
||||
'js/corpus-analysis/query-builder/structural-attribute-builder-functions.js',
|
||||
'js/corpus-analysis/query-builder/token-attribute-builder-functions.js',
|
||||
'js/corpus-analysis/app.js',
|
||||
'js/corpus-analysis/concordance-extension.js',
|
||||
'js/corpus-analysis/reader-extension.js',
|
||||
'js/corpus-analysis/static-visualization-extension.js'
|
||||
'js/corpus-analysis/index.js',
|
||||
'js/corpus-analysis/cqi/index.js',
|
||||
'js/corpus-analysis/cqi/constants.js',
|
||||
'js/corpus-analysis/cqi/errors.js',
|
||||
'js/corpus-analysis/cqi/status.js',
|
||||
'js/corpus-analysis/cqi/api/index.js',
|
||||
'js/corpus-analysis/cqi/api/client.js',
|
||||
'js/corpus-analysis/cqi/models/index.js',
|
||||
'js/corpus-analysis/cqi/models/resource.js',
|
||||
'js/corpus-analysis/cqi/models/attributes.js',
|
||||
'js/corpus-analysis/cqi/models/subcorpora.js',
|
||||
'js/corpus-analysis/cqi/models/corpora.js',
|
||||
'js/corpus-analysis/cqi/client.js',
|
||||
'js/corpus-analysis/query-builder/index.js',
|
||||
'js/corpus-analysis/query-builder/element-references.js',
|
||||
'js/corpus-analysis/query-builder/query-builder.js',
|
||||
'js/corpus-analysis/query-builder/structural-attribute-builder-functions.js',
|
||||
'js/corpus-analysis/query-builder/token-attribute-builder-functions.js',
|
||||
'js/corpus-analysis/app.js',
|
||||
'js/corpus-analysis/concordance-extension.js',
|
||||
'js/corpus-analysis/reader-extension.js',
|
||||
'js/corpus-analysis/static-visualization-extension.js'
|
||||
-%}
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets -%}
|
||||
@ -77,26 +84,22 @@
|
||||
const app = new nopaque.App();
|
||||
app.init();
|
||||
|
||||
{% if current_user.is_authenticated -%}
|
||||
// TODO: Set this as a property of the app object
|
||||
{% if current_user.is_authenticated %}
|
||||
const currentUserId = {{ current_user.hashid|tojson }};
|
||||
|
||||
// Subscribe to the current user's data events
|
||||
app.subscribeUser(currentUserId)
|
||||
app.userHub.add(currentUserId)
|
||||
.catch((error) => {throw JSON.stringify(error);});
|
||||
|
||||
// Get the current user's data
|
||||
app.getUser(currentUserId, true, true)
|
||||
.catch((error) => {throw JSON.stringify(error);});
|
||||
|
||||
{% if not current_user.terms_of_use_accepted -%}
|
||||
{% if not current_user.terms_of_use_accepted %}
|
||||
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
const currentUserId = null;
|
||||
{% endif %}
|
||||
|
||||
// Display flashed messages
|
||||
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {
|
||||
app.flash(message, message);
|
||||
app.ui.flash(message, message);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -104,7 +107,7 @@
|
||||
let languageModalSwitch = document.querySelector('#terms-of-use-modal-switch');
|
||||
let termsOfUseModalContent = document.querySelectorAll('.terms-of-use-modal-content');
|
||||
if (languageModalSwitch) {
|
||||
languageModalSwitch.addEventListener('change', function() {
|
||||
languageModalSwitch.addEventListener('change', () => {
|
||||
termsOfUseModalContent.forEach(content => {
|
||||
content.classList.toggle('hide');
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
<ul class="sidenav {{ 'sidenav-fixed' if sidenav_fixed else '' }}" id="sidenav">
|
||||
<ul class="sidenav" id="sidenav">
|
||||
{% if current_user.is_authenticated %}
|
||||
{# user view #}
|
||||
<li>
|
||||
<div class="user-view">
|
||||
@ -8,68 +9,113 @@
|
||||
<a><span class="white-text email">{{ current_user.email }}</span></a>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# general items #}
|
||||
<li {% if request.path == url_for('main.news') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('main.news') }}"><i class="material-icons">email</i>News</a>
|
||||
</li>
|
||||
{% if current_user.is_authenticated %}
|
||||
{# dashboard #}
|
||||
<li {% if request.path == url_for('main.dashboard') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a>
|
||||
</li>
|
||||
|
||||
{# contributions #}
|
||||
<li {% if request.path == url_for('contributions.index') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('contributions.index') }}">
|
||||
<i class="material-icons left">new_label</i>
|
||||
Contributions
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# social #}
|
||||
<li {% if request.path == url_for('main.social') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('main.social') }}"><i class="material-icons">groups</i>Social</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# news #}
|
||||
<li {% if request.path == url_for('main.news') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('main.news') }}"><i class="material-icons">newspaper</i>News</a>
|
||||
</li>
|
||||
|
||||
{# manual #}
|
||||
<li {% if request.path == url_for('main.manual') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('main.manual') }}"><i class="material-icons">help_outline</i>Manual</a>
|
||||
</li>
|
||||
|
||||
{# processes & services items #}
|
||||
{% if current_user.is_authenticated %}
|
||||
{# data processing & analysis section #}
|
||||
<li><div class="divider"></div></li>
|
||||
<li><a class="subheader">Data Processing & Analysis</a></li>
|
||||
|
||||
{# file setup pipeline #}
|
||||
<li class="service-color service-color-border border-darken" data-service="file-setup-pipeline" style="border-left: 10px solid;">
|
||||
<a class="waves-effect" href="{{ url_for('services.file_setup_pipeline') }}"><i class="nopaque-icons service-icons" data-service="file-setup-pipeline"></i>File setup</a>
|
||||
</li>
|
||||
|
||||
{# tesseract ocr pipeline #}
|
||||
<li class="service-color service-color-border border-darken mt-1" data-service="tesseract-ocr-pipeline" style="border-left: 10px solid;">
|
||||
<a class="waves-effect" href="{{ url_for('services.tesseract_ocr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="tesseract-ocr-pipeline"></i>OCR</a>
|
||||
</li>
|
||||
|
||||
{% if config.NOPAQUE_TRANSKRIBUS_ENABLED %}
|
||||
{# transkribus htr pipeline #}
|
||||
<li class="service-color service-color-border border-darken mt-1" data-service="transkribus-htr-pipeline" style="border-left: 10px solid;">
|
||||
<a class="waves-effect" href="{{ url_for('services.transkribus_htr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="transkribus-htr-pipeline"></i>HTR</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# spacy nlp pipeline #}
|
||||
<li class="service-color service-color-border border-darken mt-1" data-service="spacy-nlp-pipeline" style="border-left: 10px solid;">
|
||||
<a class="waves-effect" href="{{ url_for('services.spacy_nlp_pipeline') }}"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>NLP</a>
|
||||
</li>
|
||||
|
||||
{# corpus analysis #}
|
||||
<li class="service-color service-color-border border-darken mt-1" data-service="corpus-analysis" style="border-left: 10px solid;">
|
||||
<a class="waves-effect" href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# account items #}
|
||||
<li class="hide-on-large-only"><div class="divider"></div></li>
|
||||
<li class="hide-on-large-only"><a class="subheader">Account</a></li>
|
||||
<li>
|
||||
{# account section #}
|
||||
<li><div class="divider"></div></li>
|
||||
<li><a class="subheader">Account</a></li>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{# my profile #}
|
||||
<li {% if request.path == url_for('users.user', user_id=current_user.id) %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('users.user', user_id=current_user.id) }}"><i class="material-icons">person</i>My Profile</a>
|
||||
</li>
|
||||
<li class="hide-on-large-only">
|
||||
|
||||
{# settings #}
|
||||
<li {% if request.path == url_for('settings.settings') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>Settings</a>
|
||||
</li>
|
||||
<li class="hide-on-large-only">
|
||||
|
||||
{# log out #}
|
||||
<li>
|
||||
<a class="waves-effect" href="{{ url_for('auth.logout') }}"><i class="material-icons">logout</i>Log out</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{# log in #}
|
||||
<li {% if request.path == url_for('auth.login') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.login') }}">Log in</a>
|
||||
</li>
|
||||
|
||||
{# administration items #}
|
||||
{% if current_user.can('ADMINISTRATE') %}
|
||||
{# register #}
|
||||
<li {% if request.path == url_for('auth.register') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('auth.register') }}" class="btn waves-effect waves-light">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.can('ADMINISTRATE') %}
|
||||
{# administration section #}
|
||||
<li><div class="divider"></div></li>
|
||||
<li><a class="subheader">Administration</a></li>
|
||||
|
||||
{# corpora #}
|
||||
<li>
|
||||
<a class="waves-effect" href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">I</i>Corpora</a>
|
||||
</li>
|
||||
|
||||
{# users #}
|
||||
<li>
|
||||
<a class="waves-effect" href="{{ url_for('admin.users') }}"><i class="material-icons">manage_accounts</i>Users</a>
|
||||
</li>
|
||||
|
@ -1,21 +1,23 @@
|
||||
<link href="{{ url_for('static', filename='external/material-design-icons/css/material-icons.css') }}" rel="stylesheet">
|
||||
{% assets
|
||||
output='gen/nopaque.%(version)s.css',
|
||||
'css/materialize.css',
|
||||
'css/materialize.override.css',
|
||||
'css/nopaque-icons.css',
|
||||
'css/theme-colors.css',
|
||||
'css/corpus-status-colors.css',
|
||||
'css/corpus-status-text.css',
|
||||
'css/job-status-colors.css',
|
||||
'css/job-status-text.css',
|
||||
'css/service-colors.css',
|
||||
'css/pagination.css',
|
||||
'css/service-icons.css',
|
||||
'css/s-attr-colors.css',
|
||||
'css/spacing.css',
|
||||
'css/status-spinner.css',
|
||||
'css/utils.css'
|
||||
-%}
|
||||
output='gen/nopaque.%(version)s.css',
|
||||
'css/materialize.css',
|
||||
'css/materialize.override.css',
|
||||
'css/nopaque-icons.css',
|
||||
'css/theme-colors.css',
|
||||
'css/corpus-status-colors.css',
|
||||
'css/corpus-status-text.css',
|
||||
'css/height.css',
|
||||
'css/job-status-colors.css',
|
||||
'css/job-status-text.css',
|
||||
'css/service-colors.css',
|
||||
'css/pagination.css',
|
||||
'css/service-icons.css',
|
||||
'css/s-attr-colors.css',
|
||||
'css/spacing.css',
|
||||
'css/status-spinner.css',
|
||||
'css/utils.css',
|
||||
'css/width.css'
|
||||
%}
|
||||
<link href="{{ ASSET_URL }}" rel="stylesheet">
|
||||
{% endassets -%}
|
||||
{% endassets %}
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12 m8 offset-m2">
|
||||
<div class="col s12 l8 offset-l2">
|
||||
<h1 id="title">{{ title }}</h1>
|
||||
<p>Want to boost your research and get going? Nopaque is free and no download is needed. <a href="{{ url_for('.register') }}">Register now</a>!</p>
|
||||
|
||||
@ -15,14 +15,14 @@
|
||||
{{ wtf.render_field(form.user, material_icon='person') }}
|
||||
{{ wtf.render_field(form.password, material_icon='vpn_key') }}
|
||||
<div class="row">
|
||||
<div class="col s6 left-align">
|
||||
<a href="{{ url_for('.reset_password_request') }}">Forgot your password?</a>
|
||||
</div>
|
||||
<div class="col s6 right-align">
|
||||
<div class="col s12 l6">
|
||||
{{ wtf.render_field(form.remember_me) }}
|
||||
</div>
|
||||
<div class="col s12 l6 right-align">
|
||||
<a class="mr-3" href="{{ url_for('.reset_password_request') }}">Forgot your password?</a>
|
||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
||||
</div>
|
||||
</div>
|
||||
{{ wtf.render_field(form.submit, material_icon='send', class_='width-100') }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12 m8 offset-m2">
|
||||
<div class="col s12 l8 offset-l2">
|
||||
<h1 id="title">{{ title }}</h1>
|
||||
<p>
|
||||
Simply enter a username and password to receive your registration email.
|
||||
@ -22,14 +22,18 @@
|
||||
{{ wtf.render_field(form.username, material_icon='person') }}
|
||||
{{ wtf.render_field(form.password, material_icon='vpn_key') }}
|
||||
{{ wtf.render_field(form.password_2, material_icon='vpn_key') }}
|
||||
{{ wtf.render_field(form.email, class_='validate', material_icon='email', type='email') }}
|
||||
<br>
|
||||
{{ wtf.render_field(form.terms_of_use_accepted, type='checkbox')}}
|
||||
<p></p>
|
||||
<br>
|
||||
{{ wtf.render_field(form.submit, class_='width-100', material_icon='send') }}
|
||||
{{ wtf.render_field(form.email, material_icon='email', type='email') }}
|
||||
<div class="row">
|
||||
<div class="col s12 l6">
|
||||
{{ wtf.render_field(form.terms_of_use_accepted)}}
|
||||
</div>
|
||||
<div class="col s12 l6 right-align">
|
||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
@ -13,7 +13,9 @@
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.render_field(form.password) }}
|
||||
{{ wtf.render_field(form.password_2) }}
|
||||
{{ wtf.render_field(form.submit, class_='width-100', material_icon='send') }}
|
||||
<div class="right-align">
|
||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -4,15 +4,17 @@
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col s12 m8 offset-m2">
|
||||
<div class="col s12 l8 offset-l2">
|
||||
<h1 id="title">{{ title }}</h1>
|
||||
<p>After entering your email address you will receive instructions on how to reset your password.</p>
|
||||
|
||||
<form method="POST">
|
||||
<div class="card-panel">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.render_field(form.email, class_='validate', material_icon='email', type='email') }}
|
||||
{{ wtf.render_field(form.submit, class_='width-100', material_icon='send') }}
|
||||
{{ wtf.render_field(form.email, material_icon='email', type='email') }}
|
||||
<div class="right-align">
|
||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,103 +1,72 @@
|
||||
{% if title is not defined %}
|
||||
{% set title = 'nopaque' %}
|
||||
{% endif %}
|
||||
|
||||
{% if sidenav_fixed is not defined %}
|
||||
{% set sidenav_fixed = false %}
|
||||
{% endif %}
|
||||
|
||||
{% if sticky_footer is not defined %}
|
||||
{% set sticky_footer = true %}
|
||||
{% endif %}
|
||||
|
||||
{% if navbar_fixed is not defined %}
|
||||
{% set navbar_fixed = true %}
|
||||
{% endif %}
|
||||
|
||||
{% if navbar_extended is not defined %}
|
||||
{% set navbar_extended = false %}
|
||||
{% set title = 'nopaque' %}
|
||||
{% endif %}
|
||||
|
||||
{% block doc %}
|
||||
<!DOCTYPE html>
|
||||
<html {% block html_attribs %}lang="en"{% endblock html_attribs %}>
|
||||
<html {% block html_attributes %}lang="en"{% endblock html_attributes %}>
|
||||
{% block html %}
|
||||
<head>
|
||||
<head {% block head_attributes %}{% endblock head_attributes %}>
|
||||
{% block head %}
|
||||
{% block metas %}
|
||||
{% include "_base/metas.html.j2" %}
|
||||
{% include '_base/metas.html.j2' %}
|
||||
{% endblock metas %}
|
||||
|
||||
<title {% block title_attribs %}{% endblock title_attribs %}>
|
||||
{%- block title %}{{ title }}{% endblock title -%}
|
||||
{% block title %}
|
||||
{{ title }}
|
||||
{% endblock title %}
|
||||
</title>
|
||||
|
||||
<link href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}" rel="icon">
|
||||
{% block icons %}
|
||||
{% include '_base/icons.html.j2' %}
|
||||
{% endblock icons %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{% include "_base/stylesheets.html.j2" %}
|
||||
{% endblock stylesheets %}
|
||||
{% block styles %}
|
||||
{% include '_base/stylesheets.html.j2' %}
|
||||
{% endblock styles %}
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body {% block body_attribs %}data-sidenav-fixed="{{ sidenav_fixed }}" data-sticky-footer="{{ sticky_footer }}"{% endblock body_attribs %}>
|
||||
<body {% block body_attributes %}{% endblock body_attributes %}>
|
||||
{% block body %}
|
||||
<header {% block header_attribs %}{% endblock header_attribs %}>
|
||||
<header {% block header_attributes %}{% endblock header_attributes %}>
|
||||
{% block header %}
|
||||
{% if navbar_fixed %}
|
||||
<div class="navbar-fixed">
|
||||
{% endif %}
|
||||
<nav {% block navbar_attribs %}{% if navbar_extended %}class="nav-extended"{% endif %}{% endblock navbar_attribs %}>
|
||||
{% block navbar %}
|
||||
<div {% block navbar_primary_content_attribs %}class="nav-wrapper"{% endblock navbar_primary_content_attribs %}>
|
||||
{% block navbar_primary_content %}
|
||||
{% include "_base/navbar_primary_content.html.j2" %}
|
||||
{% endblock navbar_primary_content %}
|
||||
</div>
|
||||
{% if navbar_extended %}
|
||||
<div {% block navbar_secondary_content_attribs %}class="nav-content"{% endblock navbar_secondary_content_attribs %}>
|
||||
{% block navbar_secondary_content %}
|
||||
{% endblock navbar_secondary_content %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock navbar %}
|
||||
</nav>
|
||||
{% if navbar_fixed %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block navbar %}
|
||||
{% include '_base/navbar.html.j2' %}
|
||||
{% endblock navbar %}
|
||||
|
||||
{% block sidenav %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% include "_base/sidenav.html.j2" %}
|
||||
{% endif %}
|
||||
{% include '_base/sidenav.html.j2' %}
|
||||
{% endblock sidenav %}
|
||||
{% endblock header %}
|
||||
</header>
|
||||
|
||||
<main {% block main_attribs %}{% endblock main_attribs %}>
|
||||
<main {% block main_attributes %}{% endblock main_attributes %}>
|
||||
{% block main %}
|
||||
{% block page_content %}{% endblock page_content %}
|
||||
{% endblock main %}
|
||||
|
||||
<div id="dropdowns">
|
||||
{% block dropdowns %}
|
||||
{% include "_base/dropdowns.html.j2" %}
|
||||
{% endblock dropdowns %}
|
||||
</div>
|
||||
|
||||
<div id="modals">
|
||||
{% block modals %}
|
||||
{% include "_base/modals.html.j2" %}
|
||||
{% endblock modals %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer {% block footer_attribs %}class="page-footer"{% endblock footer_attribs %}>
|
||||
<footer {% block footer_attributes %}class="page-footer"{% endblock footer_attributes %}>
|
||||
{% block footer %}
|
||||
{% include "_base/footer.html.j2" %}
|
||||
{% include '_base/footer.html.j2' %}
|
||||
{% endblock footer %}
|
||||
</footer>
|
||||
|
||||
<div {% block dropdowns_attributes %}id="dropdowns"{% endblock dropdowns_attributes %}>
|
||||
{% block dropdowns %}
|
||||
{% include '_base/dropdowns.html.j2' %}
|
||||
{% endblock dropdowns %}
|
||||
</div>
|
||||
|
||||
<div {% block modals_attributes %}id="modals"{% endblock modals_attributes %}>
|
||||
{% block modals %}
|
||||
{% include '_base/modals.html.j2' %}
|
||||
{% endblock modals %}
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
{% include "_base/scripts.html.j2" %}
|
||||
{% include '_base/scripts.html.j2' %}
|
||||
{% endblock scripts %}
|
||||
{% endblock body %}
|
||||
</body>
|
||||
|
@ -5,21 +5,20 @@
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1 id="title">{{ title }}</h1>
|
||||
<p>Here you can see and edit the models that you have created. You can also create new models.</p>
|
||||
<p>
|
||||
Upload your own language models into nopaque. This is useful for
|
||||
working with different languages that are not available as standard in
|
||||
nopaque or if a you want to work with a language model that you have
|
||||
developed by yourself. Uploaded models can be found in the model list
|
||||
of the corresponding service and can be used immediately.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="spacy-nlp-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
<a href="{{ url_for('.spacy_nlp_pipeline_models.create') }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Create</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 l4">
|
||||
<h4>Tesseract OCR Pipeline Models</h4>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="col s12 l8">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="tesseract-ocr-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
|
||||
@ -29,6 +28,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 l4">
|
||||
<h4>SpaCy NLP Pipeline Models</h4>
|
||||
</div>
|
||||
|
||||
<div class="col s12 l8">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="spacy-nlp-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
<a href="{{ url_for('.spacy_nlp_pipeline_models.create') }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Create</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
@ -14,8 +14,11 @@
|
||||
{% endblock stylesheets %}
|
||||
|
||||
|
||||
{% block navbar_secondary_content %}
|
||||
<ul class="tabs tabs-transparent no-autoinit" id="corpus-analysis-extension-tabs">
|
||||
{% block main_attributes %}class="service-color lighten" data-service="corpus-analysis" id="corpus-analysis-container"{% endblock main_attributes %}
|
||||
|
||||
|
||||
{% block page_content %}
|
||||
<ul class="tabs no-autoinit" id="corpus-analysis-extension-tabs">
|
||||
<li class="tab">
|
||||
<a class="active" href="#corpus-analysis-home-container"><i class="nopaque-icons service-icons left" data-service="corpus-analysis" style="line-height: inherit;"></i>Corpus analysis</a>
|
||||
</li>
|
||||
@ -26,13 +29,7 @@
|
||||
<a href="#corpus-analysis-reader-container"><i class="material-icons left" style="line-height: inherit;">{{ reader_extension.icon }}</i>{{ reader_extension.name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock navbar_secondary_content %}
|
||||
|
||||
|
||||
{% block main_attribs %} class="service-color lighten" data-service="corpus-analysis" id="corpus-analysis-container" style="margin-top: 48px;"{% endblock main_attribs %}
|
||||
|
||||
|
||||
{% block page_content %}
|
||||
<div id="corpus-analysis-home-container">
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
@ -72,7 +69,9 @@
|
||||
{{ super() }}
|
||||
<div class="modal no-autoinit" id="corpus-analysis-init-modal">
|
||||
<div class="modal-content">
|
||||
<h4>We are preparing your analysis session</h4>
|
||||
<div class="card-panel primary-color white-text" data-service="corpus-analysis">
|
||||
<h4 class="m-3"><i class="material-icons left" style="font-size: inherit; line-height: inherit;">hourglass_empty</i>We are preparing your analysis session</h4>
|
||||
</div>
|
||||
<p>
|
||||
Our server works as hard as it can to prepare your analysis session. Please be patient and give it some time.<br>
|
||||
If initialization takes longer than usual or an error occurs, <a onclick="window.location.reload()" href="#">reload the page</a>.
|
||||
|
@ -8,7 +8,7 @@
|
||||
{% endblock stylesheets %}
|
||||
|
||||
|
||||
{% block main_attribs %} class="service-color lighten" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||
{% block main_attributes %} class="service-color lighten" data-service="corpus-analysis"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -364,8 +364,8 @@ shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||
navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value)
|
||||
.then(
|
||||
() => {app.flash('Copied!');},
|
||||
() => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');}
|
||||
() => {app.ui.flash('Copied!');},
|
||||
() => {app.ui.flash('Could not copy to clipboard. Please copy manually.', 'error');}
|
||||
);
|
||||
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="corpus-analysis"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="corpus-analysis"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="corpus-analysis"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -396,8 +396,8 @@ shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||
navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value)
|
||||
.then(
|
||||
() => {app.flash('Copied!');},
|
||||
() => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');}
|
||||
() => {app.ui.flash('Copied!');},
|
||||
() => {app.ui.flash('Could not copy to clipboard. Please copy manually.', 'error');}
|
||||
);
|
||||
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "base.html.j2" %}
|
||||
|
||||
{% block main_attribs %} class="service-scheme" data-service="{{ job.service }}"{% endblock main_attribs %}
|
||||
{% block main_attributes %} class="service-color lighten" data-service="{{ job.service }}"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
|
@ -41,10 +41,58 @@
|
||||
<div class="job-list" data-user-id="{{ current_user.hashid }}"></div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
<p><a href="#data-processing-and-analysis-modal" class="btn modal-trigger waves-effect waves-light">Create job<i class="material-icons right">add</i></a></p>
|
||||
<p><a data-target="dashboard-create-job-dropdown-content" class="btn waves-effect waves-light dropdown-trigger no-autoinit" id="dashboard-create-job-dropdown-trigger">Create job<i class="material-icons right">add</i></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
||||
|
||||
{% block dropdowns %}
|
||||
{{ super() }}
|
||||
<ul class="dropdown-content" id="dashboard-create-job-dropdown-content">
|
||||
<li>
|
||||
<a href="{{ url_for('services.file_setup_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="file-setup-pipeline"></i>
|
||||
File Setup Pipeline
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('services.tesseract_ocr_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="tesseract-ocr-pipeline"></i>
|
||||
Tesseract OCR Pipeline
|
||||
</a>
|
||||
</li>
|
||||
{% if config.NOPAQUE_TRANSKRIBUS_ENABLED %}
|
||||
<li>
|
||||
<a href="{{ url_for('services.transkribus_htr_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="transkribus-htr-pipeline"></i>
|
||||
Transkribus HTR Pipeline
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ url_for('services.spacy_nlp_pipeline') }}">
|
||||
<i class="nopaque-icons service-icons service-color-text text-darken" data-service="spacy-nlp-pipeline"></i>
|
||||
SpaCy NLP Pipeline
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock dropdowns %}
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
M.Dropdown.init(
|
||||
document.querySelector('#dashboard-create-job-dropdown-trigger'),
|
||||
{
|
||||
constrainWidth: false,
|
||||
container: document.querySelector('#dropdowns'),
|
||||
coverTrigger: false
|
||||
}
|
||||
);
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "base.html.j2" %}
|
||||
|
||||
{% block main_attribs %}class="service-color lighten" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="corpus-analysis"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<div class="col s12 m3 push-m9">
|
||||
<div class="center-align">
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="corpus-analysis"></i>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -26,8 +26,7 @@
|
||||
<div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div>
|
||||
</div>
|
||||
<div class="card-action right-align">
|
||||
<a class="btn disabled waves-effect waves-light" href="{{ url_for('corpora.import_corpus') }}">Import Corpus<i class="material-icons right">import_export</i></a>
|
||||
<a class="btn waves-effect waves-light" href="{{ url_for('corpora.create_corpus') }}">Create corpus<i class="material-icons right">add</i></a>
|
||||
<a class="btn service-color darken waves-effect waves-light" href="{{ url_for('corpora.create_corpus') }}">Create corpus<i class="material-icons right">add</i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %}class="service-color lighten" data-service="file-setup-pipeline"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="file-setup-pipeline"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -14,12 +14,12 @@
|
||||
<div class="center-align">
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="file-setup-pipeline"></i>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m9 pull-m3">
|
||||
<div class="card service-color-border border-darken" data-service="file-setup-pipeline" style="border-top: 10px solid;">
|
||||
<div class="card service-color-border border-darken" style="border-top: 10px solid;">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
@ -47,7 +47,7 @@
|
||||
{{ wtf.render_field(form.description, material_icon='description') }}
|
||||
</div>
|
||||
<div class="col s12 l9">
|
||||
{{ wtf.render_field(form.images, accept='image/jpeg, image/png, image/tiff', class_='file-setup-pipeline-color darken', placeholder='Choose JPEG, PNG or TIFF files') }}
|
||||
{{ wtf.render_field(form.images, accept='image/jpeg, image/png, image/tiff', placeholder='Choose JPEG, PNG or TIFF files') }}
|
||||
</div>
|
||||
<div class="col s12 l3">
|
||||
{{ wtf.render_field(form.version, material_icon='apps') }}
|
||||
@ -63,3 +63,18 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
function initPage() {
|
||||
let createJobFormImagesElement = document.querySelector('#{{ form.images.id }}');
|
||||
createJobFormImagesElement.parentElement.classList.add('service-color', 'darken');
|
||||
|
||||
let createJobFormSubmitElement = document.querySelector('#{{ form.submit.id }}');
|
||||
createJobFormSubmitElement.classList.add('service-color', 'darken');
|
||||
};
|
||||
|
||||
initPage();
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %}class="service-color lighten" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="spacy-nlp-pipeline"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -14,12 +14,12 @@
|
||||
<div class="center-align">
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="spacy-nlp-pipeline"></i>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m9 pull-m3">
|
||||
<div class="card service-color-border border-darken" data-service="spacy-nlp-pipeline" style="border-top: 10px solid;">
|
||||
<div class="card service-color-border border-darken" style="border-top: 10px solid;">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s12 m6">
|
||||
@ -74,7 +74,7 @@
|
||||
{{ form.model.label }}
|
||||
<span class="helper-text">
|
||||
<a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a>
|
||||
<a class="tooltipped" href="{{ url_for('spacy_nlp_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons">new_label</i></a>
|
||||
<a class="tooltipped" href="{{ url_for('contributions.spacy_nlp_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons">new_label</i></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -165,10 +165,20 @@
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// Disable user model selection if no models are available
|
||||
{% if user_spacy_nlp_pipeline_models_count == 0 %}
|
||||
optionGroupOptions = document.querySelectorAll(".optgroup-option");
|
||||
optionGroupOptions[0].classList.add("disabled");
|
||||
{% endif %}
|
||||
function initPage() {
|
||||
let createJobFormTxtElement = document.querySelector('#{{ form.txt.id }}');
|
||||
createJobFormTxtElement.parentElement.classList.add('service-color', 'darken');
|
||||
|
||||
{% if user_spacy_nlp_pipeline_models_count == 0 %}
|
||||
// Disable user model selection if no models are available
|
||||
optionGroupOptions = document.querySelectorAll(".optgroup-option");
|
||||
optionGroupOptions[0].classList.add("disabled");
|
||||
{% endif %}
|
||||
|
||||
let createJobFormSubmitElement = document.querySelector('#{{ form.submit.id }}');
|
||||
createJobFormSubmitElement.classList.add('service-color', 'darken');
|
||||
};
|
||||
|
||||
initPage();
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %}class="service-color lighten" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="tesseract-ocr-pipeline"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -14,12 +14,12 @@
|
||||
<div class="center-align">
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="tesseract-ocr-pipeline"></i>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m9 pull-m3">
|
||||
<div class="card service-color-border border-darken" data-service="tesseract-ocr-pipeline" style="border-top: 10px solid;">
|
||||
<div class="card service-color-border border-darken" style="border-top: 10px solid;">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
@ -56,7 +56,7 @@
|
||||
{{ form.model.label }}
|
||||
<span class="helper-text">
|
||||
<a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a>
|
||||
<a class="tooltipped" href="{{ url_for('tesseract_ocr_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons">new_label</i></a>
|
||||
<a class="tooltipped" href="{{ url_for('contributions.tesseract_ocr_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons">new_label</i></a>
|
||||
</span>
|
||||
{% for error in form.model.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
@ -145,15 +145,26 @@
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
function initPage() {
|
||||
let createJobFormPdfElement = document.querySelector('#{{ form.pdf.id }}');
|
||||
createJobFormPdfElement.parentElement.classList.add('service-color', 'darken');
|
||||
|
||||
// Disable user model selection if no models are available
|
||||
{% if user_tesseract_ocr_pipeline_models_count == 0 %}
|
||||
optionGroupOptions = document.querySelectorAll(".optgroup-option");
|
||||
optionGroupOptions[0].classList.add("disabled");
|
||||
{% endif %}
|
||||
{% if user_tesseract_ocr_pipeline_models_count == 0 %}
|
||||
// Disable user model selection if no models are available
|
||||
optionGroupOptions = document.querySelectorAll(".optgroup-option");
|
||||
optionGroupOptions[0].classList.add("disabled");
|
||||
{% endif %}
|
||||
|
||||
document.querySelector('#create-job-form-binarization').addEventListener('change', (event) => {
|
||||
document.querySelector('#create-job-form-ocropus_nlbin_threshold-container').classList.toggle('hide');
|
||||
});
|
||||
let createJobFormBinarizationElement = document.querySelector('#{{ form.binarization.id }}');
|
||||
let createJobFormOcropusNlbinThresholdContainerElement = document.querySelector('#create-job-form-ocropus_nlbin_threshold-container');
|
||||
createJobFormBinarizationElement.addEventListener('change', (event) => {
|
||||
createJobFormOcropusNlbinThresholdContainerElement.classList.toggle('hide');
|
||||
});
|
||||
|
||||
let createJobFormSubmitElement = document.querySelector('#{{ form.submit.id }}');
|
||||
createJobFormSubmitElement.classList.add('service-color', 'darken');
|
||||
};
|
||||
|
||||
initPage();
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html.j2" %}
|
||||
{% import "wtf.html.j2" as wtf %}
|
||||
|
||||
{% block main_attribs %}class="service-color lighten" data-service="transkribus-htr-pipeline"{% endblock main_attribs %}
|
||||
{% block main_attributes %}class="service-color lighten" data-service="transkribus-htr-pipeline"{% endblock main_attributes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="center-align">
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<p class="hide-on-small-only"> </p>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken" data-service="transkribus-htr-pipeline"></i>
|
||||
<i class="large nopaque-icons service-icons service-color-text text-darken"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -138,3 +138,18 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock modals %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
function initPage() {
|
||||
let createJobFormPdfElement = document.querySelector('#{{ form.pdf.id }}');
|
||||
createJobFormPdfElement.parentElement.classList.add('service-color', 'darken');
|
||||
|
||||
let createJobFormSubmitElement = document.querySelector('#{{ form.submit.id }}');
|
||||
createJobFormSubmitElement.classList.add('service-color', 'darken');
|
||||
};
|
||||
|
||||
initPage();
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
@ -1,47 +1,42 @@
|
||||
{% macro render_field(field) %}
|
||||
{% if field.type == 'BooleanField' %}
|
||||
{{ render_boolean_field(field, *args, **kwargs) }}
|
||||
{% elif field.type == 'DecimalRangeField' %}
|
||||
{{ render_decimal_range_field(field, *args, **kwargs) }}
|
||||
{% elif field.type == 'FileField' %}
|
||||
{{ render_file_field(field, *args, **kwargs) }}
|
||||
{% elif field.type == 'IntegerField' %}
|
||||
{{ render_integer_field(field, *args, **kwargs) }}
|
||||
{% elif field.type == 'MultipleFileField' %}
|
||||
{{ render_multiple_file_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 == 'TextAreaField' %}
|
||||
{{ render_text_area_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) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_boolean_field(field) %}
|
||||
{% set label = kwargs.pop('label', True) %}
|
||||
<div class="switch">
|
||||
{% if 'material_icon' in kwargs %}
|
||||
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label>
|
||||
{{ field(*args, **kwargs) }}
|
||||
<span class="lever"></span>
|
||||
{% if label %}
|
||||
{{ field.label.text }}
|
||||
{% endif %}
|
||||
<input id="{{ field.id }}" name="{{ field.name }}" type="checkbox">
|
||||
<span>{{ field.label.text }}</span>
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</label>
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_file_field(field) %}
|
||||
{% set placeholder = kwargs.pop('placeholder', '') %}
|
||||
|
||||
<div class="file-field input-field">
|
||||
<div class="btn">
|
||||
<span>{{ field.label.text }}</span>
|
||||
{{ field(*args, **kwargs) }}
|
||||
<input id="{{ field.id }}" name="{{ field.name }}" type="file">
|
||||
</div>
|
||||
<div class="file-path-wrapper">
|
||||
<input class="file-path validate" type="text" placeholder="{{ placeholder }}">
|
||||
@ -52,50 +47,78 @@
|
||||
</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 %}
|
||||
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
{{ field(*args, **kwargs) }}
|
||||
{% if label %}
|
||||
{{ field.label }}
|
||||
{% endif %}
|
||||
|
||||
{% macro render_multiple_file_field(field) %}
|
||||
{% set placeholder = kwargs.pop('placeholder', '') %}
|
||||
|
||||
<div class="file-field input-field">
|
||||
<div class="btn">
|
||||
<span>{{ field.label.text }}</span>
|
||||
<input id="{{ field.id }}" name="{{ field.name }}" type="file" multiple>
|
||||
</div>
|
||||
<div class="file-path-wrapper">
|
||||
<input class="file-path validate" type="text" placeholder="{{ placeholder }}">
|
||||
</div>
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_integer_field(field) %}
|
||||
<div class="input-field">
|
||||
{% if 'material_icon' in kwargs %}
|
||||
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
<input class="validate" id="{{ field.id }}" name="{{ field.name }}" type="number">
|
||||
<label for="{{ field.id }}">{{ field.label.text }}</label>
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_submit_field(field) %}
|
||||
{% 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') }}"
|
||||
{% endif %}>
|
||||
<button class="btn waves-effect waves-light" id="{{ field.id }}" name="{{ field.name }}" type="submit">
|
||||
{{ field.label.text }}
|
||||
{% if 'material_icon' in kwargs %}
|
||||
<i class="material-icons right">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_text_area_field(field) %}
|
||||
<div class="input-field">
|
||||
{% if 'material_icon' in kwargs %}
|
||||
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
<textarea class="materialize-textarea validate" id="{{ field.id }}" name="{{ field.name }}"></textarea>
|
||||
<label for="{{ field.id }}">{{ field.label.text }}</label>
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_generic_field(field) %}
|
||||
{% set classes_ = kwargs.pop('class_', '').split(' ') %}
|
||||
{% if 'validate' not in classes_ %}
|
||||
{% set _ = classes_.append('validate') %}
|
||||
{% endif %}
|
||||
{% set _ = kwargs.update({'class_': ' '.join(classes_)}) %}
|
||||
|
||||
<div class="input-field">
|
||||
{% if 'material_icon' in kwargs %}
|
||||
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
|
||||
{% endif %}
|
||||
{{ field(*args, **kwargs) }}
|
||||
{{ field.label }}
|
||||
{% for error in field.errors %}
|
||||
<span class="helper-text error-color-text">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
Reference in New Issue
Block a user