@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<?php
2
3final class PHUIPropertyListView extends AphrontView {
4
5 private $parts = array();
6 private $object;
7 private $invokedWillRenderEvent;
8 private $actionList = null;
9 private $classes = array();
10 private $stacked;
11
12 const ICON_SUMMARY = 'fa-align-left';
13 const ICON_TESTPLAN = 'fa-file-text-o';
14
15 protected function canAppendChild() {
16 return false;
17 }
18
19 public function setObject($object) {
20 $this->object = $object;
21 return $this;
22 }
23
24 public function setActionList(PhabricatorActionListView $list) {
25 $this->actionList = $list;
26 return $this;
27 }
28
29 public function getActionList() {
30 return $this->actionList;
31 }
32
33 public function setStacked($stacked) {
34 $this->stacked = $stacked;
35 return $this;
36 }
37
38 public function addClass($class) {
39 $this->classes[] = $class;
40 return $this;
41 }
42
43 public function addProperty($key, $value) {
44 $current = array_pop($this->parts);
45
46 if (!$current || $current['type'] != 'property') {
47 if ($current) {
48 $this->parts[] = $current;
49 }
50 $current = array(
51 'type' => 'property',
52 'list' => array(),
53 );
54 }
55
56 $current['list'][] = array(
57 'key' => $key,
58 'value' => $value,
59 );
60
61 $this->parts[] = $current;
62 return $this;
63 }
64
65 public function addSectionHeader($name, $icon = null) {
66 $this->parts[] = array(
67 'type' => 'section',
68 'name' => $name,
69 'icon' => $icon,
70 );
71 return $this;
72 }
73
74 public function addTextContent($content) {
75 $this->parts[] = array(
76 'type' => 'text',
77 'content' => $content,
78 );
79 return $this;
80 }
81
82 public function addRawContent($content) {
83 $this->parts[] = array(
84 'type' => 'raw',
85 'content' => $content,
86 );
87 return $this;
88 }
89
90 public function addImageContent($content) {
91 $this->parts[] = array(
92 'type' => 'image',
93 'content' => $content,
94 );
95 return $this;
96 }
97
98 public function invokeWillRenderEvent() {
99 if ($this->object && $this->getUser() && !$this->invokedWillRenderEvent) {
100 $event = new PhabricatorEvent(
101 PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES,
102 array(
103 'object' => $this->object,
104 'view' => $this,
105 ));
106 $event->setUser($this->getUser());
107 PhutilEventEngine::dispatchEvent($event);
108 }
109 $this->invokedWillRenderEvent = true;
110 }
111
112 /**
113 * Whether there are any properties to display in the list view.
114 * Useful to e.g. display an explanation via PHUIInfoView if false.
115 *
116 * @return bool
117 */
118 public function hasAnyProperties() {
119 $this->invokeWillRenderEvent();
120
121 if ($this->parts) {
122 return true;
123 }
124
125 return false;
126 }
127
128 public function render() {
129 $this->invokeWillRenderEvent();
130
131 require_celerity_resource('phui-property-list-view-css');
132
133 $items = array();
134
135 $parts = $this->parts;
136
137 // If we have an action list, make sure we render a property part, even
138 // if there are no properties. Otherwise, the action list won't render.
139 if ($this->actionList) {
140 $this->classes[] = 'phui-property-list-has-actions';
141 $have_property_part = false;
142 foreach ($this->parts as $part) {
143 if ($part['type'] == 'property') {
144 $have_property_part = true;
145 break;
146 }
147 }
148 if (!$have_property_part) {
149 $parts[] = array(
150 'type' => 'property',
151 'list' => array(),
152 );
153 }
154 }
155
156 foreach ($parts as $part) {
157 $type = $part['type'];
158 switch ($type) {
159 case 'property':
160 $items[] = $this->renderPropertyPart($part);
161 break;
162 case 'section':
163 $items[] = $this->renderSectionPart($part);
164 break;
165 case 'text':
166 case 'image':
167 $items[] = $this->renderTextPart($part);
168 break;
169 case 'raw':
170 $items[] = $this->renderRawPart($part);
171 break;
172 default:
173 throw new Exception(pht("Unknown part type '%s'!", $type));
174 }
175 }
176 $this->classes[] = 'phui-property-list-section';
177 $classes = implode(' ', $this->classes);
178
179 return phutil_tag(
180 'div',
181 array(
182 'class' => $classes,
183 ),
184 array(
185 $items,
186 ));
187 }
188
189 private function renderPropertyPart(array $part) {
190 $items = array();
191 foreach ($part['list'] as $spec) {
192 $key = $spec['key'];
193 $value = $spec['value'];
194
195 // NOTE: We append a space to each value to improve the behavior when the
196 // user double-clicks a property value (like a URI) to select it. Without
197 // the space, the label is also selected.
198
199 $items[] = phutil_tag(
200 'dt',
201 array(
202 'class' => 'phui-property-list-key',
203 ),
204 array($key, ' '));
205
206 $items[] = phutil_tag(
207 'dd',
208 array(
209 'class' => 'phui-property-list-value',
210 ),
211 array($value, ' '));
212 }
213
214 $stacked = '';
215 if ($this->stacked) {
216 $stacked = 'phui-property-list-stacked';
217 }
218
219 $list = phutil_tag(
220 'dl',
221 array(
222 'class' => 'phui-property-list-properties',
223 ),
224 $items);
225
226 $list = phutil_tag(
227 'div',
228 array(
229 'class' => 'phui-property-list-properties-wrap '.$stacked,
230 ),
231 array($list));
232
233 $action_list = null;
234 if ($this->actionList) {
235 $action_list = phutil_tag(
236 'div',
237 array(
238 'class' => 'phui-property-list-actions',
239 ),
240 $this->actionList);
241 $this->actionList = null;
242 }
243
244 return phutil_tag(
245 'div',
246 array(
247 'class' => 'phui-property-list-container grouped',
248 ),
249 array($action_list, $list));
250 }
251
252 private function renderSectionPart(array $part) {
253 $name = $part['name'];
254 if ($part['icon']) {
255 $icon = id(new PHUIIconView())
256 ->setIcon($part['icon'].' bluegrey');
257 $name = phutil_tag(
258 'span',
259 array(
260 'class' => 'phui-property-list-section-header-icon',
261 ),
262 array($icon, $name));
263 }
264
265 return phutil_tag(
266 'div',
267 array(
268 'class' => 'phui-property-list-section-header',
269 ),
270 $name);
271 }
272
273 private function renderTextPart(array $part) {
274 $classes = array();
275 $classes[] = 'phui-property-list-text-content';
276 if ($part['type'] == 'image') {
277 $classes[] = 'phui-property-list-image-content';
278 }
279 return phutil_tag(
280 'div',
281 array(
282 'class' => implode(' ', $classes),
283 ),
284 $part['content']);
285 }
286
287 private function renderRawPart(array $part) {
288 $classes = array();
289 $classes[] = 'phui-property-list-raw-content';
290 return phutil_tag(
291 'div',
292 array(
293 'class' => implode(' ', $classes),
294 ),
295 $part['content']);
296 }
297
298}