1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-27 01:02:42 +01:00

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
This commit is contained in:
epriestley 2013-03-23 14:38:01 -07:00
parent 5e53fc750a
commit 018de5dec7
12 changed files with 359 additions and 531 deletions

View file

@ -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',
),
));

View file

@ -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,
));
}
}

View file

@ -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(
'<div class="maniphest-batch-editor">'.
'<div class="batch-editor-header">%s</div>'.
'<table class="maniphest-batch-editor-layout">'.
@ -698,16 +698,28 @@ final class ManiphestTaskListController extends ManiphestController {
'<td>%s%s</td>'.
'<td>%s</td>'.
'<td id="batch-select-status-cell">%s</td>'.
'<td class="batch-select-submit-cell">%s</td>'.
'<td class="batch-select-submit-cell">%s%s</td>'.
'</tr>'.
'</table>'.
'</table>',
'</div>',
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() {

View file

@ -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;
}
}

View file

@ -1,196 +0,0 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskSummaryView extends ManiphestView {
private $task;
private $handles;
private $showBatchControls;
private $showSubpriorityControls;
public function setTask(ManiphestTask $task) {
$this->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,
));
}
}

View file

@ -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
'</div>
</div>',
pht('Open Tasks (%s)', $open),
$task_views,
$task_list,
$more_link);
return $content;

View file

@ -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',

View file

@ -1,6 +1,6 @@
<?php
final class PhabricatorObjectItemView extends AphrontView {
final class PhabricatorObjectItemView extends AphrontTagView {
private $objectName;
private $header;
@ -105,9 +105,58 @@ final class PhabricatorObjectItemView extends AphrontView {
return $this;
}
public function render() {
$content_classes = array();
protected function getTagName() {
return 'li';
}
protected function getTagAttributes() {
$item_classes = array();
$item_classes[] = 'phabricator-object-item';
if ($this->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) {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 <td /> 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 <td />, not (e.g.) the table border.
var raw = e.getRawEvent();
if (!raw.shiftKey) {
return;
}
// Check if the clicked <td /> 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 <input />, 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();
});

View file

@ -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);
});