summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/components/lists/list.js2
-rw-r--r--client/components/lists/list.styl16
-rw-r--r--client/components/lists/listBody.jade2
-rw-r--r--client/components/lists/listBody.js17
-rw-r--r--client/components/lists/listHeader.jade26
-rw-r--r--client/components/lists/listHeader.js52
-rw-r--r--client/components/sidebar/sidebarArchives.js4
-rw-r--r--client/lib/popup.js1
-rw-r--r--i18n/en-GB.i18n.json9
-rw-r--r--i18n/en.i18n.json7
-rw-r--r--i18n/pt-BR.i18n.json9
-rw-r--r--models/cards.js8
-rw-r--r--models/lists.js62
13 files changed, 202 insertions, 13 deletions
diff --git a/client/components/lists/list.js b/client/components/lists/list.js
index 9c191348..0e913207 100644
--- a/client/components/lists/list.js
+++ b/client/components/lists/list.js
@@ -22,7 +22,7 @@ BlazeComponent.extendComponent({
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',
helper(evt, item) {
diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl
index 5e20476e..f426b243 100644
--- a/client/components/lists/list.styl
+++ b/client/components/lists/list.styl
@@ -110,3 +110,19 @@
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
diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade
index 01aa7179..840fd801 100644
--- a/client/components/lists/listBody.jade
+++ b/client/components/lists/listBody.jade
@@ -1,6 +1,6 @@
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")
diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js
index 724e805b..22ed9e57 100644
--- a/client/components/lists/listBody.js
+++ b/client/components/lists/listBody.js
@@ -96,6 +96,16 @@ BlazeComponent.extendComponent({
MultiSelection.toggle(this.currentData()._id);
},
+ canSeeAddCard() {
+ return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+ },
+
+ reachedWipLimit() {
+ const list = Template.currentData();
+ if( !list.getWipLimit() ) { return false; }
+ return list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count();
+ },
+
events() {
return [{
'click .js-minicard': this.clickOnMiniCard,
@@ -239,10 +249,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 68336320..d5738dd9 100644
--- a/client/components/lists/listHeader.jade
+++ b/client/components/lists/listHeader.jade
@@ -6,6 +6,9 @@ template(name="listHeader")
h2.list-header-name(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
+ if isWipLimitEnabled
+ span
+ | ({{cards.count}}/#{wipLimit.value})
if showCardsCountForList cards.count
= cards.count
span.lowercase
@@ -33,6 +36,10 @@ template(name="listActionPopup")
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
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
@@ -64,3 +71,22 @@ 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
+ lable {{_ '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" onkeydown="return false")
+ input.wip-limit-apply(type="submit" value="{{_ 'apply'}}")
+ input.wip-limit-error
+
+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 7fe42884..9974c788 100644
--- a/client/components/lists/listHeader.js
+++ b/client/components/lists/listHeader.js
@@ -13,6 +13,14 @@ BlazeComponent.extendComponent({
return list.findWatcher(Meteor.userId());
},
+ isWipLimitEnabled() {
+ const wipLimit = this.currentData().getWipLimit();
+ if(!wipLimit) {
+ return 0;
+ }
+ return wipLimit.enabled && wipLimit.value > 0;
+ },
+
limitToShowCardsCount() {
return Meteor.user().getLimitToShowCardsCount();
},
@@ -37,6 +45,10 @@ BlazeComponent.extendComponent({
}).register('listHeader');
Template.listActionPopup.helpers({
+ isWipLimitEnabled() {
+ return Template.currentData().getWipLimit('enabled');
+ },
+
isWatching() {
return this.findWatcher(Meteor.userId());
},
@@ -61,9 +73,49 @@ 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()){
+ Template.instance().$('.wip-limit-error').click();
+ } else {
+ Meteor.call('applyWipLimit', list._id, limit);
+ Popup.back();
+ }
+ },
+
+ 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() && !list.getWipLimit('enabled') && list.getWipLimit('value') < list.cards().count()){
+ list.setWipLimit(list.cards().count());
+ }
+ Meteor.call('enableWipLimit', list._id);
+ },
+
+ 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'),
+ }];
+ },
+}).register('setWipLimitPopup');
+
Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function () {
Popup.close();
diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js
index c8196f23..2e8754b0 100644
--- a/client/components/sidebar/sidebarArchives.js
+++ b/client/components/sidebar/sidebarArchives.js
@@ -32,7 +32,9 @@ BlazeComponent.extendComponent({
return [{
'click .js-restore-card'() {
const card = this.currentData();
- card.restore();
+ if(card.canBeRestored()){
+ card.restore();
+ }
},
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
const cardId = this._id;
diff --git a/client/lib/popup.js b/client/lib/popup.js
index 3658d883..d9e29ff1 100644
--- a/client/lib/popup.js
+++ b/client/lib/popup.js
@@ -205,4 +205,3 @@ escapeActions.forEach((actionName) => {
}
);
});
-
diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json
index 2c18438d..718e487c 100644
--- a/i18n/en-GB.i18n.json
+++ b/i18n/en-GB.i18n.json
@@ -171,6 +171,7 @@
"edit": "Edit",
"edit-avatar": "Change Avatar",
"edit-profile": "Edit Profile",
+ "edit-wip-limit": "Edit WIP Limit",
"editCardStartDatePopup-title": "Change start date",
"editCardDueDatePopup-title": "Change due date",
"editLabelPopup-title": "Change Label",
@@ -189,6 +190,7 @@
"email-sent": "Email sent",
"email-verifyEmail-subject": "Verify your email address on __siteName__",
"email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.",
+ "enable-wip-limit": "Enable WIP Limit",
"error-board-doesNotExist": "This board does not exist",
"error-board-notAdmin": "You need to be admin of this board to do that",
"error-board-notAMember": "You need to be a member of this board to do that",
@@ -310,6 +312,8 @@
"save": "Save",
"search": "Search",
"select-color": "Select Color",
+ "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list:",
+ "setWipLimitPopup-title": "Set WIP Limit",
"shortcut-assign-self": "Assign yourself to current card",
"shortcut-autocomplete-emoji": "Autocomplete emoji",
"shortcut-autocomplete-members": "Autocomplete members",
@@ -350,6 +354,9 @@
"welcome-list1": "Basics",
"welcome-list2": "Advanced",
"what-to-do": "What do you want to do?",
+ "wipLimitErrorPopup-title": "Invalid WIP Limit",
+ "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
+ "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.",
"admin-panel": "Admin Panel",
"settings": "Settings",
"people": "People",
@@ -395,4 +402,4 @@
"no": "No",
"accounts": "Accounts",
"accounts-allowEmailChange": "Allow Email Change"
-} \ No newline at end of file
+}
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 95be9865..556910ff 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -171,6 +171,7 @@
"edit": "Edit",
"edit-avatar": "Change Avatar",
"edit-profile": "Edit Profile",
+ "edit-wip-limit": "Edit WIP Limit",
"editCardStartDatePopup-title": "Change start date",
"editCardDueDatePopup-title": "Change due date",
"editLabelPopup-title": "Change Label",
@@ -189,6 +190,7 @@
"email-sent": "Email sent",
"email-verifyEmail-subject": "Verify your email address on __siteName__",
"email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.",
+ "enable-wip-limit": "Enable WIP Limit",
"error-board-doesNotExist": "This board does not exist",
"error-board-notAdmin": "You need to be admin of this board to do that",
"error-board-notAMember": "You need to be a member of this board to do that",
@@ -310,6 +312,8 @@
"save": "Save",
"search": "Search",
"select-color": "Select Color",
+ "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
+ "setWipLimitPopup-title": "Set WIP Limit",
"shortcut-assign-self": "Assign yourself to current card",
"shortcut-autocomplete-emoji": "Autocomplete emoji",
"shortcut-autocomplete-members": "Autocomplete members",
@@ -350,6 +354,9 @@
"welcome-list1": "Basics",
"welcome-list2": "Advanced",
"what-to-do": "What do you want to do?",
+ "wipLimitErrorPopup-title": "Invalid WIP Limit",
+ "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
+ "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.",
"admin-panel": "Admin Panel",
"settings": "Settings",
"people": "People",
diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json
index ccb2bbe6..e219fb30 100644
--- a/i18n/pt-BR.i18n.json
+++ b/i18n/pt-BR.i18n.json
@@ -171,6 +171,7 @@
"edit": "Editar",
"edit-avatar": "Alterar Avatar",
"edit-profile": "Editar Perfil",
+ "edit-wip-limit": "Editar Limite WIP",
"editCardStartDatePopup-title": "Altera data de início",
"editCardDueDatePopup-title": "Altera data fim",
"editLabelPopup-title": "Alterar Etiqueta",
@@ -189,6 +190,7 @@
"email-sent": "Email enviado",
"email-verifyEmail-subject": "Verifique seu endereço de email em __siteName__",
"email-verifyEmail-text": "Olá __user__\nPara verificar sua conta de email, clique no link abaixo.\n__url__\nObrigado.",
+ "enable-wip-limit": "Ativar Limite WIP",
"error-board-doesNotExist": "Este quadro não existe",
"error-board-notAdmin": "Você precisa ser administrador desse quadro para fazer isto",
"error-board-notAMember": "Você precisa ser um membro desse quadro para fazer isto",
@@ -310,6 +312,8 @@
"save": "Salvar",
"search": "Buscar",
"select-color": "Selecionar Cor",
+ "set-wip-limit-value": "Defina um limite máximo para o número de tarefas nesta lista",
+ "setWipLimitPopup-title": "Definir Limite WIP",
"shortcut-assign-self": "Atribuir a si o cartão atual",
"shortcut-autocomplete-emoji": "Autocompletar emoji",
"shortcut-autocomplete-members": "Preenchimento automático de membros",
@@ -350,6 +354,9 @@
"welcome-list1": "Básico",
"welcome-list2": "Avançado",
"what-to-do": "O que você gostaria de fazer?",
+ "wipLimitErrorPopup-title": "Limite WIP Inválido",
+ "wipLimitErrorPopup-dialog-pt1": "O número de tarefas nesta lista excede o limite WIP definido.",
+ "wipLimitErrorPopup-dialog-pt2": "Por favor, mova algumas tarefas para fora desta lista, ou defina um limite WIP mais elevado.",
"admin-panel": "Painel Administrativo",
"settings": "Configurações",
"people": "Pessoas",
@@ -395,4 +402,4 @@
"no": "Não",
"accounts": "Contas",
"accounts-allowEmailChange": "Permitir Mudança de Email"
-} \ No newline at end of file
+}
diff --git a/models/cards.js b/models/cards.js
index 0a440697..5b752ec3 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -179,6 +179,14 @@ Cards.helpers({
cardId: this._id,
});
},
+
+ canBeRestored() {
+ const list = Lists.findOne({_id: this.listId});
+ if(list.getWipLimit() && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){
+ return false;
+ }
+ return true;
+ },
});
Cards.mutations({
diff --git a/models/lists.js b/models/lists.js
index d9a5b8e2..1b999b07 100644
--- a/models/lists.js
+++ b/models/lists.js
@@ -42,6 +42,31 @@ Lists.attachSchema(new SimpleSchema({
}
},
},
+ wipLimit: {
+ type: Object,
+ optional: true,
+ },
+ 'wipLimit.value': {
+ type: Number,
+ decimal: false,
+ autoValue() {
+ if(this.isInsert){
+ return 0;
+ }
+ return this.value;
+ },
+ optional: true,
+ },
+ 'wipLimit.enabled':{
+ type: Boolean,
+ autoValue() {
+ if(this.isInsert){
+ return false;
+ }
+ return this.value;
+ },
+ optional: true,
+ },
}));
Lists.allow({
@@ -72,6 +97,17 @@ Lists.helpers({
board() {
return Boards.findOne(this.boardId);
},
+
+ getWipLimit(option){
+ const list = Lists.findOne({ _id: this._id });
+ if(!list.wipLimit) { // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
+ return 0;
+ } else if(!option) {
+ return list.wipLimit;
+ } else {
+ return list.wipLimit[option] ? list.wipLimit[option] : 0; // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
+ }
+ },
});
Lists.mutations({
@@ -86,6 +122,32 @@ Lists.mutations({
restore() {
return { $set: { archived: false } };
},
+
+ toggleWipLimit(toggle) {
+ return { $set: { 'wipLimit.enabled': toggle } };
+ },
+
+ setWipLimit(limit) {
+ return { $set: { 'wipLimit.value': limit } };
+ },
+});
+
+Meteor.methods({
+ applyWipLimit(listId, limit){
+ check(listId, String);
+ check(limit, Number);
+ Lists.findOne({ _id: listId }).setWipLimit(limit);
+ },
+
+ enableWipLimit(listId) {
+ check(listId, String);
+ const list = Lists.findOne({ _id: listId });
+ if(list.getWipLimit()){ // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
+ list.toggleWipLimit(!list.getWipLimit('enabled'));
+ } else {
+ list.toggleWipLimit(true); // First time toggle is always to 'true' because default is 'false'
+ }
+ },
});
Lists.hookOptions.after.update = { fetchPrevious: false };