Browse Source

Improve scrolling

We now replace native scrollbar by custom ones on the list card (which
is required by the new ergonomics in the parent commit), but the
"scrolling engine", is still native, we just hide the scrollbar and
draw our own in HTML/CSS using the perfect-scrollbar package (from
bower).

This commit also implements component scrolling when certain actions
are performed, eg scroll to the bottom when the new card composer is
opened.
Maxime Quandalle 10 years ago
parent
commit
9a45f3752f

+ 1 - 0
.jshintrc

@@ -54,6 +54,7 @@
     "SubsManager": false,
     "SubsManager": false,
     "Mousetrap": false,
     "Mousetrap": false,
     "Avatar": true,
     "Avatar": true,
+    "Ps": true,
 
 
     // Our collections
     // Our collections
     "Boards": true,
     "Boards": true,

+ 1 - 0
.meteor/packages

@@ -32,6 +32,7 @@ audit-argument-checks
 iron:router
 iron:router
 meteorhacks:subs-manager
 meteorhacks:subs-manager
 mquandalle:autofocus
 mquandalle:autofocus
+mquandalle:bower
 mquandalle:moment
 mquandalle:moment
 ongoworks:speakingurl
 ongoworks:speakingurl
 raix:handlebar-helpers
 raix:handlebar-helpers

+ 1 - 0
.meteor/versions

@@ -77,6 +77,7 @@ mongo@1.1.0
 mongo-livedata@1.0.8
 mongo-livedata@1.0.8
 mousetrap:mousetrap@1.4.6_1
 mousetrap:mousetrap@1.4.6_1
 mquandalle:autofocus@1.0.0
 mquandalle:autofocus@1.0.0
+mquandalle:bower@1.4.1
 mquandalle:jade@0.4.3
 mquandalle:jade@0.4.3
 mquandalle:jade-compiler@0.4.3
 mquandalle:jade-compiler@0.4.3
 mquandalle:jquery-textcomplete@0.3.9_1
 mquandalle:jquery-textcomplete@0.3.9_1

+ 7 - 0
bower.json

@@ -0,0 +1,7 @@
+{
+  "name": "LibreBoard",
+  "dependencies": {
+    "perfect-scrollbar": "0.6.2"
+  },
+  "private": true
+}

+ 2 - 2
client/components/boards/body.jade

@@ -16,12 +16,12 @@ template(name="boardComponent")
               +cardDetails(currentCard)
               +cardDetails(currentCard)
           if currentUser.isBoardMember
           if currentUser.isBoardMember
             +addListForm
             +addListForm
-      +boardSidebar
+      +sidebar
   else
   else
     +message(label="board-no-found")
     +message(label="board-no-found")
 
 
 template(name="addListForm")
 template(name="addListForm")
-  .list.js-list.add-list.js-add-list
+  .list.js-list.list-composer.js-list-composer
     +inlinedForm(autoclose=false)
     +inlinedForm(autoclose=false)
       input.list-name-input(type="text" placeholder="{{_ 'add-list'}}"
       input.list-name-input(type="text" placeholder="{{_ 'add-list'}}"
         autocomplete="off" autofocus value=getCache)
         autocomplete="off" autofocus value=getCache)

+ 17 - 5
client/components/boards/body.js

@@ -22,8 +22,20 @@ BlazeComponent.extendComponent({
     });
     });
   },
   },
 
 
