mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Improve PhabricatorPropertyListView and add section headers
Summary: - For Drydock, I want to add section headers to separate user-defined attributes from global attributes. - Some day for Differential, I want to add "Summary" and "Test Plan" section headers. - Clean up some stuff a bit; drop the multiple APIs for setting text content. Explicitly disallow appendChild(). - Build out the UIExample a bit. Test Plan: {F24821} {F24822} Reviewers: chad, btrahan Reviewed By: chad CC: aran Maniphest Tasks: T2015 Differential Revision: https://secure.phabricator.com/D4000
This commit is contained in:
parent
df76ed9545
commit
10a9f2a15f
4 changed files with 234 additions and 68 deletions
|
@ -35,6 +35,90 @@ final class PhabricatorPropertyListExample extends PhabricatorUIExample {
|
|||
'viverra. Nunc tempus tempor quam id iaculis. Maecenas lectus '.
|
||||
'velit, aliquam et consequat quis, tincidunt id dolor.');
|
||||
|
||||
return $view;
|
||||
|
||||
$view->addSectionHeader('Colors of the Rainbow');
|
||||
|
||||
$view->addProperty('R', 'Red');
|
||||
$view->addProperty('O', 'Orange');
|
||||
$view->addProperty('Y', 'Yellow');
|
||||
$view->addProperty('G', 'Green');
|
||||
$view->addProperty('B', 'Blue');
|
||||
$view->addProperty('I', 'Indigo');
|
||||
$view->addProperty('V', 'Violet');
|
||||
|
||||
$view->addSectionHeader('Haiku About Pasta');
|
||||
|
||||
$view->addTextContent(
|
||||
'this is a pasta<br />'.
|
||||
'haiku. it is very bad.<br />'.
|
||||
'what did you expect?');
|
||||
|
||||
$edge_cases_header = id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Edge Cases'));
|
||||
|
||||
$edge_cases_view = new PhabricatorPropertyListView();
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Description'),
|
||||
pht('These layouts test UI edge cases in the element. This block '.
|
||||
'tests wrapping and overflow behavior.'));
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('A Very Very Very Very Very Very Very Very Very Long Property Label'),
|
||||
pht('This property label and property value are quite long. They '.
|
||||
'demonstrate the wrapping behavior of the element, or lack thereof '.
|
||||
'if something terrible has happened.'));
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('AVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUnbrokenPropertyLabel'),
|
||||
pht('Thispropertylabelandpropertyvaluearequitelongandhave'.
|
||||
'nospacestheydemonstratetheoverflowbehavioroftheelement'.
|
||||
'orlackthereof.'));
|
||||
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Description'),
|
||||
pht('The next section is an empty text block.'));
|
||||
|
||||
$edge_cases_view->addTextContent('');
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Description'),
|
||||
pht('This section should have multiple properties with the same name.'));
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Joe'),
|
||||
pht('Smith'));
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Joe'),
|
||||
pht('Smith'));
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Joe'),
|
||||
pht('Smith'));
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Description'),
|
||||
pht('The next section shows adjacent section headers.'));
|
||||
|
||||
$edge_cases_view->addSectionHeader('Several');
|
||||
$edge_cases_view->addSectionHeader('Adjacent');
|
||||
$edge_cases_view->addSectionHeader('Section');
|
||||
$edge_cases_view->addSectionHeader('Headers');
|
||||
|
||||
$edge_cases_view->addProperty(
|
||||
pht('Description'),
|
||||
pht('The next section is several adjacent text blocks.'));
|
||||
|
||||
$edge_cases_view->addTextContent('Lorem');
|
||||
$edge_cases_view->addTextContent('ipsum');
|
||||
$edge_cases_view->addTextContent('dolor');
|
||||
$edge_cases_view->addTextContent('sit');
|
||||
$edge_cases_view->addTextContent('amet...');
|
||||
|
||||
return array(
|
||||
$view,
|
||||
$edge_cases_header,
|
||||
$edge_cases_view,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,16 @@ abstract class AphrontView {
|
|||
|
||||
protected $children = array();
|
||||
|
||||
protected function canAppendChild() {
|
||||
return true;
|
||||
}
|
||||
|
||||
final public function appendChild($child) {
|
||||
if (!$this->canAppendChild()) {
|
||||
$class = get_class($this);
|
||||
throw new Exception(
|
||||
"View '{$class}' does not support children.");
|
||||
}
|
||||
$this->children[] = $child;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -2,56 +2,69 @@
|
|||
|
||||
final class PhabricatorPropertyListView extends AphrontView {
|
||||
|
||||
private $properties = array();
|
||||
private $parts = array();
|
||||
|
||||
protected function canAppendChild() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
$current = array_pop($this->parts);
|
||||
|
||||
if (!$current || $current['type'] != 'property') {
|
||||
if ($current) {
|
||||
$this->parts[] = $current;
|
||||
}
|
||||
$current = array(
|
||||
'type' => 'property',
|
||||
'list' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$current['list'][] = array(
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
);
|
||||
|
||||
$this->parts[] = $current;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSectionHeader($name) {
|
||||
$this->parts[] = array(
|
||||
'type' => 'section',
|
||||
'name' => $name,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTextContent($content) {
|
||||
return $this->appendChild(
|
||||
phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-text-content',
|
||||
),
|
||||
$content));
|
||||
$this->parts[] = array(
|
||||
'type' => 'text',
|
||||
'content' => $content,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('phabricator-property-list-view-css');
|
||||
|
||||
$items = array();
|
||||
foreach ($this->properties as $key => $value) {
|
||||
$items[] = phutil_render_tag(
|
||||
'dt',
|
||||
array(
|
||||
'class' => 'phabricator-property-key',
|
||||
),
|
||||
phutil_escape_html($key));
|
||||
$items[] = phutil_render_tag(
|
||||
'dd',
|
||||
array(
|
||||
'class' => 'phabricator-property-value',
|
||||
),
|
||||
$this->renderSingleView($value));
|
||||
foreach ($this->parts as $part) {
|
||||
$type = $part['type'];
|
||||
switch ($type) {
|
||||
case 'property':
|
||||
$items[] = $this->renderPropertyPart($part);
|
||||
break;
|
||||
case 'section':
|
||||
$items[] = $this->renderSectionPart($part);
|
||||
break;
|
||||
case 'text':
|
||||
$items[] = $this->renderTextPart($part);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown part type '{$type}'!");
|
||||
}
|
||||
|
||||
$list = phutil_render_tag(
|
||||
'dl',
|
||||
array(
|
||||
),
|
||||
$this->renderSingleView($items));
|
||||
|
||||
$content = $this->renderChildren();
|
||||
if (strlen($content)) {
|
||||
$content = phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-content',
|
||||
),
|
||||
$content);
|
||||
}
|
||||
|
||||
return phutil_render_tag(
|
||||
|
@ -59,14 +72,61 @@ final class PhabricatorPropertyListView extends AphrontView {
|
|||
array(
|
||||
'class' => 'phabricator-property-list-view',
|
||||
),
|
||||
$list.
|
||||
// NOTE: We need this (which is basically a "clear: both;" div) to make
|
||||
// sure the property list is taller than the action list for objects with
|
||||
// few properties but many actions. Otherwise, the action list may
|
||||
// obscure the document content.
|
||||
'<div class="phabriator-property-list-view-end"></div>').
|
||||
$content;
|
||||
$this->renderSingleView($items));
|
||||
}
|
||||
|
||||
private function renderPropertyPart(array $part) {
|
||||
$items = array();
|
||||
foreach ($part['list'] as $spec) {
|
||||
$key = $spec['key'];
|
||||
$value = $spec['value'];
|
||||
|
||||
$items[] = phutil_render_tag(
|
||||
'dt',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-key',
|
||||
),
|
||||
phutil_escape_html($key));
|
||||
$items[] = phutil_render_tag(
|
||||
'dd',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-value',
|
||||
),
|
||||
$this->renderSingleView($value));
|
||||
}
|
||||
|
||||
$list = phutil_render_tag(
|
||||
'dl',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-properties',
|
||||
),
|
||||
$this->renderSingleView($items));
|
||||
|
||||
return phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-container',
|
||||
),
|
||||
$list.
|
||||
'<div class="phabriator-property-list-view-end"></div>');
|
||||
}
|
||||
|
||||
private function renderSectionPart(array $part) {
|
||||
return phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-section-header',
|
||||
),
|
||||
phutil_escape_html($part['name']));
|
||||
}
|
||||
|
||||
private function renderTextPart(array $part) {
|
||||
return phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-property-list-text-content',
|
||||
),
|
||||
$part['content']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,30 +5,34 @@
|
|||
.phabricator-property-list-view {
|
||||
border-color: #dbdbdb;
|
||||
border-style: solid;
|
||||
border-width: 1px 0;
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
|
||||
.phabricator-property-list-container {
|
||||
border-color: #dbdbdb;
|
||||
border-style: solid;
|
||||
border-width: 0 0 1px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.phabriator-property-list-view-end {
|
||||
clear: both;
|
||||
.device-desktop .phabricator-property-list-container {
|
||||
padding: 1em 0 0.5em;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-property-list-view {
|
||||
padding: 1em 0 0.75em;
|
||||
.device-tablet .phabricator-property-list-container,
|
||||
.device-phone .phabricator-property-list-container {
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-property-list-view,
|
||||
.device-phone .phabricator-property-list-view {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.phabricator-property-key {
|
||||
.phabricator-property-list-key {
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-property-key {
|
||||
width: 12%;
|
||||
.device-desktop .phabricator-property-list-key {
|
||||
width: 15%;
|
||||
margin-left: 1%;
|
||||
text-align: right;
|
||||
float: left;
|
||||
|
@ -36,35 +40,44 @@
|
|||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-property-key,
|
||||
.device-phone .phabricator-property-key {
|
||||
.device-tablet .phabricator-property-list-key,
|
||||
.device-phone .phabricator-property-list-key {
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
.phabricator-property-value {
|
||||
.phabricator-property-list-value {
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-property-value {
|
||||
width: 53%;
|
||||
.device-desktop .phabricator-property-list-value {
|
||||
width: 50%;
|
||||
margin-left: 1%;
|
||||
float: left;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-property-value,
|
||||
.device-phone .phabricator-property-value {
|
||||
.device-tablet .phabricator-property-list-value,
|
||||
.device-phone .phabricator-property-list-value {
|
||||
padding-left: 1.5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.phabricator-property-list-content {
|
||||
background: #fdfdfd;
|
||||
.phabriator-property-list-view-end {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.phabricator-property-list-section-header {
|
||||
background: #f0f0f0;
|
||||
color: #666666;
|
||||
padding: 4px 18px;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.phabricator-property-list-text-content {
|
||||
padding: 12px 18px;
|
||||
background: #fdfdfd;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
/* When we follow an action list view on the Desktop, move down 30px so the
|
||||
|
|
Loading…
Reference in a new issue