@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+217 -51
+85 -1
src/applications/uiexample/examples/PhabricatorPropertyListExample.php
··· 35 35 'viverra. Nunc tempus tempor quam id iaculis. Maecenas lectus '. 36 36 'velit, aliquam et consequat quis, tincidunt id dolor.'); 37 37 38 - return $view; 38 + 39 + $view->addSectionHeader('Colors of the Rainbow'); 40 + 41 + $view->addProperty('R', 'Red'); 42 + $view->addProperty('O', 'Orange'); 43 + $view->addProperty('Y', 'Yellow'); 44 + $view->addProperty('G', 'Green'); 45 + $view->addProperty('B', 'Blue'); 46 + $view->addProperty('I', 'Indigo'); 47 + $view->addProperty('V', 'Violet'); 48 + 49 + $view->addSectionHeader('Haiku About Pasta'); 50 + 51 + $view->addTextContent( 52 + 'this is a pasta<br />'. 53 + 'haiku. it is very bad.<br />'. 54 + 'what did you expect?'); 55 + 56 + $edge_cases_header = id(new PhabricatorHeaderView()) 57 + ->setHeader(pht('Edge Cases')); 58 + 59 + $edge_cases_view = new PhabricatorPropertyListView(); 60 + 61 + $edge_cases_view->addProperty( 62 + pht('Description'), 63 + pht('These layouts test UI edge cases in the element. This block '. 64 + 'tests wrapping and overflow behavior.')); 65 + 66 + $edge_cases_view->addProperty( 67 + pht('A Very Very Very Very Very Very Very Very Very Long Property Label'), 68 + pht('This property label and property value are quite long. They '. 69 + 'demonstrate the wrapping behavior of the element, or lack thereof '. 70 + 'if something terrible has happened.')); 71 + 72 + $edge_cases_view->addProperty( 73 + pht('AVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUnbrokenPropertyLabel'), 74 + pht('Thispropertylabelandpropertyvaluearequitelongandhave'. 75 + 'nospacestheydemonstratetheoverflowbehavioroftheelement'. 76 + 'orlackthereof.')); 77 + 78 + 79 + $edge_cases_view->addProperty( 80 + pht('Description'), 81 + pht('The next section is an empty text block.')); 82 + 83 + $edge_cases_view->addTextContent(''); 84 + 85 + $edge_cases_view->addProperty( 86 + pht('Description'), 87 + pht('This section should have multiple properties with the same name.')); 88 + 89 + $edge_cases_view->addProperty( 90 + pht('Joe'), 91 + pht('Smith')); 92 + $edge_cases_view->addProperty( 93 + pht('Joe'), 94 + pht('Smith')); 95 + $edge_cases_view->addProperty( 96 + pht('Joe'), 97 + pht('Smith')); 98 + 99 + $edge_cases_view->addProperty( 100 + pht('Description'), 101 + pht('The next section shows adjacent section headers.')); 102 + 103 + $edge_cases_view->addSectionHeader('Several'); 104 + $edge_cases_view->addSectionHeader('Adjacent'); 105 + $edge_cases_view->addSectionHeader('Section'); 106 + $edge_cases_view->addSectionHeader('Headers'); 107 + 108 + $edge_cases_view->addProperty( 109 + pht('Description'), 110 + pht('The next section is several adjacent text blocks.')); 111 + 112 + $edge_cases_view->addTextContent('Lorem'); 113 + $edge_cases_view->addTextContent('ipsum'); 114 + $edge_cases_view->addTextContent('dolor'); 115 + $edge_cases_view->addTextContent('sit'); 116 + $edge_cases_view->addTextContent('amet...'); 117 + 118 + return array( 119 + $view, 120 + $edge_cases_header, 121 + $edge_cases_view, 122 + ); 39 123 } 40 124 }
+9
src/view/AphrontView.php
··· 4 4 5 5 protected $children = array(); 6 6 7 + protected function canAppendChild() { 8 + return true; 9 + } 10 + 7 11 final public function appendChild($child) { 12 + if (!$this->canAppendChild()) { 13 + $class = get_class($this); 14 + throw new Exception( 15 + "View '{$class}' does not support children."); 16 + } 8 17 $this->children[] = $child; 9 18 return $this; 10 19 }
+89 -29
src/view/layout/PhabricatorPropertyListView.php
··· 2 2 3 3 final class PhabricatorPropertyListView extends AphrontView { 4 4 5 - private $properties = array(); 5 + private $parts = array(); 6 + 7 + protected function canAppendChild() { 8 + return false; 9 + } 6 10 7 11 public function addProperty($key, $value) { 8 - $this->properties[$key] = $value; 12 + $current = array_pop($this->parts); 13 + 14 + if (!$current || $current['type'] != 'property') { 15 + if ($current) { 16 + $this->parts[] = $current; 17 + } 18 + $current = array( 19 + 'type' => 'property', 20 + 'list' => array(), 21 + ); 22 + } 23 + 24 + $current['list'][] = array( 25 + 'key' => $key, 26 + 'value' => $value, 27 + ); 28 + 29 + $this->parts[] = $current; 30 + return $this; 31 + } 32 + 33 + public function addSectionHeader($name) { 34 + $this->parts[] = array( 35 + 'type' => 'section', 36 + 'name' => $name, 37 + ); 9 38 return $this; 10 39 } 11 40 12 41 public function addTextContent($content) { 13 - return $this->appendChild( 14 - phutil_render_tag( 15 - 'div', 16 - array( 17 - 'class' => 'phabricator-property-list-text-content', 18 - ), 19 - $content)); 42 + $this->parts[] = array( 43 + 'type' => 'text', 44 + 'content' => $content, 45 + ); 46 + return $this; 20 47 } 21 48 22 49 public function render() { 23 50 require_celerity_resource('phabricator-property-list-view-css'); 24 51 25 52 $items = array(); 26 - foreach ($this->properties as $key => $value) { 53 + foreach ($this->parts as $part) { 54 + $type = $part['type']; 55 + switch ($type) { 56 + case 'property': 57 + $items[] = $this->renderPropertyPart($part); 58 + break; 59 + case 'section': 60 + $items[] = $this->renderSectionPart($part); 61 + break; 62 + case 'text': 63 + $items[] = $this->renderTextPart($part); 64 + break; 65 + default: 66 + throw new Exception("Unknown part type '{$type}'!"); 67 + } 68 + } 69 + 70 + return phutil_render_tag( 71 + 'div', 72 + array( 73 + 'class' => 'phabricator-property-list-view', 74 + ), 75 + $this->renderSingleView($items)); 76 + } 77 + 78 + private function renderPropertyPart(array $part) { 79 + $items = array(); 80 + foreach ($part['list'] as $spec) { 81 + $key = $spec['key']; 82 + $value = $spec['value']; 83 + 27 84 $items[] = phutil_render_tag( 28 85 'dt', 29 86 array( 30 - 'class' => 'phabricator-property-key', 87 + 'class' => 'phabricator-property-list-key', 31 88 ), 32 89 phutil_escape_html($key)); 33 90 $items[] = phutil_render_tag( 34 91 'dd', 35 92 array( 36 - 'class' => 'phabricator-property-value', 93 + 'class' => 'phabricator-property-list-value', 37 94 ), 38 95 $this->renderSingleView($value)); 39 96 } ··· 41 98 $list = phutil_render_tag( 42 99 'dl', 43 100 array( 101 + 'class' => 'phabricator-property-list-properties', 44 102 ), 45 103 $this->renderSingleView($items)); 46 104 47 - $content = $this->renderChildren(); 48 - if (strlen($content)) { 49 - $content = phutil_render_tag( 50 - 'div', 51 - array( 52 - 'class' => 'phabricator-property-list-content', 53 - ), 54 - $content); 55 - } 105 + return phutil_render_tag( 106 + 'div', 107 + array( 108 + 'class' => 'phabricator-property-list-container', 109 + ), 110 + $list. 111 + '<div class="phabriator-property-list-view-end"></div>'); 112 + } 56 113 114 + private function renderSectionPart(array $part) { 57 115 return phutil_render_tag( 58 116 'div', 59 117 array( 60 - 'class' => 'phabricator-property-list-view', 118 + 'class' => 'phabricator-property-list-section-header', 61 119 ), 62 - $list. 63 - // NOTE: We need this (which is basically a "clear: both;" div) to make 64 - // sure the property list is taller than the action list for objects with 65 - // few properties but many actions. Otherwise, the action list may 66 - // obscure the document content. 67 - '<div class="phabriator-property-list-view-end"></div>'). 68 - $content; 120 + phutil_escape_html($part['name'])); 69 121 } 70 122 123 + private function renderTextPart(array $part) { 124 + return phutil_render_tag( 125 + 'div', 126 + array( 127 + 'class' => 'phabricator-property-list-text-content', 128 + ), 129 + $part['content']); 130 + } 71 131 72 132 }
+34 -21
webroot/rsrc/css/layout/phabricator-property-list-view.css
··· 5 5 .phabricator-property-list-view { 6 6 border-color: #dbdbdb; 7 7 border-style: solid; 8 - border-width: 1px 0; 9 - background-color: #f9f9f9; 8 + border-width: 1px 0 0 0; 10 9 } 11 10 12 - .phabriator-property-list-view-end { 13 - clear: both; 11 + .phabricator-property-list-container { 12 + border-color: #dbdbdb; 13 + border-style: solid; 14 + border-width: 0 0 1px; 15 + background-color: #f9f9f9; 14 16 } 15 17 16 - .device-desktop .phabricator-property-list-view { 17 - padding: 1em 0 0.75em; 18 + .device-desktop .phabricator-property-list-container { 19 + padding: 1em 0 0.5em; 18 20 } 19 21 20 - .device-tablet .phabricator-property-list-view, 21 - .device-phone .phabricator-property-list-view { 22 - padding: .5em; 22 + .device-tablet .phabricator-property-list-container, 23 + .device-phone .phabricator-property-list-container { 24 + padding: .5em 0; 23 25 } 24 26 25 - .phabricator-property-key { 27 + .phabricator-property-list-key { 26 28 color: #333333; 27 29 font-weight: bold; 30 + overflow: hidden; 31 + white-space: nowrap; 28 32 } 29 33 30 - .device-desktop .phabricator-property-key { 31 - width: 12%; 34 + .device-desktop .phabricator-property-list-key { 35 + width: 15%; 32 36 margin-left: 1%; 33 37 text-align: right; 34 38 float: left; ··· 36 40 margin-bottom: .5em; 37 41 } 38 42 39 - .device-tablet .phabricator-property-key, 40 - .device-phone .phabricator-property-key { 43 + .device-tablet .phabricator-property-list-key, 44 + .device-phone .phabricator-property-list-key { 41 45 padding-left: .5em; 42 46 } 43 47 44 - .phabricator-property-value { 48 + .phabricator-property-list-value { 45 49 color: #333333; 50 + overflow: hidden; 46 51 } 47 52 48 - .device-desktop .phabricator-property-value { 49 - width: 53%; 53 + .device-desktop .phabricator-property-list-value { 54 + width: 50%; 50 55 margin-left: 1%; 51 56 float: left; 52 57 margin-bottom: .5em; 53 58 } 54 59 55 - .device-tablet .phabricator-property-value, 56 - .device-phone .phabricator-property-value { 60 + .device-tablet .phabricator-property-list-value, 61 + .device-phone .phabricator-property-list-value { 57 62 padding-left: 1.5em; 58 63 margin-bottom: .5em; 59 64 } 60 65 61 - .phabricator-property-list-content { 62 - background: #fdfdfd; 66 + .phabriator-property-list-view-end { 67 + clear: both; 68 + } 69 + 70 + .phabricator-property-list-section-header { 71 + background: #f0f0f0; 72 + color: #666666; 73 + padding: 4px 18px; 63 74 border-bottom: 1px solid #dbdbdb; 64 75 } 65 76 66 77 .phabricator-property-list-text-content { 67 78 padding: 12px 18px; 79 + background: #fdfdfd; 80 + border-bottom: 1px solid #dbdbdb; 68 81 } 69 82 70 83 /* When we follow an action list view on the Desktop, move down 30px so the