From 018de5dec742b6a38d02710670c1dd30101f5b9f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 23 Mar 2013 14:38:01 -0700 Subject: [PATCH] Use ObjectItemListView for Maniphest Summary: This isn't quite complete, but everything else is technical cleanup. Broadly: - Removed checkboxes. Selected state is now indicated with CSS, and toggled with shift-click. When nothing is selected, the text reads "Shift-Click Tasks to Select" to let users discover this feature. - Updated drag-to-reorder code to work with ObjectItemListView. - Closed/resolved is now shown with a grey footer icon. - Assigned is now shown with a user profile image handle icon, with a hover state. This could probably use some more tweaks, but overall I think it looks pretty reasonable? Test Plan: {F35897} Reviewers: chad Reviewed By: chad CC: aran Differential Revision: https://secure.phabricator.com/D5340 --- src/__celerity_resource_map__.php | 111 +++++----- .../ManiphestSubpriorityController.php | 26 ++- .../ManiphestTaskListController.php | 44 ++-- .../maniphest/view/ManiphestTaskListView.php | 73 ++++++- .../view/ManiphestTaskSummaryView.php | 196 ------------------ .../PhabricatorProjectProfileController.php | 25 +-- .../layout/PhabricatorObjectItemListView.php | 9 + src/view/layout/PhabricatorObjectItemView.php | 109 ++++++---- .../application/maniphest/task-summary.css | 116 +---------- .../phabricator-object-item-list-view.css | 4 +- .../maniphest/behavior-batch-selector.js | 134 ++++++------ .../maniphest/behavior-subpriorityeditor.js | 43 ++-- 12 files changed, 359 insertions(+), 531 deletions(-) delete mode 100644 src/applications/maniphest/view/ManiphestTaskSummaryView.php diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 5a0c1c88e2..b5cd31bcf7 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1638,13 +1638,14 @@ celerity_register_resource_map(array( ), 'javelin-behavior-maniphest-batch-selector' => array( - 'uri' => '/res/398cf8d7/rsrc/js/application/maniphest/behavior-batch-selector.js', + 'uri' => '/res/f8cf3b84/rsrc/js/application/maniphest/behavior-batch-selector.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', + 3 => 'javelin-util', ), 'disk' => '/rsrc/js/application/maniphest/behavior-batch-selector.js', ), @@ -1663,7 +1664,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-maniphest-subpriority-editor' => array( - 'uri' => '/res/5e02f19a/rsrc/js/application/maniphest/behavior-subpriorityeditor.js', + 'uri' => '/res/21b73c2a/rsrc/js/application/maniphest/behavior-subpriorityeditor.js', 'type' => 'js', 'requires' => array( @@ -2632,7 +2633,7 @@ celerity_register_resource_map(array( ), 'maniphest-task-summary-css' => array( - 'uri' => '/res/b3930263/rsrc/css/application/maniphest/task-summary.css', + 'uri' => '/res/7aa9e2eb/rsrc/css/application/maniphest/task-summary.css', 'type' => 'css', 'requires' => array( @@ -2995,7 +2996,7 @@ celerity_register_resource_map(array( ), 'phabricator-object-item-list-view-css' => array( - 'uri' => '/res/aa09c531/rsrc/css/layout/phabricator-object-item-list-view.css', + 'uri' => '/res/7d35d590/rsrc/css/layout/phabricator-object-item-list-view.css', 'type' => 'css', 'requires' => array( @@ -3693,7 +3694,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - '6c294512' => + 'a56848af' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -3735,7 +3736,7 @@ celerity_register_resource_map(array( 34 => 'phabricator-object-item-list-view-css', 35 => 'global-drag-and-drop-css', ), - 'uri' => '/res/pkg/6c294512/core.pkg.css', + 'uri' => '/res/pkg/a56848af/core.pkg.css', 'type' => 'css', ), '95ceba95' => @@ -3895,7 +3896,7 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/fe22443b/javelin.pkg.js', 'type' => 'js', ), - 'c41b4907' => + '6b1fccc6' => array( 'name' => 'maniphest.pkg.css', 'symbols' => @@ -3905,10 +3906,10 @@ celerity_register_resource_map(array( 2 => 'aphront-attached-file-view-css', 3 => 'phabricator-project-tag-css', ), - 'uri' => '/res/pkg/c41b4907/maniphest.pkg.css', + 'uri' => '/res/pkg/6b1fccc6/maniphest.pkg.css', 'type' => 'css', ), - '7707de41' => + 'f85eb6d8' => array( 'name' => 'maniphest.pkg.js', 'symbols' => @@ -3919,23 +3920,23 @@ celerity_register_resource_map(array( 3 => 'javelin-behavior-maniphest-transaction-expand', 4 => 'javelin-behavior-maniphest-subpriority-editor', ), - 'uri' => '/res/pkg/7707de41/maniphest.pkg.js', + 'uri' => '/res/pkg/f85eb6d8/maniphest.pkg.js', 'type' => 'js', ), ), 'reverse' => array( - 'aphront-attached-file-view-css' => 'c41b4907', - 'aphront-dialog-view-css' => '6c294512', - 'aphront-error-view-css' => '6c294512', - 'aphront-form-view-css' => '6c294512', - 'aphront-list-filter-view-css' => '6c294512', - 'aphront-pager-view-css' => '6c294512', - 'aphront-panel-view-css' => '6c294512', - 'aphront-table-view-css' => '6c294512', - 'aphront-tokenizer-control-css' => '6c294512', - 'aphront-tooltip-css' => '6c294512', - 'aphront-typeahead-control-css' => '6c294512', + 'aphront-attached-file-view-css' => '6b1fccc6', + 'aphront-dialog-view-css' => 'a56848af', + 'aphront-error-view-css' => 'a56848af', + 'aphront-form-view-css' => 'a56848af', + 'aphront-list-filter-view-css' => 'a56848af', + 'aphront-pager-view-css' => 'a56848af', + 'aphront-panel-view-css' => 'a56848af', + 'aphront-table-view-css' => 'a56848af', + 'aphront-tokenizer-control-css' => 'a56848af', + 'aphront-tooltip-css' => 'a56848af', + 'aphront-typeahead-control-css' => 'a56848af', 'differential-changeset-view-css' => '8aaacd1b', 'differential-core-view-css' => '8aaacd1b', 'differential-inline-comment-editor' => '322728f3', @@ -3949,7 +3950,7 @@ celerity_register_resource_map(array( 'differential-table-of-contents-css' => '8aaacd1b', 'diffusion-commit-view-css' => 'c8ce2d88', 'diffusion-icons-css' => 'c8ce2d88', - 'global-drag-and-drop-css' => '6c294512', + 'global-drag-and-drop-css' => 'a56848af', 'inline-comment-summary-css' => '8aaacd1b', 'javelin-aphlict' => '95ceba95', 'javelin-behavior' => 'fe22443b', @@ -3982,11 +3983,11 @@ celerity_register_resource_map(array( 'javelin-behavior-konami' => '95ceba95', 'javelin-behavior-lightbox-attachments' => '95ceba95', 'javelin-behavior-load-blame' => '322728f3', - 'javelin-behavior-maniphest-batch-selector' => '7707de41', - 'javelin-behavior-maniphest-subpriority-editor' => '7707de41', - 'javelin-behavior-maniphest-transaction-controls' => '7707de41', - 'javelin-behavior-maniphest-transaction-expand' => '7707de41', - 'javelin-behavior-maniphest-transaction-preview' => '7707de41', + 'javelin-behavior-maniphest-batch-selector' => 'f85eb6d8', + 'javelin-behavior-maniphest-subpriority-editor' => 'f85eb6d8', + 'javelin-behavior-maniphest-transaction-controls' => 'f85eb6d8', + 'javelin-behavior-maniphest-transaction-expand' => 'f85eb6d8', + 'javelin-behavior-maniphest-transaction-preview' => 'f85eb6d8', 'javelin-behavior-phabricator-active-nav' => '95ceba95', 'javelin-behavior-phabricator-autofocus' => '95ceba95', 'javelin-behavior-phabricator-gesture' => '95ceba95', @@ -4021,48 +4022,48 @@ celerity_register_resource_map(array( 'javelin-util' => 'fe22443b', 'javelin-vector' => 'fe22443b', 'javelin-workflow' => 'fe22443b', - 'lightbox-attachment-css' => '6c294512', - 'maniphest-task-summary-css' => 'c41b4907', - 'maniphest-transaction-detail-css' => 'c41b4907', + 'lightbox-attachment-css' => 'a56848af', + 'maniphest-task-summary-css' => '6b1fccc6', + 'maniphest-transaction-detail-css' => '6b1fccc6', 'phabricator-busy' => '95ceba95', 'phabricator-content-source-view-css' => '8aaacd1b', - 'phabricator-core-buttons-css' => '6c294512', - 'phabricator-core-css' => '6c294512', - 'phabricator-crumbs-view-css' => '6c294512', - 'phabricator-directory-css' => '6c294512', + 'phabricator-core-buttons-css' => 'a56848af', + 'phabricator-core-css' => 'a56848af', + 'phabricator-crumbs-view-css' => 'a56848af', + 'phabricator-directory-css' => 'a56848af', 'phabricator-drag-and-drop-file-upload' => '322728f3', 'phabricator-dropdown-menu' => '95ceba95', 'phabricator-file-upload' => '95ceba95', - 'phabricator-filetree-view-css' => '6c294512', - 'phabricator-flag-css' => '6c294512', - 'phabricator-form-view-css' => '6c294512', - 'phabricator-header-view-css' => '6c294512', - 'phabricator-jump-nav' => '6c294512', + 'phabricator-filetree-view-css' => 'a56848af', + 'phabricator-flag-css' => 'a56848af', + 'phabricator-form-view-css' => 'a56848af', + 'phabricator-header-view-css' => 'a56848af', + 'phabricator-jump-nav' => 'a56848af', 'phabricator-keyboard-shortcut' => '95ceba95', 'phabricator-keyboard-shortcut-manager' => '95ceba95', - 'phabricator-main-menu-view' => '6c294512', + 'phabricator-main-menu-view' => 'a56848af', 'phabricator-menu-item' => '95ceba95', - 'phabricator-nav-view-css' => '6c294512', + 'phabricator-nav-view-css' => 'a56848af', 'phabricator-notification' => '95ceba95', - 'phabricator-notification-css' => '6c294512', - 'phabricator-notification-menu-css' => '6c294512', - 'phabricator-object-item-list-view-css' => '6c294512', + 'phabricator-notification-css' => 'a56848af', + 'phabricator-notification-menu-css' => 'a56848af', + 'phabricator-object-item-list-view-css' => 'a56848af', 'phabricator-object-selector-css' => '8aaacd1b', 'phabricator-paste-file-upload' => '95ceba95', 'phabricator-prefab' => '95ceba95', - 'phabricator-project-tag-css' => 'c41b4907', - 'phabricator-remarkup-css' => '6c294512', + 'phabricator-project-tag-css' => '6b1fccc6', + 'phabricator-remarkup-css' => 'a56848af', 'phabricator-shaped-request' => '322728f3', - 'phabricator-side-menu-view-css' => '6c294512', - 'phabricator-standard-page-view' => '6c294512', + 'phabricator-side-menu-view-css' => 'a56848af', + 'phabricator-standard-page-view' => 'a56848af', 'phabricator-textareautils' => '95ceba95', 'phabricator-tooltip' => '95ceba95', - 'phabricator-transaction-view-css' => '6c294512', - 'phabricator-zindex-css' => '6c294512', - 'sprite-apps-large-css' => '6c294512', - 'sprite-gradient-css' => '6c294512', - 'sprite-icon-css' => '6c294512', - 'sprite-menu-css' => '6c294512', - 'syntax-highlighting-css' => '6c294512', + 'phabricator-transaction-view-css' => 'a56848af', + 'phabricator-zindex-css' => 'a56848af', + 'sprite-apps-large-css' => 'a56848af', + 'sprite-gradient-css' => 'a56848af', + 'sprite-icon-css' => 'a56848af', + 'sprite-menu-css' => 'a56848af', + 'syntax-highlighting-css' => 'a56848af', ), )); diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index 07fa1e174d..4cbeb661c6 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -7,6 +7,7 @@ final class ManiphestSubpriorityController extends ManiphestController { public function processRequest() { $request = $this->getRequest(); + $user = $request->getUser(); if (!$request->validateCSRF()) { return new Aphront403Response(); @@ -50,15 +51,26 @@ final class ManiphestSubpriorityController extends ManiphestController { $task->setSubpriority($new_sub); $task->save(); - $pri_class = ManiphestTaskSummaryView::getPriorityClass( - $task->getPriority()); - $class = 'maniphest-task-handle maniphest-active-handle '.$pri_class; + $phids = $task->getProjectPHIDs(); + if ($task->getOwnerPHID()) { + $phids[] = $task->getOwnerPHID(); + } - $response = array( - 'className' => $class, - ); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->setViewer($user) + ->loadHandles(); - return id(new AphrontAjaxResponse())->setContent($response); + $view = id(new ManiphestTaskListView()) + ->setUser($user) + ->setShowSubpriorityControls(true) + ->setShowBatchControls(true) + ->setHandles($handles) + ->setTasks(array($task)); + + return id(new AphrontAjaxResponse())->setContent( + array( + 'tasks' => $view, + )); } } diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php index abceea780f..7fbb2bf339 100644 --- a/src/applications/maniphest/controller/ManiphestTaskListController.php +++ b/src/applications/maniphest/controller/ManiphestTaskListController.php @@ -378,23 +378,12 @@ final class ManiphestTaskListController extends ManiphestController { $selector->appendChild($lists); $selector->appendChild($this->renderBatchEditor($query)); - $form_id = celerity_generate_unique_node_id(); - $selector = phabricator_form( - $user, - array( - 'method' => 'POST', - 'action' => '/maniphest/batch/', - 'id' => $form_id, - ), - $selector->render()); - $list_container->appendChild($selector); $list_container->appendChild($pager); Javelin::initBehavior( 'maniphest-subpriority-editor', array( - 'root' => $form_id, 'uri' => '/maniphest/subpriority/', )); } @@ -644,6 +633,8 @@ final class ManiphestTaskListController extends ManiphestController { } private function renderBatchEditor(PhabricatorSearchQuery $search_query) { + $user = $this->getRequest()->getUser(); + Javelin::initBehavior( 'maniphest-batch-selector', array( @@ -651,6 +642,8 @@ final class ManiphestTaskListController extends ManiphestController { 'selectNone' => 'batch-select-none', 'submit' => 'batch-select-submit', 'status' => 'batch-select-status-cell', + 'idContainer' => 'batch-select-id-container', + 'formID' => 'batch-select-form', )); $select_all = javelin_tag( @@ -690,7 +683,14 @@ final class ManiphestTaskListController extends ManiphestController { ), pht('Export to Excel')); - return hsprintf( + $hidden = phutil_tag( + 'div', + array( + 'id' => 'batch-select-id-container', + ), + ''); + + $editor = hsprintf( '
'. '
%s
'. ''. @@ -698,16 +698,28 @@ final class ManiphestTaskListController extends ManiphestController { ''. ''. ''. - ''. + ''. ''. '
%s%s%s%s%s%s%s
'. - '', + '
', pht('Batch Task Editor'), $select_all, $select_none, $export, - pht('0 Selected'), - $submit); + '', + $submit, + $hidden); + + $editor = phabricator_form( + $user, + array( + 'method' => 'POST', + 'action' => '/maniphest/batch/', + 'id' => 'batch-select-form', + ), + $editor); + + return $editor; } private function buildQueryFromRequest() { diff --git a/src/applications/maniphest/view/ManiphestTaskListView.php b/src/applications/maniphest/view/ManiphestTaskListView.php index 556d460753..3862913d40 100644 --- a/src/applications/maniphest/view/ManiphestTaskListView.php +++ b/src/applications/maniphest/view/ManiphestTaskListView.php @@ -33,19 +33,74 @@ final class ManiphestTaskListView extends ManiphestView { } public function render() { + $handles = $this->handles; + + $list = new PhabricatorObjectItemListView(); + $list->setCards(true); + $list->setFlush(true); + + $status_map = ManiphestTaskStatus::getTaskStatusMap(); + $color_map = array( + ManiphestTaskPriority::PRIORITY_UNBREAK_NOW => 'magenta', + ManiphestTaskPriority::PRIORITY_TRIAGE => 'violet', + ManiphestTaskPriority::PRIORITY_HIGH => 'red', + ManiphestTaskPriority::PRIORITY_NORMAL => 'orange', + ManiphestTaskPriority::PRIORITY_LOW => 'yellow', + ManiphestTaskPriority::PRIORITY_WISH => 'sky', + ); - $views = array(); foreach ($this->tasks as $task) { - $view = new ManiphestTaskSummaryView(); - $view->setTask($task); - $view->setShowBatchControls($this->showBatchControls); - $view->setShowSubpriorityControls($this->showSubpriorityControls); - $view->setUser($this->user); - $view->setHandles($this->handles); - $views[] = $view->render(); + $item = new PhabricatorObjectItemView(); + $item->setObjectName('T'.$task->getID()); + $item->setHeader($task->getTitle()); + $item->setHref('/T'.$task->getID()); + + if ($task->getOwnerPHID()) { + $owner = $handles[$task->getOwnerPHID()]; + $item->addHandleIcon( + $owner, + pht('Assigned: %s', $owner->getName())); + } + + $status = $task->getStatus(); + if ($status != ManiphestTaskStatus::STATUS_OPEN) { + $item->addFootIcon( + ($status == ManiphestTaskStatus::STATUS_CLOSED_RESOLVED) + ? 'enable-white' + : 'delete-white', + idx($status_map, $status, 'Unknown')); + } + + $item->setBarColor(idx($color_map, $task->getPriority(), 'grey')); + + $item->addIcon( + 'none', + phabricator_datetime($task->getDateModified(), $this->getUser())); + + if ($this->showSubpriorityControls) { + $item->setGrippable(true); + $item->addSigil('maniphest-task'); + } + + if ($task->getProjectPHIDs()) { + $projects_view = new ManiphestTaskProjectsView(); + $projects_view->setHandles( + array_select_keys( + $handles, + $task->getProjectPHIDs())); + + $item->addAttribute($projects_view); + } + + $item->setMetadata( + array( + 'taskID' => $task->getID(), + )); + + $list->addItem($item); } - return $views; + return $list; } } diff --git a/src/applications/maniphest/view/ManiphestTaskSummaryView.php b/src/applications/maniphest/view/ManiphestTaskSummaryView.php deleted file mode 100644 index bf0de2a01f..0000000000 --- a/src/applications/maniphest/view/ManiphestTaskSummaryView.php +++ /dev/null @@ -1,196 +0,0 @@ -task = $task; - return $this; - } - - public function setHandles(array $handles) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $this->handles = $handles; - return $this; - } - - public function setShowBatchControls($show_batch_controls) { - $this->showBatchControls = $show_batch_controls; - return $this; - } - - public function setShowSubpriorityControls($show_subpriority_controls) { - $this->showSubpriorityControls = $show_subpriority_controls; - return $this; - } - - public static function getPriorityClass($priority) { - $classes = array( - ManiphestTaskPriority::PRIORITY_UNBREAK_NOW => 'pri-unbreak', - ManiphestTaskPriority::PRIORITY_TRIAGE => 'pri-triage', - ManiphestTaskPriority::PRIORITY_HIGH => 'pri-high', - ManiphestTaskPriority::PRIORITY_NORMAL => 'pri-normal', - ManiphestTaskPriority::PRIORITY_LOW => 'pri-low', - ManiphestTaskPriority::PRIORITY_WISH => 'pri-wish', - ); - - return idx($classes, $priority); - } - - public function render() { - - if (!$this->user) { - throw new Exception("Call setUser() before rendering!"); - } - - $task = $this->task; - $handles = $this->handles; - - require_celerity_resource('maniphest-task-summary-css'); - - $pri_class = self::getPriorityClass($task->getPriority()); - $status_map = ManiphestTaskStatus::getTaskStatusMap(); - - $batch = null; - if ($this->showBatchControls) { - $batch = phutil_tag( - 'td', - array( - 'rowspan' => 2, - 'class' => 'maniphest-task-batch', - ), - javelin_tag( - 'input', - array( - 'type' => 'checkbox', - 'name' => 'batch[]', - 'value' => $task->getID(), - 'sigil' => 'maniphest-batch', - ))); - } - - $projects_view = new ManiphestTaskProjectsView(); - $projects_view->setHandles( - array_select_keys( - $this->handles, - $task->getProjectPHIDs())); - - $control_class = null; - $control_sigil = null; - if ($this->showSubpriorityControls) { - $control_class = 'maniphest-active-handle'; - $control_sigil = 'maniphest-task-handle'; - } - - $handle = javelin_tag( - 'td', - array( - 'rowspan' => 2, - 'class' => 'maniphest-task-handle '.$pri_class.' '.$control_class, - 'sigil' => $control_sigil, - ), - ''); - - $task_name = phutil_tag( - 'span', - array( - 'class' => 'maniphest-task-name', - ), - phutil_tag( - 'a', - array( - 'href' => '/T'.$task->getID(), - ), - $task->getTitle())); - - $task_updated = phutil_tag( - 'span', - array( - 'class' => 'maniphest-task-updated', - ), - phabricator_date($task->getDateModified(), $this->user)); - - $task_info = phutil_tag( - 'td', - array( - 'colspan' => 2, - 'class' => 'maniphest-task-number', - ), - array( - 'T'.$task->getID(), - $task_name, - $task_updated, - )); - - $owner = ''; - if ($task->getOwnerPHID()) { - $owner = pht('Assigned to %s', - $handles[$task->getOwnerPHID()]->renderLink()); - } - - $task_owner = phutil_tag( - 'span', - array( - 'class' => 'maniphest-task-owner', - ), - $task->getOwnerPHID() - ? $owner - : phutil_tag('em', array(), pht('None'))); - - $task_status = phutil_tag( - 'td', - array( - 'class' => 'maniphest-task-status', - ), - array( - idx($status_map, $task->getStatus(), pht('Unknown')), - $task_owner, - )); - - $task_projects = phutil_tag( - 'td', - array( - 'class' => 'maniphest-task-projects', - ), - $projects_view->render()); - - $row1 = phutil_tag( - 'tr', - array(), - array( - $handle, - $batch, - $task_info, - )); - - $row2 = phutil_tag( - 'tr', - array(), - array( - $task_status, - $task_projects, - )); - - return javelin_tag( - 'table', - array( - 'class' => 'maniphest-task-summary', - 'sigil' => 'maniphest-task', - 'meta' => array( - 'taskID' => $task->getID(), - ), - ), - array( - $row1, - $row2, - )); - } - -} diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index a4b22e6fdf..4c85607be2 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -240,6 +240,8 @@ final class PhabricatorProjectProfileController PhabricatorProject $project, PhabricatorProjectProfile $profile) { + $user = $this->getRequest()->getUser(); + $query = id(new ManiphestTaskQuery()) ->withAnyProjects(array($project->getPHID())) ->withStatus(ManiphestTaskQuery::STATUS_OPEN) @@ -250,23 +252,16 @@ final class PhabricatorProjectProfileController $count = $query->getRowCount(); $phids = mpull($tasks, 'getOwnerPHID'); + $phids = array_merge( + $phids, + array_mergev(mpull($tasks, 'getProjectPHIDs'))); $phids = array_filter($phids); $handles = $this->loadViewerHandles($phids); - $task_views = array(); - foreach ($tasks as $task) { - $view = id(new ManiphestTaskSummaryView()) - ->setTask($task) - ->setHandles($handles) - ->setUser($this->getRequest()->getUser()); - $task_views[] = $view->render(); - } - - if (empty($tasks)) { - $task_views = phutil_tag('em', array(), pht('No open tasks.')); - } else { - $task_views = phutil_implode_html('', $task_views); - } + $task_list = new ManiphestTaskListView(); + $task_list->setUser($user); + $task_list->setTasks($tasks); + $task_list->setHandles($handles); $open = number_format($count); @@ -286,7 +281,7 @@ final class PhabricatorProjectProfileController ' ', pht('Open Tasks (%s)', $open), - $task_views, + $task_list, $more_link); return $content; diff --git a/src/view/layout/PhabricatorObjectItemListView.php b/src/view/layout/PhabricatorObjectItemListView.php index a88d46af2c..74b0dc233f 100644 --- a/src/view/layout/PhabricatorObjectItemListView.php +++ b/src/view/layout/PhabricatorObjectItemListView.php @@ -8,6 +8,12 @@ final class PhabricatorObjectItemListView extends AphrontView { private $stackable; private $cards; private $noDataString; + private $flush; + + public function setFlush($flush) { + $this->flush = $flush; + return $this; + } public function setHeader($header) { $this->header = $header; @@ -74,6 +80,9 @@ final class PhabricatorObjectItemListView extends AphrontView { if ($this->cards) { $classes[] = 'phabricator-object-list-cards'; } + if ($this->flush) { + $classes[] = 'phabricator-object-list-flush'; + } return phutil_tag( 'ul', diff --git a/src/view/layout/PhabricatorObjectItemView.php b/src/view/layout/PhabricatorObjectItemView.php index 6423b10867..df5dfa1a70 100644 --- a/src/view/layout/PhabricatorObjectItemView.php +++ b/src/view/layout/PhabricatorObjectItemView.php @@ -1,6 +1,6 @@ icons) { + $item_classes[] = 'phabricator-object-item-with-icons'; + } + + if ($this->attributes) { + $item_classes[] = 'phabricator-object-item-with-attrs'; + } + + if ($this->handleIcons) { + $item_classes[] = 'phabricator-object-item-with-handle-icons'; + } + + if ($this->barColor) { + $item_classes[] = 'phabricator-object-item-bar-color-'.$this->barColor; + } + + if ($this->footIcons) { + $item_classes[] = 'phabricator-object-item-with-foot-icons'; + } + + switch ($this->effect) { + case 'highlighted': + $item_classes[] = 'phabricator-object-item-highlighted'; + break; + case 'selected': + $item_classes[] = 'phabricator-object-item-selected'; + break; + case null: + break; + default: + throw new Exception(pht("Invalid effect!")); + } + + if ($this->getGrippable()) { + $item_classes[] = 'phabricator-object-item-grippable'; + } + + return array( + 'class' => $item_classes, + ); + } + + public function getTagContent() { + $content_classes = array(); $content_classes[] = 'phabricator-object-item-content'; $header_name = null; @@ -131,10 +180,11 @@ final class PhabricatorObjectItemView extends AphrontView { ), $this->header); - $header = phutil_tag( + $header = javelin_tag( 'div', array( 'class' => 'phabricator-object-item-name', + 'sigil' => 'slippery', ), array( $header_name, @@ -162,7 +212,6 @@ final class PhabricatorObjectItemView extends AphrontView { ), $spec['label']); - if ($spec['href']) { $icon_href = phutil_tag( 'a', @@ -172,10 +221,16 @@ final class PhabricatorObjectItemView extends AphrontView { $icon_href = array($label, $icon); } + $classes = array(); + $classes[] = 'phabricator-object-item-icon'; + if ($spec['icon'] == 'none') { + $classes[] = 'phabricator-object-item-icon-none'; + } + $icon_list[] = phutil_tag( 'li', array( - 'class' => 'phabricator-object-item-icon', + 'class' => implode(' ', $classes), ), $icon_href); } @@ -186,7 +241,6 @@ final class PhabricatorObjectItemView extends AphrontView { 'class' => 'phabricator-object-item-icons', ), $icon_list); - $item_classes[] = 'phabricator-object-item-with-icons'; } if ($this->handleIcons) { @@ -200,7 +254,6 @@ final class PhabricatorObjectItemView extends AphrontView { 'class' => 'phabricator-object-item-handle-icons', ), $handle_bar); - $item_classes[] = 'phabricator-object-item-with-handle-icons'; } if ($icons) { @@ -240,7 +293,6 @@ final class PhabricatorObjectItemView extends AphrontView { 'class' => 'phabricator-object-item-attributes', ), $attrs); - $item_classes[] = 'phabricator-object-item-with-attrs'; } $foot = null; @@ -255,30 +307,10 @@ final class PhabricatorObjectItemView extends AphrontView { 'class' => 'phabricator-object-item-foot-icons', ), $foot_bar); - $item_classes[] = 'phabricator-object-item-with-foot-icons'; - } - - $item_classes[] = 'phabricator-object-item'; - if ($this->barColor) { - $item_classes[] = 'phabricator-object-item-bar-color-'.$this->barColor; - } - - switch ($this->effect) { - case 'highlighted': - $item_classes[] = 'phabricator-object-item-highlighted'; - break; - case 'selected': - $item_classes[] = 'phabricator-object-item-selected'; - break; - case null: - break; - default: - throw new Exception(pht("Invalid effect!")); } $grippable = null; if ($this->getGrippable()) { - $item_classes[] = 'phabricator-object-item-grippable'; $grippable = phutil_tag( 'div', array( @@ -300,20 +332,15 @@ final class PhabricatorObjectItemView extends AphrontView { )); return phutil_tag( - 'li', + 'div', array( - 'class' => implode(' ', $item_classes), + 'class' => 'phabricator-object-item-frame', ), - phutil_tag( - 'div', - array( - 'class' => 'phabricator-object-item-frame', - ), - array( - $grippable, - $icons, - $content, - ))); + array( + $grippable, + $icons, + $content, + )); } private function renderFootIcon($icon, $label) { diff --git a/webroot/rsrc/css/application/maniphest/task-summary.css b/webroot/rsrc/css/application/maniphest/task-summary.css index 4d0609d2c6..ddc881844e 100644 --- a/webroot/rsrc/css/application/maniphest/task-summary.css +++ b/webroot/rsrc/css/application/maniphest/task-summary.css @@ -2,123 +2,19 @@ * @provides maniphest-task-summary-css */ -.maniphest-task-summary { - width: 100%; - margin: 0 0 -1px 0; - border-collapse: separate; - color: #333; - border: 1px solid #c0c5d1; -} - .maniphest-task-group { padding-bottom: 30px; } -.maniphest-task-summary td { - padding: 0 10px; - background: #fff; -} - -.maniphest-task-summary td em { - color: #888888; -} - .maniphest-batch-selected td { background: #fff; } -.maniphest-task-summary .maniphest-task-handle { - padding: 0 4px 0 0; - width: 5px; -} - -.maniphest-task-summary td.maniphest-task-batch { - padding: 15px 4px 0 10px; - width: 8px; - text-align: center; - overflow: hidden; -} - .device-phone .maniphest-task-batch, .device-phone .maniphest-task-updated { display: none; } -.maniphest-task-summary td.maniphest-task-batch, -.maniphest-task-summary td.maniphest-task-batch input { - cursor: pointer; -} - -.maniphest-task-summary td.maniphest-task-batch input { - margin: 0; -} - -.maniphest-task-summary td.maniphest-task-number { - padding: 6px 0 2px 10px; - font-weight: bold; - color: #333; -} - -.maniphest-task-summary td.maniphest-task-status { - padding: 2px 10px 6px 10px; - text-align: left; - color: #777; - font-size: 12px; -} - -.maniphest-task-summary .maniphest-task-owner { - padding-left: 20px; -} - -.maniphest-task-summary .maniphest-task-name { - font-weight: bold; - overflow: hidden; - margin-left: 5px; -} - -.maniphest-task-summary td.maniphest-task-projects { - text-align: right; - padding: 0px 8px; -} - -.maniphest-task-summary .maniphest-task-updated { - float: right; - padding: 0 8px; - color: #777; - font-size: 11px; - font-weight: normal; -} - -.maniphest-task-summary .pri-unbreak { - border-color: #ff0000; - background-color: #ff0000; -} - -.maniphest-task-summary .pri-triage { - border-color: #ee00ee; - background-color: #ee00ee; -} - -.maniphest-task-summary .pri-high { - border-color: #ff6622; - background-color: #ff6622; -} - -.maniphest-task-summary .pri-normal { - border-color: #ffaa66; - background-color: #ffaa66; -} - -.maniphest-task-summary .pri-low { - border-color: #eecc66; - background-color: #eecc66; -} - -.maniphest-task-summary .pri-wish { - border-color: #0099ff; - background-color: #0099ff; -} - .maniphest-task-group-header { font-size: 16px; font-weight: bold; @@ -179,26 +75,20 @@ width: 100%; } -td.maniphest-active-handle { - cursor: move; - background-image: url('/rsrc/image/grippy_texture.png'); - background-position: 3px 0px; - background-repeat: repeat-y; -} - .maniphest-subpriority-target { position: relative; border: 1px dashed #aaaaaa; background: #f9f9f9; + margin: 4px; } .maniphest-task-loading { - opacity: 0.5; + opacity: 0.75; } .maniphest-task-dragging { position: relative; - opacity: 0.5; + opacity: 0.90; } .maniphest-list-container { diff --git a/webroot/rsrc/css/layout/phabricator-object-item-list-view.css b/webroot/rsrc/css/layout/phabricator-object-item-list-view.css index f58329f84b..ca07e242b3 100644 --- a/webroot/rsrc/css/layout/phabricator-object-item-list-view.css +++ b/webroot/rsrc/css/layout/phabricator-object-item-list-view.css @@ -38,7 +38,7 @@ } .phabricator-object-item-name { - display: block; + display: inline-block; font-weight: bold; font-size: 14px; padding: 0 10px; @@ -49,9 +49,9 @@ padding: 8px 0; } - .phabricator-object-item-objname { color: #222222; + cursor: text; } .phabricator-object-item-with-attrs .phabricator-object-item-name { diff --git a/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js b/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js index 0e37e80a32..d5aaa60d98 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js +++ b/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js @@ -3,39 +3,42 @@ * @requires javelin-behavior * javelin-dom * javelin-stratcom + * javelin-util */ JX.behavior('maniphest-batch-selector', function(config) { - // When a task row's selection state is changed, this issues updates to other - // parts of the application. + var selected = {}; - var onchange = function(task) { - var input = JX.DOM.find(task, 'input', 'maniphest-batch'); - var state = input.checked; + // Test if a task node is selected. - JX.DOM.alterClass(task, 'maniphest-batch-selected', state); - - JX.Stratcom.invoke( - (state ? 'maniphest-batch-task-add' : 'maniphest-batch-task-rem'), - null, - {id: input.value}) - }; + var get_id = function(task) { + return JX.Stratcom.getData(task).taskID; + } + var is_selected = function(task) { + return (get_id(task) in selected); + } // Change the selected state of a task. - // If 'to' is undefined, toggle. Otherwise, set to true or false. var change = function(task, to) { - - var input = JX.DOM.find(task, 'input', 'maniphest-batch'); - var state = input.checked; if (to === undefined) { - input.checked = !input.checked; - } else { - input.checked = to; + to = !is_selected(task); } - onchange(task); + + if (to) { + selected[get_id(task)] = true; + } else { + delete selected[get_id(task)]; + } + + JX.DOM.alterClass( + task, + 'phabricator-object-item-selected', + is_selected(task)); + + update(); }; @@ -43,62 +46,72 @@ JX.behavior('maniphest-batch-selector', function(config) { // buttons). var changeall = function(to) { - var inputs = JX.DOM.scry(document.body, 'table', 'maniphest-task'); + var inputs = JX.DOM.scry(document.body, 'li', 'maniphest-task'); for (var ii = 0; ii < inputs.length; ii++) { change(inputs[ii], to); } } + // Clear any document text selection after toggling a task via shift click, + // since errant clicks tend to start selecting various ranges otherwise. + + var clear_selection = function() { + if (window.getSelection) { + if (window.getSelection().empty) { + window.getSelection().empty(); + } else if (window.getSelection().removeAllRanges) { + window.getSelection().removeAllRanges(); + } + } else if (document.selection) { + document.selection.empty(); + } + } // Update the status text showing how many tasks are selected, and the button // state. - var selected = {}; - var selected_count = 0; - var update = function() { - var status = (selected_count == 1) - ? '1 Selected Task' - : selected_count + ' Selected Tasks'; + var count = JX.keys(selected).length; + var status; + if (count == 0) { + status = 'Shift-Click to Select Tasks'; + } else if (status == 1) { + status = '1 Selected Task'; + } else { + status = count + ' Selected Tasks'; + } JX.DOM.setContent(JX.$(config.status), status); var submit = JX.$(config.submit); - var disable = (selected_count == 0); + var disable = (count == 0); submit.disabled = disable; JX.DOM.alterClass(submit, 'disabled', disable); }; - - // When the user clicks the entire surrounding the checkbox, count it - // as a checkbox click. + // When he user shift-clicks the task, update the rest of the application + // state. JX.Stratcom.listen( 'click', 'maniphest-task', function(e) { - if (!JX.DOM.isNode(e.getTarget(), 'td')) { - // Only count clicks in the , not (e.g.) the table border. + var raw = e.getRawEvent(); + if (!raw.shiftKey) { return; } - // Check if the clicked contains a checkbox. - var inputs = JX.DOM.scry(e.getTarget(), 'input', 'maniphest-batch'); - if (!inputs.length) { + if (raw.ctrlKey || raw.altKey || raw.metaKey || e.isRightButton()) { return; } + if (JX.Stratcom.pass(e)) { + return; + } + + e.kill(); change(e.getNode('maniphest-task')); - }); - - // When he user clicks the , update the rest of the application - // state. - - JX.Stratcom.listen( - ['click', 'onchange'], - 'maniphest-batch', - function(e) { - onchange(e.getNode('maniphest-task')); + clear_selection(); }); @@ -125,30 +138,21 @@ JX.behavior('maniphest-batch-selector', function(config) { e.kill(); }); + // When the user submits the form, dump selected state into it. - JX.Stratcom.listen( - 'maniphest-batch-task-add', + JX.DOM.listen( + JX.$(config.formID), + 'submit', null, function(e) { - var id = e.getData().id; - if (!(id in selected)) { - selected[id] = true; - selected_count++; - update(); + var inputs = []; + for (var k in selected) { + inputs.push( + JX.$N('input', {type: 'hidden', name: 'batch[]', value: k})); } + JX.DOM.setContent(JX.$(config.idContainer), inputs); }); - - JX.Stratcom.listen( - 'maniphest-batch-task-rem', - null, - function(e) { - var id = e.getData().id; - if (id in selected) { - delete selected[id]; - selected_count--; - update(); - } - }); + update(); }); diff --git a/webroot/rsrc/js/application/maniphest/behavior-subpriorityeditor.js b/webroot/rsrc/js/application/maniphest/behavior-subpriorityeditor.js index 7674f1b43f..242a8dd684 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-subpriorityeditor.js +++ b/webroot/rsrc/js/application/maniphest/behavior-subpriorityeditor.js @@ -15,18 +15,27 @@ JX.behavior('maniphest-subpriority-editor', function(config) { var origin = null; var targets = null; var target = null; - var droptarget = JX.$N('div', {className: 'maniphest-subpriority-target'}); + var droptarget = JX.$N('li', {className: 'maniphest-subpriority-target'}); var ondrag = function(e) { if (dragging || sending) { return; } + if (!e.isNormalMouseEvent()) { + return; + } + + // Can't grab onto slippery nodes. + if (e.getNode('slippery')) { + return; + } + dragging = e.getNode('maniphest-task'); origin = JX.$V(e); - var tasks = JX.DOM.scry(JX.$(config.root), 'table', 'maniphest-task'); - var heads = JX.DOM.scry(JX.$(config.root), 'h1', 'task-group'); + var tasks = JX.DOM.scry(document.body, 'li', 'maniphest-task'); + var heads = JX.DOM.scry(document.body, 'h1', 'task-group'); var nodes = tasks.concat(heads); @@ -107,10 +116,19 @@ JX.behavior('maniphest-subpriority-editor', function(config) { if (cur_target) { if (cur_target.nextSibling) { - cur_target.parentNode.insertBefore( - droptarget, - cur_target.nextSibling); + if (JX.DOM.isType(cur_target, 'h1')) { + // Dropping at the beginning of a priority list. + cur_target.nextSibling.insertBefore( + droptarget, + cur_target.nextSibling.firstChild); + } else { + // Dropping in the middle of a priority list. + cur_target.parentNode.insertBefore( + droptarget, + cur_target.nextSibling); + } } else { + // Dropping at the end of a priority list. cur_target.parentNode.appendChild(droptarget); } } @@ -180,9 +198,10 @@ JX.behavior('maniphest-subpriority-editor', function(config) { JX.DOM.alterClass(sending, 'maniphest-task-loading', true); var onresponse = function(r) { - JX.DOM.alterClass(sending, 'maniphest-task-loading', false); - var handle = JX.DOM.find(sending, 'td', 'maniphest-task-handle'); - handle.className = r.className; + var nodes = JX.$H(r.tasks).getFragment().firstChild; + var task = JX.DOM.find(nodes, 'li', 'maniphest-task'); + JX.DOM.replace(sending, task); + sending = null; }; @@ -196,8 +215,8 @@ JX.behavior('maniphest-subpriority-editor', function(config) { // NOTE: Javelin does not dispatch mousemove by default. JX.enableDispatch(document.body, 'mousemove'); - JX.Stratcom.listen('mousedown', 'maniphest-task-handle', ondrag); - JX.Stratcom.listen('mousemove', null, onmove); - JX.Stratcom.listen('mouseup', null, ondrop); + JX.Stratcom.listen('mousedown', 'maniphest-task', ondrag); + JX.Stratcom.listen('mousemove', null, onmove); + JX.Stratcom.listen('mouseup', null, ondrop); });