@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.

Make token UI stronger and more consistent

Summary:
Ref T4100. Overall:

- Use token background color to communicate token type (blue = object, yellow = function, grey = disabled/closed, red = invalid).
- Use token icon color to make color choices consistent (specifically, use project icon colors in project tokens).
- For functions, use token icon to communicate function result type (e.g., viewer() has a user icon; members(...) has a group icon), since we don't need the icon to indicate "this is a function" anymore.

Test Plan:
{F374615}
{F374616}
{F374617}

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4100

Differential Revision: https://secure.phabricator.com/D12446

+210 -29
+2
src/__phutil_library_map__.php
··· 1206 1206 'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php', 1207 1207 'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php', 1208 1208 'PHUITimelineView' => 'view/phui/PHUITimelineView.php', 1209 + 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 1209 1210 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 1210 1211 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', 1211 1212 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', ··· 4489 4490 'PHUITimelineEventView' => 'AphrontView', 4490 4491 'PHUITimelineExample' => 'PhabricatorUIExample', 4491 4492 'PHUITimelineView' => 'AphrontView', 4493 + 'PHUITypeaheadExample' => 'PhabricatorUIExample', 4492 4494 'PHUIWorkboardView' => 'AphrontTagView', 4493 4495 'PHUIWorkpanelView' => 'AphrontTagView', 4494 4496 'PackageCreateMail' => 'PackageMail',
+1
src/applications/people/typeahead/PhabricatorViewerDatasource.php
··· 50 50 return $this->newFunctionResult() 51 51 ->setName(pht('Current Viewer')) 52 52 ->setPHID('viewer()') 53 + ->setIcon('fa-user') 53 54 ->setUnique(true); 54 55 } 55 56
+8
src/applications/phid/PhabricatorObjectHandle.php
··· 56 56 if ($this->tagColor) { 57 57 return $this->tagColor; 58 58 } 59 + 59 60 return 'blue'; 61 + } 62 + 63 + public function getIconColor() { 64 + if ($this->tagColor) { 65 + return $this->tagColor; 66 + } 67 + return null; 60 68 } 61 69 62 70 public function getTypeIcon() {
+1 -1
src/applications/project/typeahead/PhabricatorProjectDatasource.php
··· 63 63 ->setDisplayType('Project') 64 64 ->setURI('/tag/'.$proj->getPrimarySlug().'/') 65 65 ->setPHID($proj->getPHID()) 66 - ->setIcon($proj->getIcon().' bluegrey') 66 + ->setIcon($proj->getIcon().' '.$proj->getColor()) 67 67 ->setPriorityType('proj') 68 68 ->setClosed($closed); 69 69
+1
src/applications/project/typeahead/PhabricatorProjectMembersDatasource.php
··· 116 116 ->setDisplayName(pht('Members: %s', $project->getName())) 117 117 ->setURI('/tag/'.$project->getPrimarySlug().'/') 118 118 ->setPHID('members('.$project->getPHID().')') 119 + ->setIcon('fa-users') 119 120 ->setClosed($closed); 120 121 } 121 122
+4 -4
src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
··· 187 187 } 188 188 189 189 protected function newFunctionResult() { 190 - // TODO: Find a more consistent design. 191 190 return id(new PhabricatorTypeaheadResult()) 192 - ->setIcon('fa-magic indigo'); 191 + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION) 192 + ->setIcon('fa-asterisk'); 193 193 } 194 194 195 195 public function newInvalidToken($name) { 196 196 return id(new PhabricatorTypeaheadTokenView()) 197 - ->setKey(PhabricatorTypeaheadTokenView::KEY_INVALID) 198 197 ->setValue($name) 199 - ->setIcon('fa-exclamation-circle red'); 198 + ->setIcon('fa-exclamation-circle') 199 + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID); 200 200 } 201 201 202 202 /* -( Token Functions )---------------------------------------------------- */
+14
src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
··· 13 13 private $imageSprite; 14 14 private $icon; 15 15 private $closed; 16 + private $tokenType; 16 17 private $unique; 17 18 18 19 public function setIcon($icon) { ··· 91 92 return $this; 92 93 } 93 94 95 + public function setTokenType($type) { 96 + $this->tokenType = $type; 97 + return $this; 98 + } 99 + 100 + public function getTokenType() { 101 + if ($this->closed && !$this->tokenType) { 102 + return PhabricatorTypeaheadTokenView::TYPE_DISABLED; 103 + } 104 + return $this->tokenType; 105 + } 106 + 94 107 public function getSortKey() { 95 108 // Put unique results (special parameter functions) ahead of other 96 109 // results. ··· 116 129 $this->getIcon(), 117 130 $this->closed, 118 131 $this->imageSprite ? (string)$this->imageSprite : null, 132 + $this->tokenType, 119 133 $this->unique ? 1 : null, 120 134 ); 121 135 while (end($data) === null) {
+44 -6
src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php
··· 3 3 final class PhabricatorTypeaheadTokenView 4 4 extends AphrontTagView { 5 5 6 + const TYPE_OBJECT = 'object'; 7 + const TYPE_DISABLED = 'disabled'; 8 + const TYPE_FUNCTION = 'function'; 9 + const TYPE_INVALID = 'invalid'; 10 + 6 11 private $key; 7 12 private $icon; 8 13 private $inputName; 9 14 private $value; 10 - 11 - const KEY_INVALID = '<invalid>'; 15 + private $tokenType = self::TYPE_OBJECT; 12 16 13 17 public static function newFromTypeaheadResult( 14 18 PhabricatorTypeaheadResult $result) { ··· 16 20 return id(new PhabricatorTypeaheadTokenView()) 17 21 ->setKey($result->getPHID()) 18 22 ->setIcon($result->getIcon()) 19 - ->setValue($result->getDisplayName()); 23 + ->setValue($result->getDisplayName()) 24 + ->setTokenType($result->getTokenType()); 20 25 } 21 26 22 27 public static function newFromHandle( 23 28 PhabricatorObjectHandle $handle) { 24 29 25 - return id(new PhabricatorTypeaheadTokenView()) 30 + $token = id(new PhabricatorTypeaheadTokenView()) 26 31 ->setKey($handle->getPHID()) 27 32 ->setValue($handle->getFullName()) 28 - ->setIcon($handle->getIcon()); 33 + ->setIcon(rtrim($handle->getIcon().' '.$handle->getIconColor())); 34 + 35 + if ($handle->isDisabled() || 36 + $handle->getStatus() == PhabricatorObjectHandleStatus::STATUS_CLOSED) { 37 + $token->setTokenType(self::TYPE_DISABLED); 38 + } 39 + 40 + return $token; 29 41 } 30 42 31 43 public function setKey($key) { ··· 37 49 return $this->key; 38 50 } 39 51 52 + public function setTokenType($token_type) { 53 + $this->tokenType = $token_type; 54 + return $this; 55 + } 56 + 57 + public function getTokenType() { 58 + return $this->tokenType; 59 + } 60 + 40 61 public function setInputName($input_name) { 41 62 $this->inputName = $input_name; 42 63 return $this; ··· 69 90 } 70 91 71 92 protected function getTagAttributes() { 93 + $classes = array(); 94 + $classes[] = 'jx-tokenizer-token'; 95 + switch ($this->getTokenType()) { 96 + case self::TYPE_FUNCTION: 97 + $classes[] = 'jx-tokenizer-token-function'; 98 + break; 99 + case self::TYPE_INVALID: 100 + $classes[] = 'jx-tokenizer-token-invalid'; 101 + break; 102 + case self::TYPE_DISABLED: 103 + $classes[] = 'jx-tokenizer-token-disabled'; 104 + break; 105 + case self::TYPE_OBJECT: 106 + default: 107 + break; 108 + } 109 + 72 110 return array( 73 - 'class' => 'jx-tokenizer-token', 111 + 'class' => $classes, 74 112 ); 75 113 } 76 114
+57
src/applications/uiexample/examples/PHUITypeaheadExample.php
··· 1 + <?php 2 + 3 + final class PHUITypeaheadExample extends PhabricatorUIExample { 4 + 5 + public function getName() { 6 + return 'Typeaheads'; 7 + } 8 + 9 + public function getDescription() { 10 + return pht('Typeaheads, tokenizers and tokens.'); 11 + } 12 + 13 + public function renderExample() { 14 + 15 + $token_list = array(); 16 + 17 + $token_list[] = id(new PhabricatorTypeaheadTokenView()) 18 + ->setValue(pht('Normal Object')) 19 + ->setIcon('fa-user'); 20 + 21 + $token_list[] = id(new PhabricatorTypeaheadTokenView()) 22 + ->setValue(pht('Disabled Object')) 23 + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_DISABLED) 24 + ->setIcon('fa-user'); 25 + 26 + $token_list[] = id(new PhabricatorTypeaheadTokenView()) 27 + ->setValue(pht('Custom Object')) 28 + ->setIcon('fa-tag green'); 29 + 30 + $token_list[] = id(new PhabricatorTypeaheadTokenView()) 31 + ->setValue(pht('Function Token')) 32 + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION) 33 + ->setIcon('fa-users'); 34 + 35 + $token_list[] = id(new PhabricatorTypeaheadTokenView()) 36 + ->setValue(pht('Invalid Token')) 37 + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID) 38 + ->setIcon('fa-exclamation-circle'); 39 + 40 + 41 + $token_list = phutil_tag( 42 + 'div', 43 + array( 44 + 'class' => 'grouped', 45 + 'style' => 'padding: 8px', 46 + ), 47 + $token_list); 48 + 49 + $output = array(); 50 + 51 + $output[] = id(new PHUIObjectBoxView()) 52 + ->setHeaderText('Tokens') 53 + ->appendChild($token_list); 54 + 55 + return $output; 56 + } 57 + }
+12 -8
src/view/form/control/AphrontFormTokenizerControl.php
··· 51 51 } 52 52 53 53 $datasource = $this->datasource; 54 - $datasource->setViewer($this->getUser()); 54 + if ($datasource) { 55 + $datasource->setViewer($this->getUser()); 56 + } 55 57 56 58 $placeholder = null; 57 59 if (!strlen($this->placeholder)) { ··· 84 86 $token = $datasource->newInvalidToken($name); 85 87 } 86 88 87 - if ($token->getKey() == PhabricatorTypeaheadTokenView::KEY_INVALID) { 89 + $type = $token->getTokenType(); 90 + if ($type == PhabricatorTypeaheadTokenView::TYPE_INVALID) { 88 91 $token->setKey($value); 89 92 } 90 93 } ··· 117 120 118 121 if (!$this->disableBehavior) { 119 122 Javelin::initBehavior('aphront-basic-tokenizer', array( 120 - 'id' => $id, 121 - 'src' => $datasource_uri, 122 - 'value' => mpull($tokens, 'getValue', 'getKey'), 123 - 'icons' => mpull($tokens, 'getIcon', 'getKey'), 124 - 'limit' => $this->limit, 125 - 'username' => $username, 123 + 'id' => $id, 124 + 'src' => $datasource_uri, 125 + 'value' => mpull($tokens, 'getValue', 'getKey'), 126 + 'icons' => mpull($tokens, 'getIcon', 'getKey'), 127 + 'types' => mpull($tokens, 'getTokenType', 'getKey'), 128 + 'limit' => $this->limit, 129 + 'username' => $username, 126 130 'placeholder' => $placeholder, 127 131 'browseURI' => $browse_uri, 128 132 ));
+45
webroot/rsrc/css/aphront/tokenizer.css
··· 83 83 color: {$bluetext}; 84 84 } 85 85 86 + a.jx-tokenizer-token-function { 87 + border-color: {$sh-lightyellowborder}; 88 + background: {$sh-yellowbackground}; 89 + color: {$sh-yellowtext}; 90 + } 91 + 92 + a.jx-tokenizer-token-function:hover { 93 + border-color: {$sh-yellowborder}; 94 + background: {$lightyellow}; 95 + } 96 + 97 + .jx-tokenizer-token-function .phui-icon-view { 98 + color: {$sh-yellowicon}; 99 + } 100 + 101 + a.jx-tokenizer-token-disabled { 102 + border-color: {$sh-lightgreyborder}; 103 + background: {$sh-greybackground}; 104 + color: {$sh-greytext}; 105 + } 106 + 107 + a.jx-tokenizer-token-disabled:hover { 108 + border-color: {$sh-greyborder}; 109 + background: {$greybackground}; 110 + } 111 + 112 + .jx-tokenizer-token-disabled .phui-icon-view { 113 + color: {$sh-greyicon}; 114 + } 115 + 116 + a.jx-tokenizer-token-invalid { 117 + border-color: {$sh-lightredborder}; 118 + background: {$sh-redbackground}; 119 + color: {$sh-redtext}; 120 + } 121 + 122 + a.jx-tokenizer-token-invalid:hover { 123 + border-color: {$sh-redborder}; 124 + background: {$lightred}; 125 + } 126 + 127 + .jx-tokenizer-token-invalid .phui-icon-view { 128 + color: {$sh-redicon}; 129 + } 130 + 86 131 .tokenizer-result { 87 132 position: relative; 88 133 padding: 5px 8px 5px 28px;
+12 -6
webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
··· 348 348 }, '\u00d7'); // U+00D7 multiplication sign 349 349 350 350 var display_token = value; 351 - var render_callback = this.getRenderTokenCallback(); 352 - if (render_callback) { 353 - display_token = render_callback(value, key); 354 - } 355 351 356 - return JX.$N('a', { 352 + var attrs = { 357 353 className: 'jx-tokenizer-token', 358 354 sigil: 'token', 359 355 meta: {key: key} 360 - }, [display_token, input, remove]); 356 + }; 357 + var container = JX.$N('a', attrs); 358 + 359 + var render_callback = this.getRenderTokenCallback(); 360 + if (render_callback) { 361 + display_token = render_callback(value, key, container); 362 + } 363 + 364 + JX.DOM.setContent(container, [display_token, input, remove]); 365 + 366 + return container; 361 367 }, 362 368 363 369 getTokens : function() {
+9 -4
webroot/rsrc/js/core/Prefab.js
··· 172 172 173 173 var tokenizer = new JX.Tokenizer(root); 174 174 tokenizer.setTypeahead(typeahead); 175 - tokenizer.setRenderTokenCallback(function(value, key) { 175 + tokenizer.setRenderTokenCallback(function(value, key, container) { 176 176 var result = datasource.getResult(key); 177 177 178 178 var icon; 179 + var type; 179 180 if (result) { 180 181 icon = result.icon; 181 182 value = result.displayName; 183 + type = result.tokenType; 182 184 } else { 183 185 icon = config.icons[key]; 186 + type = config.types[key]; 184 187 } 185 188 186 189 if (icon) { 187 190 icon = JX.Prefab._renderIcon(icon); 188 191 } 189 192 190 - // TODO: Maybe we should render these closed tags in grey? Figure out 191 - // how we're going to use color. 193 + if (type) { 194 + JX.DOM.alterClass(container, 'jx-tokenizer-token-' + type, true); 195 + } 192 196 193 197 return [icon, value]; 194 198 }); ··· 288 292 closed: closed, 289 293 type: fields[5], 290 294 sprite: fields[10], 291 - unique: fields[11] || false 295 + tokenType: fields[11], 296 + unique: fields[12] || false 292 297 }; 293 298 }, 294 299