From 622d32fa45e6223d1b163dab33e240c9402ff359 Mon Sep 17 00:00:00 2001
From: Patrick Jentsch <p.jentsch@uni-bielefeld.de>
Date: Tue, 21 Mar 2023 10:50:29 +0100
Subject: [PATCH] UI enhancements

---
 app/admin/routes.py                  | 32 ++++++++--
 app/templates/_sidenav.html.j2       | 11 ++--
 app/templates/admin/admin.html.j2    | 11 ++++
 app/templates/admin/corpora.html.j2  | 23 +++++++
 app/templates/admin/user.html.j2     | 91 +++++++++++++++++-----------
 app/templates/main/dashboard.html.j2 | 55 +++++++++--------
 app/templates/users/profile.html.j2  | 16 +++--
 app/users/routes.py                  |  2 +-
 8 files changed, 163 insertions(+), 78 deletions(-)
 create mode 100644 app/templates/admin/admin.html.j2
 create mode 100644 app/templates/admin/corpora.html.j2

diff --git a/app/admin/routes.py b/app/admin/routes.py
index 15ff0d9f..b2d0643b 100644
--- a/app/admin/routes.py
+++ b/app/admin/routes.py
@@ -1,9 +1,9 @@
-from flask import flash, redirect, render_template, url_for
+from flask import flash, redirect, render_template, request, url_for
 from flask_breadcrumbs import register_breadcrumb
 from app import db, hashids
