Browse Source

HUE-1387 [core] Follow me tour plugin

The question mark is not shown if no tours are available
Preference checkbox added on the admin wizard
Enrico Berti 12 years ago
parent
commit
b625deb591

+ 18 - 4
apps/about/src/about/templates/admin_wizard.mako

@@ -182,7 +182,20 @@ ${ commonheader(_('Quick Start'), "quickstart", user, "100px") | n,unicode }
         </div>
         </div>
       </div>
       </div>
 
 
-      <br/>
+      <div class="widget-box">
+        <div class="widget-title">
+          <span class="icon">
+            <i class="icon-th-list"></i>
+          </span>
+          <h5>${ _('Tours and tutorials') }</h5>
+        </div>
+        <div class="widget-content" style="padding-left: 14px">
+          <label class="checkbox">
+            <input class="updatePreferences" type="checkbox" name="tours_and_tutorials" style="margin-right: 10px" title="${ ('Check to enable the tours and tutorials') }" ${ tours_and_tutorials and "checked" }/>
+            ${ ('Display the "Available Tours" question mark when tours are available for a specific page.') }
+          </label>
+        </div>
+      </div>
 
 
       <div class="widget-box">
       <div class="widget-box">
         <div class="widget-title">
         <div class="widget-title">
@@ -193,7 +206,7 @@ ${ commonheader(_('Quick Start'), "quickstart", user, "100px") | n,unicode }
         </div>
         </div>
         <div class="widget-content" style="padding-left: 14px">
         <div class="widget-content" style="padding-left: 14px">
           <label class="checkbox">
           <label class="checkbox">
-            <input id="collectUsageBtn" type="checkbox" name="collect_usage" style="margin-right: 10px" title="${ ('Check to enable usage analytics') }" ${ collect_usage and "checked" }/>
+            <input class="updatePreferences" type="checkbox" name="collect_usage" style="margin-right: 10px" title="${ ('Check to enable usage analytics') }" ${ collect_usage and "checked" }/>
             ${ ('Help improve Hue with anonymous usage analytics.') }
             ${ ('Help improve Hue with anonymous usage analytics.') }
             <a href="javascript:void(0)" style="display: inline" data-trigger="hover" data-toggle="popover" data-placement="right" rel="popover"
             <a href="javascript:void(0)" style="display: inline" data-trigger="hover" data-toggle="popover" data-placement="right" rel="popover"
                title="${_('How does it work?') }"
                title="${_('How does it work?') }"
@@ -331,8 +344,8 @@ $(document).ready(function(){
     }
     }
   });
   });
 
 
-  $("#collectUsageBtn").click(function () {
-    $.post("${ url('about:collect_usage') }", $("input").serialize(), function(data) {
+  $(".updatePreferences").click(function () {
+    $.post("${ url('about:update_preferences') }", $("input").serialize(), function(data) {
       if (data.status == 0) {
       if (data.status == 0) {
         $.jHueNotify.info('${ _("Configuration updated") }');
         $.jHueNotify.info('${ _("Configuration updated") }');
       } else {
       } else {
@@ -340,6 +353,7 @@ $(document).ready(function(){
       }
       }
     });
     });
   });
   });
+
 });
 });
 </script>
 </script>
 % endif
 % endif

+ 4 - 2
apps/about/src/about/tests.py

@@ -59,18 +59,20 @@ class TestAboutWithNoCluster(TestAboutBase):
 
 
   def test_collect_usage(self):
   def test_collect_usage(self):
     collect_usage = Settings.get_settings().collect_usage
     collect_usage = Settings.get_settings().collect_usage
+    tours_and_tutorials = Settings.get_settings().tours_and_tutorials
 
 
     try:
     try:
-      response = self.client.post(reverse('about:collect_usage'), {'collect_usage': False})
+      response = self.client.post(reverse('about:update_preferences'), {'collect_usage': False})
       data = json.loads(response.content)
       data = json.loads(response.content)
       assert_equal(data['status'], 0)
       assert_equal(data['status'], 0)
       assert_false(data['collect_usage'] == True) # Weird but works
       assert_false(data['collect_usage'] == True) # Weird but works
 
 
