Use browser history instead of hash for annotation URL.
authorAlexandre Segura <mex.zktk@gmail.com>
Mon, 17 Apr 2017 14:42:38 +0200
changeset 470 6c43539e5c67
parent 469 287af0822445
child 471 d80f62757142
Use browser history instead of hash for annotation URL.
src/iconolab/templates/iconolab/collection_home.html
src/iconolab/templates/iconolab/detail_image.html
src/iconolab/templates/iconolab_base.html
src/iconolab/templates/partials/header.html
src/iconolab/templates/partials/image_annotations_list.html
src/iconolab/templates/partials/user_pages/annotations_commented_panel.html
src/iconolab/templates/partials/user_pages/annotations_contributed_panel.html
src/iconolab/templates/partials/user_pages/annotations_created_panel.html
src/iconolab/templatetags/iconolab_tags.py
src/iconolab/urls.py
src/iconolab/views/objects.py
src_js/iconolab-bundle/src/components/editor/AnnotationList.vue
--- a/src/iconolab/templates/iconolab/collection_home.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/iconolab/collection_home.html	Mon Apr 17 14:42:38 2017 +0200
@@ -45,7 +45,7 @@
           <div class="col-md-4">
             <div class="fragment-container" style="position: relative">
               {% thumbnail annotation.image.media "250x250" crop=False as im %}
-                <a href="{% url 'image_detail' collection_name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+                <a href="{% url 'annotation_detail' collection_name annotation.image.image_guid annotation.annotation_guid %}">
                   <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
                   <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                     <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
--- a/src/iconolab/templates/iconolab/detail_image.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/iconolab/detail_image.html	Mon Apr 17 14:42:38 2017 +0200
@@ -22,6 +22,7 @@
         <annotation-list
           v-bind:annotations="annotations"
           v-bind:annotation="annotation"
+          annotation-url="{% url 'annotation_detail' collection_name image_guid ':annotation_guid' %}"
           v-on:click:annotation="onAnnotationClick($event)"
           v-on:close:annotation="onAnnotationClose"><annotation-list>
       {% else %}
@@ -100,8 +101,18 @@
 
   var currentPath = "{{ request.path }}";
   var getCommentFormURL = "{% url 'get_comment_form' ':annotation_guid' %}"
+  var imageURL = "{% url 'image_detail' collection_name image_guid %}"
+  var annotationURL = "{% url 'annotation_detail' collection_name image_guid ':annotation_guid' %}"
   var isAuthenticated = {% if user.is_authenticated %}true{% else %}false{% endif %};
 
