2012-11-22 02:24:56 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorTimelineEventView extends AphrontView {
|
|
|
|
|
|
|
|
private $userHandle;
|
|
|
|
private $title;
|
2012-12-11 22:59:35 +01:00
|
|
|
private $icon;
|
|
|
|
private $color;
|
2012-11-22 02:24:56 +01:00
|
|
|
private $classes = array();
|
2012-12-11 22:59:51 +01:00
|
|
|
private $contentSource;
|
|
|
|
private $dateCreated;
|
|
|
|
private $anchor;
|
2012-12-11 23:01:51 +01:00
|
|
|
private $isEditable;
|
|
|
|
private $isEdited;
|
|
|
|
private $transactionPHID;
|
2012-12-21 14:51:33 +01:00
|
|
|
private $isPreview;
|
2013-09-24 23:35:35 +02:00
|
|
|
private $eventGroup = array();
|
2012-12-11 23:01:51 +01:00
|
|
|
|
|
|
|
public function setTransactionPHID($transaction_phid) {
|
|
|
|
$this->transactionPHID = $transaction_phid;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTransactionPHID() {
|
|
|
|
return $this->transactionPHID;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsEdited($is_edited) {
|
|
|
|
$this->isEdited = $is_edited;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsEdited() {
|
|
|
|
return $this->isEdited;
|
|
|
|
}
|
|
|
|
|
2012-12-21 14:51:33 +01:00
|
|
|
public function setIsPreview($is_preview) {
|
|
|
|
$this->isPreview = $is_preview;
|
2012-12-11 23:01:51 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-21 14:51:33 +01:00
|
|
|
public function getIsPreview() {
|
|
|
|
return $this->isPreview;
|
2012-12-11 23:01:51 +01:00
|
|
|
}
|
2012-12-11 22:59:51 +01:00
|
|
|
|
2012-12-21 14:51:33 +01:00
|
|
|
public function setIsEditable($is_editable) {
|
|
|
|
$this->isEditable = $is_editable;
|
2012-12-11 22:59:51 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-21 14:51:33 +01:00
|
|
|
public function getIsEditable() {
|
|
|
|
return $this->isEditable;
|
2012-12-11 22:59:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function setDateCreated($date_created) {
|
|
|
|
$this->dateCreated = $date_created;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDateCreated() {
|
|
|
|
return $this->dateCreated;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setContentSource(PhabricatorContentSource $content_source) {
|
|
|
|
$this->contentSource = $content_source;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getContentSource() {
|
|
|
|
return $this->contentSource;
|
|
|
|
}
|
2012-11-22 02:24:56 +01:00
|
|
|
|
|
|
|
public function setUserHandle(PhabricatorObjectHandle $handle) {
|
|
|
|
$this->userHandle = $handle;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-11 22:59:51 +01:00
|
|
|
public function setAnchor($anchor) {
|
|
|
|
$this->anchor = $anchor;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:24:56 +01:00
|
|
|
public function setTitle($title) {
|
|
|
|
$this->title = $title;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function addClass($class) {
|
|
|
|
$this->classes[] = $class;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-11 22:59:35 +01:00
|
|
|
public function setIcon($icon) {
|
|
|
|
$this->icon = $icon;
|
2012-11-22 02:24:56 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-11 22:59:35 +01:00
|
|
|
public function setColor($color) {
|
|
|
|
$this->color = $color;
|
2012-11-22 02:24:56 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-09-24 23:35:35 +02:00
|
|
|
public function getEventGroup() {
|
|
|
|
return array_merge(array($this), $this->eventGroup);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function addEventToGroup(PhabricatorTimelineEventView $event) {
|
|
|
|
$this->eventGroup[] = $event;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
protected function renderEventTitle($is_first_event, $force_icon) {
|
2012-11-22 02:24:56 +01:00
|
|
|
$title = $this->title;
|
Provide hasChildren() to replace isEmptyContent()
Summary:
Fixes T3698. Sometimes views need to render differently depending on whether they contain content or not. The existing approach for this is `isEmptyContent()`, which doesn't work well and is sort of hacky (it implies double-rendering content, which is not always free or side-effect free).
Instead, provide a test for an element without children. This test is powerful enough to catch the easy cases of `null`, etc., and just do the expected thing, but will not catch a View which is reduced upon rendering. Since this is rare and we have no actual need for it today, just accept that as a limitation.
Test Plan:
Viewed Timeline and Feed UI examples. Viewed Feed (feed), Pholio (timelineview), and Differential (old transactionview).
{F53915}
Reviewers: chad, btrahan
Reviewed By: chad
CC: aran
Maniphest Tasks: T3698
Differential Revision: https://secure.phabricator.com/D6718
2013-08-12 16:51:01 +02:00
|
|
|
if (($title === null) && !$this->hasChildren()) {
|
2012-11-22 02:24:56 +01:00
|
|
|
$title = '';
|
|
|
|
}
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($is_first_event) {
|
|
|
|
$extra = array();
|
|
|
|
$is_first_extra = true;
|
|
|
|
foreach ($this->getEventGroup() as $event) {
|
2013-12-27 14:51:15 +01:00
|
|
|
$extra[] = $event->renderExtra($is_first_extra);
|
2013-12-27 04:24:04 +01:00
|
|
|
$is_first_extra = false;
|
|
|
|
}
|
2013-12-27 14:51:15 +01:00
|
|
|
$extra = array_reverse($extra);
|
|
|
|
$extra = array_mergev($extra);
|
2013-12-27 04:24:04 +01:00
|
|
|
$extra = phutil_tag(
|
|
|
|
'span',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-timeline-extra',
|
|
|
|
),
|
2013-12-27 14:51:15 +01:00
|
|
|
phutil_implode_html(" \xC2\xB7 ", $extra));
|
2013-12-27 04:24:04 +01:00
|
|
|
} else {
|
|
|
|
$extra = null;
|
|
|
|
}
|
2012-12-11 22:59:51 +01:00
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($title !== null || $extra) {
|
2012-11-22 02:24:56 +01:00
|
|
|
$title_classes = array();
|
|
|
|
$title_classes[] = 'phabricator-timeline-title';
|
2012-12-11 22:59:35 +01:00
|
|
|
|
|
|
|
$icon = null;
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($this->icon || $force_icon) {
|
2012-12-11 22:59:35 +01:00
|
|
|
$title_classes[] = 'phabricator-timeline-title-with-icon';
|
2013-12-27 04:24:04 +01:00
|
|
|
}
|
2012-12-11 22:59:35 +01:00
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($this->icon) {
|
2013-09-24 23:35:35 +02:00
|
|
|
$fill_classes = array();
|
|
|
|
$fill_classes[] = 'phabricator-timeline-icon-fill';
|
|
|
|
if ($this->color) {
|
|
|
|
$fill_classes[] = 'phabricator-timeline-icon-fill-'.$this->color;
|
|
|
|
}
|
|
|
|
|
2013-01-18 03:47:13 +01:00
|
|
|
$icon = phutil_tag(
|
2012-12-11 22:59:35 +01:00
|
|
|
'span',
|
|
|
|
array(
|
2013-09-24 23:35:35 +02:00
|
|
|
'class' => implode(' ', $fill_classes),
|
2012-12-11 22:59:35 +01:00
|
|
|
),
|
2013-01-18 03:57:09 +01:00
|
|
|
phutil_tag(
|
2012-12-11 22:59:35 +01:00
|
|
|
'span',
|
|
|
|
array(
|
Add action icons to object list views
Summary:
We have a few interfaces where add "Edit", "Delete" or some other action to a list. Currently, this happens via icons, but these are cumbersome and weird, are inconsistent, can't be workflow'd, are hard to hit on desktops and virtually impossible to hit on mobile, and generally just feel iffy to me. Prominent examples are Projects and Flags. I'd like to try adding an "edit" action to Maniphest (to provide quick edit from list views, basically). It looks like some of Releeph would benefit here, as well.
Instead, provide first-class actions:
{F42978}
They produce targets which my meaty ham-fists can plausibly hit on mobile, too:
{F42979}
(We could do some kind of swipe-to-expose thing eventually, but I think putting them by default is OK?)
Test Plan: Added UIExamples. Checked desktop/mobile.
Reviewers: chad, btrahan, edward
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D5890
2013-05-10 21:57:01 +02:00
|
|
|
'class' => 'phabricator-timeline-icon sprite-icons '.
|
|
|
|
'icons-'.$this->icon.'-white',
|
2012-12-11 22:59:35 +01:00
|
|
|
),
|
|
|
|
''));
|
2012-11-22 02:24:56 +01:00
|
|
|
}
|
|
|
|
|
2013-01-29 03:09:33 +01:00
|
|
|
$title = phutil_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => implode(' ', $title_classes),
|
|
|
|
),
|
2013-09-24 23:35:35 +02:00
|
|
|
array($icon, $title, $extra));
|
|
|
|
}
|
2012-12-11 23:02:51 +01:00
|
|
|
|
2013-09-24 23:35:35 +02:00
|
|
|
return $title;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function render() {
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
$events = $this->getEventGroup();
|
|
|
|
|
|
|
|
// Move events with icons first.
|
|
|
|
$icon_keys = array();
|
|
|
|
foreach ($this->getEventGroup() as $key => $event) {
|
|
|
|
if ($event->icon) {
|
|
|
|
$icon_keys[] = $key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$events = array_select_keys($events, $icon_keys) + $events;
|
|
|
|
$force_icon = (bool)$icon_keys;
|
|
|
|
|
2013-09-24 23:35:35 +02:00
|
|
|
$group_titles = array();
|
|
|
|
$group_children = array();
|
2013-12-27 04:24:04 +01:00
|
|
|
$is_first_event = true;
|
|
|
|
foreach ($events as $event) {
|
|
|
|
$group_titles[] = $event->renderEventTitle($is_first_event, $force_icon);
|
|
|
|
$is_first_event = false;
|
2013-09-24 23:35:35 +02:00
|
|
|
if ($event->hasChildren()) {
|
|
|
|
$group_children[] = $event->renderChildren();
|
|
|
|
}
|
2012-11-22 02:24:56 +01:00
|
|
|
}
|
|
|
|
|
2013-01-18 03:57:09 +01:00
|
|
|
$wedge = phutil_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-timeline-wedge phabricator-timeline-border',
|
|
|
|
),
|
|
|
|
'');
|
|
|
|
|
|
|
|
$image_uri = $this->userHandle->getImageURI();
|
2013-01-18 03:57:09 +01:00
|
|
|
$image = phutil_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'style' => 'background-image: url('.$image_uri.')',
|
|
|
|
'class' => 'phabricator-timeline-image',
|
|
|
|
),
|
|
|
|
'');
|
|
|
|
|
|
|
|
$content_classes = array();
|
|
|
|
$content_classes[] = 'phabricator-timeline-content';
|
|
|
|
|
|
|
|
$classes = array();
|
|
|
|
$classes[] = 'phabricator-timeline-event-view';
|
2013-09-24 23:35:35 +02:00
|
|
|
if ($group_children) {
|
2012-11-22 02:24:56 +01:00
|
|
|
$classes[] = 'phabricator-timeline-major-event';
|
2013-01-29 03:09:33 +01:00
|
|
|
$content = phutil_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
2013-09-24 23:35:35 +02:00
|
|
|
'class' => 'phabricator-timeline-inner-content',
|
2012-11-22 02:24:56 +01:00
|
|
|
),
|
2013-09-24 23:35:35 +02:00
|
|
|
array(
|
|
|
|
$group_titles,
|
|
|
|
phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-timeline-core-content',
|
|
|
|
),
|
|
|
|
$group_children),
|
|
|
|
));
|
2012-11-22 02:24:56 +01:00
|
|
|
} else {
|
|
|
|
$classes[] = 'phabricator-timeline-minor-event';
|
2013-09-24 23:35:35 +02:00
|
|
|
$content = $group_titles;
|
2012-11-22 02:24:56 +01:00
|
|
|
}
|
|
|
|
|
2013-09-24 23:35:35 +02:00
|
|
|
$content = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-timeline-group phabricator-timeline-border',
|
|
|
|
),
|
|
|
|
$content);
|
|
|
|
|
|
|
|
$content = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => implode(' ', $content_classes),
|
|
|
|
),
|
|
|
|
array($image, $wedge, $content));
|
|
|
|
|
2012-12-11 22:59:35 +01:00
|
|
|
$outer_classes = $this->classes;
|
2012-12-11 22:59:51 +01:00
|
|
|
$outer_classes[] = 'phabricator-timeline-shell';
|
2013-09-24 23:35:35 +02:00
|
|
|
$color = null;
|
|
|
|
foreach ($this->getEventGroup() as $event) {
|
|
|
|
if ($event->color) {
|
|
|
|
$color = $event->color;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($color) {
|
|
|
|
$outer_classes[] = 'phabricator-timeline-'.$color;
|
2012-12-11 22:59:35 +01:00
|
|
|
}
|
|
|
|
|
2012-12-11 23:02:12 +01:00
|
|
|
$sigil = null;
|
|
|
|
$meta = null;
|
|
|
|
if ($this->getTransactionPHID()) {
|
|
|
|
$sigil = 'transaction';
|
|
|
|
$meta = array(
|
|
|
|
'phid' => $this->getTransactionPHID(),
|
|
|
|
'anchor' => $this->anchor,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-01-29 03:09:33 +01:00
|
|
|
return javelin_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
2012-12-11 22:59:35 +01:00
|
|
|
'class' => implode(' ', $outer_classes),
|
2012-12-11 22:59:51 +01:00
|
|
|
'id' => $this->anchor ? 'anchor-'.$this->anchor : null,
|
2012-12-11 23:02:12 +01:00
|
|
|
'sigil' => $sigil,
|
|
|
|
'meta' => $meta,
|
2012-11-22 02:24:56 +01:00
|
|
|
),
|
2013-01-29 03:09:33 +01:00
|
|
|
phutil_tag(
|
2012-11-22 02:24:56 +01:00
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => implode(' ', $classes),
|
|
|
|
),
|
|
|
|
$content));
|
|
|
|
}
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
private function renderExtra($is_first_extra) {
|
2012-12-21 14:51:33 +01:00
|
|
|
$extra = array();
|
|
|
|
|
|
|
|
if ($this->getIsPreview()) {
|
|
|
|
$extra[] = pht('PREVIEW');
|
|
|
|
} else {
|
|
|
|
$xaction_phid = $this->getTransactionPHID();
|
|
|
|
|
|
|
|
if ($this->getIsEdited()) {
|
2013-01-25 21:57:17 +01:00
|
|
|
$extra[] = javelin_tag(
|
2012-12-21 14:51:33 +01:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '/transactions/history/'.$xaction_phid.'/',
|
|
|
|
'sigil' => 'workflow',
|
|
|
|
),
|
|
|
|
pht('Edited'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsEditable()) {
|
2013-01-25 21:57:17 +01:00
|
|
|
$extra[] = javelin_tag(
|
2012-12-21 14:51:33 +01:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '/transactions/edit/'.$xaction_phid.'/',
|
|
|
|
'sigil' => 'workflow transaction-edit',
|
|
|
|
),
|
|
|
|
pht('Edit'));
|
|
|
|
}
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($is_first_extra) {
|
|
|
|
$source = $this->getContentSource();
|
|
|
|
if ($source) {
|
|
|
|
$extra[] = id(new PhabricatorContentSourceView())
|
|
|
|
->setContentSource($source)
|
|
|
|
->setUser($this->getUser())
|
2012-12-21 14:51:33 +01:00
|
|
|
->render();
|
2013-12-27 04:24:04 +01:00
|
|
|
}
|
2012-12-21 14:51:33 +01:00
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
$date_created = null;
|
|
|
|
foreach ($this->getEventGroup() as $event) {
|
|
|
|
if ($event->getDateCreated()) {
|
|
|
|
if ($date_created === null) {
|
|
|
|
$date_created = $event->getDateCreated();
|
|
|
|
} else {
|
|
|
|
$date_created = min($event->getDateCreated(), $date_created);
|
|
|
|
}
|
|
|
|
}
|
2012-12-21 14:51:33 +01:00
|
|
|
}
|
|
|
|
|
2013-12-27 04:24:04 +01:00
|
|
|
if ($date_created) {
|
|
|
|
$date = phabricator_datetime(
|
|
|
|
$this->getDateCreated(),
|
|
|
|
$this->getUser());
|
|
|
|
if ($this->anchor) {
|
|
|
|
Javelin::initBehavior('phabricator-watch-anchor');
|
|
|
|
|
|
|
|
$anchor = id(new PhabricatorAnchorView())
|
|
|
|
->setAnchorName($this->anchor)
|
|
|
|
->render();
|
|
|
|
|
|
|
|
$date = array(
|
|
|
|
$anchor,
|
|
|
|
phutil_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '#'.$this->anchor,
|
|
|
|
),
|
|
|
|
$date),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$extra[] = $date;
|
|
|
|
}
|
|
|
|
}
|
2012-12-21 14:51:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $extra;
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:24:56 +01:00
|
|
|
}
|