-      response = self.client.post(reverse('about:collect_usage'), {'collect_usage': True})
+      response = self.client.post(reverse('about:update_preferences'), {'collect_usage': True})
       data = json.loads(response.content)
       data = json.loads(response.content)
       assert_equal(data['status'], 0)
       assert_equal(data['status'], 0)
       assert_true(data['collect_usage'])
       assert_true(data['collect_usage'])
     finally:
     finally:
       settings = Settings.get_settings()
       settings = Settings.get_settings()
       settings.collect_usage = collect_usage
       settings.collect_usage = collect_usage
+      settings.tours_and_tutorials = tours_and_tutorials
       settings.save()
       settings.save()

+ 1 - 1
apps/about/src/about/urls.py

@@ -21,5 +21,5 @@ urlpatterns = patterns('about.views',
   url(r'^$', 'admin_wizard', name='index'),
   url(r'^$', 'admin_wizard', name='index'),
   url(r'^admin_wizard$', 'admin_wizard', name='admin_wizard'),
   url(r'^admin_wizard$', 'admin_wizard', name='admin_wizard'),
 
 
-  url(r'^collect_usage$', 'collect_usage', name='collect_usage'),
+  url(r'^update_preferences$', 'update_preferences', name='update_preferences'),
 )
 )

+ 5 - 1
apps/about/src/about/views.py

@@ -37,6 +37,7 @@ def admin_wizard(request):
   app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)]
   app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)]
 
 
   collect_usage = Settings.get_settings().collect_usage
   collect_usage = Settings.get_settings().collect_usage
+  tours_and_tutorials = Settings.get_settings().tours_and_tutorials
 
 
   return render('admin_wizard.mako', request, {
   return render('admin_wizard.mako', request, {
       'version': settings.HUE_DESKTOP_VERSION,
       'version': settings.HUE_DESKTOP_VERSION,
@@ -44,20 +45,23 @@ def admin_wizard(request):
       'apps': dict([(app.name, app) for app in apps]),
       'apps': dict([(app.name, app) for app in apps]),
       'app_names': app_names,
       'app_names': app_names,
       'collect_usage': collect_usage,
       'collect_usage': collect_usage,
+      'tours_and_tutorials': tours_and_tutorials,
       'trash_enabled': get_trash_interval()
       'trash_enabled': get_trash_interval()
   })
   })
 
 
 
 
-def collect_usage(request):
+def update_preferences(request):
   response = {'status': -1, 'data': ''}
   response = {'status': -1, 'data': ''}
 
 
   if request.method == 'POST':
   if request.method == 'POST':
     try:
     try:
       settings = Settings.get_settings()
       settings = Settings.get_settings()
       settings.collect_usage = request.POST.get('collect_usage', False)
       settings.collect_usage = request.POST.get('collect_usage', False)
+      settings.tours_and_tutorials = request.POST.get('tours_and_tutorials', False)
       settings.save()
       settings.save()
       response['status'] = 0
       response['status'] = 0
       response['collect_usage'] = settings.collect_usage
       response['collect_usage'] = settings.collect_usage
+      response['tours_and_tutorials'] = settings.tours_and_tutorials
     except Exception, e:
     except Exception, e:
       response['data'] = str(e)
       response['data'] = str(e)
   else:
   else:

+ 73 - 0
desktop/core/src/desktop/migrations/0006_settings_add_tour.py

