diff --git a/module/kanban/config.php b/module/kanban/config.php
index 344fb4e96f..2e69265696 100644
--- a/module/kanban/config.php
+++ b/module/kanban/config.php
@@ -4,6 +4,7 @@ $config->kanban = new stdclass();
$config->kanban->require = new stdclass();
$config->kanban->require->createregion = 'name';
+$config->kanban->require->createlane = 'name';
$config->kanban->setwip = new stdclass();
$config->kanban->setlane = new stdclass();
@@ -83,4 +84,5 @@ $config->kanban->taskColumnStatusList['pause'] = 'pause';
$config->kanban->taskColumnStatusList['canceled'] = 'cancel';
$config->kanban->taskColumnStatusList['closed'] = 'closed';
-$config->kanban->laneColorList = array('#7ec5ff', '#333', '#2b529c', '#e48600', '#d2323d', '#229f24', '#777', '#d2691e', '#008b8b', '#2e8b57', '#4169e1', '#4b0082', '#fa8072', '#ba55d3', '#2e8b57', '#6b8e23');
+$config->kanban->laneColorList = array('#7ec5ff', '#333', '#2b529c', '#e48600', '#d2323d', '#229f24', '#777', '#d2691e', '#008b8b', '#2e8b57', '#4169e1', '#4b0082', '#fa8072', '#ba55d3', '#2e8b57', '#6b8e23');
+$config->kanban->columnColorList = array('#333', '#2b519c', '#e48610', '#d2313d', '#2a9f23', '#777', '#d2691e', '#2e8b8b', '#2f8b58', '#4168e0', '#4b0082', '#f58072', '#ba55d3', '#6a8e22');
diff --git a/module/kanban/control.php b/module/kanban/control.php
index c7f01045b0..371d0c0234 100644
--- a/module/kanban/control.php
+++ b/module/kanban/control.php
@@ -194,6 +194,29 @@ class kanban extends control
$this->display();
}
+ /**
+ * Create a lane for a kanban.
+ *
+ * @param int $kanbanID
+ * @param int $regionID
+ * @access public
+ * @return void
+ */
+ public function createLane($kanbanID, $regionID)
+ {
+ if(!empty($_POST))
+ {
+ $laneID = $this->kanban->createLane($kanbanID, $regionID, $lane = null);
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => 'closeModalAndUpdateKanban', 'callback_params' => $regionID));
+ }
+
+ $this->view->lanes = $this->kanban->getLanePairsByRegion($regionID);
+ $this->display();
+ }
+
+
/**
* Set WIP.
*
diff --git a/module/kanban/css/common.css b/module/kanban/css/common.css
new file mode 100644
index 0000000000..7088d7d0f5
--- /dev/null
+++ b/module/kanban/css/common.css
@@ -0,0 +1,4 @@
+.color-picker-item {width: 20px; height: 20px; margin-right: 10px; border-radius: 50% 50%; float: left; cursor: pointer; text-align: center; line-height: 20px;}
+.color-picker-item > .icon {color: #fff; display: none}
+.checked > .icon {display: inline-block;}
+
diff --git a/module/kanban/js/common.js b/module/kanban/js/common.js
index 8418f1e508..5912d2d2f3 100644
--- a/module/kanban/js/common.js
+++ b/module/kanban/js/common.js
@@ -33,3 +33,34 @@ function setMailto(field, value)
$('#team').chosen();
})
}
+
+/**
+ * Initialize custom color selector.
+ *
+ * @access public
+ * @return void
+ */
+function initColorPicker()
+{
+ var colorList = ['#272E33', '#3DC6FD', '#2B529C', '#E48600', '#D2323D', '#229F24', '#777777', '#D2691E', '#008B8B', '#2E8B57', '#4169E1', '#4B0082', '#FA8072', '#BA55D3', '#6B8E23'];
+
+ var selectedColor = $('input[name=color]').val();
+ if(selectedColor && $.inArray(selectedColor.toUpperCase(), colorList) == -1) colorList.unshift(selectedColor);
+ colorList.forEach(function(color, index)
+ {
+ var itemClass = color.toUpperCase() == $('input[name=color]').val().toUpperCase() ? 'color-picker-item checked' : 'color-picker-item';
+ var colorItem = "
";
+ colorItem += "";
+ colorItem += "
";
+ $('#color-picker').append(colorItem);
+ });
+
+ $('.color-picker-item').click(function()
+ {
+ var color = $(this).attr('data-color');
+ $('input[name=color]').val(color);
+ $(this).addClass('checked');
+ $(this).siblings().removeClass('checked');
+ })
+}
+
diff --git a/module/kanban/js/createlane.js b/module/kanban/js/createlane.js
new file mode 100644
index 0000000000..a2fe47e34c
--- /dev/null
+++ b/module/kanban/js/createlane.js
@@ -0,0 +1,10 @@
+$(document).ready(function()
+{
+ initColorPicker();
+
+ $('input[name=mode]').change(function()
+ {
+ $('#otherLane').parents('tr').toggle($(this).val() == 'sameAsOther');
+ });
+})
+
diff --git a/module/kanban/js/view.js b/module/kanban/js/view.js
index da4cb1c40e..de75293401 100644
--- a/module/kanban/js/view.js
+++ b/module/kanban/js/view.js
@@ -624,15 +624,15 @@ function createColumnMenu(options)
if(!privs.length) return [];
var items = [];
- if(privs.includes('setWIP')) items.push({label: kanbanLang.WIP, icon: '', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), attrs: {'data-toggle': 'modal'}});
- if(privs.includes('editColumn')) items.push({label: kanbanLang.editColumn, icon: '', url: createLink('kanban', 'editColumn', 'columnID=' + column.id), attrs: {'data-toggle': 'modal'}});
+ if(privs.includes('setWIP')) items.push({label: kanbanLang.WIP, icon: '', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}});
+ if(privs.includes('editColumn')) items.push({label: kanbanLang.editColumn, icon: '', url: createLink('kanban', 'setColumn', 'columnID=' + column.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('createColumn'))
{
- items.push({label: kanbanLang.createColumnOnLeft, icon: '', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=left'), attrs: {'data-toggle': 'modal'}});
- items.push({label: kanbanLang.createColumnOnRight, icon: '', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=right'), attrs: {'data-toggle': 'modal'}});
+ items.push({label: kanbanLang.createColumnOnLeft, icon: '', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=left'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
+ items.push({label: kanbanLang.createColumnOnRight, icon: '', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=right'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
}
- if(privs.includes('copyColumn')) items.push({label: kanbanLang.copyColumn, icon: '', url: createLink('kanban', 'copyColumn', 'columnID=' + column.id), attrs: {'data-toggle': 'modal'}});
- if(privs.includes('splitColumn')) items.push({label: kanbanLang.splitColumn, icon: '', url: createLink('kanban', 'splitColumn', 'columnID=' + column.id), attrs: {'data-toggle': 'modal'}});
+ if(privs.includes('copyColumn')) items.push({label: kanbanLang.copyColumn, icon: '', url: createLink('kanban', 'copyColumn', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}});
+ if(privs.includes('splitColumn')) items.push({label: kanbanLang.splitColumn, icon: '', url: createLink('kanban', 'splitColumn', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('archiveColumn')) items.push({label: kanbanLang.archiveColumn, icon: '', url: createLink('kanban', 'archiveColumn', 'columnID=' + column.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbancolumnLang.confirmArchive, 'data-confirmDetail': kanbancolumnLang.confirmArchiveDetail, 'data-confirmButton': lang.archive, 'data-confirming': lang.archiving}});
if(privs.includes('deleteColumn')) items.push({label: kanbanLang.deleteColumn, icon: '', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbancolumnLang.confirmDelete, 'data-confirmDetail': kanbancolumnLang.confirmDeleteDetail}});
diff --git a/module/kanban/lang/en.php b/module/kanban/lang/en.php
index ed1cdfdf78..0190d209c4 100644
--- a/module/kanban/lang/en.php
+++ b/module/kanban/lang/en.php
@@ -100,6 +100,9 @@ $lang->kanban->laneGroup = 'Lane Group';
$lang->kanban->cardsSort = 'Cards Sortting';
$lang->kanban->moreAction = 'More Action';
$lang->kanban->noGroup = 'None';
+$lang->kanban->limitExceeded = 'Limit Exceeded';
+$lang->kanban->fullScreen = 'Full Screen';
+$lang->kanban->setting = 'Setting';
$lang->kanban->error = new stdclass();
$lang->kanban->error->mustBeInt = 'The WIPs must be positive integer.';
@@ -177,9 +180,22 @@ $lang->kanbancolumn->name = $lang->kanban->columnName;
$lang->kanbancolumn->limit = $lang->kanban->WIPCount;
$lang->kanbanlane = new stdclass();
+$lang->kanbanlane->name = $lang->kanban->laneName;
+$lang->kanbanlane->common = 'Lane';
+$lang->kanbanlane->default = 'Default Lane';
+$lang->kanbanlane->column = 'Lane Kanban column';
+$lang->kanbanlane->otherlane = 'Select the lane that shares the Kanban column';
+$lang->kanbanlane->color = 'Lane color';
+
+$lang->kanbanlane->modeList['sameAsOther'] = 'Use the same Kanban column as other lanes';
+$lang->kanbanlane->modeList['independent'] = 'Independent Kanban column is adopted';
+
$lang->kanbanlane->name = $lang->kanban->laneName;
+$lang->kanbanlane->common = 'Lane';
$lang->kanbanlane->default = 'Default Lane';
$lang->kanbanregion = new stdclass();
$lang->kanbanregion->name = 'Kanban Region';
$lang->kanbanregion->default = 'Default Region';
+
+$lang->kanbancard = new stdclass();
\ No newline at end of file
diff --git a/module/kanban/lang/zh-cn.php b/module/kanban/lang/zh-cn.php
index adc416eeb3..a0119217e1 100644
--- a/module/kanban/lang/zh-cn.php
+++ b/module/kanban/lang/zh-cn.php
@@ -180,9 +180,15 @@ $lang->kanbancolumn->name = $lang->kanban->columnName;
$lang->kanbancolumn->limit = $lang->kanban->WIPCount;
$lang->kanbanlane = new stdclass();
-$lang->kanbanlane->name = $lang->kanban->laneName;
-$lang->kanbanlane->common = '泳道';
-$lang->kanbanlane->default = '默认泳道';
+$lang->kanbanlane->name = $lang->kanban->laneName;
+$lang->kanbanlane->common = '泳道';
+$lang->kanbanlane->default = '默认泳道';
+$lang->kanbanlane->column = '泳道看板列';
+$lang->kanbanlane->otherlane = '选择共享看板列的泳道';
+$lang->kanbanlane->color = '泳道颜色';
+
+$lang->kanbanlane->modeList['sameAsOther'] = '与其他泳道使用相同看板列';
+$lang->kanbanlane->modeList['independent'] = '采用独立的看板列';
$lang->kanbanregion = new stdclass();
$lang->kanbanregion->name = '看板区域';
diff --git a/module/kanban/model.php b/module/kanban/model.php
index dfc2d786b3..5fe61ff674 100644
--- a/module/kanban/model.php
+++ b/module/kanban/model.php
@@ -152,6 +152,7 @@ class kanbanModel extends model
$lane->region = $regionID;
$lane->type = 'common';
$lane->lastEditedTime = helper::now();
+ $lane->color = '#7ec5ff';
$this->dao->insert(TABLE_KANBANLANE)->data($lane)->exec();
$laneID = $this->dao->lastInsertId();
@@ -180,6 +181,7 @@ class kanbanModel extends model
$column->name = $columnName;
$column->type = $index;
$column->limit = -1;
+ $column->color = '#333';
$this->dao->insert(TABLE_KANBANCOLUMN)->data($column)->exec();
@@ -688,6 +690,72 @@ class kanbanModel extends model
}
/**
+ * Get lane pairs by region id.
+ *
+ * @param array $regionID
+ * @access public
+ * @return array
+ */
+ public function getLanePairsByRegion($regionID)
+ {
+ return $this->dao->select('id, name')->from(TABLE_KANBANLANE)
+ ->where('deleted')->eq('0')
+ ->andWhere('region')->eq($regionID)
+ ->fetchPairs();
+ }
+
+ /**
+ * Create a lane.
+ *
+ * @param int $kanbanID
+ * @param int $regionID
+ * @param object $lane
+ * @access public
+ * @return int
+ */
+ public function createLane($kanbanID, $regionID, $lane = null)
+ {
+ if(empty($lane))
+ {
+ $maxOrder = $this->dao->select('MAX(`order`) AS maxOrder')->from(TABLE_KANBANLANE)
+ ->where('region')->eq($regionID)
+ ->fetch('maxOrder');
+ $lane = fixer::input('post')
+ ->add('region', $regionID)
+ ->add('order', $maxOrder + 1)
+ ->add('lastEditedTime', helper::today())
+ ->add('type', 'common')
+ ->setDefault('color', '#3DC6FD')
+ ->get();
+
+ $mode = zget($lane, 'mode', '');
+ if($mode == 'sameAsOther')
+ {
+ $otherLane = zget($lane, 'otherLane', 0);
+ if($otherLane) $lane->group = $this->dao->select('`group`')->from(TABLE_KANBANLANE)->where('id')->eq($otherLane)->fetch('group');
+ }
+ elseif($mode == 'independent')
+ {
+ $groupID = $this->createGroup($kanbanID, $regionID);
+ $this->createDefaultColumns($kanban, $regionID, $groupID);
+
+ $lane->group = $groupID;
+ }
+ }
+
+ $this->dao->insert(TABLE_KANBANLANE)->data($lane, $skip = 'mode,otherLane')
+ ->batchCheck($this->config->kanban->require->createlane, 'notempty')
+ ->autoCheck()
+ ->checkIF(!empty($lane->name), 'name', 'unique', "`type`='common'")
+ ->exec();
+ if(dao::isError()) return false;
+
+ $laneID = $this->dao->lastInsertID();
+ $this->loadModel('action')->create('kanbanLane', $laneID, 'Created');
+
+ return $laneID;
+ }
+
* Create a kanban.
*
* @access public
diff --git a/module/kanban/view/createlane.html.php b/module/kanban/view/createlane.html.php
new file mode 100644
index 0000000000..3a5206b9de
--- /dev/null
+++ b/module/kanban/view/createlane.html.php
@@ -0,0 +1,46 @@
+
+
+
+
+
+ kanban->createLane;?>
+
+
+
+
+
+