new ui that also works without javascript

This commit is contained in:
Laura Klünder 2016-12-20 20:29:18 +01:00
parent d4289fa5e4
commit a6f4b3c9b2
7 changed files with 306 additions and 124 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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) {
_locations_changed: function(e) {
var url;
if ($('.main-view').is('.mode-location')) {
url = '/l/'+$(':input[name=location]').val()+'/';
} else {
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);
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);

View file

@ -1,9 +1,28 @@
{% load i18n %}
<div class="form-group col-md-6">
<div class="col-md-{% if name == 'location' %}12{% else %}6{% endif %} location-group {{ name }}-select{% if location %} selected{% endif %}">
<div class="form-group{% if search == name and not search_results %} has-error{% endif %}" data-name="{{ name }}">
<label for="{{ name }}_input">{{ heading }}</label>
<div class="locationselect {% if location %} selected{% endif %}">
<div class="locationselect">
<div class="locationselect-input">
<input type="text" class="form-control input-lg" id="{{ name }}_input" name="{{ name }}_search" placeholder="{% trans 'Search any Location…' %}">
{% if search == name and search_results %}
<span class="twitter-typeahead" style="position: relative; display: inline-block;">
{% endif %}
<input type="text" class="form-control input-lg" id="{{ name }}_input"
name="{{ name }}_search" placeholder="{% trans 'Search any Location…' %}"
{% if search and not location %} value="{% if search == name %}{{ search_query }}{% endif %}"{% endif %}>
{% if search == name and search_results %}
<div class="tt-menu tt-open noscript" style="display: block;">
<div class="tt-dataset tt-dataset-0">
{% for result in search_results %}
<a href="{{ result.url }}" class="location tt-suggestion">
<span class="title">{{ result.title }}</span>
<small class="subtitle">{{ result.subtitle }}</small>
</a>
{% endfor %}
</div>
</div>
</span>
{% endif %}
</div>
<div class="locationselect-selected">
<div class="location form-control input-lg">
@ -11,9 +30,31 @@
<small class="subtitle">{{ location.subtitle }}</small>
</div>
<div class="icons">
<a href="" class="reset"></a>
{% if name != 'location' %}
<a href="{% if location %}{% url 'site.location' location=location.location_id %}{% endif %}" class="link"></a>
{% endif %}
<a href="{{ reset_url }}" class="reset"></a>
</div>
<input type="hidden" name="{{ name }}" value="{{ location.location_id }}" class="id-field">
</div>
</div>
{% if search == name and not search_results %}
<span class="help-block">{% trans 'No location matched your search query.' %}</span>
{% endif %}
</div>
{% if name == 'location' %}
<div class="row only-if-selected">
<div class="col-xs-6">
<a class="btn btn-primary btn-block" id="route-to-here" href="{% if location %}{% url 'site.destination' destination=location.location_id %}{% endif %}">{% trans 'Route to here' %}</a>
</div>
<div class="col-xs-6">
<a class="btn btn-default btn-block" id="route-from-here" href="{% if location %}{% url 'site.origin' origin=location.location_id %}{% endif %}">{% trans 'Route from here' %}</a>
</div>
</div>
{% endif %}
{% if not location %}
<noscript>
<button type="submit" class="btn btn-primary btn-block">{% trans 'Search' %}</button>
</noscript>
{% endif %}
</div>

View file

@ -4,15 +4,22 @@
{% load i18n %}
{% block content %}
<form method="post" id="main_view">
<form method="post" class="main-view mode-{{ mode }}{% if origin and destination %} can-route{% endif %}">
{% csrf_token %}
<div class="row">
<div class="row locations">
{% 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 %}
</div>
<div class="routing">
<div class="row settings">
<div class="form-group col-md-2">
<label for="stairs-select">{% trans 'Stairs' %}</label>
@ -54,6 +61,7 @@
<button type="submit" id="submitbtn" class="btn btn-block btn-lg btn-primary">{% trans 'Get Route' %}</button>
</div>
</div>
</div>
</form>
{% if route %}

View file

@ -4,8 +4,9 @@ from c3nav.site.views import level_image, main
urlpatterns = [
url(r'^map/(?P<level>[a-z0-9-_:]+).png$', level_image, name='site.level_image'),
url(r'^(?P<origin>[a-z0-9-_:]+)/$', main, name='site.main'),
url(r'^_/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.main'),
url(r'^(?P<origin>[a-z0-9-_:]+)/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.main'),
url(r'^$', main, name='site.main')
url(r'^l/(?P<location>[a-z0-9-_:]+)/$', main, name='site.location'),
url(r'^o/(?P<origin>[a-z0-9-_:]+)/$', main, name='site.origin'),
url(r'^d/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.destination'),
url(r'^r/(?P<origin>[a-z0-9-_:]+)/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.route'),
url(r'^$', main, name='site.index')
]

View file

@ -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):
def get_location_or_404(request, location):
if location is None:
return None
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:
origin = get_location(request, origin)
if origin is None:
raise Http404
kwargs['origin'] = origin.location_id
if destination:
destination = get_location(request, destination)
if destination is None:
raise Http404
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,