@@ -0,0 +1,73 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'Settings'
+        db.add_column('desktop_settings', 'tours_and_tutorials', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True, blank=True))
+        db.send_create_signal('desktop', ['Settings'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'Settings'
+        db.delete_column('desktop_settings', 'tours_and_tutorials')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'desktop.settings': {
+            'Meta': {'object_name': 'Settings'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'collect_usage': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'tours_and_tutorials': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'})
+        },
+        'desktop.userpreferences': {
+            'Meta': {'object_name': 'UserPreferences'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'value': ('django.db.models.fields.TextField', [], {'max_length': '4096'})
+        }
+    }
+
+    complete_apps = ['desktop']

+ 1 - 0
desktop/core/src/desktop/models.py

@@ -28,6 +28,7 @@ class UserPreferences(models.Model):
 
 
 class Settings(models.Model):
 class Settings(models.Model):
   collect_usage = models.BooleanField(db_index=True, default=True)
   collect_usage = models.BooleanField(db_index=True, default=True)
+  tours_and_tutorials = models.BooleanField(db_index=True, default=True)
 
 
   @classmethod
   @classmethod
   def get_settings(cls):
   def get_settings(cls):

+ 4 - 2
desktop/core/src/desktop/templates/common_footer.html

@@ -90,7 +90,7 @@ limitations under the License.
             }
             }
           }, 200);
           }, 200);
         }
         }
-
+        {% if tours_and_tutorials %}
         $.jHueTour({});
         $.jHueTour({});
         if ($.totalStorage("jHueTourExtras") != null) {
         if ($.totalStorage("jHueTourExtras") != null) {
           $.jHueTour({tours: $.totalStorage("jHueTourExtras")});
           $.jHueTour({tours: $.totalStorage("jHueTourExtras")});
@@ -105,6 +105,7 @@ limitations under the License.
                   results = regex.exec(_qs);
                   results = regex.exec(_qs);
           return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
           return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
         }
         }
+        {% endif %}
       });
       });
 
 
       function resetPrimaryButtonsStatus() {
       function resetPrimaryButtonsStatus() {
@@ -163,7 +164,8 @@ limitations under the License.
       {% endif %}
       {% endif %}
 
 
     </script>
     </script>
-
+    {% if tours_and_tutorials %}
     <script src="/static/js/Source/jHue/available.tours.js"></script>
     <script src="/static/js/Source/jHue/available.tours.js"></script>
+    {% endif %}
   </body>
   </body>
 </html>
 </html>

+ 2 - 1
desktop/core/src/desktop/views.py

@@ -329,7 +329,8 @@ def commonfooter(messages=None):
   return render_to_string("common_footer.html", {
   return render_to_string("common_footer.html", {
     'messages': messages,
     'messages': messages,
     'version': settings.HUE_DESKTOP_VERSION,
     'version': settings.HUE_DESKTOP_VERSION,
-    'collect_usage': hue_settings.collect_usage
+    'collect_usage': hue_settings.collect_usage,
+    'tours_and_tutorials': hue_settings.tours_and_tutorials
   })
   })
 
 
 
 

+ 31 - 24
desktop/core/static/js/Source/jHue/jquery.tour.js

@@ -68,7 +68,8 @@
       },
       },
       tours: [],
       tours: [],
       showRemote: false,
       showRemote: false,