-  scrollLeft: function() {
-    // TODO
+  scrollLeft: function(position) {
+    position = position || 0;
+    var $container = $(this.find('.js-lists'));
+    var containerWidth = $container.width();
+    var currentScrollPosition = $container.scrollLeft();
+    if (position < currentScrollPosition) {
+      $container.animate({
+        scrollLeft: position
+      });
+    } else if (position > currentScrollPosition + containerWidth) {
+      $container.animate({
+        scrollLeft: Math.max(0, position - containerWidth)
+      });
+    }
   },
   },
 
 
   currentCardIsInThisList: function() {
   currentCardIsInThisList: function() {
@@ -67,14 +79,14 @@ BlazeComponent.extendComponent({
       tolerance: 'pointer',
       tolerance: 'pointer',
       appendTo: '.js-lists',
       appendTo: '.js-lists',
       helper: 'clone',
       helper: 'clone',
-      items: '.js-list:not(.add-list)',
+      items: '.js-list:not(.js-list-composer)',
       placeholder: 'list placeholder',
       placeholder: 'list placeholder',
       start: function(event, ui) {
       start: function(event, ui) {
         $('.list.placeholder').height(ui.item.height());
         $('.list.placeholder').height(ui.item.height());
         Popup.close();
         Popup.close();
       },
       },
       stop: function() {
       stop: function() {
-        self.$('.js-lists').find('.js-list:not(.add-list)').each(
+        self.$('.js-lists').find('.js-list:not(.js-list-composer)').each(
           function(i, list) {
           function(i, list) {
             var data = Blaze.getData(list);
             var data = Blaze.getData(list);
             Lists.update(data._id, {
             Lists.update(data._id, {
@@ -95,7 +107,7 @@ BlazeComponent.extendComponent({
   },
   },
 
 
   sidebarSize: function() {
   sidebarSize: function() {
-    var sidebar = this.componentChildren('boardSidebar')[0];
+    var sidebar = this.componentChildren('sidebar')[0];
     if (sidebar && sidebar.isOpen())
     if (sidebar && sidebar.isOpen())
       return 'next-sidebar';
       return 'next-sidebar';
   }
   }

+ 0 - 16
client/components/boards/body.styl

@@ -32,19 +32,3 @@
   right: 0
   right: 0
   bottom: 0
   bottom: 0
   left: 0
   left: 0
-
-  &::-webkit-scrollbar
-    height: 13px
-    width: 13px
-
-  &::-webkit-scrollbar-thumb:vertical,
-  &::-webkit-scrollbar-thumb:horizontal
-    background: rgba(255, 255, 255, .4)
-
-  &::-webkit-scrollbar-track-piece
-    background: rgba(0, 0, 0, .15)
-
-  &::-webkit-scrollbar-button
-    display: block
-    height: 5px
-    width: 5px

+ 8 - 0
client/components/cards/details.js

@@ -17,6 +17,14 @@ BlazeComponent.extendComponent({
     activitiesComponent.loadNextPage();
     activitiesComponent.loadNextPage();
   },
   },
 
 
+  onRendered: function() {
+    var bodyBoardComponent = this.componentParent();
+    var additionalMargin = 550;
+    var $cardDetails = this.$(this.firstNode());
+    var scollLeft = $cardDetails.offset().left + additionalMargin;
+    bodyBoardComponent.scrollLeft(scollLeft);
+  },
+
   events: function() {
   events: function() {
     return [{
     return [{
       'click .js-move-card': Popup.open('moveCard'),
       'click .js-move-card': Popup.open('moveCard'),

+ 8 - 6
client/components/cards/minicard.styl

@@ -8,6 +8,9 @@
   position: relative
   position: relative
   z-index: 0
   z-index: 0
   overflow: hidden
   overflow: hidden
+  transition: transform 0.2s,
+              border-radius 0.2s,
+              border-left 0.2s
 
 
   a
   a
     color: #4d4d4d
     color: #4d4d4d
@@ -39,19 +42,15 @@
   .minicard-details
   .minicard-details
     padding: 6px 8px 2px
     padding: 6px 8px 2px
     position: relative
     position: relative
-    z-index: 10
+    // z-index: 1
 
 
   &.is-selected
   &.is-selected
-    margin-left: -11px
-    transform: translateX(- @margin-left)
+    transform: translateX(11px)
     border-bottom-right-radius: 0
     border-bottom-right-radius: 0
     border-top-right-radius: 0
     border-top-right-radius: 0
     z-index: 100
     z-index: 100
     box-shadow: -2px 1px 2px rgba(0,0,0,.2)
     box-shadow: -2px 1px 2px rgba(0,0,0,.2)
 
 
-    .minicard-details
-      margin-right: 11px
-
   a.minicard-details
   a.minicard-details
     text-decoration:none
     text-decoration:none
 
 
@@ -122,6 +121,9 @@
   .minicard-members:empty
   .minicard-members:empty
     display: none
     display: none
 
 
+  &.ui-sortable-helper
+    transform: rotate(4deg)
+
 .badges
 .badges
   float: left
   float: left
 
 

+ 40 - 39
client/components/lists/body.jade

@@ -1,43 +1,44 @@
 template(name="listBody")
 template(name="listBody")
-  .minicards.clearfix.js-minicards
-    if cards.count
-      +inlinedForm(autoclose=false position="top")
-        +addCardForm(listId=_id position="top")
-    each cards
-      .minicard.card.js-minicard(
-        class="{{#if isSelected}}is-selected{{/if}}")
-        a.minicard-details.clearfix.show(href=absoluteUrl)
-          if cover
-            .minicard-cover.js-card-cover(style="background-image: url({{cover.url}});")
-          if labels
-            .minicard-labels
-              each labels
-                .minicard-label(class="card-label-{{color}}" title="{{name}}")
-          .minicard-title= title
-          if members
-            .minicard-members.js-minicard-members
-              each members
-                +userAvatar(userId=this size="small" cardId="{{../_id}}")
-          .badges
-            if comments.count
-              .badge(title="{{_ 'card-comments-title' comments.count }}")
-                span.badge-icon.icon-sm.fa.fa-comment-o
-                .badge-text= comments.count
-            if description
-              .badge.badge-state-image-only(title=description)
-                span.badge-icon.icon-sm.fa.fa-align-left
-            if attachments.count
-              .badge
-                span.badge-icon.icon-sm.fa.fa-paperclip
-                span.badge-text= attachments.count
-    if currentUser.isBoardMember
-      +inlinedForm(autoclose=false position="bottom")
-        +addCardForm(listId=_id position="bottom")
-      else
-        if newCardFormIsVisible.get
-          a.open-card-composer.js-open-inlined-form
-            i.fa.fa-plus
-            | {{_ 'add-card'}}
+  .list-body.js-perfect-scrollbar
+    .minicards.clearfix.js-minicards
+      if cards.count
+        +inlinedForm(autoclose=false position="top")
+          +addCardForm(listId=_id position="top")
+      each cards
+        .minicard.card.js-minicard(
+          class="{{#if isSelected}}is-selected{{/if}}")
+          a.minicard-details.clearfix.show(href=absoluteUrl)
+            if cover
+              .minicard-cover.js-card-cover(style="background-image: url({{cover.url}});")
+            if labels
+              .minicard-labels
+                each labels
+                  .minicard-label(class="card-label-{{color}}" title="{{name}}")
+            .minicard-title= title
+            if members
+              .minicard-members.js-minicard-members
+                each members
+                  +userAvatar(userId=this size="small" cardId="{{../_id}}")
+            .badges
+              if comments.count
+                .badge(title="{{_ 'card-comments-title' comments.count }}")
+                  span.badge-icon.icon-sm.fa.fa-comment-o
+                  .badge-text= comments.count
+              if description
+                .badge.badge-state-image-only(title=description)
+                  span.badge-icon.icon-sm.fa.fa-align-left
+              if attachments.count
+                .badge
+                  span.badge-icon.icon-sm.fa.fa-paperclip
+                  span.badge-text= attachments.count
+      if currentUser.isBoardMember
+        +inlinedForm(autoclose=false position="bottom")
+          +addCardForm(listId=_id position="bottom")
+        else
+          if newCardFormIsVisible.get
+            a.open-card-composer.js-open-inlined-form
+              i.fa.fa-plus
+              | {{_ 'add-card'}}
 
 
 template(name="addCardForm")
 template(name="addCardForm")
   .minicard.js-composer
   .minicard.js-composer

+ 13 - 1
client/components/lists/body.js

@@ -3,6 +3,10 @@ BlazeComponent.extendComponent({
     return 'listBody';
     return 'listBody';
   },
   },
 
 
+  mixins: function() {
+    return [Mixins.PerfectScrollbar];
+  },
+
   isSelected: function() {
   isSelected: function() {
     return Session.equals('currentCard', this.currentData()._id);
     return Session.equals('currentCard', this.currentData()._id);
   },
   },
@@ -62,13 +66,21 @@ BlazeComponent.extendComponent({
     this.newCardFormIsVisible.set(value);
     this.newCardFormIsVisible.set(value);
   },
   },
 
 
+  scrollToBottom: function() {
+    var $container = $(this.firstNode());
+    $container.animate({
+      scrollTop: $container.height()
+    });
+  },
+
   onCreated: function() {
   onCreated: function() {
     this.newCardFormIsVisible = new ReactiveVar(true);
     this.newCardFormIsVisible = new ReactiveVar(true);
   },
   },
 
 
   events: function() {
   events: function() {
     return [{
     return [{
-      submit: this.addCard
+      submit: this.addCard,
+      'click .open-card-composer': this.scrollToBottom
     }];
     }];
   }
   }
 }).register('listBody');
 }).register('listBody');

+ 2 - 3
client/components/lists/main.jade

@@ -1,5 +1,4 @@
 template(name='list')
 template(name='list')
   .list.js-list(id="js-list-{{_id}}")
   .list.js-list(id="js-list-{{_id}}")
-    .list-wrapper
-      +listHeader
-      +listBody
+    +listHeader
+    +listBody

+ 4 - 3
client/components/lists/main.js

@@ -19,9 +19,10 @@ BlazeComponent.extendComponent({
   // XXX The jQuery UI sortable plugin is far from ideal here. First we include
   // XXX The jQuery UI sortable plugin is far from ideal here. First we include
   // all jQuery components but only use one. Second, it modifies the DOM itself,
   // all jQuery components but only use one. Second, it modifies the DOM itself,
   // resulting in Blaze abandoning reactive update of the nodes that have been
   // resulting in Blaze abandoning reactive update of the nodes that have been
-  // moved which result in bugs if multiple users use the board in real time.
-  // I tried sortable:sortable but that was not better. Should we “simply” write
-  // the drag&drop code ourselves?
+  // moved which result in bugs if multiple users use the board in real time. I
+  // tried sortable:sortable but that was not better. And dragula is not
+  // powerful enough for our use casesShould we “simply” write the drag&drop
+  // code ourselves?
   onRendered: function() {
   onRendered: function() {
     if (Meteor.user().isBoardMember()) {
     if (Meteor.user().isBoardMember()) {
       var boardComponent = this.componentParent();
       var boardComponent = this.componentParent();

+ 24 - 35
client/components/lists/main.styl

@@ -11,8 +11,7 @@
   background: darken(white, 10%)
   background: darken(white, 10%)
   height: 100%
   height: 100%
   border-left: 1px solid darken(white, 20%)
   border-left: 1px solid darken(white, 20%)
-  padding: 12px 7px 5px
-  overflow-y: auto
+  padding: 0
 
 
   &:first-child
   &:first-child
     margin-left: 5px
     margin-left: 5px
@@ -21,15 +20,20 @@
   .card-detail + &
   .card-detail + &
     border-left: none
     border-left: none
 
 
-  &.editable
-    cursor: grab
+  &.ui-sortable-helper
+    cursor: grabbing
+    box-shadow: -2px 2px 8px rgba(0, 0, 0, .3),
+                0 0 1px rgba(0, 0, 0, .5)
+    transform: rotate(4deg)
 
 
-    .list-wrapper
-      cursor: default
+  &.placeholder
+    background-color: rgba(0, 0, 0, .2)
+    border-color: transparent
+    box-shadow: none
+    height: 100px
 
 
-  &.add-list
-    &.fade
-      opacity: 0
+  &.list-composer
+    padding: 17px
 
 
     .list-name-input
     .list-name-input
       background: rgba(0, 0, 0, .05)
       background: rgba(0, 0, 0, .05)
@@ -55,7 +59,7 @@
 
 
 .list-header
 .list-header
   flex: 0 0 auto
   flex: 0 0 auto
-  padding: 10px 26px 4px 6px
+  margin: 20px 15px 4px
   position: relative
   position: relative
   min-height: 20px
   min-height: 20px
 
 
@@ -74,24 +78,23 @@
   .list-header-menu-icon
   .list-header-menu-icon
     background-clip: content-box
     background-clip: content-box
     background-origin: content-box
     background-origin: content-box
-    padding: 6px 8px
+    // padding: 6px 8px
     position: absolute
     position: absolute
-    top: 3px
-    right: -5px
+    top: 0
+    right: 0
     color: #a6a6a6
     color: #a6a6a6
 
 
   .list-header-num-cards
   .list-header-num-cards
     color: #8c8c8c
     color: #8c8c8c
     margin: 0
     margin: 0
 
 
-.minicards
-  padding: 4px 4px 1px
-  z-index: 1
-  height: 100%
+.list-body
+  flex: 1
+  overflow-y: auto
+  padding: 5px 11px
 
 
-  &::-webkit-scrollbar-button
-    display: block
-    height: 4px
+  .ps-scrollbar-y-rail
+    transform: translateX(2px)
 
 
 .open-card-composer
 .open-card-composer
   border-radius: 2px
   border-radius: 2px
@@ -100,6 +103,7 @@
   padding: 7px 10px
   padding: 7px 10px
   position: relative
   position: relative
   text-decoration: none
   text-decoration: none
+  animation: fadeIn 0.3s
 
 
   i.fa
   i.fa
     margin-right: 7px
     margin-right: 7px
@@ -117,18 +121,3 @@
     opacity: 0
     opacity: 0
   to
   to
     opacity: 1
     opacity: 1
-
-.list.placeholder
-  background-color: rgba(0, 0, 0, .2)
-  border-color: transparent
-  box-shadow: none
-  height: 100px
-
-.list.ui-sortable-helper
-  cursor: grabbing
-  box-shadow: -2px 2px 8px rgba(0, 0, 0, .3), 0 0 1px rgba(0, 0, 0, .5)
-  transform: rotate(4deg)
-
-
-.list.ui-sortable-helper .list-header-menu-icon
-  display: none

+ 0 - 0
client/components/sidebar/infiniteScrolling.js → client/components/mixins/infiniteScrolling.js


+ 6 - 0
client/components/mixins/perfectScrollbar.js

@@ -0,0 +1,6 @@
+Mixins.PerfectScrollbar = BlazeComponent.extendComponent({
+  onRendered: function() {
+    var component = this.mixinParent();
+    Ps.initialize(component.find('.js-perfect-scrollbar'));
+  }
+});

+ 2 - 0
client/components/mixins/perfectScrollbar.styl

@@ -0,0 +1,2 @@
+.ps-container
+  position: relative

+ 1 - 1
client/components/sidebar/helpers.js

@@ -3,7 +3,7 @@ var widgetTitles = {
   background: 'change-background'
   background: 'change-background'
 };
 };
 
 
-Template.boardSidebar.helpers({
+Template.sidebar.helpers({
   currentWidget: function() {
   currentWidget: function() {
     return Session.get('currentWidget') + 'Sidebar';
     return Session.get('currentWidget') + 'Sidebar';
   },
   },

+ 0 - 21
client/components/sidebar/rendered.js

@@ -1,21 +0,0 @@
-Template.membersWidget.onRendered(function() {
-  var self = this;
-  if (! Meteor.user().isBoardMember())
-    return;
-
-  _.each(['.js-member', '.js-label'], function(className) {
-    $(document).on('mouseover', function() {
-      self.$(className).draggable({
-        appendTo: 'body',
-        helper: 'clone',
-        revert: 'invalid',
-        revertDuration: 150,
-        snap: false,
-        snapMode: 'both',
-        start: function() {
-          Popup.close();
-        }
-      });
-    });
-  });
-});

+ 2 - 2
client/components/sidebar/templates.jade → client/components/sidebar/sidebar.jade

@@ -1,9 +1,9 @@
-template(name="boardSidebar")
+template(name="sidebar")
   .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
   .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
     a.sidebar-tongue.js-toogle-sidebar(
     a.sidebar-tongue.js-toogle-sidebar(
       class="{{#if isTongueHidden}}is-hidden{{/if}}")
       class="{{#if isTongueHidden}}is-hidden{{/if}}")
       i.fa.fa-chevron-left
       i.fa.fa-chevron-left
-    .sidebar-content.js-board-sidebar-content
+    .sidebar-content.js-board-sidebar-content.js-perfect-scrollbar
       //- XXX https://github.com/peerlibrary/meteor-blaze-components/issues/30
       //- XXX https://github.com/peerlibrary/meteor-blaze-components/issues/30
       if Filter.isActive
       if Filter.isActive
         +filterSidebar
         +filterSidebar

+ 23 - 3
client/components/sidebar/sidebar.js

@@ -1,10 +1,10 @@
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   template: function() {
   template: function() {
-    return 'boardSidebar';
+    return 'sidebar';
   },
   },
 
 
   mixins: function() {
   mixins: function() {
-    return [Mixins.InfiniteScrolling];
+    return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
   },
   },
 
 
   onCreated: function() {
   onCreated: function() {
@@ -46,6 +46,26 @@ BlazeComponent.extendComponent({
     return this.isOpen() && Filter.isActive();
     return this.isOpen() && Filter.isActive();
   },
   },
 
 
+  onRendered: function() {
+    var self = this;
+    if (! Meteor.user().isBoardMember())
+      return;
+
+    $(document).on('mouseover', function() {
+      self.$('.js-member,.js-label').draggable({
+        appendTo: 'body',
+        helper: 'clone',
+        revert: 'invalid',
+        revertDuration: 150,
+        snap: false,
+        snapMode: 'both',
+        start: function() {
+          Popup.close();
+        }
+      });
+    });
+  },
+
   events: function() {
   events: function() {
     // XXX Hacky, we need some kind of `super`
     // XXX Hacky, we need some kind of `super`
     var mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events();
     var mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events();
@@ -53,4 +73,4 @@ BlazeComponent.extendComponent({
       'click .js-toogle-sidebar': this.toogle
       'click .js-toogle-sidebar': this.toogle
     }]);
     }]);
   }
   }
-}).register('boardSidebar');
+}).register('sidebar');

+ 0 - 45
client/styles/fancy-scrollbar.styl

@@ -1,45 +0,0 @@
-.fancy-scrollbar
-  -webkit-overflow-scrolling: touch
-
-  .fancy-scrollbar::-webkit-scrollbar
-    height: 9px
-    width: 9px
-
-  &::-webkit-scrollbar-button:start:decrement,
-  &::-webkit-scrollbar-button:end:increment
-    background: transparent
-    display: none
-
-  &::-webkit-scrollbar-track-piece
-    background: #dbdbdb
-
-    &:vertical:start
-      border-top-left-radius: 5px
-      border-top-right-radius: 5px
-      border-bottom-right-radius: 0
-      border-bottom-left-radius: 0
-
-    &:vertical:end
-      border-top-left-radius: 0
-      border-top-right-radius: 0
-      border-bottom-right-radius: 5px
-      border-bottom-left-radius: 5px
-
-    &:horizontal:start
-      border-top-left-radius: 5px
-      border-top-right-radius: 0
-      border-bottom-right-radius: 0
-      border-bottom-left-radius: 5px
-
-    &:horizontal:end
-      border-top-left-radius: 0
-      border-top-right-radius: 5px
-      border-bottom-right-radius: 5px
-      border-bottom-left-radius: 0
-
-  &::-webkit-scrollbar-thumb:vertical,
-  &::-webkit-scrollbar-thumb:horizontal
-    background: #c2c2c2
-    border-radius: 5px
-    display: block
-    height: 50px