diff options
Diffstat (limited to 'client/components/lists')
-rw-r--r-- | client/components/lists/list.jade | 4 | ||||
-rw-r--r-- | client/components/lists/list.js | 28 | ||||
-rw-r--r-- | client/components/lists/list.styl | 88 | ||||
-rw-r--r-- | client/components/lists/listBody.jade | 4 | ||||
-rw-r--r-- | client/components/lists/listBody.js | 32 | ||||
-rw-r--r-- | client/components/lists/listHeader.jade | 61 | ||||
-rw-r--r-- | client/components/lists/listHeader.js | 89 |
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 + | ( + 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(); |