-      placement: "right"
+      questionMarkPlacement: "right",
+      hideIfNoneAvailable: true
     };
     };
 
 
   function Plugin(element, options) {
   function Plugin(element, options) {
@@ -98,10 +99,6 @@
     var _this = this;
     var _this = this;
     _this.initQuestionMark();
     _this.initQuestionMark();
     var _tourMask = $("<div>").attr("id", "jHueTourMask");
     var _tourMask = $("<div>").attr("id", "jHueTourMask");
-    _tourMask.addClass("jHueTourBadge");
-    if (_this.options.placement == "left"){
-      _tourMask.addClass("jHueTourBadgeLeft");
-    }
     _tourMask.width($(document).width()).height($(document).height())
     _tourMask.width($(document).width()).height($(document).height())
     _tourMask.click(function () {
     _tourMask.click(function () {
       _this.closeCurtains();
       _this.closeCurtains();
@@ -120,24 +117,20 @@
     var _this = this;
     var _this = this;
     $("#jHueTourQuestion").remove();
     $("#jHueTourQuestion").remove();
     var _questionMark = $("<div>").attr("id", "jHueTourQuestion").html('<i class="icon-question"></i>').addClass("jHueTourBadge");
     var _questionMark = $("<div>").attr("id", "jHueTourQuestion").html('<i class="icon-question"></i>').addClass("jHueTourBadge");
-    _questionMark.click(function () {
-      if ($.totalStorage("jHueTourExtras") != null) {
-        var _newTours = [];
-        $.each(_this.options.tours, function (cnt, tour) {
-          if (tour.remote == undefined || !tour.remote) {
-            _newTours.push(tour);
-          }
-        });
-        _this.options.tours = _newTours.concat($.totalStorage("jHueTourExtras"));
-      }
-
-      var _closeBtn = $("<a>");
-      _closeBtn.addClass("btn").addClass("btn-mini").html('<i class="icon-remove"></i>').css("float", "right").css("margin-top", "-4px").css("margin-right", "-6px");
-      _closeBtn.click(function () {
-        $(".popover").remove();
+    if (_this.options.questionMarkPlacement == "left"){
+      _questionMark.addClass("jHueTourBadgeLeft");
+    }
+    if ($.totalStorage("jHueTourExtras") != null) {
+      var _newTours = [];
+      $.each(_this.options.tours, function (cnt, tour) {
+        if (tour.remote == undefined || !tour.remote) {
+          _newTours.push(tour);
+        }
       });
       });
+      _this.options.tours = _newTours.concat($.totalStorage("jHueTourExtras"));
+    }
 
 
-      var _toursHtml = '<ul class="nav nav-pills nav-stacked">'
+    var _toursHtml = '<ul class="nav nav-pills nav-stacked">'
       var _added = 0;
       var _added = 0;
       $.each(_this.options.tours, function (ctn, tour) {
       $.each(_this.options.tours, function (ctn, tour) {
         if (tour.path === undefined || RegExp(tour.path).test(location.pathname)) {
         if (tour.path === undefined || RegExp(tour.path).test(location.pathname)) {
@@ -161,7 +154,12 @@
         }
         }
       });
       });
       if (_added == 0) {
       if (_added == 0) {
-        _toursHtml += '<li>' + _this.options.labels.NO_AVAILABLE_TOURS + '</li>';
+        if (_this.options.hideIfNoneAvailable){
+          _questionMark.css("display", "none");
+        }
+        else {
+          _toursHtml += '<li>' + _this.options.labels.NO_AVAILABLE_TOURS + '</li>';
+        }
       }
       }
       if (_this.options.showRemote){
       if (_this.options.showRemote){
         _toursHtml += '<li>' +
         _toursHtml += '<li>' +
@@ -172,19 +170,28 @@
           ' </div>' +
           ' </div>' +
           '</li>';
           '</li>';
       }
       }
-      _toursHtml += '</ul>';
+    _toursHtml += '</ul>';
+
+    _questionMark.click(function () {
+
+      var _closeBtn = $("<a>");
+      _closeBtn.addClass("btn").addClass("btn-mini").html('<i class="icon-remove"></i>').css("float", "right").css("margin-top", "-4px").css("margin-right", "-6px");
+      _closeBtn.click(function () {
+        $(".popover").remove();
+      });
 
 
       _questionMark.popover("destroy").popover({
       _questionMark.popover("destroy").popover({
         title: _this.options.labels.AVAILABLE_TOURS,
         title: _this.options.labels.AVAILABLE_TOURS,
         content: _toursHtml,
         content: _toursHtml,
         html: true,
         html: true,
         trigger: "manual",
         trigger: "manual",
-        placement: "left"
+        placement: _this.options.questionMarkPlacement == "left"?"right":"left"
       }).popover("show");
       }).popover("show");
       if ($(".popover").position().top <= 0) {
       if ($(".popover").position().top <= 0) {
         $(".popover").css("top", "10px");
         $(".popover").css("top", "10px");
       }
       }
       _closeBtn.prependTo($(".popover-title"));
       _closeBtn.prependTo($(".popover-title"));
+
     });
     });
     _questionMark.appendTo($("body"));
     _questionMark.appendTo($("body"));
   };
   };