+  window.onpopstate = function(event) {
+    if (event.state.annotation_guid) {
+      vm.annotation = getAnnotationByGuid(event.state.annotation_guid);
+    } else {
+      vm.annotation = null;
+    }
+  };
+
   Vue.component('comment-form', {
     template: '<div></div>',
     data: function() {
@@ -119,7 +130,12 @@
     {% endfor %}
   {% endif %}
 
-  var annotation = getAnnotationFromHash();
+  {% if annotation %}
+  var annotation = getAnnotationByGuid("{{ annotation.annotation_guid }}");
+  {% else %}
+  var annotation = null;
+  {% endif %}
+
   if (annotation) {
     getCommentForm(annotation).then(function(template) {
       updateCommentFormComponent(template);
@@ -129,9 +145,10 @@
     createViewModel();
   }
 
+  var vm;
   function createViewModel() {
 
-    var vm = new Vue({
+    vm = new Vue({
       el: '.annotation-navigator',
       data: function() {
         return {
@@ -146,12 +163,12 @@
           getCommentForm(annotation).then(function(template) {
             updateCommentFormComponent(template);
             self.annotation = annotation;
-            location.hash = '#' + annotation.annotation_guid;
+            history.pushState({ annotation_guid: annotation.annotation_guid }, '', annotationURL.replace(':annotation_guid', annotation.annotation_guid));
           });
         },
         onAnnotationClose: function() {
           this.annotation = null;
-          location.hash = '';
+          history.pushState({ annotation_guid: null }, '', imageURL);
         }
       }
     });
@@ -220,18 +237,14 @@
 
   function getCommentForm(annotation) {
     return $.get(getCommentFormURL.replace(':annotation_guid', annotation.annotation_guid), {
-      next: currentPath + '#' + annotation.annotation_guid
+      next: annotationURL.replace(':annotation_guid', annotation.annotation_guid)
     })
   }
 
-  function getAnnotationFromHash() {
-    var url = location.hash.slice(1);
-    if (url.length) {
-      var annotation_guid = url;
-      return _.find(annotations, function(annotation) {
-        return annotation.annotation_guid === annotation_guid;
-      });
-    }
+  function getAnnotationByGuid(guid) {
+    return _.find(annotations, function(annotation) {
+      return annotation.annotation_guid === guid;
+    });
   }
 
 </script>
--- a/src/iconolab/templates/iconolab_base.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/iconolab_base.html	Mon Apr 17 14:42:38 2017 +0200
@@ -23,7 +23,7 @@
 	<body>
 		{% include "partials/header.html" %}
 
-	<div class="{% if request.resolver_match.url_name == 'image_detail' %}container-fluid{% else %}container{% endif %}">
+	<div class="{% container_class %}">
 	{% block content %}{% endblock %}
 	</div>
 
--- a/src/iconolab/templates/partials/header.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/partials/header.html	Mon Apr 17 14:42:38 2017 +0200
@@ -1,10 +1,11 @@
 {% load notifications_tags %}
+{% load iconolab_tags %}
 {% load staticfiles %}
 
 <header>
   <section id="topnav">
     <nav class="navbar navbar-default">
-      <div class="{% if request.resolver_match.url_name == 'image_detail' %}container-fluid{% else %}container{% endif %}">
+      <div class="{% container_class %}">
         <div class="navbar-header">
             {# {% if not homepage %} #}
               <a class="navbar-brand" href="{% url 'home' %}">
@@ -37,6 +38,6 @@
   </section>
 </header>
 
-<div class="{% if request.resolver_match.url_name == 'image_detail' %}container-fluid{% else %}container{% endif %}">
+<div class="{% container_class %}">
 {% include "partials/header_breadcrumbs.html" %}
 </div>
--- a/src/iconolab/templates/partials/image_annotations_list.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/partials/image_annotations_list.html	Mon Apr 17 14:42:38 2017 +0200
@@ -25,7 +25,7 @@
             <td class="col-md-2">
               <div class="fragment-container" style="position: relative">
                 {% thumbnail annotation.image.media "150x150" crop=False as im %}
-                  <a href="{% url 'image_detail' collection_name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+                  <a href="{% url 'annotation_detail' collection_name annotation.image.image_guid annotation.annotation_guid %}">
                     <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
                     <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                       <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
--- a/src/iconolab/templates/partials/user_pages/annotations_commented_panel.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/partials/user_pages/annotations_commented_panel.html	Mon Apr 17 14:42:38 2017 +0200
@@ -14,7 +14,7 @@
       <div style="position:relative" class="{% if large_image %}large-{% endif %}image-detail">
       {% if large_image %}
         {% thumbnail annotation.image.media "400x400" crop=False as im %}
-          <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+          <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
             <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
             <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
               <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -25,7 +25,7 @@
         {% endthumbnail %}
       {% else %}
         {% thumbnail annotation.image.media "150x150" crop=False as im %}
-        <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+        <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
             <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
             <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
               <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -45,7 +45,7 @@
           <dt>A commenté le : </dt>
           <dd>{{annotation_data.latest_comment_date}}</dd>
         </dl>
-        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">Voir annotation</a>
+        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">Voir annotation</a>
       </div>
       {% if with_stats %}
       <div class="stats-annotation-userpage">
--- a/src/iconolab/templates/partials/user_pages/annotations_contributed_panel.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/partials/user_pages/annotations_contributed_panel.html	Mon Apr 17 14:42:38 2017 +0200
@@ -14,7 +14,7 @@
       <div style="position:relative" class="{% if large_image %}large-{% endif %}image-detail">
         {% if large_image %}
           {% thumbnail annotation.image.media "400x400" crop=False as im %}
-            <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+            <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
               <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
               <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                 <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -25,7 +25,7 @@
           {% endthumbnail %}
         {% else %}
           {% thumbnail annotation.image.media "150x150" crop=False as im %}
-            <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+            <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
               <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
               <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                 <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -47,7 +47,7 @@
           <dt>.. Révisions acceptées: </dt>
           <dd><span class="badge {% if annotation_data.accepted_count %}badge-success{% endif %}">{{annotation_data.accepted_count}}</span></dd>
         </dl>
-        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">Voir annotation</a>
+        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">Voir annotation</a>
       </div>
       {% if with_stats %}
       <div class="stats-annotation-userpage">
--- a/src/iconolab/templates/partials/user_pages/annotations_created_panel.html	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templates/partials/user_pages/annotations_created_panel.html	Mon Apr 17 14:42:38 2017 +0200
@@ -13,7 +13,7 @@
       <div style="position:relative" class="{% if large_image %}large-{% endif %}image-detail">
         {% if large_image %}
           {% thumbnail annotation.image.media "400x400" crop=False as im %}
-            <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+            <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
               <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
               <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                 <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -24,7 +24,7 @@
           {% endthumbnail %}
         {% else %}
           {% thumbnail annotation.image.media "150x150" crop=False as im %}
-          <a href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">
+          <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
               <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
               <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
                 <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
@@ -46,7 +46,7 @@
           <dd><span class="badge {% if annotation.stats.awaiting_revisions_count > 0 %}badge-warning{% endif %}">{{annotation.stats.awaiting_revisions_count}}</span></dd>
         </dl>
         </dl>
-        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'image_detail' annotation.image.item.collection.name annotation.image.image_guid %}#{{ annotation.annotation_guid }}">Voir annotation</a>
+        <a class="btn btn-default btn-sm userpage-annotation-btn pull-left" href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">Voir annotation</a>
       </div>
       {% if with_stats %}
       <div class="stats-annotation-userpage">
--- a/src/iconolab/templatetags/iconolab_tags.py	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/templatetags/iconolab_tags.py	Mon Apr 17 14:42:38 2017 +0200
@@ -65,3 +65,11 @@
         serializer = AnnotationRevisionSerializer(data)
         return JSONRenderer().render(serializer.data)
     return serialize('json', [data])
+
+@register.simple_tag(takes_context=True)
+def container_class(context):
+    request = context['request']
+    routes = ['image_detail', 'annotation_detail']
+    if request.resolver_match.url_name in routes:
+        return 'container-fluid'
+    return 'container'
--- a/src/iconolab/urls.py	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/urls.py	Mon Apr 17 14:42:38 2017 +0200
@@ -38,7 +38,8 @@
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)$', views.objects.ShowImageView.as_view(), name='image_detail'),
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/?$', django_views.generic.RedirectView.as_view(pattern_name="image_detail")),
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/create$', login_required(views.objects.CreateAnnotationView.as_view()), name='annotation_create'),
-    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.objects.ShowAnnotationView.as_view(), name='annotation_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/?$', views.objects.ShowAnnotationView.as_view(), name='annotation_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.objects.ShowAnnotationViewOld.as_view(), name='annotation_detail_old'),
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/readonly$', views.objects.ReadonlyAnnotationView.as_view(), name='annotation_readonly'),
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.objects.EditAnnotationView.as_view()), name='annotation_edit'),
     url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/?$', django_views.generic.RedirectView.as_view(pattern_name="annotation_detail")),
--- a/src/iconolab/views/objects.py	Mon Apr 17 13:31:59 2017 +0200
+++ b/src/iconolab/views/objects.py	Mon Apr 17 14:42:38 2017 +0200
@@ -450,6 +450,23 @@
         return render(request, 'iconolab/change_annotation.html', context)
 
 class ShowAnnotationView(View, ContextMixin, IconolabObjectView):
+    def get(self, request, *args, **kwargs):
+        success, result = self.check_kwargs(kwargs)
+        if success:
+            (collection, image, annotation) = result
+        else:
+            return result(request)
+        context = super(ShowAnnotationView, self).get_context_data(**kwargs)
+        context['collection_name'] = self.kwargs.get('collection_name', '')
+        context['image_guid'] = self.kwargs.get('image_guid', '')
+        context['collection'] = collection
+        context['image'] = image
+        context['item'] = image.item
+        context['annotation'] = annotation
+        context['form'] = AnnotationRevisionForm()
+        return render(request, 'iconolab/detail_image.html', context)
+
+class ShowAnnotationViewOld(View, ContextMixin, IconolabObjectView):
     """
         View that show a given annotation with the corresponding data, links to
         submit new revisions and the paginated comments thread.
@@ -608,8 +625,8 @@
             if (annotation.author != revision_author):
                 messages.add_message(request, messages.INFO, "Votre modification a été prise en compte. Le créateur de l'annotation a été notifié.")
 
-            redirect_url = reverse('image_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid})
-            return redirect(redirect_url + '#' + str(annotation.annotation_guid))
+            redirect_url = reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': str(annotation.annotation_guid)})
+            return redirect(redirect_url)
         context = self.get_context_data(**kwargs)
         context['image'] = image
         context['form'] = annotation_form
--- a/src_js/iconolab-bundle/src/components/editor/AnnotationList.vue	Mon Apr 17 13:31:59 2017 +0200
+++ b/src_js/iconolab-bundle/src/components/editor/AnnotationList.vue	Mon Apr 17 14:42:38 2017 +0200
@@ -3,7 +3,7 @@
         <a v-for="annotation in annotations"
             ref="annotations"
             @click="toggleAnnotation($event, annotation)"
-            :href="'#' + annotation.annotation_guid"
+            :href="getAnnotationURL(annotation.annotation_guid)"
             class="list-group-item"
             v-bind:class="{ active: isActive(annotation) }">
             <h3 class="small list-group-item-heading">
@@ -24,7 +24,8 @@
     export default {
         props: [
             'annotations',
-            'annotation'
+            'annotation',
+            'annotationUrl'
         ],
         mounted() {
             if (this.annotation) {
@@ -39,6 +40,9 @@
             }
         },
         methods: {
+            getAnnotationURL: function(guid) {
+                return this.annotationUrl.replace(':annotation_guid', guid);
+            },
             toggleAnnotation: function(e, annotation) {
                 e.preventDefault();
                 if (this.annotation && this.annotation === annotation) {
@@ -56,7 +60,7 @@
             },
             slideToAnnotation: function() {
                 var el = _.find(this.$refs.annotations, (el) => {
-                    return $(el).attr('href') === ('#' + this.annotation.annotation_guid);
+                    return $(el).attr('href') === this.getAnnotationURL(this.annotation.annotation_guid);
                 });
                 if (el) {
                     var $container = $(this.$el.closest('.panel'));