-from app.models import Role, User, UserSettingJobStatusMailNotificationLevel
-from app.users.forms import EditNotificationSettingsForm
-from app.users.forms import EditProfileSettingsForm
+from app.models import Corpus, Role, User, UserSettingJobStatusMailNotificationLevel
+from app.settings.forms import EditNotificationSettingsForm
+from app.settings.forms import EditProfileSettingsForm
 from . import bp
 from .forms import AdminEditUserForm
 from app.users.utils import (
@@ -15,7 +15,21 @@ from app.users.utils import (
 @bp.route('')
 @register_breadcrumb(bp, '.', '<i class="material-icons left">admin_panel_settings</i>Administration')
 def index():
-    return redirect(url_for('.users'))
+    return render_template(
+        'admin/admin.html.j2',
+        title='Administration'
+    )
+
+
+@bp.route('/corpora')
+@register_breadcrumb(bp, '.corpora', 'Corpora')
+def corpora():
+    corpora = [x.to_json_serializeable(backrefs=True) for x in Corpus.query.all()]
+    return render_template(
+        'admin/corpora.html.j2',
+        corpora=corpora,
+        title='Corpora'
+    )
 
 
 @bp.route('/users')
@@ -33,7 +47,13 @@ def users():
 @register_breadcrumb(bp, '.users.entity', '', dynamic_list_constructor=user_dlc)
 def user(user_id):
     user = User.query.get_or_404(user_id)
-    return render_template('admin/user.html.j2', title='User', user=user)
+    return render_template('admin/user.html.j2', title=user.username, user=user)
+
+
+@bp.route('/users/<hashid:user_id>/dashboard')
+@register_breadcrumb(bp, '.users.entity.dashboard', 'Dashboard', endpoint_arguments_constructor=user_eac)
+def user_dashboard(user_id):
+    return render_template('main/dashboard.html.j2', title='Dashboard')
 
 
 @bp.route('/users/<hashid:user_id>/edit', methods=['GET', 'POST'])
diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2
index 5157e262..5f6f6482 100644
--- a/app/templates/_sidenav.html.j2
+++ b/app/templates/_sidenav.html.j2
@@ -48,13 +48,16 @@
   <li class="hide-on-large-only"><a class="subheader">Account</a></li>
   <li class="hide-on-large-only"><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>Settings</a></li>
   <li class="hide-on-large-only"><a href="{{ url_for('auth.logout') }}">Log out</a></li>
-  {% if current_user.can('ADMINISTRATE') or current_user.can('USE_API') %}
-  <li><div class="divider"></div></li>
-  {% endif %}
   {% if current_user.can('ADMINISTRATE') %}
-  <li><a href="{{ url_for('admin.index') }}"><i class="material-icons">admin_panel_settings</i>Administration</a></li>
+  <li><div class="divider"></div></li>
+  <li><a class="subheader">Administration</a></li>
+  <li><a href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">I</i>Corpora</a></li>
+  <li><a href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">J</i>Jobs</a></li>
+  <li><a href="{{ url_for('admin.users') }}"><i class="material-icons">group</i>Users</a></li>
   {% endif %}
   {% if current_user.can('USE_API') %}
+  <li><div class="divider"></div></li>
+  <li><a class="subheader">API</a></li>
   <li><a href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a></li>
   {% endif %}
 </ul>
diff --git a/app/templates/admin/admin.html.j2 b/app/templates/admin/admin.html.j2
new file mode 100644
index 00000000..d00adf58
--- /dev/null
+++ b/app/templates/admin/admin.html.j2
@@ -0,0 +1,11 @@
+{% extends "base.html.j2" %}
+
+{% block page_content %}
+<div class="container">
+  <div class="row">
+    <div class="col s12">
+      <h1 id="title">{{ title }}</h1>
+    </div>
+  </div>
+</div>
+{% endblock page_content %}
diff --git a/app/templates/admin/corpora.html.j2 b/app/templates/admin/corpora.html.j2
new file mode 100644
index 00000000..99fcc661
--- /dev/null
+++ b/app/templates/admin/corpora.html.j2
@@ -0,0 +1,23 @@
+{% extends "base.html.j2" %}
+
+{% block page_content %}
+<div class="container">
+  <h1 id="title">{{ title }}</h1>
+
+  <div class="card">
+    <div class="card-content">
+      <div class="corpus-list no-autoinit" id="corpus-list"></div>
+    </div>
+  </div>
+  </div>
+</div>
+{% endblock page_content %}
+
+{% block scripts %}
+{{ super() }}
+<script>
+  let corpusListElement = document.querySelector('#corpus-list');
+  let corpusList = new CorpusList(corpusListElement);
+  corpusList.add({{ corpora|tojson }});
+</script>
+{% endblock scripts %}
diff --git a/app/templates/admin/user.html.j2 b/app/templates/admin/user.html.j2
index e3864e75..30f7c084 100644
--- a/app/templates/admin/user.html.j2
+++ b/app/templates/admin/user.html.j2
@@ -3,52 +3,49 @@
 {% block page_content %}
 <div class="container">
   <div class="row">
-    <div class="col s12">
+    <div class="col s12 l2">
+      <p>&nbsp;</p>
+      <img src="{{ url_for('users.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img">
+    </div>
+    <div class="col s12 l10">
       <h1 id="title">{{ title }}</h1>
+      <span class="chip hoverable tooltipped no-autoinit" id="user-role-chip">{{ user.role.name }}</span>
+      <span class="chip white-text" id="user-confirmed-chip" style="background-color: {{ '#4caf50' if user.confirmed else '#f44336' }};">{{ 'confirmed' if user.confirmed else 'unconfirmed' }}</span>
+      <p id="description">{{ user.about_me }}</p>
     </div>
 
-    <div class="col s12 m4">
-      <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p>
-      <a class="waves-effect waves-light btn" href="{{ url_for('.users') }}"><i class="material-icons left">arrow_back</i>Back to Users</a>
+    <div class="col s12">&nbsp;</div>
+
+    <div class="col s12">
+      <ul class="tabs tabs-fixed-width z-depth-1">
+        <li class="tab"><a href="#user-info">User info</a></li>
+        <li class="tab"><a href="#user-corpora">Corpora</a></li>
+        <li class="tab"><a href="#user-jobs">Jobs</a></li>
+        <li class="tab"><a href="#user-tesseract-ocr-pipeline-models">Tesseract OCR Pipeline Models</a></li>
+        <li class="tab"><a href="#user-spacy-nlp-pipeline-models">SpaCy NLP Pipeline Models</a></li>
+      </ul>
     </div>
 
-    <div class="col s12 m8">
+    <div class="col s12" id="user-info">
       <div class="card">
         <div class="card-content">
-          <span class="card-title">User information</span>
           <ul>
             <li>Username: {{ user.username }}</li>
             <li>Email: {{ user.email }}</li>
             <li>Id: {{ user.id }}</li>
             <li>Hashid: {{ user.hashid }}</li>
             <li>Member since: {{ user.member_since }}</li>
-            <li>Confirmed status: {{ user.confirmed }}</li>
             <li>Last seen: {{ user.last_seen }}</li>
-            <li>Permissions as Int: {{ user.role.permissions }}</li>
-            <li>Role: {{ user.role.name }}</li>
-            <li>Permissions:
-              <ul style="margin-left: 25px;">
-                {% for permission in ['ADMINISTRATE', 'CONTRIBUTE', 'USE_API'] %}
-                <li>
-                  <label>
-                    <input type="checkbox" {{ 'checked' if user.can(permission) }}>
-                    <span>{{ permission|capitalize }}</span>
-                  </label>
-                </li>
-                {% endfor %}
-              </ul>
-            </li>
           </ul>
         </div>
         <div class="card-action right-align">
-          <a href="{{ url_for('.edit_user', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit</a>
-          <a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a>
+          <a class="btn waves-effect waves-light" href="{{ url_for('.edit_user', user_id=user.id) }}"><i class="material-icons left">edit</i>Edit</a>
+          <a class="btn red modal-trigger waves-effect waves-light" data-target="delete-user-modal"><i class="material-icons left">delete</i>Delete</a>
         </div>
       </div>
     </div>
 
-    <div class="col s12 l6">
-      <h3>Corpora</h3>
+    <div class="col s12" id="user-corpora">
       <div class="card">
         <div class="card-content">
           <div class="corpus-list" data-user-id="{{ user.hashid }}"></div>
@@ -56,8 +53,7 @@
       </div>
     </div>
 
-    <div class="col s12 l6">
-      <h3>Jobs</h3>
+    <div class="col s12" id="user-jobs">
       <div class="card">
         <div class="card-content">
           <div class="job-list" data-user-id="{{ user.hashid }}"></div>
@@ -65,12 +61,7 @@
       </div>
     </div>
 
-    <div class="col s12">
-      <h2>Contributions</h2>
-    </div>
-
-    <div class="col s12 l6">
-      <h3>SpaCy NLP Pipeline Models</h3>
+    <div class="col s12" id="user-spacy-nlp-pipeline-models">
       <div class="card">
         <div class="card-content">
           <div class="spacy-nlp-pipeline-model-list" data-user-id="{{ user.hashid }}"></div>
@@ -78,8 +69,7 @@
       </div>
     </div>
 
-    <div class="col s12 l6">
-      <h3>Tesseract OCR Pipeline Models</h3>
+    <div class="col s12" id="user-tesseract-ocr-pipeline-models">
       <div class="card">
         <div class="card-content">
           <div class="tesseract-ocr-pipeline-model-list" data-user-id="{{ user.hashid }}"></div>
@@ -99,8 +89,35 @@
     <p>Do you really want to delete the user {{ user.username }}? All associated data will be permanently deleted!</p>
   </div>
   <div class="modal-footer">
-    <a href="#!" class="modal-close waves-effect waves-light btn">Cancel</a>
-    <a href="{{ url_for('.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-light btn red"><i class="material-icons left">delete</i>Delete</a>
+    <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
+    <a href="{{ url_for('.delete_user', user_id=user.id) }}" class="btn red modal-close waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
   </div>
 </div>
 {% endblock modals %}
+
+
+{% block scripts %}
+{{ super() }}
+<script>
+  let userRoleChip = document.querySelector('#user-role-chip');
+  let userRoleChipTooltip = M.Tooltip.init(
+    userRoleChip,
+    {
+      html: `
+        <p>Permissions</p>
+        <p class="left-align">
+          {% for permission in ['ADMINISTRATE', 'CONTRIBUTE', 'USE_API'] %}
+          <label>
+            <input class="filled-in" type="checkbox" {{ 'checked' if user.can(permission) }}>
+            <span>{{ permission|capitalize }}</span>
+          </label>
+          {% if not loop.last %}
+          <br>
+          {% endif %}
+          {% endfor %}
+        </p>
+      `.trim()
+    }
+  );
+</script>
+{% endblock scripts %}
diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2
index 0b8e6f31..54f91eaa 100644
--- a/app/templates/main/dashboard.html.j2
+++ b/app/templates/main/dashboard.html.j2
@@ -8,8 +8,11 @@
     </div>
 
     <div class="col s12" id="corpora">
-      <h3>My Corpora</h3>
+      <h2>My Corpora</h2>
       <p>Create a corpus to interactively perform linguistic analysis.</p>
+    </div>
+
+    <div class="col s12">
       <div class="card">
         <div class="card-content">
           <div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div>
@@ -22,7 +25,7 @@
     </div>
 
     <div class="col s12" id="jobs">
-      <h3>My Jobs</h3>
+      <h2>My Jobs</h2>
       <p>
         A job is the execution of a service provided by nopaque. You can
         create any number of jobs and let them be processed simultaneously. We
@@ -32,6 +35,9 @@
         next step.
       </p>
       <p><b>Where is my Job data?</b> Don't worry, please read <a href="{{ url_for('main.news', _anchor='april-2022-update') }}">this news</a> entry</p>
+    </div>
+
+    <div class="col s12">
       <div class="card">
         <div class="card-content">
           <div class="job-list" data-user-id="{{ current_user.hashid }}"></div>
@@ -43,34 +49,35 @@
     </div>
 
     <div class="col s12" id="contributions">
-      <h3>My Contributions</h3>
-      <div class="col s4">
-        <div class="card extension-selector hoverable service-color" data-service="tesseract-ocr-pipeline">
-          <a href="{{ url_for('contributions.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
-          <div class="card-content">
-            <span class="card-title" data-service="tesseract-ocr-pipeline">Tesseract OCR Pipeline Models</span>
-            <p>Here you can see and edit the models that you have created. You can also create new models.</p>
-          </div>
+      <h2>My Contributions</h2>
+    </div>
+
+    <div class="col s4">
+      <div class="card extension-selector hoverable service-color" data-service="tesseract-ocr-pipeline">
+        <a href="{{ url_for('contributions.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
+        <div class="card-content">
+          <span class="card-title" data-service="tesseract-ocr-pipeline">Tesseract OCR Pipeline Models</span>
+          <p>Here you can see and edit the models that you have created. You can also create new models.</p>
         </div>
       </div>
+    </div>
 
-      <div class="col s4">
-        <div class="card extension-selector hoverable service-color" data-service="spacy-nlp-pipeline">
-          <a href="{{ url_for('contributions.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
-          <div class="card-content">
-            <span class="card-title">SpaCy NLP Pipeline Models</span>
-            <p>Here you can see and edit the models that you have created. You can also create new models.</p>
-          </div>
+    <div class="col s4">
+      <div class="card extension-selector hoverable service-color" data-service="spacy-nlp-pipeline">
+        <a href="{{ url_for('contributions.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
+        <div class="card-content">
+          <span class="card-title">SpaCy NLP Pipeline Models</span>
+          <p>Here you can see and edit the models that you have created. You can also create new models.</p>
         </div>
       </div>
+    </div>
 
-      <div class="col s4">
-        <div class="card extension-selector hoverable service-color" data-service="transkribus-htr-pipeline">
-        <a href="{{ url_for('contributions.transkribus_htr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
-          <div class="card-content">
-            <span class="card-title">Transkribus HTR Pipeline Models</span>
-            <p>Here you can see and edit the models that you have created. You can also create new models.</p>
-          </div>
+    <div class="col s4">
+      <div class="card extension-selector hoverable service-color" data-service="transkribus-htr-pipeline">
+      <a href="{{ url_for('contributions.transkribus_htr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
+        <div class="card-content">
+          <span class="card-title">Transkribus HTR Pipeline Models</span>
+          <p>Here you can see and edit the models that you have created. You can also create new models.</p>
         </div>
       </div>
     </div>
diff --git a/app/templates/users/profile.html.j2 b/app/templates/users/profile.html.j2
index 895fba82..6e02a42c 100644
--- a/app/templates/users/profile.html.j2
+++ b/app/templates/users/profile.html.j2
@@ -18,10 +18,14 @@
               <div class="col s7">
                 <div class="row">
                   <div class="col s12">
+                    <h1>{{ user.username }}</h1>
+                    {% if user.role.name != 'User' %}
+                    <span class="chip">{{ user.role.name }}</span>
+                    {% endif %}
                     {% if user.is_public %}
-                    <h3 style="float:left">{{ user.username }}<span class="new badge green" id="public-information-badge" data-badge-caption="Your profile is public" style="margin-top:20px;"></span></h3>
+                    <span class="chip white-text" style="background-color: #4caf50;">Public Profile</span>
                     {% else %}
-                    <h3 style="float:left">{{ user.username }}<span class="new badge red" id="public-information-badge" data-badge-caption="Your profile is private" style="margin-top:20px;"></span></h3>
+                    <span class="chip white-text" style="background-color: #f44336;">Private Profile</span>
                     {% endif %}
                   </div>
                   <div class="col 12">
@@ -34,10 +38,10 @@
                     <p></p>
                     <br>
                     {% if user.about_me %}
-                    <div style="border-left: solid 3px #E91E63; padding-left: 15px;">
+                    <blockquote>
                       <h5>About me</h5>
                       <p>{{ user.about_me }}</p>
-                    </div>
+                    </blockquote>
                     {% endif %}
                   </div>
                 </div>
@@ -80,8 +84,8 @@
                 {% endif %}
                 <p></p>
                 <br>
-                {% if current_user.is_authenticated and current_user.hashid == user.id %}
-                <a class="waves-effect waves-light btn-small" href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}">Edit profile</a>
+                {% if current_user.hashid == user.id %}
+                <a class="waves-effect waves-light btn-small" href="{{ url_for('settings.settings') }}">Edit profile</a>
                 {% endif %}
               </div>
             </div>
diff --git a/app/users/routes.py b/app/users/routes.py
index 0bf8eb1d..5d51e81f 100644
--- a/app/users/routes.py
+++ b/app/users/routes.py
@@ -42,7 +42,7 @@ def user(user_id):
         last_seen=last_seen,
         member_since=member_since,
         own_public_corpora=own_public_corpora,
-        user=user.to_json_serializeable(),
+        user=user,
         user_id=user_id,
         title=user.username
     )
-- 
GitLab