Răsfoiți Sursa

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 ani în urmă
părinte
comite
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"));
   };
   };