summaryrefslogtreecommitdiffstats
path: root/client/components/lists
diff options
context:
space:
mode:
Diffstat (limited to 'client/components/lists')
-rw-r--r--client/components/lists/list.jade4
-rw-r--r--client/components/lists/list.js28
-rw-r--r--client/components/lists/list.styl88
-rw-r--r--client/components/lists/listBody.jade4
-rw-r--r--client/components/lists/listBody.js32
-rw-r--r--client/components/lists/listHeader.jade61
-rw-r--r--client/components/lists/listHeader.js89
7 files changed, 268 insertions, 38 deletions
diff --git a/client/components/lists/list.jade b/client/components/lists/list.jade
index c959b87f..c02e0dd6 100644
--- a/client/components/lists/list.jade
+++ b/client/components/lists/list.jade
@@ -2,3 +2,7 @@ template(name='list')
.list.js-list(id="js-list-{{_id}}")
+listHeader
+listBody
+
+template(name='miniList')
+ a.mini-list.js-select-list.js-list(id="js-list-{{_id}}")
+ +listHeader
diff --git a/client/components/lists/list.js b/client/components/lists/list.js
index 9c191348..38a87674 100644
--- a/client/components/lists/list.js
+++ b/client/components/lists/list.js
@@ -18,13 +18,18 @@ BlazeComponent.extendComponent({
// callback, we basically solve all issues related to reactive updates. A
// comment below provides further details.
onRendered() {
- const boardComponent = this.parentComponent();
+ const boardComponent = this.parentComponent().parentComponent();
+
+ function userIsMember() {
+ return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+ }
+
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
$cards.sortable({
- connectWith: '.js-minicards',
+ connectWith: '.js-minicards:not(.js-list-full)',
tolerance: 'pointer',
- appendTo: 'body',
+ appendTo: '.board-canvas',
helper(evt, item) {
const helper = item.clone();
if (MultiSelection.isActive()) {
@@ -40,7 +45,6 @@ BlazeComponent.extendComponent({
},
distance: 7,
items: itemsSelector,
- scroll: false,
placeholder: 'minicard-wrapper placeholder',
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
@@ -55,6 +59,7 @@ BlazeComponent.extendComponent({
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
+ const swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
@@ -67,21 +72,17 @@ BlazeComponent.extendComponent({
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
- card.move(listId, sortIndex.base + i * sortIndex.increment);
+ card.move(swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
});
} else {
const cardDomElement = ui.item.get(0);
const card = Blaze.getData(cardDomElement);
- card.move(listId, sortIndex.base);
+ card.move(swimlaneId, listId, sortIndex.base);
}
boardComponent.setIsDragging(false);
},
});
- function userIsMember() {
- return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
- }
-
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
$cards.sortable('option', 'disabled', !userIsMember());
@@ -114,3 +115,10 @@ BlazeComponent.extendComponent({
});
},
}).register('list');
+
+Template.miniList.events({
+ 'click .js-select-list'() {
+ const listId = this._id;
+ Session.set('currentList', listId);
+ },
+});
diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl
index cf939a6e..fa32ff6d 100644
--- a/client/components/lists/list.styl
+++ b/client/components/lists/list.styl
@@ -9,8 +9,8 @@
// Even if this background color is the same as the body we can't leave it
// transparent, because that won't work during a list drag.
background: darken(white, 13%)
- height: 100%
border-left: 1px solid darken(white, 20%)
+ border-bottom: 1px solid #CCC
padding: 0
float: left
@@ -53,6 +53,9 @@
&.ui-sortable-handle
cursor: grab
+ .list-header-left-icon
+ display: none
+
.list-header-name
display: inline
font-size: 16px
@@ -69,21 +72,30 @@
padding-left: 10px
color: #a6a6a6
- .list-header-menu-icon
+ .list-header-menu
position: absolute
padding: 7px
margin-top: 1px
top: -@padding
right: -@padding
+ .list-header-plus-icon
+ color: #a6a6a6
+ margin-right: 10px
+
+ .highlight
+ color: #ce1414
+
.list-body
- flex: 1
+ flex: 1 1 auto
+ flex-direction: column
display: flex
overflow-y: auto
padding: 5px 11px
.minicards
- flex: 1
+ flex-grow: 1
+ flex-shrink: 0
form
margin-bottom: 9px
@@ -107,3 +119,71 @@
background: #fafafa
color: #222
box-shadow: 0 1px 2px rgba(0,0,0,.2)
+
+#js-wip-limit-edit
+ padding-top: 2%
+
+ p
+ margin-bottom: 0
+
+ input
+ display: inline-block
+
+ .wip-limit-value
+ width: 20%
+ margin-right: 5%
+
+ .wip-limit-error
+ display: none
+
+ .soft-wip-limit
+ margin-right: 8px
+
+ div
+ float: left
+
+@media screen and (max-width: 800px)
+ .mini-list
+ flex: 0 0 60px
+ height: 60px
+ width: 100%
+ border-left: 0px
+ border-bottom: 1px solid darken(white, 20%)
+
+ .list
+ display: block
+ width: 100%
+ border-left: 0px
+
+ &.ui-sortable-helper
+ flex: 0 0 60px
+ height: 60px
+ width: 100%
+ border-left: 0px
+ border-bottom: 1px solid darken(white, 20%)
+
+ .list-header.ui-sortable-handle
+ cursor: grabbing
+
+ &.placeholder
+ flex: 0 0 60px
+ height: 60px
+ width: 100%
+ border-left: 0px
+ border-bottom: 1px solid darken(white, 20%)
+
+ .list-header
+
+ .list-header-left-icon
+ display: inline
+ padding: 7px
+ padding-right: 27px
+ margin-top: 1px
+ top: -@padding
+ left: -@padding
+
+ .list-header-menu-icon
+ position: absolute
+ padding: 7px
+ top: -@padding
+ right: 17px
diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade
index 01aa7179..32c6b278 100644
--- a/client/components/lists/listBody.jade
+++ b/client/components/lists/listBody.jade
@@ -1,10 +1,10 @@
template(name="listBody")
.list-body.js-perfect-scrollbar
- .minicards.clearfix.js-minicards
+ .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
if cards.count
+inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top")
- each cards
+ each (cards (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=absoluteUrl
class="{{#if cardIsSelected}}is-selected{{/if}}"
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js
index edac5b03..24e5cf5d 100644
--- a/client/components/lists/listBody.js
+++ b/client/components/lists/listBody.js
@@ -40,6 +40,14 @@ BlazeComponent.extendComponent({
console.log("labelIds", labelIds);
console.log("customFields", customFields);
+ const boardId = this.data().board()._id;
+ let swimlaneId = '';
+ const boardView = Meteor.user().profile.boardView;
+ if (boardView === 'board-view-swimlanes')
+ swimlaneId = this.parentComponent().parentComponent().data()._id;
+ else if (boardView === 'board-view-lists')
+ swimlaneId = Swimlanes.findOne({boardId})._id;
+
if (title) {
const _id = Cards.insert({
title,
@@ -49,6 +57,7 @@ BlazeComponent.extendComponent({
listId: this.data()._id,
boardId: this.data().board()._id,
sort: sortIndex,
+ swimlaneId,
});
// In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the
@@ -101,6 +110,22 @@ BlazeComponent.extendComponent({
MultiSelection.toggle(this.currentData()._id);
},
+ idOrNull(swimlaneId) {
+ const currentUser = Meteor.user();
+ if (currentUser.profile.boardView === 'board-view-swimlanes')
+ return swimlaneId;
+ return undefined;
+ },
+
+ canSeeAddCard() {
+ return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+ },
+
+ reachedWipLimit() {
+ const list = Template.currentData();
+ return !list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
+ },
+
events() {
return [{
'click .js-minicard': this.clickOnMiniCard,
@@ -246,10 +271,3 @@ BlazeComponent.extendComponent({
});
},
}).register('addCardForm');
-
-
-Template.listBody.helpers({
- canSeeAddCard() {
- return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
- },
-});
diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade
index 11905586..61771449 100644
--- a/client/components/lists/listHeader.jade
+++ b/client/components/lists/listHeader.jade
@@ -3,17 +3,40 @@ template(name="listHeader")
+inlinedForm
+editListTitleForm
else
+ if isMiniScreen
+ if currentList
+ a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
h2.list-header-name(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
+ if wipLimit.enabled
+ |&nbsp;(
+ span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}}
+ |/#{wipLimit.value})
+
if showCardsCountForList cards.count
= cards.count
- span.lowercase
- | {{_ 'cards'}}
- if currentUser.isBoardMember
+ span
+ | {{_ 'cards-count'}}
+ if isMiniScreen
+ if currentList
+ if isWatching
+ i.list-header-watch-icon.fa.fa-eye
+ div.list-header-menu
+ unless currentUser.isCommentOnly
+ if canSeeAddCard
+ a.js-add-card.fa.fa-plus.list-header-plus-icon
+ a.fa.fa-navicon.js-open-list-menu
+ else
+ a.list-header-menu-icon.fa.fa-angle-right.js-select-list
+ else if currentUser.isBoardMember
if isWatching
i.list-header-watch-icon.fa.fa-eye
- a.list-header-menu-icon.fa.fa-navicon.js-open-list-menu
+ div.list-header-menu
+ unless currentUser.isCommentOnly
+ if canSeeAddCard
+ a.js-add-card.fa.fa-plus.list-header-plus-icon
+ a.fa.fa-navicon.js-open-list-menu
template(name="editListTitleForm")
.list-composer
@@ -28,10 +51,13 @@ template(name="listActionPopup")
unless currentUser.isCommentOnly
hr
ul.pop-over-list
- li: a.js-add-card {{_ 'add-card'}}
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
- hr
+ hr
+ if currentUser.isBoardAdmin
+ ul.pop-over-list
+ li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+ hr
ul.pop-over-list
li: a.js-close-list {{_ 'archive-list'}}
hr
@@ -63,3 +89,26 @@ template(name="listDeletePopup")
unless archived
p {{_ "list-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+
+template(name="setWipLimitPopup")
+ #js-wip-limit-edit
+ label {{_ 'set-wip-limit-value'}}
+ ul.pop-over-list
+ li: a.js-enable-wip-limit {{_ 'enable-wip-limit'}}
+ if isWipLimitEnabled
+ i.fa.fa-check
+ if isWipLimitEnabled
+ p
+ input.wip-limit-value(type="number" value="{{ wipLimitValue }}" min="1" max="99")
+ input.wip-limit-apply(type="submit" value="{{_ 'apply'}}")
+ input.wip-limit-error
+ p
+ .soft-wip-limit
+ .materialCheckBox(class="{{#if isWipLimitSoft}}is-checked{{/if}}")
+ label {{_ 'soft-wip-limit'}}
+
+template(name="wipLimitErrorPopup")
+ .wip-limit-invalid
+ p {{_ 'wipLimitErrorPopup-dialog-pt1'}}
+ p {{_ 'wipLimitErrorPopup-dialog-pt2'}}
+ button.full.js-back-view(type="submit") {{_ 'cancel'}}
diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js
index 1ad9f9dd..4b6bf196 100644
--- a/client/components/lists/listHeader.js
+++ b/client/components/lists/listHeader.js
@@ -1,4 +1,9 @@
BlazeComponent.extendComponent({
+ canSeeAddCard() {
+ const list = Template.currentData();
+ return !list.getWipLimit('enabled') || list.getWipLimit('soft') || !this.reachedWipLimit();
+ },
+
editTitle(evt) {
evt.preventDefault();
const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
@@ -17,33 +22,45 @@ BlazeComponent.extendComponent({
return Meteor.user().getLimitToShowCardsCount();
},
+ reachedWipLimit() {
+ const list = Template.currentData();
+ return list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
+ },
+
showCardsCountForList(count) {
- return count > this.limitToShowCardsCount();
+ const limit = this.limitToShowCardsCount();
+ return limit > 0 && count > limit;
},
events() {
return [{
'click .js-open-list-menu': Popup.open('listAction'),
+ 'click .js-add-card' (evt) {
+ const listDom = $(evt.target).parents(`#js-list-${this.currentData()._id}`)[0];
+ const listComponent = BlazeComponent.getComponentForElement(listDom);
+ listComponent.openForm({
+ position: 'top',
+ });
+ },
+ 'click .js-unselect-list'() {
+ Session.set('currentList', null);
+ },
submit: this.editTitle,
}];
},
}).register('listHeader');
Template.listActionPopup.helpers({
+ isWipLimitEnabled() {
+ return Template.currentData().getWipLimit('enabled');
+ },
+
isWatching() {
return this.findWatcher(Meteor.userId());
},
});
Template.listActionPopup.events({
- 'click .js-add-card' () {
- const listDom = document.getElementById(`js-list-${this._id}`);
- const listComponent = BlazeComponent.getComponentForElement(listDom);
- listComponent.openForm({
- position: 'top',
- });
- Popup.close();
- },
'click .js-list-subscribe' () {},
'click .js-select-cards' () {
const cardIds = this.allCards().map((card) => card._id);
@@ -62,9 +79,63 @@ Template.listActionPopup.events({
this.archive();
Popup.close();
},
+ 'click .js-set-wip-limit': Popup.open('setWipLimit'),
'click .js-more': Popup.open('listMore'),
});
+BlazeComponent.extendComponent({
+ applyWipLimit() {
+ const list = Template.currentData();
+ const limit = parseInt(Template.instance().$('.wip-limit-value').val(), 10);
+
+ if(limit < list.cards().count() && !list.getWipLimit('soft')){
+ Template.instance().$('.wip-limit-error').click();
+ } else {
+ Meteor.call('applyWipLimit', list._id, limit);
+ Popup.back();
+ }
+ },
+
+ enableSoftLimit() {
+ const list = Template.currentData();
+
+ if(list.getWipLimit('soft') && list.getWipLimit('value') < list.cards().count()){
+ list.setWipLimit(list.cards().count());
+ }
+ Meteor.call('enableSoftLimit', Template.currentData()._id);
+ },
+
+ enableWipLimit() {
+ const list = Template.currentData();
+ // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
+ if(!list.getWipLimit('enabled') && list.getWipLimit('value') < list.cards().count()){
+ list.setWipLimit(list.cards().count());
+ }
+ Meteor.call('enableWipLimit', list._id);
+ },
+
+ isWipLimitSoft() {
+ return Template.currentData().getWipLimit('soft');
+ },
+
+ isWipLimitEnabled() {
+ return Template.currentData().getWipLimit('enabled');
+ },
+
+ wipLimitValue(){
+ return Template.currentData().getWipLimit('value');
+ },
+
+ events() {
+ return [{
+ 'click .js-enable-wip-limit': this.enableWipLimit,
+ 'click .wip-limit-apply': this.applyWipLimit,
+ 'click .wip-limit-error': Popup.open('wipLimitError'),
+ 'click .materialCheckBox': this.enableSoftLimit,
+ }];
+ },
+}).register('setWipLimitPopup');
+
Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function () {
Popup.close();