From 9a45f3752fe7c8499960b4fb6d185f9f5f8afbda Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Sun, 24 May 2015 21:40:21 +0200 Subject: [PATCH] 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. --- .jshintrc | 1 + .meteor/packages | 1 + .meteor/versions | 1 + bower.json | 7 ++ client/components/boards/body.jade | 4 +- client/components/boards/body.js | 22 ++++-- client/components/boards/body.styl | 16 ---- client/components/cards/details.js | 8 ++ client/components/cards/minicard.styl | 14 ++-- client/components/lists/body.jade | 79 ++++++++++--------- client/components/lists/body.js | 14 +++- client/components/lists/main.jade | 5 +- client/components/lists/main.js | 7 +- client/components/lists/main.styl | 59 ++++++-------- .../{sidebar => mixins}/infiniteScrolling.js | 0 client/components/mixins/perfectScrollbar.js | 6 ++ .../components/mixins/perfectScrollbar.styl | 2 + client/components/sidebar/helpers.js | 2 +- client/components/sidebar/rendered.js | 21 ----- .../sidebar/{templates.jade => sidebar.jade} | 4 +- client/components/sidebar/sidebar.js | 26 +++++- client/styles/fancy-scrollbar.styl | 45 ----------- 22 files changed, 162 insertions(+), 182 deletions(-) create mode 100644 bower.json rename client/components/{sidebar => mixins}/infiniteScrolling.js (100%) create mode 100644 client/components/mixins/perfectScrollbar.js create mode 100644 client/components/mixins/perfectScrollbar.styl delete mode 100644 client/components/sidebar/rendered.js rename client/components/sidebar/{templates.jade => sidebar.jade} (97%) delete mode 100644 client/styles/fancy-scrollbar.styl diff --git a/.jshintrc b/.jshintrc index ebbf3c244..c6fcaf8dc 100644 --- a/.jshintrc +++ b/.jshintrc @@ -54,6 +54,7 @@ "SubsManager": false, "Mousetrap": false, "Avatar": true, + "Ps": true, // Our collections "Boards": true, diff --git a/.meteor/packages b/.meteor/packages index 96888a0d5..c04186c6e 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -32,6 +32,7 @@ audit-argument-checks iron:router meteorhacks:subs-manager mquandalle:autofocus +mquandalle:bower mquandalle:moment ongoworks:speakingurl raix:handlebar-helpers diff --git a/.meteor/versions b/.meteor/versions index 9b5f0177b..bfa2145d3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -77,6 +77,7 @@ mongo@1.1.0 mongo-livedata@1.0.8 mousetrap:mousetrap@1.4.6_1 mquandalle:autofocus@1.0.0 +mquandalle:bower@1.4.1 mquandalle:jade@0.4.3 mquandalle:jade-compiler@0.4.3 mquandalle:jquery-textcomplete@0.3.9_1 diff --git a/bower.json b/bower.json new file mode 100644 index 000000000..deca1fda2 --- /dev/null +++ b/bower.json @@ -0,0 +1,7 @@ +{ + "name": "LibreBoard", + "dependencies": { + "perfect-scrollbar": "0.6.2" + }, + "private": true +} diff --git a/client/components/boards/body.jade b/client/components/boards/body.jade index 4b4c2b90a..b157b7428 100644 --- a/client/components/boards/body.jade +++ b/client/components/boards/body.jade @@ -16,12 +16,12 @@ template(name="boardComponent") +cardDetails(currentCard) if currentUser.isBoardMember +addListForm - +boardSidebar + +sidebar else +message(label="board-no-found") template(name="addListForm") - .list.js-list.add-list.js-add-list + .list.js-list.list-composer.js-list-composer +inlinedForm(autoclose=false) input.list-name-input(type="text" placeholder="{{_ 'add-list'}}" autocomplete="off" autofocus value=getCache) diff --git a/client/components/boards/body.js b/client/components/boards/body.js index 5e743001f..e4aad6463 100644 --- a/client/components/boards/body.js +++ b/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() { @@ -67,14 +79,14 @@ BlazeComponent.extendComponent({ tolerance: 'pointer', appendTo: '.js-lists', helper: 'clone', - items: '.js-list:not(.add-list)', + items: '.js-list:not(.js-list-composer)', placeholder: 'list placeholder', start: function(event, ui) { $('.list.placeholder').height(ui.item.height()); Popup.close(); }, 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) { var data = Blaze.getData(list); Lists.update(data._id, { @@ -95,7 +107,7 @@ BlazeComponent.extendComponent({ }, sidebarSize: function() { - var sidebar = this.componentChildren('boardSidebar')[0]; + var sidebar = this.componentChildren('sidebar')[0]; if (sidebar && sidebar.isOpen()) return 'next-sidebar'; } diff --git a/client/components/boards/body.styl b/client/components/boards/body.styl index 07f35bb8d..de4963ab1 100644 --- a/client/components/boards/body.styl +++ b/client/components/boards/body.styl @@ -32,19 +32,3 @@ right: 0 bottom: 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 diff --git a/client/components/cards/details.js b/client/components/cards/details.js index d0395129d..385310bbf 100644 --- a/client/components/cards/details.js +++ b/client/components/cards/details.js @@ -17,6 +17,14 @@ BlazeComponent.extendComponent({ 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() { return [{ 'click .js-move-card': Popup.open('moveCard'), diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 1b9e60b5e..775d31ebf 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -8,6 +8,9 @@ position: relative z-index: 0 overflow: hidden + transition: transform 0.2s, + border-radius 0.2s, + border-left 0.2s a color: #4d4d4d @@ -39,19 +42,15 @@ .minicard-details padding: 6px 8px 2px position: relative - z-index: 10 + // z-index: 1 &.is-selected - margin-left: -11px - transform: translateX(- @margin-left) + transform: translateX(11px) border-bottom-right-radius: 0 border-top-right-radius: 0 z-index: 100 box-shadow: -2px 1px 2px rgba(0,0,0,.2) - .minicard-details - margin-right: 11px - a.minicard-details text-decoration:none @@ -122,6 +121,9 @@ .minicard-members:empty display: none + &.ui-sortable-helper + transform: rotate(4deg) + .badges float: left diff --git a/client/components/lists/body.jade b/client/components/lists/body.jade index dfbe05b72..9d4a903d1 100644 --- a/client/components/lists/body.jade +++ b/client/components/lists/body.jade @@ -1,43 +1,44 @@ 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") .minicard.js-composer diff --git a/client/components/lists/body.js b/client/components/lists/body.js index 70db42d1a..d8238c9ad 100644 --- a/client/components/lists/body.js +++ b/client/components/lists/body.js @@ -3,6 +3,10 @@ BlazeComponent.extendComponent({ return 'listBody'; }, + mixins: function() { + return [Mixins.PerfectScrollbar]; + }, + isSelected: function() { return Session.equals('currentCard', this.currentData()._id); }, @@ -62,13 +66,21 @@ BlazeComponent.extendComponent({ this.newCardFormIsVisible.set(value); }, + scrollToBottom: function() { + var $container = $(this.firstNode()); + $container.animate({ + scrollTop: $container.height() + }); + }, + onCreated: function() { this.newCardFormIsVisible = new ReactiveVar(true); }, events: function() { return [{ - submit: this.addCard + submit: this.addCard, + 'click .open-card-composer': this.scrollToBottom }]; } }).register('listBody'); diff --git a/client/components/lists/main.jade b/client/components/lists/main.jade index dd4bb49af..c959b87f2 100644 --- a/client/components/lists/main.jade +++ b/client/components/lists/main.jade @@ -1,5 +1,4 @@ template(name='list') .list.js-list(id="js-list-{{_id}}") - .list-wrapper - +listHeader - +listBody + +listHeader + +listBody diff --git a/client/components/lists/main.js b/client/components/lists/main.js index 8a96f5ce9..3464865a8 100644 --- a/client/components/lists/main.js +++ b/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 // 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 - // 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() { if (Meteor.user().isBoardMember()) { var boardComponent = this.componentParent(); diff --git a/client/components/lists/main.styl b/client/components/lists/main.styl index 60a6ab98e..47dfcf280 100644 --- a/client/components/lists/main.styl +++ b/client/components/lists/main.styl @@ -11,8 +11,7 @@ background: darken(white, 10%) height: 100% border-left: 1px solid darken(white, 20%) - padding: 12px 7px 5px - overflow-y: auto + padding: 0 &:first-child margin-left: 5px @@ -21,15 +20,20 @@ .card-detail + & 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 background: rgba(0, 0, 0, .05) @@ -55,7 +59,7 @@ .list-header flex: 0 0 auto - padding: 10px 26px 4px 6px + margin: 20px 15px 4px position: relative min-height: 20px @@ -74,24 +78,23 @@ .list-header-menu-icon background-clip: content-box background-origin: content-box - padding: 6px 8px + // padding: 6px 8px position: absolute - top: 3px - right: -5px + top: 0 + right: 0 color: #a6a6a6 .list-header-num-cards color: #8c8c8c 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 border-radius: 2px @@ -100,6 +103,7 @@ padding: 7px 10px position: relative text-decoration: none + animation: fadeIn 0.3s i.fa margin-right: 7px @@ -117,18 +121,3 @@ opacity: 0 to 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 diff --git a/client/components/sidebar/infiniteScrolling.js b/client/components/mixins/infiniteScrolling.js similarity index 100% rename from client/components/sidebar/infiniteScrolling.js rename to client/components/mixins/infiniteScrolling.js diff --git a/client/components/mixins/perfectScrollbar.js b/client/components/mixins/perfectScrollbar.js new file mode 100644 index 000000000..06e8aedd2 --- /dev/null +++ b/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')); + } +}); diff --git a/client/components/mixins/perfectScrollbar.styl b/client/components/mixins/perfectScrollbar.styl new file mode 100644 index 000000000..c82676685 --- /dev/null +++ b/client/components/mixins/perfectScrollbar.styl @@ -0,0 +1,2 @@ +.ps-container + position: relative diff --git a/client/components/sidebar/helpers.js b/client/components/sidebar/helpers.js index a76dad7f1..15035bd4e 100644 --- a/client/components/sidebar/helpers.js +++ b/client/components/sidebar/helpers.js @@ -3,7 +3,7 @@ var widgetTitles = { background: 'change-background' }; -Template.boardSidebar.helpers({ +Template.sidebar.helpers({ currentWidget: function() { return Session.get('currentWidget') + 'Sidebar'; }, diff --git a/client/components/sidebar/rendered.js b/client/components/sidebar/rendered.js deleted file mode 100644 index 36b1255c9..000000000 --- a/client/components/sidebar/rendered.js +++ /dev/null @@ -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(); - } - }); - }); - }); -}); diff --git a/client/components/sidebar/templates.jade b/client/components/sidebar/sidebar.jade similarity index 97% rename from client/components/sidebar/templates.jade rename to client/components/sidebar/sidebar.jade index 23a1a87ef..07cd777c1 100644 --- a/client/components/sidebar/templates.jade +++ b/client/components/sidebar/sidebar.jade @@ -1,9 +1,9 @@ -template(name="boardSidebar") +template(name="sidebar") .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}") a.sidebar-tongue.js-toogle-sidebar( class="{{#if isTongueHidden}}is-hidden{{/if}}") 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 if Filter.isActive +filterSidebar diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index af676bf26..764f16eb2 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,10 +1,10 @@ BlazeComponent.extendComponent({ template: function() { - return 'boardSidebar'; + return 'sidebar'; }, mixins: function() { - return [Mixins.InfiniteScrolling]; + return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; }, onCreated: function() { @@ -46,6 +46,26 @@ BlazeComponent.extendComponent({ 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() { // XXX Hacky, we need some kind of `super` var mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events(); @@ -53,4 +73,4 @@ BlazeComponent.extendComponent({ 'click .js-toogle-sidebar': this.toogle }]); } -}).register('boardSidebar'); +}).register('sidebar'); diff --git a/client/styles/fancy-scrollbar.styl b/client/styles/fancy-scrollbar.styl deleted file mode 100644 index c7a30018d..000000000 --- a/client/styles/fancy-scrollbar.styl +++ /dev/null @@ -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