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.
This commit is contained in:
Maxime Quandalle 2015-05-24 21:40:21 +02:00
parent 781577db04
commit 9a45f3752f
22 changed files with 162 additions and 182 deletions

View file

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

View file

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

View file

@ -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
bower.json Normal file
View file

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

View file

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

View file

@ -22,8 +22,20 @@ BlazeComponent.extendComponent({
}); });
}, },
scrollLeft: function() { scrollLeft: function(position) {
// TODO 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';
} }

View file

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

View file

@ -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'),

View file

@ -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(11px)
transform: translateX(- @margin-left)
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

View file

@ -1,4 +1,5 @@
template(name="listBody") template(name="listBody")
.list-body.js-perfect-scrollbar
.minicards.clearfix.js-minicards .minicards.clearfix.js-minicards
if cards.count if cards.count
+inlinedForm(autoclose=false position="top") +inlinedForm(autoclose=false position="top")

View file

@ -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');

View file

@ -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 +listHeader
+listBody +listBody

View file

@ -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. // moved which result in bugs if multiple users use the board in real time. I
// I tried sortable:sortable but that was not better. Should we “simply” write // tried sortable:sortable but that was not better. And dragula is not
// the drag&drop code ourselves? // 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();

View file

@ -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 padding: 0
overflow-y: auto
&: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 &.ui-sortable-helper
cursor: grab 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 &.placeholder
cursor: default background-color: rgba(0, 0, 0, .2)
border-color: transparent
box-shadow: none
height: 100px
&.add-list &.list-composer
&.fade padding: 17px
opacity: 0
.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 top: 0
right: -5px right: 0
color: #a6a6a6 color: #a6a6a6
.list-header-num-cards .list-header-num-cards
color: #8c8c8c color: #8c8c8c
margin: 0 margin: 0
.minicards .list-body
padding: 4px 4px 1px flex: 1
z-index: 1 overflow-y: auto
height: 100% padding: 5px 11px
&::-webkit-scrollbar-button .ps-scrollbar-y-rail
display: block transform: translateX(2px)
height: 4px
.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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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');

View file

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