diff options
Diffstat (limited to 'client/components/boards')
-rw-r--r-- | client/components/boards/boardArchive.jade | 2 | ||||
-rw-r--r-- | client/components/boards/boardBody.jade | 28 | ||||
-rw-r--r-- | client/components/boards/boardBody.js | 238 | ||||
-rw-r--r-- | client/components/boards/boardBody.styl | 35 | ||||
-rw-r--r-- | client/components/boards/boardColors.styl | 14 | ||||
-rw-r--r-- | client/components/boards/boardHeader.jade | 14 | ||||
-rw-r--r-- | client/components/boards/boardHeader.js | 20 | ||||
-rw-r--r-- | client/components/boards/boardsList.jade | 6 | ||||
-rw-r--r-- | client/components/boards/boardsList.js | 16 | ||||
-rw-r--r-- | client/components/boards/boardsList.styl | 22 |
10 files changed, 196 insertions, 199 deletions
diff --git a/client/components/boards/boardArchive.jade b/client/components/boards/boardArchive.jade index 724e6569..6576f742 100644 --- a/client/components/boards/boardArchive.jade +++ b/client/components/boards/boardArchive.jade @@ -11,4 +11,4 @@ template(name="archivedBoards") | {{_ 'restore-board'}} = title else - li.no-items-message {{_ 'no-archived-board'}} + li.no-items-message {{_ 'no-archived-boards'}} diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index fe10c921..29a613b9 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -14,30 +14,14 @@ template(name="board") template(name="boardBody") .board-wrapper(class=currentBoard.colorClass) +sidebar - .board-canvas( + .board-canvas.js-swimlanes.js-perfect-scrollbar( class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}" class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}" class="{{#if draggingActive.get}}is-dragging-active{{/if}}") if showOverlay.get .board-overlay - .lists.js-lists - each currentBoard.lists - +list(this) - if currentCardIsInThisList - +cardDetails(currentCard) - if canSeeAddList - +addListForm - -template(name="addListForm") - .list.js-list.list-composer.js-list-composer - .list-header - +inlinedForm(autoclose=false) - input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}" - autocomplete="off" autofocus) - .edit-controls.clearfix - button.primary.confirm(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form - else - a.open-list-composer.js-open-inlined-form - i.fa.fa-plus - | {{_ 'add-list'}} + if isViewSwimlanes + each currentBoard.swimlanes + +swimlane(this) + if isViewLists + +listsGroup diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index b3880c61..456bf9b3 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,9 +1,8 @@ const subManager = new SubsManager(); +const { calculateIndex } = Utils; BlazeComponent.extendComponent({ onCreated() { - this.draggingActive = new ReactiveVar(false); - this.showOverlay = new ReactiveVar(false); this.isBoardReady = new ReactiveVar(false); // The pattern we use to manually handle data loading is described here: @@ -21,40 +20,90 @@ BlazeComponent.extendComponent({ }); }); }); + }, - this._isDragging = false; - this._lastDragPositionX = 0; + onlyShowCurrentCard() { + return Utils.isMiniScreen() && Session.get('currentCard'); + }, + +}).register('board'); +BlazeComponent.extendComponent({ + onCreated() { + this.showOverlay = new ReactiveVar(false); + this.draggingActive = new ReactiveVar(false); + this._isDragging = false; // Used to set the overlay this.mouseHasEnterCardDetails = false; }, + onRendered() { + const boardComponent = this; + const $swimlanesDom = boardComponent.$('.js-swimlanes'); + + $swimlanesDom.sortable({ + tolerance: 'pointer', + appendTo: '.board-canvas', + helper: 'clone', + handle: '.js-swimlane-header', + items: '.js-swimlane:not(.placeholder)', + placeholder: 'swimlane placeholder', + distance: 7, + start(evt, ui) { + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + boardComponent.setIsDragging(true); + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + const prevSwimlaneDom = ui.item.prev('.js-swimlane').get(0); + const nextSwimlaneDom = ui.item.next('.js-swimlane').get(0); + const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1); + + $swimlanesDom.sortable('cancel'); + const swimlaneDomElement = ui.item.get(0); + const swimlane = Blaze.getData(swimlaneDomElement); + + Swimlanes.update(swimlane._id, { + $set: { + sort: sortIndex.base, + }, + }); - openNewListForm() { - this.childComponents('addListForm')[0].open(); - }, + boardComponent.setIsDragging(false); + }, + }); - // XXX Flow components allow us to avoid creating these two setter methods by - // exposing a public API to modify the component state. We need to investigate - // best practices here. - setIsDragging(bool) { - this.draggingActive.set(bool); + function userIsMember() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + } + + // If there is no data in the board (ie, no lists) we autofocus the list + // creation form by clicking on the corresponding element. + const currentBoard = Boards.findOne(Session.get('currentBoard')); + if (userIsMember() && currentBoard.lists().count() === 0) { + boardComponent.openNewListForm(); + } }, - scrollLeft(position = 0) { - const lists = this.$('.js-lists'); - lists && lists.animate({ - scrollLeft: position, - }); + isViewSwimlanes() { + const currentUser = Meteor.user(); + return (currentUser.profile.boardView === 'board-view-swimlanes'); }, - currentCardIsInThisList() { - const currentCard = Cards.findOne(Session.get('currentCard')); - const listId = this.currentData()._id; - return currentCard && currentCard.listId === listId; + isViewLists() { + const currentUser = Meteor.user(); + return (currentUser.profile.boardView === 'board-view-lists'); }, - onlyShowCurrentCard() { - return Utils.isMiniScreen() && Session.get('currentCard'); + openNewListForm() { + if (this.isViewSwimlanes()) { + this.childComponents('swimlane')[0] + .childComponents('addListAndSwimlaneForm')[0].open(); + } else if (this.isViewLists()) { + this.childComponents('listsGroup')[0] + .childComponents('addListForm')[0].open(); + } }, events() { @@ -66,147 +115,26 @@ BlazeComponent.extendComponent({ this.showOverlay.set(false); } }, - - // Click-and-drag action - 'mousedown .board-canvas'(evt) { - // Translating the board canvas using the click-and-drag action can - // conflict with the build-in browser mechanism to select text. We - // define a list of elements in which we disable the dragging because - // the user will legitimately expect to be able to select some text with - // his mouse. - const noDragInside = ['a', 'input', 'textarea', 'p', '.js-list-header']; - if ($(evt.target).closest(noDragInside.join(',')).length === 0 && $('.lists').prop('clientHeight') > evt.offsetY) { - this._isDragging = true; - this._lastDragPositionX = evt.clientX; - } - }, 'mouseup'() { if (this._isDragging) { this._isDragging = false; } }, - 'mousemove'(evt) { - if (this._isDragging) { - // Update the canvas position - this.listsDom.scrollLeft -= evt.clientX - this._lastDragPositionX; - this._lastDragPositionX = evt.clientX; - // Disable browser text selection while dragging - evt.stopPropagation(); - evt.preventDefault(); - // Don't close opened card or inlined form at the end of the - // click-and-drag. - EscapeActions.executeUpTo('popup-close'); - EscapeActions.preventNextClick(); - } - }, }]; }, -}).register('board'); - -Template.boardBody.onRendered(function() { - const self = BlazeComponent.getComponentForElement(this.firstNode); - - self.listsDom = this.find('.js-lists'); - - if (!Session.get('currentCard')) { - self.scrollLeft(); - } - - // We want to animate the card details window closing. We rely on CSS - // transition for the actual animation. - self.listsDom._uihooks = { - removeElement(node) { - const removeNode = _.once(() => { - node.parentNode.removeChild(node); - }); - if ($(node).hasClass('js-card-details')) { - $(node).css({ - flexBasis: 0, - padding: 0, - }); - $(self.listsDom).one(CSSEvents.transitionend, removeNode); - } else { - removeNode(); - } - }, - }; - - $(self.listsDom).sortable({ - tolerance: 'pointer', - helper: 'clone', - handle: '.js-list-header', - items: '.js-list:not(.js-list-composer)', - placeholder: 'list placeholder', - distance: 7, - start(evt, ui) { - ui.placeholder.height(ui.helper.height()); - Popup.close(); - }, - stop() { - $(self.listsDom).find('.js-list:not(.js-list-composer)').each( - (i, list) => { - const data = Blaze.getData(list); - Lists.update(data._id, { - $set: { - sort: i, - }, - }); - } - ); - }, - }); - - function userIsMember() { - return Meteor.user() && Meteor.user().isBoardMember(); - } - - // Disable drag-dropping while in multi-selection mode, or if the current user - // is not a board member - self.autorun(() => { - const $listDom = $(self.listsDom); - if ($listDom.data('sortable')) { - $(self.listsDom).sortable('option', 'disabled', - MultiSelection.isActive() || !userIsMember()); - } - }); - // If there is no data in the board (ie, no lists) we autofocus the list - // creation form by clicking on the corresponding element. - const currentBoard = Boards.findOne(Session.get('currentBoard')); - if (userIsMember() && currentBoard.lists().count() === 0) { - self.openNewListForm(); - } -}); - -BlazeComponent.extendComponent({ - // Proxy - open() { - this.childComponents('inlinedForm')[0].open(); + // XXX Flow components allow us to avoid creating these two setter methods by + // exposing a public API to modify the component state. We need to investigate + // best practices here. + setIsDragging(bool) { + this.draggingActive.set(bool); }, - events() { - return [{ - submit(evt) { - evt.preventDefault(); - const titleInput = this.find('.list-name-input'); - const title = titleInput.value.trim(); - if (title) { - Lists.insert({ - title, - boardId: Session.get('currentBoard'), - sort: $('.list').length, - }); - - titleInput.value = ''; - titleInput.focus(); - } - }, - }]; + scrollLeft(position = 0) { + const swimlanes = this.$('.js-swimlanes'); + swimlanes && swimlanes.animate({ + scrollLeft: position, + }); }, -}).register('addListForm'); -Template.boardBody.helpers({ - canSeeAddList() { - return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); - }, -}); +}).register('boardBody'); diff --git a/client/components/boards/boardBody.styl b/client/components/boards/boardBody.styl index df5696a2..a614c7ed 100644 --- a/client/components/boards/boardBody.styl +++ b/client/components/boards/boardBody.styl @@ -1,8 +1,11 @@ @import 'nib' position() - if arguments[0] == cover - position: absolute + if arguments[0] == cover || arguments[0] == fixed-cover + if arguments[0] == cover + position: absolute + else + position: fixed left: 0 right: 0 top: 0 @@ -12,26 +15,18 @@ position() .board-wrapper position: cover + overflow-y: hidden; .board-canvas position: cover transition: margin .1s + overflow-y: auto; &.is-sibling-sidebar-open margin-right: 248px - .lists - align-items: flex-start - display: flex - flex-direction: row - margin: 0 0 10px - padding: 0 40px 5px 0 - overflow-x: auto - overflow-y: hidden - position: cover - .board-overlay - position: cover + position: fixed-cover top: -100px right: -400px background: black @@ -43,3 +38,17 @@ position() .open-minicard-composer, .minicard-wrapper.is-checked display: none + +@media screen and (max-width: 800px) + .board-wrapper + + .board-canvas + + .swimlane + border-bottom: 1px solid #CCC + display: flex + flex-direction: column + margin: 0 + padding: 0 40px 0px 0 + overflow-x: hidden + overflow-y: auto diff --git a/client/components/boards/boardColors.styl b/client/components/boards/boardColors.styl index 8e28fcfa..233659ca 100644 --- a/client/components/boards/boardColors.styl +++ b/client/components/boards/boardColors.styl @@ -51,11 +51,25 @@ setBoardColor(color) &:not(.is-checked) + .minicard:hover:not(.minicard-composer) background: lighten(color, 97%) + .toggle-label + + &:after + background-color: darken(color, 20%) + + .toggle-switch:checked ~ .toggle-label + background-color: lighten(color, 20%) + + &:after + background-color: darken(color, 20%) + @media screen and (max-width: 800px) &.pop-over .header background: color color: white + &#header ul li.current, &#header-quick-access ul li.current + border-bottom: 4px solid lighten(color, 20%) + .board-color-nephritis setBoardColor(#27AE60) diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 67acdc9e..b4ccd3b3 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -2,7 +2,8 @@ template(name="boardHeaderBar") h1.header-board-menu with currentBoard a(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}") - = title + +viewer + = title .board-header-btns.left unless isMiniScreen @@ -71,7 +72,7 @@ template(name="boardHeaderBar") title="{{_ 'log-in'}}") i.fa.fa-sign-in span {{_ 'log-in'}} - + if isSandstorm if currentUser a.board-header-btn.js-open-archived-board @@ -87,6 +88,15 @@ template(name="boardHeaderBar") a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") i.fa.fa-times-thin + a.board-header-btn.js-open-search-view(title="{{_ 'search'}}") + i.fa.fa-search + span {{_ 'search'}} + + a.board-header-btn.js-toggle-board-view( + title="{{_ 'board-view'}}") + i.fa.fa-th-large + span {{_ currentUser.profile.boardView}} + if canModifyBoard a.board-header-btn.js-multiselection-activate( title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 8983c722..e0b19246 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -62,10 +62,6 @@ BlazeComponent.extendComponent({ return user && user.hasStarred(boardId); }, - isMiniScreen() { - return Utils.isMiniScreen(); - }, - // Only show the star counter if the number of star is greater than 2 showStarCounter() { const currentBoard = Boards.findOne(Session.get('currentBoard')); @@ -84,6 +80,14 @@ BlazeComponent.extendComponent({ 'click .js-open-archived-board'() { Modal.open('archivedBoards'); }, + 'click .js-toggle-board-view'() { + const currentUser = Meteor.user(); + if (currentUser.profile.boardView === 'board-view-swimlanes') { + currentUser.setBoardView('board-view-lists'); + } else if (currentUser.profile.boardView === 'board-view-lists') { + currentUser.setBoardView('board-view-swimlanes'); + } + }, 'click .js-open-filter-view'() { Sidebar.setView('filter'); }, @@ -92,6 +96,9 @@ BlazeComponent.extendComponent({ Sidebar.setView(); Filter.reset(); }, + 'click .js-open-search-view'() { + Sidebar.setView('search'); + }, 'click .js-multiselection-activate'() { const currentCard = Session.get('currentCard'); MultiSelection.activate(); @@ -172,6 +179,11 @@ const CreateBoard = BlazeComponent.extendComponent({ permission: visibility, })); + Swimlanes.insert({ + title: 'Default', + boardId: this.boardId.get(), + }); + Utils.goBoardId(this.boardId.get()); }, diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index ae82dfa9..95ce3678 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -20,6 +20,12 @@ template(name="boardList") i.fa.js-star-board( class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" title="{{_ 'star-board-title'}}") + + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + p.board-list-item-desc= description li.js-add-board a.board-list-item.label {{_ 'add-board'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index e4bb050e..1ed88146 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,4 +1,10 @@ +const subManager = new SubsManager(); + BlazeComponent.extendComponent({ + onCreated() { + Meteor.subscribe('setting'); + }, + boards() { return Boards.find({ archived: false, @@ -13,6 +19,16 @@ BlazeComponent.extendComponent({ return user && user.hasStarred(this.currentData()._id); }, + hasOvertimeCards() { + subManager.subscribe('board', this.currentData()._id); + return this.currentData().hasOvertimeCards(); + }, + + hasSpentTimeCards() { + subManager.subscribe('board', this.currentData()._id); + return this.currentData().hasSpentTimeCards(); + }, + isInvited() { const user = Meteor.user(); return user && user.isInvitedTo(this.currentData()._id); diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl index 4b5245f9..80e47685 100644 --- a/client/components/boards/boardsList.styl +++ b/client/components/boards/boardsList.styl @@ -17,6 +17,7 @@ $spaceBetweenTiles = 16px opacity: 1 .board-list-item + overflow: hidden; background-color: #999 color: #f6f6f6 height: 90px @@ -43,9 +44,9 @@ $spaceBetweenTiles = 16px line-height: 22px .board-list-item-desc - color: rgba(255, 255, 255, .5) + color: #fff display: block - font-size: 10px + font-size: 14px font-weight: 400 line-height: 18px @@ -73,6 +74,23 @@ $spaceBetweenTiles = 16px transition-duration: .15s transition-property: color, font-size, background + .fa-circle + bottom: 0; + font-size: 10px; + height: 10px; + line-height: 10px; + padding: 9px 9px; + position: absolute; + right: 0; + transition-duration: .15s + transition-property: color, font-size, background + + .has-overtime-card-active + color: #eb4646 !important + + .no-overtime-card-active + color: #3cb500 !important + .is-star-active color: white |