From a6f4b3c9b2fae2065620455f3e2de7d519b814bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 20 Dec 2016 20:29:18 +0100 Subject: [PATCH] new ui that also works without javascript --- src/c3nav/mapdata/models/locations.py | 29 +++++ src/c3nav/site/static/site/css/c3nav.css | 44 +++++-- src/c3nav/site/static/site/js/c3nav.js | 83 +++++++++----- .../templates/site/fragment_location.html | 67 ++++++++--- src/c3nav/site/templates/site/main.html | 90 ++++++++------- src/c3nav/site/urls.py | 9 +- src/c3nav/site/views.py | 108 +++++++++++++----- 7 files changed, 306 insertions(+), 124 deletions(-) diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index b9bc9904..3001aad6 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -3,6 +3,7 @@ from collections import OrderedDict from django.core.cache import cache from django.db import models +from django.db.models import Q from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ @@ -245,6 +246,34 @@ def get_location(request, name): return filter_queryset_by_package_access(request, AreaLocation.objects.filter(name=name)).first() +def filter_words(queryset, words): + for word in words: + queryset = queryset.filter(Q(name__icontains=word) | Q(titles__icontains=word)) + return queryset + + +def search_location(request, search): + results = [] + location = get_location(request, search) + if location: + results.append(location) + + words = search.split(' ')[:10] + + queryset = AreaLocation.objects.all() + if isinstance(location, AreaLocation): + queryset.exclude(name=location.name) + results += sorted(filter_words(filter_queryset_by_package_access(request, queryset), words), + key=AreaLocation.get_sort_key, reverse=True) + + queryset = LocationGroup.objects.all() + if isinstance(location, LocationGroup): + queryset.exclude(name=location.name) + results += list(filter_words(filter_queryset_by_package_access(request, queryset), words)[:10]) + + return results + + class PointLocation(Location): def __init__(self, level: Level, x: int, y: int): self.level = level diff --git a/src/c3nav/site/static/site/css/c3nav.css b/src/c3nav/site/static/site/css/c3nav.css index d488d50b..c276bfcc 100644 --- a/src/c3nav/site/static/site/css/c3nav.css +++ b/src/c3nav/site/static/site/css/c3nav.css @@ -3,6 +3,14 @@ body, .btn { } +.main-view.mode-location .locations > .origin-select, +.main-view.mode-location .locations > .destination-select, +.main-view.mode-route .locations > .location-select, +.main-view.mode-location .routing { + display:none +} + + .locationselect .input-lg { height: 62px; } @@ -12,10 +20,10 @@ body, .btn { .locationselect .locationselect-selected { display:none; } -.locationselect.selected .locationselect-selected { +.location-group.selected .locationselect-selected { display:block; } -.locationselect.selected .locationselect-input { +.location-group.selected .locationselect-input { display:none; } .locationselect .icons { @@ -33,6 +41,16 @@ body, .btn { .locationselect .icons .reset { background-image:url('../img/icons/cross.svg'); } +.locationselect .icons .link { + background-image:url('../img/icons/link.svg'); +} + +.location-group .only-if-selected { + display:none; +} +.location-group.selected .only-if-selected { + display:block; +} .location { @@ -67,6 +85,9 @@ body, .btn { overflow:hidden; padding-bottom:4px; } +.tt-menu.noscript { + margin-top:0; +} .tt-dataset { background-color: #ffffff; border-width: 1px; @@ -77,8 +98,15 @@ body, .btn { -webkit-box-shadow: 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: 0 0 8px rgba(102, 175, 233, 0.6); } +.noscript .tt-dataset { + border-width:0 1px 1px; + border-color: #E7E7E7; + -webkit-box-shadow: 0 0 8px rgba(231, 231, 231, 0.6); + box-shadow: 0 0 8px rgba(231, 231, 231, 0.6); +} .tt-suggestion { padding: 6px 10px; + display:block; } .tt-suggestion.tt-cursor { color: #fff; @@ -91,6 +119,13 @@ body, .btn { margin: 0; } +.routing { + display:none; +} +.can-route .routing { + display:block; +} + svg { width:100%; height:auto; @@ -183,8 +218,3 @@ circle.pos { vertical-align:middle; line-height: 1.42857143; } - - -.login .container { - max-width:420px; -} diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index 3a9de2ac..f2a14215 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -1,6 +1,6 @@ c3nav = { init: function() { - if (!$('#main_view').length) return; + if (!$('.main-view').length) return; c3nav._typeahead_locations = new Bloodhound({ datumTokenizer: function(data) { @@ -29,19 +29,43 @@ c3nav = { } }; - c3nav.update_history_state(true); c3nav.init_typeahead($('.locationselect input:text')); $('.locationselect:not(.selected) .locationselect-input .tt-input').first().focus(); - $('.locationselect .icons .reset').click(c3nav._locationselect_reset) + $('.locationselect .icons .reset').click(c3nav._locationselect_reset); + $('#route-from-here').click(c3nav._click_route_from_here); + $('#route-to-here').click(c3nav._click_route_to_here); + + window.onpopstate = c3nav._onpopstate; }, _locationselect_reset: function(e) { e.preventDefault(); - var locationselect = $(this).closest('.locationselect'); - locationselect.find('.id-field').val(''); - locationselect.removeClass('selected').find('.tt-input').focus().keyup().removeData('enter_item'); - c3nav.update_history_state(); + var location_group = $(this).closest('.location-group'); + location_group.find('.id-field').val(''); + location_group.removeClass('selected').find('.tt-input').focus().removeData('enter_item'); + location_group,find('.tt-suggestion').remove(); + c3nav._locations_changed(); + }, + locationselect_focus: function() { + $('.location-group:visible:not(.selected) .locationselect-input .tt-input').first().focus(); + }, + + _click_route_from_here: function(e) { + c3nav._click_route_x_here(e, $('.origin-select')); + }, + _click_route_to_here: function(e) { + c3nav._click_route_x_here(e, $('.destination-select')); + }, + _click_route_x_here: function(e, location_group) { + e.preventDefault(); + $('.main-view').removeClass('mode-location').addClass('mode-route'); + from_group = $('.location-select'); + from_group.removeClass('selected'); + location_group.addClass('selected').find('.id-field').val(from_group.find('.id-field').val()); + location_group.find('.locationselect-selected .location').html(from_group.find('.locationselect-selected .location').html()); + c3nav._locations_changed(); + c3nav.locationselect_focus(); }, init_typeahead: function(elem) { @@ -55,6 +79,7 @@ c3nav = { }, _typeahead_keydown: function(e) { if (e.which == 13) { + e.preventDefault(); var target = $(e.target); enter_item = target.data('enter_item'); if (enter_item !== undefined) { @@ -63,16 +88,15 @@ c3nav = { } }, _typeahead_select: function(e, item) { - var locationselect = $(e.target).closest('.locationselect'); - locationselect.addClass('selected'); - var selected = locationselect.find('.locationselect-selected'); + var location_group = $(e.target).closest('.location-group'); + location_group.addClass('selected'); + var selected = location_group.find('.locationselect-selected'); selected.find('.title').text(item.title); selected.find('.subtitle').text(item.subtitle); selected.find('.id-field').val(item.id); e.target.blur(); - c3nav.update_history_state(); - - $('.locationselect:not(.selected) .locationselect-input .tt-input').first().focus(); + c3nav._locations_changed(); + c3nav.locationselect_focus(); }, _typeahead_blur: function(e) { $(e.target).val(''); @@ -81,24 +105,25 @@ c3nav = { $(e.target).data('enter_item', item); }, - update_history_state: function(e, replace) { - var origin = $(':input[name=origin]').val(); - var destination = $(':input[name=destination]').val(); - url = '/'; - if (origin !== '') { - url += origin + '/' - if (destination !== '') { - url += destination + '/' - } - } else if (destination !== '') { - url += '_/' + destination + '/' - } - if (replace) { - history.replaceState({}, '', url); + _locations_changed: function(e) { + var url; + if ($('.main-view').is('.mode-location')) { + url = '/l/'+$(':input[name=location]').val()+'/'; } else { - history.pushState({}, '', url); + var origin = $(':input[name=origin]').val(); + var destination = $(':input[name=destination]').val(); + if (origin !== '') { + url = (destination !== '') ? '/r/'+origin+'/'+destination+'/' : '/o/'+origin+'/'; + } else { + url = (destination !== '') ? '/d/'+destination+'/' : '/'; + } + $('.main-view').toggleClass('can-route', (origin !== '' && destination !== '')); } - + history.pushState({}, '', url); + }, + _onpopstate: function() { + document.location.href = document.location; } + }; $(document).ready(c3nav.init); diff --git a/src/c3nav/site/templates/site/fragment_location.html b/src/c3nav/site/templates/site/fragment_location.html index e3fa2075..f1ab7c15 100644 --- a/src/c3nav/site/templates/site/fragment_location.html +++ b/src/c3nav/site/templates/site/fragment_location.html @@ -1,19 +1,60 @@ {% load i18n %} -
- -
-
- -
-
-
- {{ location.title }} - {{ location.subtitle }} +
+ diff --git a/src/c3nav/site/templates/site/main.html b/src/c3nav/site/templates/site/main.html index aac0c101..f11d1ba7 100644 --- a/src/c3nav/site/templates/site/main.html +++ b/src/c3nav/site/templates/site/main.html @@ -4,54 +4,62 @@ {% load i18n %} {% block content %} -
+ {% csrf_token %} -
+
+ {% trans "Location" as heading %} + {% url 'site.index' as reset_url %} + {% include 'site/fragment_location.html' with name='location' location=location heading=heading reset_url=reset_url %} + {% trans "Origin" as heading %} - {% include 'site/fragment_location.html' with name='origin' location=origin heading=heading %} + {% url 'site.destination' destination=destination.location_id as reset_url %} + {% include 'site/fragment_location.html' with name='origin' location=origin heading=heading reset_url=reset_url %} {% trans "Destination" as heading %} - {% include 'site/fragment_location.html' with name='destination' location=destination heading=heading %} + {% url 'site.origin' origin=origin.location_id as reset_url %} + {% include 'site/fragment_location.html' with name='destination' location=destination heading=heading reset_url=reset_url %}
-
-
- - {% include 'site/fragment_select.html' with name='stairs' value=stairs %} -
-
- - {% include 'site/fragment_select.html' with name='escalators' value=escalators %} -
-
- - {% include 'site/fragment_select.html' with name='elevators' value=elevators %} -
-
- -

- {% for name, includable in includables %} - - {% endfor %} - {% for name, excludable in excludables %} - - {% endfor %} -

-
-
-
-
-
- +
+
+
+ + {% include 'site/fragment_select.html' with name='stairs' value=stairs %} +
+
+ + {% include 'site/fragment_select.html' with name='escalators' value=escalators %} +
+
+ + {% include 'site/fragment_select.html' with name='elevators' value=elevators %} +
+
+ +

+ {% for name, includable in includables %} + + {% endfor %} + {% for name, excludable in excludables %} + + {% endfor %} +

-
- +
+
+
+ +
+
+
+ +
diff --git a/src/c3nav/site/urls.py b/src/c3nav/site/urls.py index 70c40201..c0643399 100644 --- a/src/c3nav/site/urls.py +++ b/src/c3nav/site/urls.py @@ -4,8 +4,9 @@ from c3nav.site.views import level_image, main urlpatterns = [ url(r'^map/(?P[a-z0-9-_:]+).png$', level_image, name='site.level_image'), - url(r'^(?P[a-z0-9-_:]+)/$', main, name='site.main'), - url(r'^_/(?P[a-z0-9-_:]+)/$', main, name='site.main'), - url(r'^(?P[a-z0-9-_:]+)/(?P[a-z0-9-_:]+)/$', main, name='site.main'), - url(r'^$', main, name='site.main') + url(r'^l/(?P[a-z0-9-_:]+)/$', main, name='site.location'), + url(r'^o/(?P[a-z0-9-_:]+)/$', main, name='site.origin'), + url(r'^d/(?P[a-z0-9-_:]+)/$', main, name='site.destination'), + url(r'^r/(?P[a-z0-9-_:]+)/(?P[a-z0-9-_:]+)/$', main, name='site.route'), + url(r'^$', main, name='site.index') ] diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index f642c29e..85640107 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -2,10 +2,11 @@ from datetime import timedelta from django.http import Http404 from django.shortcuts import get_object_or_404, render +from django.urls import reverse from django.utils import timezone from c3nav.mapdata.models import Level -from c3nav.mapdata.models.locations import get_location +from c3nav.mapdata.models.locations import get_location, search_location from c3nav.mapdata.permissions import get_excludables_includables from c3nav.mapdata.render.compose import composer from c3nav.mapdata.utils.misc import get_dimensions @@ -30,17 +31,61 @@ def reverse_ctypes(ctypes, name): return 'down' if name + '_down' in ctypes else 'no' -def main(request, origin=None, destination=None): - if origin: - origin = get_location(request, origin) - if origin is None: - raise Http404 +def get_location_or_404(request, location): + if location is None: + return None - if destination: - destination = get_location(request, destination) - if destination is None: - raise Http404 + location = get_location(request, location) + if location is None: + raise Http404 + return location + + +def main(request, location=None, origin=None, destination=None): + location = get_location_or_404(request, location) + origin = get_location_or_404(request, origin) + destination = get_location_or_404(request, destination) + + mode = 'location' if not origin and not destination else 'route' + + ctx = { + 'location': location, + 'origin': origin, + 'destination': destination, + 'mode': mode, + } + + search = None + if not origin and not destination: + search = 'location' + elif origin and not destination: + search = 'destination' + elif destination and not origin: + search = 'origin' + + if search is not None: + search_query = request.POST.get(search+'_search', '').strip() or None + if search_query: + results = search_location(request, search_query) + + url = 'site.location' if search == 'location' else 'site.route' + kwargs = {} + if origin: + kwargs['origin'] = origin.location_id + if destination: + kwargs['destination'] = destination.location_id + for result in results: + kwargs[search] = result.location_id + result.url = reverse(url, kwargs=kwargs) + + ctx.update({ + 'search': search, + 'search_query': search_query, + 'search_results': results, + }) + + # everything about settings include = () avoid = () stairs = 'yes' @@ -50,7 +95,6 @@ def main(request, origin=None, destination=None): save_settings = False if 'c3nav_settings' in request.COOKIES: cookie_value = request.COOKIES['c3nav_settings'] - print(cookie_value) if isinstance(cookie_value, dict): stairs = cookie_value.get('stairs', stairs) @@ -65,7 +109,7 @@ def main(request, origin=None, destination=None): save_settings = True - if request.method in 'POST': + if request.method == 'POST': stairs = request.POST.get('stairs', stairs) escalators = request.POST.get('escalators', escalators) elevators = request.POST.get('elevators', elevators) @@ -86,11 +130,22 @@ def main(request, origin=None, destination=None): include = set(include) & set(includables) avoid = set(avoid) & set(excludables) - if request.method in 'POST': + if request.method == 'POST': save_settings = request.POST.get('save_settings', '') == '1' - route = None - if request.method in 'POST' and origin and destination: + ctx.update({ + 'stairs': stairs, + 'escalators': escalators, + 'elevators': elevators, + 'excludables': excludables.items(), + 'includables': includables.items(), + 'include': include, + 'avoid': avoid, + 'save_settings': save_settings, + }) + + # routing + if request.method == 'POST' and origin and destination: public = ':public' not in avoid nonpublic = ':nonpublic' in include @@ -100,29 +155,22 @@ def main(request, origin=None, destination=None): route = route.split() route.create_routeparts() + ctx.update({ + 'route': route, + }) + width, height = get_dimensions() - response = render(request, 'site/main.html', { - 'origin': origin, - 'destination': destination, - - 'stairs': stairs, - 'escalators': escalators, - 'elevators': elevators, - 'excludables': excludables.items(), - 'includables': includables.items(), - 'include': include, - 'avoid': avoid, - 'save_settings': save_settings, - - 'route': route, + ctx.update({ 'width': width, 'height': height, 'svg_width': width*6, 'svg_height': height*6, }) - if request.method in 'POST' and save_settings: + response = render(request, 'site/main.html', ctx) + + if request.method == 'POST' and save_settings: cookie_value = { 'stairs': stairs, 'escalators': escalators,