diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -145,7 +145,7 @@ 'rsrc/css/phui/phui-text.css' => '23e9b4b7', 'rsrc/css/phui/phui-timeline-view.css' => 'bbd990d0', 'rsrc/css/phui/phui-workboard-view.css' => '2bf82d00', - 'rsrc/css/phui/phui-workpanel-view.css' => 'e26044fa', + 'rsrc/css/phui/phui-workpanel-view.css' => '6e53b4ac', 'rsrc/css/sprite-apps-large.css' => '20ec0cc0', 'rsrc/css/sprite-apps.css' => 'd5baed0f', 'rsrc/css/sprite-conpherence.css' => '3b4a0487', @@ -415,7 +415,7 @@ 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => 'fe9a552f', 'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b', 'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d', - 'rsrc/js/application/projects/behavior-project-boards.js' => 'e4b6c65a', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'a6c6a058', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -639,7 +639,7 @@ 'javelin-behavior-policy-control' => 'f3fef818', 'javelin-behavior-policy-rule-editor' => 'fe9a552f', 'javelin-behavior-ponder-votebox' => '4e9b766b', - 'javelin-behavior-project-boards' => 'e4b6c65a', + 'javelin-behavior-project-boards' => 'a6c6a058', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', @@ -795,7 +795,7 @@ 'phui-text-css' => '23e9b4b7', 'phui-timeline-view-css' => 'bbd990d0', 'phui-workboard-view-css' => '2bf82d00', - 'phui-workpanel-view-css' => 'e26044fa', + 'phui-workpanel-view-css' => '6e53b4ac', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '6e8cefa4', 'phuix-dropdown-menu' => 'bd4c8dca', @@ -1475,6 +1475,14 @@ 'a5d7cf86' => array( 'javelin-dom', ), + 'a6c6a058' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1774,14 +1782,6 @@ 'javelin-dom', 'javelin-uri', ), - 'e4b6c65a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), 'e566f52c' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1930,12 +1930,12 @@ 'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php', 'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php', 'PhabricatorProjectBoardDeleteController' => 'applications/project/controller/PhabricatorProjectBoardDeleteController.php', - 'PhabricatorProjectBoardEditController' => 'applications/project/controller/PhabricatorProjectBoardEditController.php', 'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', + 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', 'PhabricatorProjectColumnPHIDType' => 'applications/project/phid/PhabricatorProjectColumnPHIDType.php', 'PhabricatorProjectColumnPosition' => 'applications/project/storage/PhabricatorProjectColumnPosition.php', 'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php', @@ -4771,7 +4771,6 @@ 'PhabricatorProjectArchiveController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardDeleteController' => 'PhabricatorProjectBoardController', - 'PhabricatorProjectBoardEditController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', @@ -4781,6 +4780,7 @@ 'PhabricatorDestructibleInterface', ), 'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController', + 'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumnPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectColumnPosition' => array( 'PhabricatorProjectDAO', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -66,7 +66,7 @@ 'move/(?P[1-9]\d*)/' => 'PhabricatorProjectMoveController', 'board/(?P[1-9]\d*)/' => array( 'edit/(?:(?P\d+)/)?' - => 'PhabricatorProjectBoardEditController', + => 'PhabricatorProjectColumnEditController', 'delete/(?:(?P\d+)/)?' => 'PhabricatorProjectBoardDeleteController', 'column/(?:(?P\d+)/)?' diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -225,7 +225,8 @@ $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) - ->setHeaderColor($column->getHeaderColor()); + ->setHeaderColor($column->getHeaderColor()) + ->addSigil('workpanel'); $column_menu = $this->buildColumnMenu($project, $column); $panel->addHeaderAction($column_menu); @@ -252,6 +253,7 @@ 'columnPHID' => $column->getPHID(), 'countTagID' => $tag_id, 'countTagContentID' => $tag_content_id, + 'pointLimit' => $column->getPointLimit(), )); foreach ($column_tasks as $task) { @@ -268,11 +270,6 @@ ->getItem()); } $panel->setCards($cards); - - if (!$column_tasks) { - $cards->addClass('project-column-empty'); - } - $board->addPanel($panel); } diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -160,6 +160,12 @@ pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + + $limit = $column->getPointLimit(); + $properties->addProperty( + pht('Point Limit'), + $limit ? $limit : pht('No Limit')); + return $properties; } diff --git a/src/applications/project/controller/PhabricatorProjectBoardEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php rename from src/applications/project/controller/PhabricatorProjectBoardEditController.php rename to src/applications/project/controller/PhabricatorProjectColumnEditController.php --- a/src/applications/project/controller/PhabricatorProjectBoardEditController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php @@ -1,6 +1,6 @@ getPointLimit(); + $v_name = $column->getName(); + $validation_exception = null; $base_uri = '/board/'.$this->projectID.'/'; if ($is_new) { @@ -60,7 +65,8 @@ } if ($request->isFormPost()) { - $new_name = $request->getStr('name'); + $v_name = $request->getStr('name'); + $v_limit = $request->getStr('limit'); if ($is_new) { $column->setProjectPHID($project->getPHID()); @@ -79,10 +85,17 @@ $column->setSequence($new_sequence); } + $xactions = array(); + $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME; - $xactions = array(id(new PhabricatorProjectColumnTransaction()) + $xactions[] = id(new PhabricatorProjectColumnTransaction()) ->setTransactionType($type_name) - ->setNewValue($new_name)); + ->setNewValue($v_name); + + $type_limit = PhabricatorProjectColumnTransaction::TYPE_LIMIT; + $xactions[] = id(new PhabricatorProjectColumnTransaction()) + ->setTransactionType($type_limit) + ->setNewValue($v_limit); try { $editor = id(new PhabricatorProjectColumnTransactionEditor()) @@ -93,20 +106,31 @@ return id(new AphrontRedirectResponse())->setURI($view_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $e_name = $ex->getShortMessage($type_name); + $e_limit = $ex->getShortMessage($type_limit); $validation_exception = $ex; } } $form = new AphrontFormView(); - $form->setUser($request->getUser()) + $form + ->setUser($request->getUser()) ->appendChild( id(new AphrontFormTextControl()) - ->setValue($column->getName()) + ->setValue($v_name) ->setLabel(pht('Name')) ->setName('name') ->setError($e_name) ->setCaption( - pht('This will be displayed as the header of the column.'))); + pht('This will be displayed as the header of the column.'))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setValue($v_limit) + ->setLabel(pht('Point Limit')) + ->setName('limit') + ->setError($e_limit) + ->setCaption( + pht('Maximum number of points of tasks allowed in the column.'))); + if ($is_new) { $title = pht('Create Column'); diff --git a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php --- a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php @@ -16,6 +16,7 @@ $types[] = PhabricatorProjectColumnTransaction::TYPE_NAME; $types[] = PhabricatorProjectColumnTransaction::TYPE_STATUS; + $types[] = PhabricatorProjectColumnTransaction::TYPE_LIMIT; return $types; } @@ -29,6 +30,9 @@ return $object->getName(); case PhabricatorProjectColumnTransaction::TYPE_STATUS: return $object->getStatus(); + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + return $object->getPointLimit(); + } return parent::getCustomTransactionOldValue($object, $xaction); @@ -42,6 +46,11 @@ case PhabricatorProjectColumnTransaction::TYPE_NAME: case PhabricatorProjectColumnTransaction::TYPE_STATUS: return $xaction->getNewValue(); + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + if ($xaction->getNewValue()) { + return (int)$xaction->getNewValue(); + } + return null; } return parent::getCustomTransactionNewValue($object, $xaction); @@ -58,6 +67,9 @@ case PhabricatorProjectColumnTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); return; + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + $object->setPointLimit($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -70,6 +82,7 @@ switch ($xaction->getTransactionType()) { case PhabricatorProjectColumnTransaction::TYPE_NAME: case PhabricatorProjectColumnTransaction::TYPE_STATUS: + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: return; } @@ -84,6 +97,18 @@ $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + if (strlen($value) && !preg_match('/^\d+\z/', $value)) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Column point limit must be empty, or a positive integer.'), + $xaction); + } + } + break; case PhabricatorProjectColumnTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -73,7 +73,7 @@ public function getHeaderColor() { if ($this->isHidden()) { - return PHUIActionHeaderView::HEADER_LIGHTRED; + return PHUIActionHeaderView::HEADER_LIGHTBLUE; } if ($this->isDefaultColumn()) { @@ -92,6 +92,15 @@ return $this; } + public function getPointLimit() { + return $this->getProperty('pointLimit'); + } + + public function setPointLimit($limit) { + $this->setProperty('pointLimit', $limit); + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php --- a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php @@ -5,6 +5,7 @@ const TYPE_NAME = 'project:col:name'; const TYPE_STATUS = 'project:col:status'; + const TYPE_LIMIT = 'project:col:limit'; public function getApplicationName() { return 'project'; @@ -43,6 +44,24 @@ $author_handle); } } + case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + if (!$old) { + return pht( + '%s set the point limit for this column to %s.', + $author_handle, + $new); + } else if (!$new) { + return pht( + '%s removed the point limit for this column.', + $author_handle); + } else { + return pht( + '%s changed point limit for this column from %s to %s.', + $author_handle, + $old, + $new); + } + case PhabricatorProjectColumnTransaction::TYPE_STATUS: switch ($new) { case PhabricatorProjectColumn::STATUS_ACTIVE: diff --git a/webroot/rsrc/css/phui/phui-workpanel-view.css b/webroot/rsrc/css/phui/phui-workpanel-view.css --- a/webroot/rsrc/css/phui/phui-workpanel-view.css +++ b/webroot/rsrc/css/phui/phui-workpanel-view.css @@ -93,25 +93,32 @@ width: auto; } -.project-column-empty { +.project-panel-empty .phui-object-item-list-view { background: rgba(255,255,255,.4); border-radius: 3px; margin-bottom: 4px; border: 1px dashed #fff; } -.project-column-empty .drag-ghost { +.project-panel-empty .phui-object-item-list-view .drag-ghost { display: none; } -.project-column-empty.drag-target-list { +.project-panel-empty .phui-object-item-list-view.drag-target-list { background: rgba(255,255,255,.7); } -.phui-workpanel-view .phui-workpanel-lightred .phui-action-header { - border-top: 1px solid {$redborder}; - border-left: 1px solid {$redborder}; - border-right: 1px solid {$redborder}; +.project-panel-over-limit .phui-workpanel-body { + background: {$lightredbackground}; + border-width: 0 1px 1px; + border-style: solid; + border-color: {$redborder}; +} + +.phui-workpanel-view .phui-workpanel-lightblue .phui-action-header { + border-top: 1px solid {$blueborder}; + border-left: 1px solid {$blueborder}; + border-right: 1px solid {$blueborder}; } /* - Workpanel Cards ----------------------------------------------------------- diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -18,9 +18,6 @@ var data = JX.Stratcom.getData(col); var cards = finditems(col); - // Add the "empty" CSS class if the column has nothing in it. - JX.DOM.alterClass(col, 'project-column-empty', !cards.length); - // Update the count of tasks in the column header. if (!data.countTagNode) { data.countTagNode = JX.$(data.countTagID); @@ -33,17 +30,34 @@ sum += 1; } - JX.DOM.setContent(JX.$(data.countTagContentID), sum); - // TODO: This is a little bit hacky, but we don't have a PHUIX version of // this element yet. + var over_limit = (data.pointLimit && (sum > data.pointLimit)); + + var display_value = sum; + if (data.pointLimit) { + display_value = sum + ' / ' + data.pointLimit; + } + JX.DOM.setContent(JX.$(data.countTagContentID), display_value); + + + var panel_map = { + 'project-panel-empty': !cards.length, + 'project-panel-over-limit': over_limit + }; + var panel = JX.DOM.findAbove(col, 'div', 'workpanel'); + for (var k in panel_map) { + JX.DOM.alterClass(panel, k, !!panel_map[k]); + } + var color_map = { 'phui-tag-shade-disabled': (sum === 0), - 'phui-tag-shade-blue': (sum > 0) + 'phui-tag-shade-blue': (sum > 0 && !over_limit), + 'phui-tag-shade-red': (over_limit) }; for (var k in color_map) { - JX.DOM.alterClass(data.countTagNode, k, color_map[k]); + JX.DOM.alterClass(data.countTagNode, k, !!color_map[k]); } }