1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-25 15:00:58 +01:00

Improve design of PhabricatorObjectListView

Summary: See discussion in T2014. Aligns this element more closely with @chad's `frame_v3.psd` mock, and implements the icon/label element. Removes "details".

Test Plan: {F27062} {F27063} {F27064} {F27065}

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2014

Differential Revision: https://secure.phabricator.com/D4179
This commit is contained in:
epriestley 2012-12-13 10:59:29 -08:00
parent f3702a9f3b
commit 883829e667
11 changed files with 319 additions and 192 deletions

View file

@ -68,14 +68,19 @@ final class PhabricatorCalendarViewStatusController
} }
$from = phabricator_datetime($status->getDateFrom(), $user); $from = phabricator_datetime($status->getDateFrom(), $user);
$to = phabricator_datetime($status->getDateTo(), $user); $to = phabricator_datetime($status->getDateTo(), $user);
$color = ($status->getStatus() == PhabricatorUserStatus::STATUS_AWAY)
? 'red'
: 'yellow';
$item = id(new PhabricatorObjectItemView()) $item = id(new PhabricatorObjectItemView())
->setHeader($status->getTerseSummary($user)) ->setHeader($status->getTerseSummary($user))
->setHref($href) ->setHref($href)
->addDetail( ->setBarColor($color)
pht('Description'), ->addAttribute(pht('From %s to %s', $from, $to))
phutil_escape_html($status->getDescription())) ->addAttribute(
->addAttribute(pht('From %s', $from)) phutil_escape_html(
->addAttribute(pht('To %s', $to)); phutil_utf8_shorten($status->getDescription(), 64)));
$list->addItem($item); $list->addItem($item);
} }

View file

