mirror of
https://github.com/wekan/wekan.git
synced 2025-12-15 15:00:12 +01:00
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:
parent
781577db04
commit
9a45f3752f
22 changed files with 162 additions and 182 deletions
|
|
@ -54,6 +54,7 @@
|
|||
"SubsManager": false,
|
||||
"Mousetrap": false,
|
||||
"Avatar": true,
|
||||
"Ps": true,
|
||||
|
||||
// Our collections
|
||||
"Boards": true,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ audit-argument-checks
|
|||
iron:router
|
||||
meteorhacks:subs-manager
|
||||
mquandalle:autofocus
|
||||
mquandalle:bower
|
||||
mquandalle:moment
|
||||
ongoworks:speakingurl
|
||||
raix:handlebar-helpers
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
7
bower.json
Normal file
7
bower.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "LibreBoard",
|
||||
"dependencies": {
|
||||
"perfect-scrollbar": "0.6.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
template(name='list')
|
||||
.list.js-list(id="js-list-{{_id}}")
|
||||
.list-wrapper
|
||||
+listHeader
|
||||
+listBody
|
||||
+listHeader
|
||||
+listBody
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
6
client/components/mixins/perfectScrollbar.js
Normal file
6
client/components/mixins/perfectScrollbar.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Mixins.PerfectScrollbar = BlazeComponent.extendComponent({
|
||||
onRendered: function() {
|
||||
var component = this.mixinParent();
|
||||
Ps.initialize(component.find('.js-perfect-scrollbar'));
|
||||
}
|
||||
});
|
||||
2
client/components/mixins/perfectScrollbar.styl
Normal file
2
client/components/mixins/perfectScrollbar.styl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.ps-container
|
||||
position: relative
|
||||
|
|
@ -3,7 +3,7 @@ var widgetTitles = {
|
|||
background: 'change-background'
|
||||
};
|
||||
|
||||
Template.boardSidebar.helpers({
|
||||
Template.sidebar.helpers({
|
||||
currentWidget: function() {
|
||||
return Session.get('currentWidget') + 'Sidebar';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue