Navigational context selector

This commit is contained in:
Raphael Michel
2018-07-14 16:39:19 +02:00
parent 886b938f08
commit 053de88173
9 changed files with 286 additions and 96 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ def contextprocessor(request):
ctx['selected_subevents'] = request.event.subevents.filter(pk=request.GET.get('subevent'))
elif getattr(request, 'organizer', None) and request.user.is_authenticated:
ctx['nav_items'] = get_organizer_navigation(request)
else:
elif request.user.is_authenticated:
ctx['nav_items'] = get_global_navigation(request)
ctx['js_payment_weekdays_disabled'] = _js_payment_weekdays_disabled
+34 -1
View File
@@ -288,7 +288,7 @@ def get_global_navigation(request):
{
'label': _('Organizers'),
'url': reverse('control:organizers'),
'active': 'events' in url.url_name,
'active': 'organizers' in url.url_name,
'icon': 'group',
},
{
@@ -297,6 +297,39 @@ def get_global_navigation(request):
'active': 'search.orders' in url.url_name,
'icon': 'search',
},
{
'label': _('User settings'),
'url': reverse('control:user.settings'),
'active': False,
'icon': 'user',
'children': [
{
'label': _('General'),
'url': reverse('control:user.settings'),
'active': 'user.settings' == url.url_name,
},
{
'label': _('Notifications'),
'url': reverse('control:user.settings.notifications'),
'active': 'user.settings.notifications' == url.url_name,
},
{
'label': _('2FA'),
'url': reverse('control:user.settings.2fa'),
'active': 'user.settings.2fa' in url.url_name,
},
{
'label': _('Authorized apps'),
'url': reverse('control:user.settings.oauth.list'),
'active': 'user.settings.oauth' in url.url_name,
},
{
'label': _('Account history'),
'url': reverse('control:user.settings.history'),
'active': 'user.settings.history' in url.url_name,
},
]
},
]
if has_staff_session:
nav.append({
@@ -95,21 +95,6 @@
</a>
</div>
<ul class="nav navbar-nav navbar-top-links navbar-left hidden-xs">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-calendar"></i>
<span class="event-name">{{ request.event }}</span>
<span class="caret"></span></a>
<ul class="dropdown-menu event-dropdown" role="menu" data-event-typeahead
data-source="{% url "control:events.typeahead" %}">
<li class="query-holder">
<div class="form-box">
<input type="text" class="form-control"
placeholder="{% trans "Search for events" %}"
data-typeahead-query>
</div>
</li>
</ul>
</li>
{% if request.event %}
<li>
{% if has_domain and not request.event.live %}
@@ -206,6 +191,34 @@
</ul>
</div>
<div class="sidebar-nav navbar-nav-collapse navbar-collapse">
<div class="dropdown context-selector">
{% if request.event %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-calendar fa-fw"></i>
<span class="event-name">{{ request.event }}</span>
<span class="caret"></span></a>
{% elif request.organizer %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-group fa-fw"></i>
<span class="event-name">{{ request.organizer }}</span>
<span class="caret"></span></a>
{% else %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user fa-fw"></i>
<span class="event-name">{{ request.user }}</span>
<span class="caret"></span></a>
{% endif %}
<ul class="dropdown-menu event-dropdown" role="menu" data-event-typeahead
data-source="{% url "control:nav.typeahead" %}">
<li class="query-holder">
<div class="form-box">
<input type="text" class="form-control"
placeholder="{% trans "Search for events" %}"
data-typeahead-query>
</div>
</li>
</ul>
</div>
<ul class="nav" id="side-menu">
{% block nav %}
{% for nav in nav_items %}
@@ -53,24 +53,6 @@
{% endif %}
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="">{% trans "Authorized applications" %}</label>
<div class="col-md-9 static-form-row">
<a href="{% url "control:user.settings.oauth.list" %}">
<span class="fa fa-plug"></span>
{% trans "Show applications" %}
</a>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="">{% trans "Account history" %}</label>
<div class="col-md-9 static-form-row">
<a href="{% url "control:user.settings.history" %}">
<span class="fa fa-history"></span>
{% trans "Show account history" %}
</a>
</div>
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
+1
View File
@@ -80,6 +80,7 @@ urlpatterns = [
url(r'^events/$', main.EventList.as_view(), name='events'),
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
url(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
url(r'^$', dashboards.event_index, name='event.index'),
+92 -26
View File
@@ -17,6 +17,55 @@ from pretix.helpers.daterange import daterange
from pretix.helpers.i18n import i18ncomp
def serialize_user(u):
return {
'id': u.pk,
'type': 'user',
'name': str(u),
'text': str(u),
'url': reverse('control:index')
}
def serialize_orga(o):
return {
'id': o.pk,
'slug': o.slug,
'type': 'organizer',
'name': str(o.name),
'text': str(o.name),
'url': reverse('control:organizer', kwargs={
'organizer': o.slug
})
}
def serialize_event(e):
dr = e.get_date_range_display()
if e.has_subevents:
if e.min_from is None:
dr = pgettext('subevent', 'No dates')
else:
tz = pytz.timezone(e.settings.timezone)
dr = _('Series:') + ' ' + daterange(
e.min_from.astimezone(tz),
(e.max_fromto or e.max_to or e.max_from).astimezone(tz)
)
return {
'id': e.pk,
'slug': e.slug,
'type': 'event',
'organizer': str(e.organizer.name),
'name': str(e.name),
'text': str(e.name),
'date_range': dr,
'url': reverse('control:event.index', kwargs={
'event': e.slug,
'organizer': e.organizer.slug
})
}
def event_list(request):
query = request.GET.get('query', '')
try:
@@ -35,37 +84,12 @@ def event_list(request):
order_from=Coalesce('min_from', 'date_from'),
).order_by('-order_from')
def serialize(e):
dr = e.get_date_range_display()
if e.has_subevents:
if e.min_from is None:
dr = pgettext('subevent', 'No dates')
else:
tz = pytz.timezone(e.settings.timezone)
dr = _('Series:') + ' ' + daterange(
e.min_from.astimezone(tz),
(e.max_fromto or e.max_to or e.max_from).astimezone(tz)
)
return {
'id': e.pk,
'slug': e.slug,
'organizer': str(e.organizer.name),
'name': str(e.name),
'text': str(e.name),
'date_range': dr,
'url': reverse('control:event.index', kwargs={
'event': e.slug,
'organizer': e.organizer.slug
})
}
total = qs.count()
pagesize = 20
offset = (page - 1) * pagesize
doc = {
'results': [
serialize(e) for e in qs.select_related('organizer')[offset:offset + pagesize]
serialize_event(e) for e in qs.select_related('organizer')[offset:offset + pagesize]
],
'pagination': {
"more": total >= (offset + pagesize)
@@ -74,6 +98,48 @@ def event_list(request):
return JsonResponse(doc)
def nav_context_list(request):
query = request.GET.get('query', '')
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
qs_events = request.user.get_events_with_any_permission(request).filter(
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query)
).annotate(
min_from=Min('subevents__date_from'),
max_from=Max('subevents__date_from'),
max_to=Max('subevents__date_to'),
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate(
order_from=Coalesce('min_from', 'date_from'),
).order_by('-order_from')
if request.user.has_active_staff_session(request.session.session_key):
qs_orga = Organizer.objects.all()
else:
qs_orga = Organizer.objects.filter(pk__in=request.user.teams.values_list('organizer', flat=True))
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query))
total = qs_events.count() + qs_orga.count()
pagesize = 20
offset = (page - 1) * pagesize
results = [
serialize_user(request.user)
] + [
serialize_orga(e) for e in qs_orga[offset:offset + pagesize]
] + [
serialize_event(e) for e in qs_events.select_related('organizer')[offset:offset + pagesize]
]
doc = {
'results': results,
'pagination': {
"more": total >= (offset + pagesize)
}
}
return JsonResponse(doc)
@event_permission_required(None)
def subevent_select2(request, **kwargs):
query = request.GET.get('query', '')
+61 -6
View File
@@ -1,45 +1,84 @@
.context-selector {
font-size: 16px;
margin-bottom: 16px;
.fa {
margin-right: 5px;
color: rgba($navbar-inverse-bg, 0.7);
}
a {
padding: 8px 15px 8px 14px;
color: $navbar-inverse-bg;
text-decoration: none !important;
&:hover {
color: $brand-primary;
}
}
.event-dropdown {
margin-top: 8px;
width: 260px;
}
}
.navbar-inverse {
box-shadow:0 2px 5px 0 rgba(78,50,92,.1),0 1px 1px 0 rgba(0,0,0,.07);
box-shadow: 0 2px 5px 0 rgba(78, 50, 92, .1), 0 1px 1px 0 rgba(0, 0, 0, .07);
margin: 0;
border-radius: 0;
z-index: 100;
}
.navbar-fixed {
position: fixed;
width: 100%;
}
.sidebar-nav li > a > .fa {
color: rgba($navbar-inverse-bg, 0.7);
margin-right: 5px;
}
.sidebar-nav li > a > .fa-spin {
margin-left: 5px;
position: absolute;
top: 11px;
}
.sidebar-nav li li > a > .fa-spin {
top: 6px;
}
.sidebar-nav .arrow {
display: none;
}
.sidebar-nav li > a:hover > .fa {
}
.sidebar-nav {
padding-top: 10px;
}
.sidebar-nav li > a {
padding: 8px 15px;
}
.sidebar-nav > ul > li > a {
color: $navbar-inverse-bg;
}
.sidebar-nav li li a {
padding-top: 2px;
padding-bottom: 2px;
color: desaturate($brand-primary, 10%);
}
.sidebar-nav li a:hover {
color: $brand-primary;
}
.sidebar-nav li ul {
margin-bottom: 10px;
}
.sidebar-nav li.active > a:not(.has-children), .sidebar-nav li a.active {
font-weight: bold;
color: $brand-primary;
@@ -52,6 +91,7 @@
.panel-heading {
padding: 10px 15px;
}
.panel-heading legend, .panel-heading .panel-title {
margin-top: 0;
margin-bottom: 0;
@@ -132,21 +172,25 @@
}
.box-shadow-small {
box-shadow:0 2px 5px 0 rgba(78,50,92,.1),0 1px 1px 0 rgba(0,0,0,.07);
box-shadow: 0 2px 5px 0 rgba(78, 50, 92, .1), 0 1px 1px 0 rgba(0, 0, 0, .07);
}
.box-shadow-medium {
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1), 0 3px 6px 0 rgba(0, 0, 0, .07);
}
.box-shadow-medium {
box-shadow: 0 15px 35px 0 rgba(78, 50, 92, 0.1),0 5px 15px 0 rgba(0,0,0,.07);
box-shadow: 0 15px 35px 0 rgba(78, 50, 92, 0.1), 0 5px 15px 0 rgba(0, 0, 0, .07);
}
.panel {
box-shadow:0 2px 5px 0 rgba(78,50,92,.1),0 1px 1px 0 rgba(0,0,0,.07);
box-shadow: 0 2px 5px 0 rgba(78, 50, 92, .1), 0 1px 1px 0 rgba(0, 0, 0, .07);
}
.panel-default {
border: none;
}
.panel-default > .panel-heading, .panel-default .panel-footer {
background: white;
}
@@ -157,18 +201,21 @@
.panel > .table-responsive table th:first-child {
padding-left: $panel-body-padding;
}
.panel > table td:last-child,
.panel > .table-responsive table td:last-child,
.panel > table th:last-child,
.panel > .table-responsive table th:last-child {
padding-right: $panel-body-padding;
}
.panel > table td,
.panel > table th,
.panel > .table-responsive table td,
.panel > .table-responsive table th {
vertical-align: middle;
}
.panel-with-btn .panel-title {
line-height: 30px;
}
@@ -176,17 +223,19 @@
.panel-danger > .panel-heading, .panel-success > .panel-heading, .panel-default > .panel-heading, .panel-info > .panel-heading, .panel-warning > .panel-heading {
color: #000000;
}
.panel-primary .panel-heading {
color: white;
}
.alert {
text-align: left;
}
.alert-legal {
border-color: $state-info-border;
}
.alert-success, .alert-danger, .alert-info, .alert-warning, .alert-legal {
position: relative;
padding-left: 65px;
@@ -208,27 +257,33 @@
-moz-osx-font-smoothing: grayscale;
}
}
.sr-only.alert::before {
background: none !important;
}
.alert-success::before {
background: $state-success-border;
content: "\f00c";
}
.alert-info::before {
background: $state-info-border;
content: "\f129";
}
.alert-warning::before {
background: $state-warning-border;
content: "\f071";
font-size: 22px;
padding-top: 14px;
}
.alert-legal::before {
background: $state-info-border;
content: "\f0e3";
}
.alert-danger::before {
background: $state-danger-border;
content: "\f071";
@@ -1,6 +1,6 @@
/*global $,u2f */
$(function () {
$('ul.navbar-nav .dropdown, .navbar-events-collapse').on('shown.bs.collapse shown.bs.dropdown', function () {
$('.context-selector.dropdown, .navbar-events-collapse').on('shown.bs.collapse shown.bs.dropdown', function () {
$(this).parent().find("input").val("").change().focus();
});
$('.dropdown-menu .form-box input').click(function (e) {
@@ -23,30 +23,70 @@ $(function () {
function (data) {
$container.find("li:not(.query-holder)").remove();
$.each(data.results, function (i, res) {
$container.append(
$("<li>").append(
$("<a>").attr("href", res.url).append(
$("<div>").append(
$("<span>").addClass("event-name-full").append($("<div>").text(res.name).html())
).append(
$("<span>").addClass("event-organizer").append(
$("<span>").addClass("fa fa-users fa-fw")
).append(" ").append($("<div>").text(res.organizer).html())
).append(
$("<span>").addClass("event-daterange").append(
$("<span>").addClass("fa fa-calendar fa-fw")
).append(" ").append(res.date_range)
)
).on("mousedown", function (event) {
if ($(this).length) {
location.href = $(this).attr("href");
}
$(this).parent().addClass("active");
event.preventDefault();
event.stopPropagation();
})
)
);
if (res.type === "organizer") {
$container.append(
$("<li>").append(
$("<a>").attr("href", res.url).append(
$("<div>").append(
$("<span>").addClass("event-name-full").append(
$("<span>").addClass("fa fa-users fa-fw")
).append(" ").append($("<div>").text(res.name).html())
)
).on("mousedown", function (event) {
if ($(this).length) {
location.href = $(this).attr("href");
}
$(this).parent().addClass("active");
event.preventDefault();
event.stopPropagation();
})
)
);
} else if (res.type === "user") {
$container.append(
$("<li>").append(
$("<a>").attr("href", res.url).append(
$("<div>").append(
$("<span>").addClass("event-name-full").append(
$("<span>").addClass("fa fa-user fa-fw")
).append(" ").append($("<div>").text(res.name).html())
)
).on("mousedown", function (event) {
if ($(this).length) {
location.href = $(this).attr("href");
}
$(this).parent().addClass("active");
event.preventDefault();
event.stopPropagation();
})
)
);
} else {
$container.append(
$("<li>").append(
$("<a>").attr("href", res.url).append(
$("<div>").append(
$("<span>").addClass("event-name-full").append($("<div>").text(res.name).html())
).append(
$("<span>").addClass("event-organizer").append(
$("<span>").addClass("fa fa-users fa-fw")
).append(" ").append($("<div>").text(res.organizer).html())
).append(
$("<span>").addClass("event-daterange").append(
$("<span>").addClass("fa fa-calendar fa-fw")
).append(" ").append(res.date_range)
)
).on("mousedown", function (event) {
if ($(this).length) {
location.href = $(this).attr("href");
}
$(this).parent().addClass("active");
event.preventDefault();
event.stopPropagation();
})
)
);
}
});
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
}
@@ -309,7 +309,7 @@ body.loading #wrapper {
.event-name {
display: inline-block;
max-width: 150px;
max-width: 160px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -320,9 +320,6 @@ body.loading #wrapper {
padding: 3px 10px;
}
.event-dropdown {
width: 300px;
}
.event-dropdown, .mobile-event-dropdown, .select2-results {
.event-name-full, .primary {
display: block;
@@ -333,8 +330,11 @@ body.loading #wrapper {
color: $text-muted;
}
.active .event-daterange, .active .event-organizer, .active a,
.active .primary, .active .event-name-full,
.select2-results__option--highlighted .event-daterange,
.select2-results__option--highlighted .secondary,
.select2-results__option--highlighted .primary,
.select2-results__option--highlighted .event-name-full,
.select2-results__option--highlighted .event-organizer {
color: white;
}