From 0782c97d4f0ad7f5f22c632927a9484edffe7b93 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 23:33:59 +0100 Subject: [PATCH 01/12] card colors: force overwrite of text color This allows to show checks on the color with the correct color instead of plain white. --- client/components/cards/cardDetails.styl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 5a486d849..c18e1d2d7 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -144,16 +144,16 @@ input[type="submit"].attachment-add-link-submit card-details-color(background, color...) background: background !important if color - color: color //overwrite text for better visibility + color: color !important //overwrite text for better visibility .card-details-green card-details-color(#3cb500, #ffffff) //White text for better visibility .card-details-yellow - card-details-color(#fad900) + card-details-color(#fad900, #000) //Black text for better visibility .card-details-orange - card-details-color(#ff9f19) + card-details-color(#ff9f19, #000) //Black text for better visibility .card-details-red card-details-color(#eb4646, #ffffff) //White text for better visibility @@ -165,7 +165,7 @@ card-details-color(background, color...) card-details-color(#0079bf, #ffffff) //White text for better visibility .card-details-pink - card-details-color(#ff78cb) + card-details-color(#ff78cb, #000) //Black text for better visibility .card-details-sky card-details-color(#00c2e0, #ffffff) //White text for better visibility @@ -174,19 +174,19 @@ card-details-color(background, color...) card-details-color(#4d4d4d, #ffffff) //White text for better visibility .card-details-lime - card-details-color(#51e898) + card-details-color(#51e898, #000) //Black text for better visibility .card-details-silver - card-details-color(#c0c0c0) + card-details-color(#c0c0c0, #000) //Black text for better visibility .card-details-peachpuff - card-details-color(#ffdab9) + card-details-color(#ffdab9, #000) //Black text for better visibility .card-details-crimson card-details-color(#dc143c, #ffffff) //White text for better visibility .card-details-plum - card-details-color(#dda0dd) + card-details-color(#dda0dd, #000) //Black text for better visibility .card-details-darkgreen card-details-color(#006400, #ffffff) //White text for better visibility @@ -198,7 +198,7 @@ card-details-color(background, color...) card-details-color(#ff00ff, #ffffff) //White text for better visibility .card-details-gold - card-details-color(#ffd700) + card-details-color(#ffd700, #000) //Black text for better visibility .card-details-navy card-details-color(#000080, #ffffff) //White text for better visibility @@ -210,10 +210,10 @@ card-details-color(background, color...) card-details-color(#8b4513, #ffffff) //White text for better visibility .card-details-paleturquoise - card-details-color(#afeeee) + card-details-color(#afeeee, #000) //Black text for better visibility .card-details-mistyrose - card-details-color(#ffe4e1) + card-details-color(#ffe4e1, #000) //Black text for better visibility .card-details-indigo card-details-color(#4b0082, #ffffff) //White text for better visibility From 8a48ff96efc29687c8a8c58d02d6741b50c83424 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 15:24:55 +0100 Subject: [PATCH 02/12] set card colors: properly set the title of the popups --- client/components/cards/cardDetails.jade | 5 +---- i18n/en.i18n.json | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index c1e771cb1..f6cbbba6e 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -234,7 +234,7 @@ template(name="cardDetailsActionsPopup") li: a.js-due-date {{_ 'editCardDueDatePopup-title'}} li: a.js-end-date {{_ 'editCardEndDatePopup-title'}} li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}} - li: a.js-set-card-color {{_ 'setCardColor-title'}} + li: a.js-set-card-color {{_ 'setCardColorPopup-title'}} hr ul.pop-over-list li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}} @@ -337,9 +337,6 @@ template(name="cardMorePopup") a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} template(name="setCardColorPopup") - p.quiet - span.clearfix - label {{_ "select-color"}} form.edit-label .palette-colors: each colors span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 7097af7da..930e88c5b 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -517,7 +517,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", - "setCardColor-title": "Set color", + "setCardColorPopup-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", From 5769d438a05d01bd5f35cd5830b7ad3c03a21ed2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 23:36:43 +0100 Subject: [PATCH 03/12] rules: set card color: use the color picker --- .../components/rules/actions/cardActions.jade | 37 ++++---------- .../components/rules/actions/cardActions.js | 50 ++++++++++++++++++- client/components/rules/rules.styl | 9 ++++ i18n/en.i18n.json | 1 + 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade index dd92d8fea..1fee5231a 100644 --- a/client/components/rules/actions/cardActions.jade +++ b/client/components/rules/actions/cardActions.jade @@ -39,32 +39,15 @@ template(name="cardActions") div.trigger-content div.trigger-text | {{{_'r-set-color'}}} - div.trigger-dropdown - select(id="color-action") - option(value="white") {{{_'color-white'}}} - option(value="green") {{{_'color-green'}}} - option(value="yellow") {{{_'color-yellow'}}} - option(value="orange") {{{_'color-orange'}}} - option(value="red") {{{_'color-red'}}} - option(value="purple") {{{_'color-purple'}}} - option(value="blue") {{{_'color-blue'}}} - option(value="sky") {{{_'color-sky'}}} - option(value="lime") {{{_'color-lime'}}} - option(value="pink") {{{_'color-pink'}}} - option(value="black") {{{_'color-black'}}} - option(value="silver") {{{_'color-silver'}}} - option(value="peachpuff") {{{_'color-peachpuff'}}} - option(value="crimson") {{{_'color-crimson'}}} - option(value="plum") {{{_'color-plum'}}} - option(value="darkgreen") {{{_'color-darkgreen'}}} - option(value="slateblue") {{{_'color-slateblue'}}} - option(value="magenta") {{{_'color-magenta'}}} - option(value="gold") {{{_'color-gold'}}} - option(value="navy") {{{_'color-navy'}}} - option(value="gray") {{{_'color-gray'}}} - option(value="saddlebrown") {{{_'color-saddlebrown'}}} - option(value="paleturquoise") {{{_'color-paleturquoise'}}} - option(value="mistyrose") {{{_'color-mistyrose'}}} - option(value="indigo") {{{_'color-indigo'}}} + button.trigger-button.trigger-button-color.card-details-green.js-show-color-palette(id="color-action") + | {{{_'color-green'}}} div.trigger-button.js-set-color-action.js-goto-rules i.fa.fa-plus + +template(name="setCardActionsColorPopup") + form.edit-label + .palette-colors: each colors + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") + if(isSelected color) + i.fa.fa-check + button.primary.confirm.js-submit {{_ 'save'}} diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js index b66556b4e..6c8583585 100644 --- a/client/components/rules/actions/cardActions.js +++ b/client/components/rules/actions/cardActions.js @@ -1,3 +1,8 @@ +let cardColors; +Meteor.startup(() => { + cardColors = Cards.simpleSchema()._schema.color.allowedValues; +}); + BlazeComponent.extendComponent({ onCreated() { this.subscribe('allRules'); @@ -112,10 +117,22 @@ BlazeComponent.extendComponent({ boardId, }); }, + 'click .js-show-color-palette'(event){ + const funct = Popup.open('setCardActionsColor'); + const colorButton = this.find('#color-action'); + if (colorButton.value === '') { + colorButton.value = 'green'; + } + funct.call(this, event); + }, 'click .js-set-color-action' (event) { const ruleName = this.data().ruleName.get(); const trigger = this.data().triggerVar.get(); - const selectedColor = this.find('#color-action').value; + const colorButton = this.find('#color-action'); + if (colorButton.value === '') { + colorButton.value = 'green'; + } + const selectedColor = colorButton.value; const boardId = Session.get('currentBoard'); const desc = Utils.getTriggerActionDesc(event, this); const triggerId = Triggers.insert(trigger); @@ -136,3 +153,34 @@ BlazeComponent.extendComponent({ }, }).register('cardActions'); + +BlazeComponent.extendComponent({ + onCreated() { + this.currentAction = this.currentData(); + this.colorButton = Popup.getOpenerComponent().find('#color-action'); + this.currentColor = new ReactiveVar(this.colorButton.value); + }, + + colors() { + return cardColors.map((color) => ({ color, name: '' })); + }, + + isSelected(color) { + return this.currentColor.get() === color; + }, + + events() { + return [{ + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + 'click .js-submit' () { + this.colorButton.classList.remove(`card-details-${ this.colorButton.value }`); + this.colorButton.value = this.currentColor.get(); + this.colorButton.innerText = `${TAPi18n.__(`color-${ this.currentColor.get() }`)}`; + this.colorButton.classList.add(`card-details-${ this.colorButton.value }`); + Popup.close(); + }, + }]; + }, +}).register('setCardActionsColorPopup'); diff --git a/client/components/rules/rules.styl b/client/components/rules/rules.styl index 27463d12b..05302f7ff 100644 --- a/client/components/rules/rules.styl +++ b/client/components/rules/rules.styl @@ -174,6 +174,15 @@ top:30px .trigger-button.trigger-button-person right:-40px + .trigger-button.trigger-button-color + top: unset + position: unset + transform: unset + font-size: 16px + width:auto + padding-left: 10px + padding-right: 10px + height:40px .trigger-item.trigger-item-mail height:300px diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 930e88c5b..6c5f22a5e 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -518,6 +518,7 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", From 6e9bad57723919dc3fd63a5748902e9049320603 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 23:35:12 +0100 Subject: [PATCH 04/12] IFTTT: card colors: add an actual white entry To unset the color through the IFTTT, we need a white entry. However, we do not want to show the white enry in the hamburger `Set Color` entry. We can also give the `white` capability to the API, it won't hurt and be more straightforward. --- client/components/cards/cardDetails.jade | 7 ++++--- client/components/cards/cardDetails.js | 3 +++ client/components/cards/cardDetails.styl | 4 ++++ models/cards.js | 13 ++++++++----- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index f6cbbba6e..25316d043 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -339,9 +339,10 @@ template(name="cardMorePopup") template(name="setCardColorPopup") form.edit-label .palette-colors: each colors - span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") - if(isSelected color) - i.fa.fa-check + unless $eq color 'white' + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") + if(isSelected color) + i.fa.fa-check button.primary.confirm.js-submit {{_ 'save'}} button.js-remove-color.negate.wide.right {{_ 'unset-color'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index cc04b8307..046200843 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -601,6 +601,9 @@ BlazeComponent.extendComponent({ }, isSelected(color) { + if (this.currentColor.get() === null) { + return color === 'white'; + } return this.currentColor.get() === color; }, diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index c18e1d2d7..bf50c0719 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -146,6 +146,10 @@ card-details-color(background, color...) if color color: color !important //overwrite text for better visibility +.card-details-white + card-details-color(unset, #000) //Black text for better visibility + border: 1px solid #eee + .card-details-green card-details-color(#3cb500, #ffffff) //White text for better visibility diff --git a/models/cards.js b/models/cards.js index c5d9bf052..9b32e89a1 100644 --- a/models/cards.js +++ b/models/cards.js @@ -69,7 +69,7 @@ Cards.attachSchema(new SimpleSchema({ type: String, optional: true, allowedValues: [ - 'green', 'yellow', 'orange', 'red', 'purple', + 'white', 'green', 'yellow', 'orange', 'red', 'purple', 'blue', 'sky', 'lime', 'pink', 'black', 'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', 'slateblue', 'magenta', 'gold', 'navy', 'gray', @@ -1571,13 +1571,16 @@ if (Meteor.isServer) { * * @description Edit a card * - * The color has to be chosen between `green`, `yellow`, `orange`, `red`, - * `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, - * `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, - * `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`: + * The color has to be chosen between `white`, `green`, `yellow`, `orange`, + * `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, + * `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, + * `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, + * `indigo`: * * Wekan card colors * + * Note: setting the color to white has the same effect than removing it. + * * @param {string} boardId the board ID of the card * @param {string} list the list ID of the card * @param {string} cardId the ID of the card From 5fa0821e078ff03647d23909517ddf6984f8baf5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 16:41:23 +0100 Subject: [PATCH 05/12] card colors: remove unused variables --- client/components/cards/cardDetails.js | 1 - client/components/cards/minicard.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 046200843..79a686a71 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -27,7 +27,6 @@ BlazeComponent.extendComponent({ onCreated() { this.currentBoard = Boards.findOne(Session.get('currentBoard')); this.isLoaded = new ReactiveVar(false); - this.currentColor = new ReactiveVar(this.data().color); const boardBody = this.parentComponent().parentComponent(); //in Miniview parent is Board, not BoardBody. if (boardBody !== null) { diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index e468ec567..da7f9e01b 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -3,10 +3,6 @@ // }); BlazeComponent.extendComponent({ - onCreated() { - this.currentColor = new ReactiveVar(this.data().color); - }, - template() { return 'minicard'; }, From dd88eb4cc191a06f7eb84213b026dfb93546f245 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 12:09:23 +0100 Subject: [PATCH 06/12] swimlane-view: have the swimlane header horizontal This allows to use the header as a separator between swimlanes. This will be most useful when we can set the background color of these headers. --- client/components/boards/boardBody.jade | 2 ++ client/components/lists/list.styl | 1 - client/components/swimlanes/swimlanes.jade | 36 ++++++++------------- client/components/swimlanes/swimlanes.js | 37 +++++++--------------- client/components/swimlanes/swimlanes.styl | 18 ++++------- 5 files changed, 33 insertions(+), 61 deletions(-) diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 9e4b9c61b..382c04f36 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -23,6 +23,8 @@ template(name="boardBody") if isViewSwimlanes each currentBoard.swimlanes +swimlane(this) + if currentUser.isBoardMember + +addSwimlaneForm if isViewLists +listsGroup if isViewCalendar diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl index 72cb19f42..ec8359612 100644 --- a/client/components/lists/list.styl +++ b/client/components/lists/list.styl @@ -10,7 +10,6 @@ // transparent, because that won't work during a list drag. background: darken(white, 13%) border-left: 1px solid darken(white, 20%) - border-bottom: 1px solid #CCC padding: 0 float: left diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index 76f54c660..4380de2b3 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -1,21 +1,22 @@ template(name="swimlane") .swimlane.js-lists.js-swimlane +swimlaneHeader - if isMiniScreen - if currentList - +list(currentList) + .swimlane.list-group.js-lists + if isMiniScreen + if currentList + +list(currentList) + else + each currentBoard.lists + +miniList(this) + if currentUser.isBoardMember + +addListForm else each currentBoard.lists - +miniList(this) + +list(this) + if currentCardIsInThisList _id ../_id + +cardDetails(currentCard) if currentUser.isBoardMember +addListForm - else - each currentBoard.lists - +list(this) - if currentCardIsInThisList _id ../_id - +cardDetails(currentCard) - if currentUser.isBoardMember - +addListAndSwimlaneForm template(name="listsGroup") .swimlane.list-group.js-lists @@ -35,19 +36,8 @@ template(name="listsGroup") if currentUser.isBoardMember +addListForm -template(name="addListAndSwimlaneForm") +template(name="addSwimlaneForm") .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'}} .list-header +inlinedForm(autoclose=false) input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index 865895a9c..a7743ec7f 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -185,37 +185,22 @@ BlazeComponent.extendComponent({ return [{ submit(evt) { evt.preventDefault(); - let titleInput = this.find('.list-name-input'); - if (titleInput) { - const title = titleInput.value.trim(); - if (title) { - Lists.insert({ - title, - boardId: Session.get('currentBoard'), - sort: $('.list').length, - }); + let titleInput = this.find('.swimlane-name-input'); + const title = titleInput.value.trim(); + if (title) { + Swimlanes.insert({ + title, + boardId: Session.get('currentBoard'), + sort: $('.swimlane').length, + }); - titleInput.value = ''; - titleInput.focus(); - } - } else { - titleInput = this.find('.swimlane-name-input'); - const title = titleInput.value.trim(); - if (title) { - Swimlanes.insert({ - title, - boardId: Session.get('currentBoard'), - sort: $('.swimlane').length, - }); - - titleInput.value = ''; - titleInput.focus(); - } + titleInput.value = ''; + titleInput.focus(); } }, }]; }, -}).register('addListAndSwimlaneForm'); +}).register('addSwimlaneForm'); Template.swimlane.helpers({ canSeeAddList() { diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl index abcc90d4e..fe7f5e539 100644 --- a/client/components/swimlanes/swimlanes.styl +++ b/client/components/swimlanes/swimlanes.styl @@ -5,7 +5,7 @@ // transparent, because that won't work during a swimlane drag. background: darken(white, 13%) display: flex - flex-direction: row + flex-direction: column overflow: 0; max-height: 100% @@ -27,20 +27,15 @@ .swimlane-header-wrap display: flex; flex-direction: row; - flex: 0 0 50px; - padding-bottom: 30px; - border-bottom: 1px solid #CCC + flex: 0 0 24px; + background-color: #ccc; .swimlane-header - -ms-writing-mode: tb-rl; - writing-mode: vertical-rl; - transform: rotate(180deg); font-size: 14px; - line-height: 50px; - margin-top: 50px; + padding: 5px 5px font-weight: bold; min-height: 9px; - width: 50px; + width: 100%; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; @@ -49,7 +44,8 @@ .swimlane-header-menu position: absolute - padding: 20px 20px + padding: 5px 5px .list-group + flex-direction: row height: 100% From 416b17062e57f215206e93a85b02ef9eb1ab4902 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 15:16:13 +0100 Subject: [PATCH 07/12] Remove the 'Add Swimlane' entry and replace it by a plus sign Still need to create the swimlane right after the one that has been created --- client/components/boards/boardBody.jade | 2 -- .../components/swimlanes/swimlaneHeader.jade | 9 ++++++ client/components/swimlanes/swimlaneHeader.js | 28 +++++++++++++++++++ client/components/swimlanes/swimlanes.jade | 14 ---------- client/components/swimlanes/swimlanes.js | 27 ------------------ client/components/swimlanes/swimlanes.styl | 4 +++ i18n/en.i18n.json | 1 + 7 files changed, 42 insertions(+), 43 deletions(-) diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 382c04f36..9e4b9c61b 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -23,8 +23,6 @@ template(name="boardBody") if isViewSwimlanes each currentBoard.swimlanes +swimlane(this) - if currentUser.isBoardMember - +addSwimlaneForm if isViewLists +listsGroup if isViewCalendar diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 483de06f4..3e20e2d00 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -8,6 +8,7 @@ template(name="swimlaneHeader") = title .swimlane-header-menu unless currentUser.isCommentOnly + a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon a.fa.fa-navicon.js-open-swimlane-menu template(name="editSwimlaneTitleForm") @@ -21,3 +22,11 @@ template(name="swimlaneActionPopup") unless currentUser.isCommentOnly ul.pop-over-list li: a.js-close-swimlane {{_ 'archive-swimlane'}} + +template(name="swimlaneAddPopup") + unless currentUser.isCommentOnly + form + input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" + autocomplete="off" autofocus) + .edit-controls.clearfix + button.primary.confirm(type="submit") {{_ 'add'}} diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 50635f861..72437ba42 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -11,6 +11,7 @@ BlazeComponent.extendComponent({ events() { return [{ 'click .js-open-swimlane-menu': Popup.open('swimlaneAction'), + 'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'), submit: this.editTitle, }]; }, @@ -23,3 +24,30 @@ Template.swimlaneActionPopup.events({ Popup.close(); }, }); + +BlazeComponent.extendComponent({ + events() { + return [{ + submit(evt) { + evt.preventDefault(); + const titleInput = this.find('.swimlane-name-input'); + const title = titleInput.value.trim(); + if (title) { + Swimlanes.insert({ + title, + boardId: Session.get('currentBoard'), + // XXX we should insert the swimlane right after the caller + sort: $('.swimlane').length, + }); + + titleInput.value = ''; + titleInput.focus(); + } + // XXX ideally, we should move the popup to the newly + // created swimlane so a user can add more than one swimlane + // with a minimum of interactions + Popup.close(); + }, + }]; + }, +}).register('swimlaneAddPopup'); diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index 4380de2b3..cd864a7c9 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -36,20 +36,6 @@ template(name="listsGroup") if currentUser.isBoardMember +addListForm -template(name="addSwimlaneForm") - .list.list-composer.js-list-composer - .list-header - +inlinedForm(autoclose=false) - input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" - 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-swimlane'}} - template(name="addListForm") .list.list-composer.js-list-composer .list-header diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index a7743ec7f..71317714e 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -175,33 +175,6 @@ BlazeComponent.extendComponent({ }, }).register('addListForm'); -BlazeComponent.extendComponent({ - // Proxy - open() { - this.childComponents('inlinedForm')[0].open(); - }, - - events() { - return [{ - submit(evt) { - evt.preventDefault(); - let titleInput = this.find('.swimlane-name-input'); - const title = titleInput.value.trim(); - if (title) { - Swimlanes.insert({ - title, - boardId: Session.get('currentBoard'), - sort: $('.swimlane').length, - }); - - titleInput.value = ''; - titleInput.focus(); - } - }, - }]; - }, -}).register('addSwimlaneForm'); - Template.swimlane.helpers({ canSeeAddList() { return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl index fe7f5e539..71089bb4f 100644 --- a/client/components/swimlanes/swimlanes.styl +++ b/client/components/swimlanes/swimlanes.styl @@ -46,6 +46,10 @@ position: absolute padding: 5px 5px + .swimlane-header-plus-icon + margin-left: 5px + margin-right: 10px + .list-group flex-direction: row height: 100% diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 6c5f22a5e..1890f4887 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -337,6 +337,7 @@ "list-select-cards": "Select all cards in this list", "listActionPopup-title": "List Actions", "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", "listMorePopup-title": "More", "link-list": "Link to this list", From c075187088e69d30db31489d75b22f991e1972ff Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 20:45:52 +0100 Subject: [PATCH 08/12] swimlane: insert the new swimlane after the one we clicked on --- client/components/swimlanes/swimlaneHeader.js | 13 +++++++++++-- models/boards.js | 11 +++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 72437ba42..632a0f508 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -1,3 +1,5 @@ +const { calculateIndexData } = Utils; + BlazeComponent.extendComponent({ editTitle(evt) { evt.preventDefault(); @@ -26,18 +28,25 @@ Template.swimlaneActionPopup.events({ }); BlazeComponent.extendComponent({ + onCreated() { + this.currentSwimlane = this.currentData(); + }, + events() { return [{ submit(evt) { evt.preventDefault(); + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const nextSwimlane = currentBoard.nextSwimlane(this.currentSwimlane); const titleInput = this.find('.swimlane-name-input'); const title = titleInput.value.trim(); + const sortValue = calculateIndexData(this.currentSwimlane, nextSwimlane, 1); + if (title) { Swimlanes.insert({ title, boardId: Session.get('currentBoard'), - // XXX we should insert the swimlane right after the caller - sort: $('.swimlane').length, + sort: sortValue.base, }); titleInput.value = ''; diff --git a/models/boards.js b/models/boards.js index 99480ca70..d92bec47a 100644 --- a/models/boards.js +++ b/models/boards.js @@ -351,6 +351,17 @@ Boards.helpers({ return Swimlanes.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, + nextSwimlane(swimlane) { + return Swimlanes.findOne({ + boardId: this._id, + archived: false, + sort: { $gte: swimlane.sort }, + _id: { $ne: swimlane._id }, + }, { + sort: { sort: 1 }, + }); + }, + hasOvertimeCards(){ const card = Cards.findOne({isOvertime: true, boardId: this._id, archived: false} ); return card !== undefined; From 03efeaeb1abae0c8c39ad5644d44bad36f415d99 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 24 Jan 2019 16:47:09 +0100 Subject: [PATCH 09/12] Add colors to swimlanes fixes #1688 --- .../components/swimlanes/swimlaneHeader.jade | 14 +++- client/components/swimlanes/swimlaneHeader.js | 37 +++++++++ client/components/swimlanes/swimlanes.styl | 81 +++++++++++++++++++ i18n/en.i18n.json | 1 + models/swimlanes.js | 32 ++++++++ 5 files changed, 164 insertions(+), 1 deletion(-) diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 3e20e2d00..33eb5731f 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -1,5 +1,5 @@ template(name="swimlaneHeader") - .swimlane-header-wrap.js-swimlane-header + .swimlane-header-wrap.js-swimlane-header(class='{{#if colorClass}}swimlane-{{colorClass}}{{/if}}') +inlinedForm +editSwimlaneTitleForm else @@ -20,6 +20,9 @@ template(name="editSwimlaneTitleForm") template(name="swimlaneActionPopup") unless currentUser.isCommentOnly + ul.pop-over-list + li: a.js-set-swimlane-color {{_ 'select-color'}} + hr ul.pop-over-list li: a.js-close-swimlane {{_ 'archive-swimlane'}} @@ -30,3 +33,12 @@ template(name="swimlaneAddPopup") autocomplete="off" autofocus) .edit-controls.clearfix button.primary.confirm(type="submit") {{_ 'add'}} + +template(name="setSwimlaneColorPopup") + form.edit-label + .palette-colors: each colors + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") + if(isSelected color) + i.fa.fa-check + button.primary.confirm.js-submit {{_ 'save'}} + button.js-remove-color.negate.wide.right {{_ 'unset-color'}} diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 632a0f508..1004cb254 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -1,5 +1,10 @@ const { calculateIndexData } = Utils; +let swimlaneColors; +Meteor.startup(() => { + swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues; +}); + BlazeComponent.extendComponent({ editTitle(evt) { evt.preventDefault(); @@ -20,6 +25,7 @@ BlazeComponent.extendComponent({ }).register('swimlaneHeader'); Template.swimlaneActionPopup.events({ + 'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'), 'click .js-close-swimlane' (evt) { evt.preventDefault(); this.archive(); @@ -60,3 +66,34 @@ BlazeComponent.extendComponent({ }]; }, }).register('swimlaneAddPopup'); + +BlazeComponent.extendComponent({ + onCreated() { + this.currentSwimlane = this.currentData(); + this.currentColor = new ReactiveVar(this.currentSwimlane.color); + }, + + colors() { + return swimlaneColors.map((color) => ({ color, name: '' })); + }, + + isSelected(color) { + return this.currentColor.get() === color; + }, + + events() { + return [{ + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + 'click .js-submit' () { + this.currentSwimlane.setColor(this.currentColor.get()); + Popup.close(); + }, + 'click .js-remove-color'() { + this.currentSwimlane.setColor(null); + Popup.close(); + }, + }]; + }, +}).register('setSwimlaneColorPopup'); diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl index 71089bb4f..e4e2cd3b9 100644 --- a/client/components/swimlanes/swimlanes.styl +++ b/client/components/swimlanes/swimlanes.styl @@ -53,3 +53,84 @@ .list-group flex-direction: row height: 100% + +swimlane-color(background, color...) + background: background !important + if color + color: color !important //overwrite text for better visibility + +.swimlane-white + swimlane-color(#ffffff, #4d4d4d) //Black text for better visibility + border: 1px solid #eee + +.swimlane-green + swimlane-color(#3cb500, #ffffff) //White text for better visibility + +.swimlane-yellow + swimlane-color(#fad900, #4d4d4d) //Black text for better visibility + +.swimlane-orange + swimlane-color(#ff9f19, #4d4d4d) //Black text for better visibility + +.swimlane-red + swimlane-color(#eb4646, #ffffff) //White text for better visibility + +.swimlane-purple + swimlane-color(#a632db, #ffffff) //White text for better visibility + +.swimlane-blue + swimlane-color(#0079bf, #ffffff) //White text for better visibility + +.swimlane-pink + swimlane-color(#ff78cb, #4d4d4d) //Black text for better visibility + +.swimlane-sky + swimlane-color(#00c2e0, #ffffff) //White text for better visibility + +.swimlane-black + swimlane-color(#4d4d4d, #ffffff) //White text for better visibility + +.swimlane-lime + swimlane-color(#51e898, #4d4d4d) //Black text for better visibility + +.swimlane-silver + swimlane-color(unset, #4d4d4d) //Black text for better visibility + +.swimlane-peachpuff + swimlane-color(#ffdab9, #4d4d4d) //Black text for better visibility + +.swimlane-crimson + swimlane-color(#dc143c, #ffffff) //White text for better visibility + +.swimlane-plum + swimlane-color(#dda0dd, #4d4d4d) //Black text for better visibility + +.swimlane-darkgreen + swimlane-color(#006400, #ffffff) //White text for better visibility + +.swimlane-slateblue + swimlane-color(#6a5acd, #ffffff) //White text for better visibility + +.swimlane-magenta + swimlane-color(#ff00ff, #ffffff) //White text for better visibility + +.swimlane-gold + swimlane-color(#ffd700, #4d4d4d) //Black text for better visibility + +.swimlane-navy + swimlane-color(#000080, #ffffff) //White text for better visibility + +.swimlane-gray + swimlane-color(#808080, #ffffff) //White text for better visibility + +.swimlane-saddlebrown + swimlane-color(#8b4513, #ffffff) //White text for better visibility + +.swimlane-paleturquoise + swimlane-color(#afeeee, #4d4d4d) //Black text for better visibility + +.swimlane-mistyrose + swimlane-color(#ffe4e1, #4d4d4d) //Black text for better visibility + +.swimlane-indigo + swimlane-color(#4b0082, #ffffff) //White text for better visibility diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 1890f4887..409946bbb 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -520,6 +520,7 @@ "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", diff --git a/models/swimlanes.js b/models/swimlanes.js index fa5245da0..93057362e 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -49,6 +49,21 @@ Swimlanes.attachSchema(new SimpleSchema({ // XXX We should probably provide a default optional: true, }, + color: { + /** + * the color of the swimlane + */ + type: String, + optional: true, + // silver is the default, so it is left out + allowedValues: [ + 'white', 'green', 'yellow', 'orange', 'red', 'purple', + 'blue', 'sky', 'lime', 'pink', 'black', + 'peachpuff', 'crimson', 'plum', 'darkgreen', + 'slateblue', 'magenta', 'gold', 'navy', 'gray', + 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo', + ], + }, updatedAt: { /** * when was the swimlane last edited @@ -93,6 +108,12 @@ Swimlanes.helpers({ board() { return Boards.findOne(this.boardId); }, + + colorClass() { + if (this.color) + return this.color; + return ''; + }, }); Swimlanes.mutations({ @@ -107,6 +128,17 @@ Swimlanes.mutations({ restore() { return { $set: { archived: false } }; }, + + setColor(newColor) { + if (newColor === 'silver') { + newColor = null; + } + return { + $set: { + color: newColor, + }, + }; + }, }); Swimlanes.hookOptions.after.update = { fetchPrevious: false }; From 5c6a725712a443b4d03b4f86262033ddfb66bc3d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 25 Jan 2019 10:47:36 +0100 Subject: [PATCH 10/12] Make sure Swimlanes and Lists have a populated sort field When moving around the swimlanes or the lists, if one element has a sort with a null value, the computation of the new sort value is aborted, meaning that there are glitches in the UI. This happens on the first swimlane created with the new board, or when a swimlane or a list gets added through the API. --- client/components/boards/boardBody.js | 31 +++++++++++++++++++++++++++ models/boards.js | 16 ++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index ccbd0f23b..ae5b67fd4 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -35,6 +35,37 @@ BlazeComponent.extendComponent({ this._isDragging = false; // Used to set the overlay this.mouseHasEnterCardDetails = false; + + // fix swimlanes sort field if there are null values + const currentBoardData = Boards.findOne(Session.get('currentBoard')); + const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); + if (nullSortSwimlanes.count() > 0) { + const swimlanes = currentBoardData.swimlanes(); + let count = 0; + swimlanes.forEach((s) => { + Swimlanes.update(s._id, { + $set: { + sort: count, + }, + }); + count += 1; + }); + } + + // fix lists sort field if there are null values + const nullSortLists = currentBoardData.nullSortLists(); + if (nullSortLists.count() > 0) { + const lists = currentBoardData.lists(); + let count = 0; + lists.forEach((l) => { + Lists.update(l._id, { + $set: { + sort: count, + }, + }); + count += 1; + }); + } }, onRendered() { const boardComponent = this; diff --git a/models/boards.js b/models/boards.js index d92bec47a..b0f5cecba 100644 --- a/models/boards.js +++ b/models/boards.js @@ -347,6 +347,14 @@ Boards.helpers({ return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, + nullSortLists() { + return Lists.find({ + boardId: this._id, + archived: false, + sort: { $eq: null }, + }); + }, + swimlanes() { return Swimlanes.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, @@ -362,6 +370,14 @@ Boards.helpers({ }); }, + nullSortSwimlanes() { + return Swimlanes.find({ + boardId: this._id, + archived: false, + sort: { $eq: null }, + }); + }, + hasOvertimeCards(){ const card = Cards.findOne({isOvertime: true, boardId: this._id, archived: false} ); return card !== undefined; From b5411841cf6aa33b2c0d29d85cbc795e3faa7f4f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 25 Jan 2019 10:57:46 +0100 Subject: [PATCH 11/12] api: fix the sort field when inserting a swimlane or a list This has the side effect of always inserting the element at the end. --- models/lists.js | 2 ++ models/swimlanes.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/models/lists.js b/models/lists.js index 0e1ba8013..39ff130a1 100644 --- a/models/lists.js +++ b/models/lists.js @@ -314,9 +314,11 @@ if (Meteor.isServer) { try { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; + const board = Boards.findOne(paramBoardId); const id = Lists.insert({ title: req.body.title, boardId: paramBoardId, + sort: board.lists().count(), }); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/swimlanes.js b/models/swimlanes.js index 93057362e..e2c3925c4 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -256,9 +256,11 @@ if (Meteor.isServer) { try { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; + const board = Boards.findOne(paramBoardId); const id = Swimlanes.insert({ title: req.body.title, boardId: paramBoardId, + sort: board.swimlanes().count(), }); JsonRoutes.sendResult(res, { code: 200, From 6c3dbc3c6f52a42ddbeeaec9bbfcc82c1c839f7d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 25 Jan 2019 12:44:27 +0100 Subject: [PATCH 12/12] api: new_card: add the card at the end of the list If we keep the `0` value, the card might be inserted in the middle of the list, making it hard to find it later on. Always append the card at the end of the list by setting a sort value based on the number of cards in the list. --- models/cards.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/cards.js b/models/cards.js index 9b32e89a1..ff19a9a0e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1526,6 +1526,10 @@ if (Meteor.isServer) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; + const currentCards = Cards.find({ + listId: paramListId, + archived: false, + }, { sort: ['sort'] }); const check = Users.findOne({ _id: req.body.authorId, }); @@ -1538,7 +1542,7 @@ if (Meteor.isServer) { description: req.body.description, userId: req.body.authorId, swimlaneId: req.body.swimlaneId, - sort: 0, + sort: currentCards.count(), members, }); JsonRoutes.sendResult(res, {