@ -78,8 +78,8 @@ final class PhabricatorMacroListController
$nav->appendChild($filter_view); $nav->appendChild($filter_view);
if ($macros) {
$pinboard = new PhabricatorPinboardView(); $pinboard = new PhabricatorPinboardView();
$pinboard->setNoDataString($nodata);
foreach ($macros as $macro) { foreach ($macros as $macro) {
$file_phid = $macro->getFilePHID(); $file_phid = $macro->getFilePHID();
$file = idx($files_map, $file_phid); $file = idx($files_map, $file_phid);
@ -106,11 +106,6 @@ final class PhabricatorMacroListController
$pinboard->addItem($item); $pinboard->addItem($item);
} }
$nav->appendChild($pinboard); $nav->appendChild($pinboard);
} else {
$list = new PhabricatorObjectItemListView();
$list->setNoDataString($nodata);
$nav->appendChild($list);
}
if (!strlen($filter)) { if (!strlen($filter)) {
$nav->appendChild($pager); $nav->appendChild($pager);

View file

@ -39,11 +39,17 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
$pastes = $query->executeWithCursorPager($pager); $pastes = $query->executeWithCursorPager($pager);
$list = $this->buildPasteList($pastes); $list = $this->buildPasteList($pastes);
$list->setHeader($title);
$list->setPager($pager); $list->setPager($pager);
$list->setNoDataString($nodata); $list->setNoDataString($nodata);
$nav->appendChild($list); $header = id(new PhabricatorHeaderView())
->setHeader($title);
$nav->appendChild(
array(
$header,
$list,
));
$crumbs = $this $crumbs = $this
->buildApplicationCrumbs($nav) ->buildApplicationCrumbs($nav)
@ -71,17 +77,16 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
$this->loadHandles(mpull($pastes, 'getAuthorPHID')); $this->loadHandles(mpull($pastes, 'getAuthorPHID'));
$list = new PhabricatorObjectItemListView(); $list = new PhabricatorObjectItemListView();
$list->setViewer($user);
foreach ($pastes as $paste) { foreach ($pastes as $paste) {
$created = phabricator_datetime($paste->getDateCreated(), $user); $created = phabricator_date($paste->getDateCreated(), $user);
$author = $this->getHandle($paste->getAuthorPHID())->renderLink();
$item = id(new PhabricatorObjectItemView()) $item = id(new PhabricatorObjectItemView())
->setHeader($paste->getFullName()) ->setHeader($paste->getFullName())
->setHref('/P'.$paste->getID()) ->setHref('/P'.$paste->getID())
->addDetail( ->setObject($paste)
pht('Author'), ->addAttribute(pht('Created %s by %s', $created, $author));
$this->getHandle($paste->getAuthorPHID())->renderLink())
->addAttribute(pht('Created %s', $created));
$list->addItem($item); $list->addItem($item);
} }

View file

@ -41,30 +41,40 @@ abstract class PhameController extends PhabricatorController {
assert_instances_of($posts, 'PhamePost'); assert_instances_of($posts, 'PhamePost');
$list = id(new PhabricatorObjectItemListView()) $list = id(new PhabricatorObjectItemListView())
->setViewer($user)
->setNoDataString($nodata); ->setNoDataString($nodata);
foreach ($posts as $post) { foreach ($posts as $post) {
$item = id(new PhabricatorObjectItemView()) $blogger = $this->getHandle($post->getBloggerPHID())->renderLink();
->setHeader($post->getTitle())
->setHref($this->getApplicationURI('post/view/'.$post->getID().'/'))
->addDetail(
pht('Blogger'),
$this->getHandle($post->getBloggerPHID())->renderLink())
->addDetail(
pht('Blog'),
$post->getBlog()
? $this->getHandle($post->getBlog()->getPHID())->renderLink()
: '-');
if ($post->isDraft()) { $blog = null;
$item->addAttribute(pht('Draft')); if ($post->getBlog()) {
} else { $blog = $this->getHandle($post->getBlog()->getPHID())->renderLink();
$date_published = phabricator_datetime(
$post->getDatePublished(),
$user);
$item->addAttribute(pht('Published on %s', $date_published));
} }
$published = null;
if ($post->getDatePublished()) {
$published = phabricator_date($post->getDatePublished(), $user);
}
$draft = $post->isDraft();
$item = id(new PhabricatorObjectItemView())
->setObject($post)
->setHeader($post->getTitle())
->setHref($this->getApplicationURI('post/view/'.$post->getID().'/'));
if ($blog) {
$item->addAttribute($blog);
}
if ($draft) {
$desc = pht('Draft by %s', $blogger);
} else {
$desc = pht('Published on %s by %s', $published, $blogger);
}
$item->addAttribute($desc);
$list->addItem($item); $list->addItem($item);
} }

View file

@ -71,14 +71,13 @@ final class PhameBlogListController extends PhameController {
$view = new PhabricatorObjectItemListView(); $view = new PhabricatorObjectItemListView();
$view->setNoDataString($nodata); $view->setNoDataString($nodata);
$view->setViewer($user);
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
$item = id(new PhabricatorObjectItemView()) $item = id(new PhabricatorObjectItemView())
->setHeader($blog->getName()) ->setHeader($blog->getName())
->setHref($this->getApplicationURI('blog/view/'.$blog->getID().'/')) ->setHref($this->getApplicationURI('blog/view/'.$blog->getID().'/'))
->addDetail( ->setObject($blog);
pht('Custom Domain'),
phutil_escape_html($blog->getDomain()));
$view->addItem($item); $view->addItem($item);
} }

View file

@ -90,35 +90,22 @@ final class PonderFeedController extends PonderController {
$user = $this->getRequest()->getUser(); $user = $this->getRequest()->getUser();
$view = new PhabricatorObjectItemListView(); $view = new PhabricatorObjectItemListView();
$view->setViewer($user);
$view->setNoDataString(pht('No matching questions.')); $view->setNoDataString(pht('No matching questions.'));
foreach ($questions as $question) { foreach ($questions as $question) {
$item = new PhabricatorObjectItemView(); $item = new PhabricatorObjectItemView();
$item->setHeader('Q'.$question->getID().' '.$question->getTitle()); $item->setHeader('Q'.$question->getID().' '.$question->getTitle());
$item->setHref('/Q'.$question->getID()); $item->setHref('/Q'.$question->getID());
$item->setObject($question);
$desc = $question->getContent(); $item->addAttribute(
if ($desc) { pht(
$item->addDetail( 'Asked by %s on %s',
pht('Description'), $this->getHandle($question->getAuthorPHID())->renderLink(),
phutil_escape_html(phutil_utf8_shorten($desc, 128))); phabricator_date($question->getDateCreated(), $user)));
}
$item->addDetail( $item->addAttribute(
pht('Author'), pht('%d Answer(s)', $question->getAnswerCount()));
$this->getHandle($question->getAuthorPHID())->renderLink());
$item->addDetail(
pht('Votes'),
$question->getVoteCount());
$item->addDetail(
pht('Answers'),
$question->getAnswerCount());
$created = pht(
'Created %s',
phabricator_date($question->getDateCreated(), $user));
$item->addAttribute($created);
$view->addItem($item); $view->addItem($item);
} }

View file

@ -61,6 +61,7 @@ final class PonderUserProfileView extends AphrontView {
$answers = $apagebuttons->sliceResults($answers); $answers = $apagebuttons->sliceResults($answers);
$view = new PhabricatorObjectItemListView(); $view = new PhabricatorObjectItemListView();
$view->setViewer($user);
$view->setNoDataString(pht('No matching answers.')); $view->setNoDataString(pht('No matching answers.'));
foreach ($answers as $answer) { foreach ($answers as $answer) {
@ -68,6 +69,7 @@ final class PonderUserProfileView extends AphrontView {
$author_phid = $question->getAuthorPHID(); $author_phid = $question->getAuthorPHID();
$item = new PhabricatorObjectItemView(); $item = new PhabricatorObjectItemView();
$item->setObject($answer);
$href = id(new PhutilURI('/Q' . $question->getID())) $href = id(new PhutilURI('/Q' . $question->getID()))
->setFragment('A' . $answer->getID()); ->setFragment('A' . $answer->getID());
$item->setHeader( $item->setHeader(
@ -75,19 +77,20 @@ final class PonderUserProfileView extends AphrontView {
); );
$item->setHref($href); $item->setHref($href);
$item->addDetail( $item->addAttribute(
pht('Votes'), pht('Created %s', phabricator_date($answer->getDateCreated(), $user)));
$answer->getVoteCount()
);
$item->addDetail( $item->addAttribute(pht('%d Vote(s)', $answer->getVoteCount()));
pht('Question'),
self::abbreviate($question->getTitle())
);
$item->addAttribute( $item->addAttribute(
pht('Created %s', phabricator_date($answer->getDateCreated(), $user)) pht(
); 'Answer to %s',
phutil_render_tag(
'a',
array(
'href' => '/Q'.$question->getID(),
),
self::abbreviate($question->getTitle()))));
$view->addItem($item); $view->addItem($item);
} }

View file

@ -6,6 +6,16 @@ final class PhabricatorObjectItemListView extends AphrontView {
private $items; private $items;
private $pager; private $pager;
private $noDataString; private $noDataString;
private $viewer;
public function setViewer($viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setHeader($header) { public function setHeader($header) {
$this->header = $header; $this->header = $header;
@ -30,12 +40,15 @@ final class PhabricatorObjectItemListView extends AphrontView {
public function render() { public function render() {
require_celerity_resource('phabricator-object-item-list-view-css'); require_celerity_resource('phabricator-object-item-list-view-css');
$header = null;
if (strlen($this->header)) {
$header = phutil_render_tag( $header = phutil_render_tag(
'h1', 'h1',
array( array(
'class' => 'phabricator-object-item-list-header', 'class' => 'phabricator-object-item-list-header',
), ),
phutil_escape_html($this->header)); phutil_escape_html($this->header));
}
if ($this->items) { if ($this->items) {
$items = $this->renderSingleView($this->items); $items = $this->renderSingleView($this->items);

View file

@ -7,6 +7,18 @@ final class PhabricatorObjectItemView extends AphrontView {
private $attributes = array(); private $attributes = array();
private $details = array(); private $details = array();
private $dates = array(); private $dates = array();
private $icons = array();
private $barColor;
private $object;
public function setObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
public function setHref($href) { public function setHref($href) {
$this->href = $href; $this->href = $href;
@ -26,14 +38,23 @@ final class PhabricatorObjectItemView extends AphrontView {
return $this->header; return $this->header;
} }
public function addDetail($name, $value, $class = null) { public function addIcon($icon, $label = null) {
$this->details[] = array( $this->icons[] = array(
'name' => $name, 'icon' => $icon,
'value' => $value, 'label' => $label,
); );
return $this; return $this;
} }
public function setBarColor($bar_color) {
$this->barColor = $bar_color;
return $this;
}
public function getBarColor() {
return $this->barColor;
}
public function addAttribute($attribute) { public function addAttribute($attribute) {
$this->attributes[] = $attribute; $this->attributes[] = $attribute;
return $this; return $this;
@ -48,32 +69,61 @@ final class PhabricatorObjectItemView extends AphrontView {
), ),
phutil_escape_html($this->header)); phutil_escape_html($this->header));
$details = null; $icons = null;
if ($this->details) { if ($this->icons) {
$details = array(); $icon_list = array();
foreach ($this->details as $detail) { foreach ($this->icons as $spec) {
$details[] = $icon = $spec['icon'];
'<dt class="phabricator-object-detail-key">'.
phutil_escape_html($detail['name']). $icon = phutil_render_tag(
'</dt>'; 'span',
$details[] =
'<dd class="phabricator-object-detail-value">'.
$detail['value'].
'</dt>';
}
$details = phutil_render_tag(
'dl',
array( array(
'class' => 'phabricator-object-detail-list', 'class' => 'phabricator-object-item-icon-image '.
'sprite-icon action-'.$icon,
), ),
implode('', $details)); '');
$label = phutil_render_tag(
'span',
array(
'class' => 'phabricator-object-item-icon-label',
),
phutil_escape_html($spec['label']));
$icon_list[] = phutil_render_tag(
'li',
array(
'class' => 'phabricator-object-item-icon',
),
$label.$icon);
}
$icons = phutil_render_tag(
'ul',
array(
'class' => 'phabricator-object-item-icons',
),
implode('', $icon_list));
} }
$attrs = null; $attrs = null;
if ($this->attributes) { if ($this->attributes) {
$attrs = array(); $attrs = array();
$spacer = phutil_render_tag(
'span',
array(
'class' => 'phabricator-object-item-attribute-spacer',
),
'&middot;');
$first = true;
foreach ($this->attributes as $attribute) { foreach ($this->attributes as $attribute) {
$attrs[] = '<li>'.$attribute.'</li>'; $attrs[] = phutil_render_tag(
'li',
array(
'class' => 'phabricator-object-item-attribute',
),
($first ? null : $spacer).$attribute);
$first = false;
} }
$attrs = phutil_render_tag( $attrs = phutil_render_tag(
'ul', 'ul',
@ -83,12 +133,18 @@ final class PhabricatorObjectItemView extends AphrontView {
implode('', $attrs)); implode('', $attrs));
} }
$classes = array();
$classes[] = 'phabricator-object-item';
if ($this->barColor) {
$classes[] = 'phabricator-object-item-bar-color-'.$this->barColor;
}
return phutil_render_tag( return phutil_render_tag(
'div', 'div',
array( array(
'class' => 'phabricator-object-item', 'class' => implode(' ', $classes),
), ),
$header.$details.$attrs); $icons.$header.$attrs);
} }
} }

View file

@ -3,6 +3,12 @@
final class PhabricatorPinboardView extends AphrontView { final class PhabricatorPinboardView extends AphrontView {
private $items = array(); private $items = array();
private $noDataString;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function addItem(PhabricatorPinboardItemView $item) { public function addItem(PhabricatorPinboardItemView $item) {
$this->items[] = $item; $this->items[] = $item;
@ -12,6 +18,14 @@ final class PhabricatorPinboardView extends AphrontView {
public function render() { public function render() {
require_celerity_resource('phabricator-pinboard-view-css'); require_celerity_resource('phabricator-pinboard-view-css');
if (!$this->items) {
$string = nonempty($this->noDataString, pht('No data.'));
return id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NODATA)
->appendChild(phutil_escape_html($string))
->render();
}
return phutil_render_tag( return phutil_render_tag(
'div', 'div',
array( array(

View file

@ -3,100 +3,140 @@
*/ */
.phabricator-object-item-list-view { .phabricator-object-item-list-view {
padding: 0 2%; padding: 8px 6px;
margin: 1em 0; background: #f4f5f6;
} }
.phabricator-object-item-list-header {
color: #333333;
font-size: 15px;
margin-bottom: 12px;
}
.phabricator-object-item { .phabricator-object-item {
background: #f9f9f9; background: #ffffff;
border: 1px solid #dbdbdb; border-style: solid;
border-radius: 2px; border-color: #d7d7d7 #e4e3e4 #bcbcbd #d7d7d7;
margin: 6px 0; border-width: 1px 1px 1px 3px;
margin: 3px 0;
overflow: hidden; overflow: hidden;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10); box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.10);
} }
.phabricator-object-item-name { .phabricator-object-item-name {
display: block; display: block;
background: #e9e9e9; padding: 4px 8px 4px;
padding: 2px 1em; margin-right: 20px;
font-weight: bold; font-weight: bold;
} }
.device-desktop .phabricator-object-item-name { /* - Attribute List ------------------------------
border: solid #dbdbdb;
border-width: 0 0 1px 0;
}
/* - Detail List ------------------------------------------------------------
Object details, used to render things like authors, reviewers, etc.
*/
.phabricator-object-detail-list {
font-size: 11px;
width: 55%;
padding: 2px 1%;
}
.device-desktop .phabricator-object-detail-list,
.device-tablet .phabricator-object-detail-list {
float: left;
}
.phabricator-object-detail-key {
color: #555555;
float: left;
clear: left;
width: 20%;
text-align: right;
padding: 2px 6px;
overflow: hidden;
}
.phabricator-object-detail-value {
color: #444444;
float: left;
padding: 2px 6px;
}
/* - Attribute List ------------------------------------------------------------
Object attributes, commonly used to render created date, etc. Object attributes, commonly used to render created date, etc.
*/ */
.phabricator-object-item-attributes { .phabricator-object-item-attributes {
padding: 0px 8px 4px;
}
.phabricator-object-item-attribute {
display: inline;
color: #555555; color: #555555;
text-align: right;
font-size: 11px; font-size: 11px;
padding: 2px 1%;
} }
.device-desktop .phabricator-object-item-attributes, .phabricator-object-item-attribute-spacer {
.device-tablet .phabricator-object-item-attributes { padding: 0 4px;
width: 40%; }
/* - Attribute List ------------------------------
Object attributes, commonly used to render created date, etc.
*/
.phabricator-object-item-icons {
float: right; float: right;
padding: 2px 4px 0;
} }
.device-phone .phabricator-object-item-attributes { .device-desktop .phabricator-object-item-icons {
clear: both; width: 120px;
} }
.phabricator-object-item-attributes li { .device .phabricator-object-item-icons {
padding: 2px 1%; width: 18px;
}
.device .phabricator-object-item-icon-label {
display: none;
vertical-align: middle;
}
.phabricator-object-item-icon {
position: relative;
font-size: 11px;
color: #666666;
text-align: right;
white-space: nowrap;
overflow: hidden;
min-height: 18px;
line-height: 18px;
}
.device-desktop .phabricator-object-item-icon {
padding-right: 22px;
}
.phabricator-object-item-icon-image {
position: absolute;
right: 2px;
top: 2px;
width: 14px;
height: 14px;
}
/* - Bar Colors --------------------------------
*/
.phabricator-object-item-bar-color-red {
border-left-color: #cc0000;
}
.phabricator-object-item-bar-color-orange {
border-left-color: #cc7300;
}
.phabricator-object-item-bar-color-yellow {
border-left-color: #ccc000;
}
.phabricator-object-item-bar-color-green {
border-left-color: #009b2d;
}
.phabricator-object-item-bar-color-sky {
border-left-color: #6498cf;
}
.phabricator-object-item-bar-color-blue {
border-left-color: #00659a;
}
.phabricator-object-item-bar-color-indigo {
border-left-color: #3a00cc;
}
.phabricator-object-item-bar-color-violet {
border-left-color: #67009b;
}
.phabricator-object-item-bar-color-grey {
border-left-color: #999999;
}
.phabricator-object-item-bar-color-black {
border-left-color: #333333;
} }