فهرست منبع

Merge branch 'issue783' of https://github.com/amadilsons/wekan into amadilsons-issue783

Lauri Ojansivu 7 سال پیش
والد
کامیت
fb060ed2c5

+ 1 - 1
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) {

+ 16 - 0
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

+ 1 - 1
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")

+ 10 - 7
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();
-  },
-});

+ 26 - 0
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'}}

+ 52 - 0
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();

+ 3 - 1
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;

+ 0 - 1
client/lib/popup.js

@@ -205,4 +205,3 @@ escapeActions.forEach((actionName) => {
     }
   );
 });
-

+ 8 - 1
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"
-}
+}

+ 7 - 0
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",

+ 8 - 1
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"
-}
+}

+ 8 - 0
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({

+ 62 - 0
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 };