From 5f8f4593442e948e3b835af12591fb2672ab2d5c Mon Sep 17 00:00:00 2001 From: Frank Sauerburger <frank@sauerburger.com> Date: Tue, 1 Jun 2021 18:47:52 +0200 Subject: [PATCH] Implement existing uhepp component in dashboard layout --- uhepp-js/src/components/Breadcrumbs.jsx | 19 +++++++ uhepp-js/src/components/Dashboard.jsx | 41 +++++++++++++ uhepp-js/src/components/ViewDate.jsx | 18 ++++++ uhepp-js/src/components/VisibilityIcon.jsx | 17 ++++++ uhepp-js/src/index.js | 2 + .../templates/uhepp_vault/plot_dashboard.html | 57 ++++++------------- uhepp_org/uhepp_vault/views.py | 48 ++++++++++++++++ 7 files changed, 163 insertions(+), 39 deletions(-) create mode 100644 uhepp-js/src/components/Breadcrumbs.jsx create mode 100644 uhepp-js/src/components/Dashboard.jsx create mode 100644 uhepp-js/src/components/ViewDate.jsx create mode 100644 uhepp-js/src/components/VisibilityIcon.jsx diff --git a/uhepp-js/src/components/Breadcrumbs.jsx b/uhepp-js/src/components/Breadcrumbs.jsx new file mode 100644 index 0000000..be582a5 --- /dev/null +++ b/uhepp-js/src/components/Breadcrumbs.jsx @@ -0,0 +1,19 @@ + +import React from "react" + +/** + * Render a condensed version of the breadcumbs for the dasboard page. + * + * The content property has to be a list of (display text, url) pairs. + */ +const Breadcrumbs = ({content}) => ( + <nav aria-label="breadcrumb" className="d-none d-lg-block small"> + <ol className="breadcrumb my-2 bg-white p-0"> + { content.map(([text, url], i) => + <li key={i} className="breadcrumb-item"><a href={url}>{text}</a></li> + )} + </ol> + </nav> +) + +export default Breadcrumbs diff --git a/uhepp-js/src/components/Dashboard.jsx b/uhepp-js/src/components/Dashboard.jsx new file mode 100644 index 0000000..196773e --- /dev/null +++ b/uhepp-js/src/components/Dashboard.jsx @@ -0,0 +1,41 @@ + +import React from "react" +import VisibilityIcon from "./VisibilityIcon.jsx" +import Breadcrumbs from "./Breadcrumbs.jsx" +import ViewDate from "./ViewDate.jsx" +import Uhepp from "./Uhepp.jsx" + +/** + * Main UX component to show plots including the its edit menu + * + * The dashboard provides ux components to view and manipulate plots. The + * dashboard encompasses columns for a left sidebar with the modification + * menu, the main column with the actual plot and a right sidebar with + * metadata about the plot. The dashboard expects to be mounted within a + * bootstrap container component. + */ +const Dashboard = ({title, visibility, breadcrumbs, viewdate, uuid}) => ( +<div className="row"> + <div className="col-md-4 col-lg-3 sidebar bg-light shadow"> + Menu + </div> + + <div className="col-md-6 col-lg-7"> + <Breadcrumbs content={breadcrumbs} /> + + <h1 className="mb-0"> + { title } <VisibilityIcon level={visibility} /> + </h1> + + <ViewDate data={viewdate} /> + + <Uhepp width={555} height={400} uuid={uuid} /> + </div> + + <div className="col-md-2 sidebar border-left"> + Metadata + </div> +</div> +) + +export default Dashboard diff --git a/uhepp-js/src/components/ViewDate.jsx b/uhepp-js/src/components/ViewDate.jsx new file mode 100644 index 0000000..442e66f --- /dev/null +++ b/uhepp-js/src/components/ViewDate.jsx @@ -0,0 +1,18 @@ + +import React from "react" + +/** + * Small helper component to render the view count and the uploaded date + * + * Argument need to pre-rendered pluralized and humanized strings. + */ +const ViewDate = ({data}) => ( + <div className="d-none d-lg-inline text-muted small"> + <i className="fas fa-eye"></i> { data.viewstring } {' '} + <span title={data.uploaded}> + <i className="fas fa-clock"></i> { data.datestring } + </span> + </div> +) + +export default ViewDate diff --git a/uhepp-js/src/components/VisibilityIcon.jsx b/uhepp-js/src/components/VisibilityIcon.jsx new file mode 100644 index 0000000..edee9ea --- /dev/null +++ b/uhepp-js/src/components/VisibilityIcon.jsx @@ -0,0 +1,17 @@ + +import React from "react" +import classNames from "classnames" + +/** + * Produces as FontAwesome <i> elemnt based on the given visibility level + */ +const VisibilityIcon = ({level}) => ( + <i className={ + classNames("text-muted", "fas", + {"fa-globe-europe": level >= 30, + "fa-shield-alt": level < 30 && level >= 20, + "fa-lock": level < 20 && level >= 10})}> + </i> +) + +export default VisibilityIcon diff --git a/uhepp-js/src/index.js b/uhepp-js/src/index.js index 725bae5..80b68ee 100644 --- a/uhepp-js/src/index.js +++ b/uhepp-js/src/index.js @@ -5,6 +5,7 @@ import './common.scss' import 'bootstrap' import Uhepp from './components/Uhepp.jsx'; +import Dashboard from './components/Dashboard.jsx'; import React from "react"; import ReactDOM from "react-dom"; @@ -17,4 +18,5 @@ export const fe = { React: React, ReactDOM: ReactDOM, Uhepp: Uhepp, + Dashboard: Dashboard, }; diff --git a/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html b/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html index 842b771..40aa7b8 100644 --- a/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html +++ b/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html @@ -8,11 +8,11 @@ {% block nav-container %} <div class="container-fluid"> <div class="row"> - <div class="col-md-3 col-lg-2" style="background-color: black"> + <div class="col-md-4 col-lg-3 shadow" style="background-color: black"> {% endblock %} {% block nav-container-mid %} </div> - <div class="col-md-9 col-lg-10"> + <div class="col-md-8 col-lg-9"> {% endblock %} {% block nav-container2 %} </div> @@ -21,49 +21,28 @@ {% endblock %} {% block content-fluid %} -<div class="container-fluid"><div class="row"> - - <div class="col-md-3 col-lg-2 sidebar bg-light shadow"> - Menu - </div> - - <div class="col-md-7 col-lg-8"> - <nav aria-label="breadcrumb" class="d-none d-lg-block small"> - <ol class="breadcrumb my-2 bg-white p-0"> - <li class="breadcrumb-item"><a href="/">Home</a></li> - <li class="breadcrumb-item"><a href="{% url 'uhepp_vault:user-detail' plot.collection.owner.username %}">{{ plot.collection.owner }}</a></li> - <li class="breadcrumb-item"><a href="{% url 'uhepp_vault:collection-detail' plot.collection.id %}">{{ plot.collection.title }}</a></li> - <li class="breadcrumb-item active"><a href="{% url 'uhepp_vault:plot-detail' plot.uuid %}">{{ plot }}</a></li> - </ol> - </nav> - - <h1 class="mb-0"> - {{ plot }} - <i class="text-muted fas {% if plot.collection.visibility >= 30 %} fa-globe-europe - {% elif plot.collection.visibility >= 20 %} fa-shield-alt - {% elif plot.collection.visibility >= 10 %} fa-lock - {% endif %}"></i> - </h1> - - <div class="d-none d-lg-inline text-muted small"> - <i class="fas fa-eye"></i> - {{ plot.view_count }} view{{ plot.view_count|pluralize }}, - <span title="{{ plot.uploaded|date:"Y-m-d H:m" }}"> - <i class="fas fa-clock"></i> - {{ plot.uploaded|naturaltime }} - </span> +<div class="container-fluid" id="app-root"> + <div class="row"> + <div class="col-12"> + <div class="d-flex justify-content-center my-5"> + <div class="spinner-border text-primary" role="status"> + <span class="sr-only">Loading...</span> + </div> + </div> + <div class="text-center">Loading dashboard...</div> </div> </div> - - <div class="col-md-2 sidebar shadow-sm"> - Metadata - </div> - -</div></div> +</div> {% endblock %} {% block loadscript %} +{{ dashboard_props|json_script:"dashboard-props" }} <script> +var props = JSON.parse(document.getElementById('dashboard-props').textContent); +uhepp.fe.ReactDOM.render( + uhepp.fe.React.createElement(uhepp.fe.Dashboard, props), + document.getElementById('app-root') +); /*uhepp.fe.ReactDOM.render( uhepp.fe.React.createElement(uhepp.fe.Uhepp, { width: "555", diff --git a/uhepp_org/uhepp_vault/views.py b/uhepp_org/uhepp_vault/views.py index 024d3f7..905b71c 100644 --- a/uhepp_org/uhepp_vault/views.py +++ b/uhepp_org/uhepp_vault/views.py @@ -12,6 +12,8 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User, Group from django.utils.translation import gettext_lazy as _ +from django.template.defaultfilters import pluralize +from django.contrib.humanize.templatetags.humanize import naturaltime from uhepp_api.models import Token from uhepp_api.views import MaskedRelatedMixin @@ -250,6 +252,52 @@ class PlotDetail(generic.DetailView): raise original return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path)) + def get_breadcrumbs_map(self): + """Return a list of tuples with display text and link url""" + plot = self.object + return [ + ("Home", "/"), + (str(plot.collection.owner), + reverse("uhepp_vault:user-detail", args=[plot.collection.owner.username])), + (plot.collection.title, + reverse("uhepp_vault:collection-detail", args=[plot.collection.id])), + (str(plot), + reverse("uhepp_vault:collection-detail", args=[plot.uuid])), + ] + + def get_viewstring(self): + """Return the human-readable, pluralized view count string""" + view_count = self.object.view_count() + plural = pluralize(view_count) + return f"{view_count:d} view{plural:s}" + + def get_datestring(self): + """Return the human-readable creation time string""" + return naturaltime(self.object.uploaded) + + def get_viewdate(self): + """Build sub-dict with rendered view coutn and creation date strs""" + return { + "viewstring": self.get_viewstring(), + "datestring": self.get_datestring(), + "uploaded": self.object.uploaded.strftime("%Y-%m-%d %H:%m"), + } + + + def get_context_data(self, **kwargs): + """Add dashboard props to context""" + context = {} + context["dashboard_props"] = { + "title": self.object.title, + "visibility": self.object.collection.visibility, + "breadcrumbs": self.get_breadcrumbs_map(), + "viewdate": self.get_viewdate(), + "uuid": self.object.uuid, + } + context.update(kwargs) + return super().get_context_data(**context) + + class PlotDeleteView(LoginRequiredMixin, generic.DeleteView): model = Plot slug_field = 'uuid' -- GitLab