Browse Source

Merge pull request #2126 from bentiss/color-swimlanes

Color for swimlanes
Lauri Ojansivu 6 years ago
parent
commit
ba15526516

+ 31 - 0
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;

+ 5 - 7
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,14 +337,12 @@ 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}}")
-        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'}}
 

+ 3 - 1
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) {
@@ -601,6 +600,9 @@ BlazeComponent.extendComponent({
   },
 
   isSelected(color) {
+    if (this.currentColor.get() === null) {
+      return color === 'white';
+    }
     return this.currentColor.get() === color;
   },
 

+ 15 - 11
client/components/cards/cardDetails.styl

@@ -144,16 +144,20 @@ 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-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
 
 .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 +169,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 +178,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 +202,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 +214,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

+ 0 - 4
client/components/cards/minicard.js

@@ -3,10 +3,6 @@
 // });
 
 BlazeComponent.extendComponent({
-  onCreated() {
-    this.currentColor = new ReactiveVar(this.data().color);
-  },
-
   template() {
     return 'minicard';
   },

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

+ 10 - 27
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'}}

+ 49 - 1
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');

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

+ 22 - 1
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
@@ -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")
@@ -19,5 +20,25 @@ 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'}}
+
+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'}}
+
+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'}}

+ 74 - 0
client/components/swimlanes/swimlaneHeader.js

@@ -1,3 +1,10 @@
+const { calculateIndexData } = Utils;
+
+let swimlaneColors;
+Meteor.startup(() => {
+  swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
+});
+
 BlazeComponent.extendComponent({
   editTitle(evt) {
     evt.preventDefault();
@@ -11,15 +18,82 @@ BlazeComponent.extendComponent({
   events() {
     return [{
       'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
+      'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
       submit: this.editTitle,
     }];
   },
 }).register('swimlaneHeader');
 
 Template.swimlaneActionPopup.events({
+  'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
   'click .js-close-swimlane' (evt) {
     evt.preventDefault();
     this.archive();
     Popup.close();
   },
 });
+
+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'),
+            sort: sortValue.base,
+          });
+
+          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');
+
+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');

+ 12 - 36
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,31 +36,6 @@ template(name="listsGroup")
       if currentUser.isBoardMember
         +addListForm
 
-template(name="addListAndSwimlaneForm")
-  .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'}}"
-          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

+ 0 - 42
client/components/swimlanes/swimlanes.js

@@ -175,48 +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('.list-name-input');
-        if (titleInput) {
-          const title = titleInput.value.trim();
-          if (title) {
-            Lists.insert({
-              title,
-              boardId: Session.get('currentBoard'),
-              sort: $('.list').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();
-          }
-        }
-      },
-    }];
-  },
-}).register('addListAndSwimlaneForm');
-
 Template.swimlane.helpers({
   canSeeAddList() {
     return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();

+ 92 - 11
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,93 @@
 
     .swimlane-header-menu
       position: absolute
-      padding: 20px 20px
+      padding: 5px 5px
+
+    .swimlane-header-plus-icon
+      margin-left: 5px
+      margin-right: 10px
 
 .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

+ 4 - 1
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",
@@ -517,7 +518,9 @@
     "card-end-on": "Ends on",
     "editCardReceivedDatePopup-title": "Change received date",
     "editCardEndDatePopup-title": "Change end date",
-    "setCardColor-title": "Set color",
+    "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.",

+ 27 - 0
models/boards.js

@@ -347,10 +347,37 @@ 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 } });
   },
 
+  nextSwimlane(swimlane) {
+    return Swimlanes.findOne({
+      boardId: this._id,
+      archived: false,
+      sort: { $gte: swimlane.sort },
+      _id: { $ne: swimlane._id },
+    }, {
+      sort: { sort: 1 },
+    });
+  },
+
+  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;

+ 13 - 6
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',
@@ -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, {
@@ -1571,13 +1575,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`:
    *
    * <img src="/card-colors.png" width="40%" alt="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

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

+ 34 - 0
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 };
@@ -224,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,