summaryrefslogtreecommitdiffstats
path: root/client/components/boards
diff options
context:
space:
mode:
Diffstat (limited to 'client/components/boards')
-rw-r--r--client/components/boards/boardArchive.jade2
-rw-r--r--client/components/boards/boardBody.jade28
-rw-r--r--client/components/boards/boardBody.js238
-rw-r--r--client/components/boards/boardBody.styl35
-rw-r--r--client/components/boards/boardColors.styl14
-rw-r--r--client/components/boards/boardHeader.jade14
-rw-r--r--client/components/boards/boardHeader.js20
-rw-r--r--client/components/boards/boardsList.jade6
-rw-r--r--client/components/boards/boardsList.js16
-rw-r--r--client/components/boards/boardsList.styl22
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