1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-30 02:32:42 +01:00

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
This commit is contained in:
epriestley 2015-04-17 07:00:43 -07:00
parent 845466b49b
commit 76448a75de
13 changed files with 210 additions and 29 deletions

View file

@ -1206,6 +1206,7 @@ phutil_register_library_map(array(
'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php', 'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php',
'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php', 'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php',
'PHUITimelineView' => 'view/phui/PHUITimelineView.php', 'PHUITimelineView' => 'view/phui/PHUITimelineView.php',
'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php',
'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php',
'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php',
'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php',
@ -4489,6 +4490,7 @@ phutil_register_library_map(array(
'PHUITimelineEventView' => 'AphrontView', 'PHUITimelineEventView' => 'AphrontView',
'PHUITimelineExample' => 'PhabricatorUIExample', 'PHUITimelineExample' => 'PhabricatorUIExample',
'PHUITimelineView' => 'AphrontView', 'PHUITimelineView' => 'AphrontView',
'PHUITypeaheadExample' => 'PhabricatorUIExample',
'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkboardView' => 'AphrontTagView',
'PHUIWorkpanelView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView',
'PackageCreateMail' => 'PackageMail', 'PackageCreateMail' => 'PackageMail',

View file

@ -50,6 +50,7 @@ final class PhabricatorViewerDatasource
return $this->newFunctionResult() return $this->newFunctionResult()
->setName(pht('Current Viewer')) ->setName(pht('Current Viewer'))
->setPHID('viewer()') ->setPHID('viewer()')
->setIcon('fa-user')
->setUnique(true); ->setUnique(true);
} }

View file

@ -56,9 +56,17 @@ final class PhabricatorObjectHandle
if ($this->tagColor) { if ($this->tagColor) {
return $this->tagColor; return $this->tagColor;
} }
return 'blue'; return 'blue';
} }
public function getIconColor() {
if ($this->tagColor) {
return $this->tagColor;
}
return null;
}
public function getTypeIcon() { public function getTypeIcon() {
if ($this->getPHIDType()) { if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeIcon(); return $this->getPHIDType()->getTypeIcon();

View file

@ -63,7 +63,7 @@ final class PhabricatorProjectDatasource
->setDisplayType('Project') ->setDisplayType('Project')
->setURI('/tag/'.$proj->getPrimarySlug().'/') ->setURI('/tag/'.$proj->getPrimarySlug().'/')
->setPHID($proj->getPHID()) ->setPHID($proj->getPHID())
->setIcon($proj->getIcon().' bluegrey') ->setIcon($proj->getIcon().' '.$proj->getColor())
->setPriorityType('proj') ->setPriorityType('proj')
->setClosed($closed); ->setClosed($closed);

View file

@ -116,6 +116,7 @@ final class PhabricatorProjectMembersDatasource
->setDisplayName(pht('Members: %s', $project->getName())) ->setDisplayName(pht('Members: %s', $project->getName()))
->setURI('/tag/'.$project->getPrimarySlug().'/') ->setURI('/tag/'.$project->getPrimarySlug().'/')
->setPHID('members('.$project->getPHID().')') ->setPHID('members('.$project->getPHID().')')
->setIcon('fa-users')
->setClosed($closed); ->setClosed($closed);
} }

View file

@ -187,16 +187,16 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
} }
protected function newFunctionResult() { protected function newFunctionResult() {
// TODO: Find a more consistent design.
return id(new PhabricatorTypeaheadResult()) return id(new PhabricatorTypeaheadResult())
->setIcon('fa-magic indigo'); ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setIcon('fa-asterisk');
} }
public function newInvalidToken($name) { public function newInvalidToken($name) {
return id(new PhabricatorTypeaheadTokenView()) return id(new PhabricatorTypeaheadTokenView())
->setKey(PhabricatorTypeaheadTokenView::KEY_INVALID)
->setValue($name) ->setValue($name)
->setIcon('fa-exclamation-circle red'); ->setIcon('fa-exclamation-circle')
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID);
} }
/* -( Token Functions )---------------------------------------------------- */ /* -( Token Functions )---------------------------------------------------- */

View file

@ -13,6 +13,7 @@ final class PhabricatorTypeaheadResult {
private $imageSprite; private $imageSprite;
private $icon; private $icon;
private $closed; private $closed;
private $tokenType;
private $unique; private $unique;
public function setIcon($icon) { public function setIcon($icon) {
@ -91,6 +92,18 @@ final class PhabricatorTypeaheadResult {
return $this; return $this;
} }
public function setTokenType($type) {
$this->tokenType = $type;
return $this;
}
public function getTokenType() {
if ($this->closed && !$this->tokenType) {
return PhabricatorTypeaheadTokenView::TYPE_DISABLED;
}
return $this->tokenType;
}
public function getSortKey() { public function getSortKey() {
// Put unique results (special parameter functions) ahead of other // Put unique results (special parameter functions) ahead of other
// results. // results.
@ -116,6 +129,7 @@ final class PhabricatorTypeaheadResult {
$this->getIcon(), $this->getIcon(),
$this->closed, $this->closed,
$this->imageSprite ? (string)$this->imageSprite : null, $this->imageSprite ? (string)$this->imageSprite : null,
$this->tokenType,
$this->unique ? 1 : null, $this->unique ? 1 : null,
); );
while (end($data) === null) { while (end($data) === null) {

View file

@ -3,12 +3,16 @@
final class PhabricatorTypeaheadTokenView final class PhabricatorTypeaheadTokenView
extends AphrontTagView { extends AphrontTagView {
const TYPE_OBJECT = 'object';
const TYPE_DISABLED = 'disabled';
const TYPE_FUNCTION = 'function';
const TYPE_INVALID = 'invalid';
private $key; private $key;
private $icon; private $icon;
private $inputName; private $inputName;
private $value; private $value;
private $tokenType = self::TYPE_OBJECT;
const KEY_INVALID = '<invalid>';
public static function newFromTypeaheadResult( public static function newFromTypeaheadResult(
PhabricatorTypeaheadResult $result) { PhabricatorTypeaheadResult $result) {
@ -16,16 +20,24 @@ final class PhabricatorTypeaheadTokenView
return id(new PhabricatorTypeaheadTokenView()) return id(new PhabricatorTypeaheadTokenView())
->setKey($result->getPHID()) ->setKey($result->getPHID())
->setIcon($result->getIcon()) ->setIcon($result->getIcon())
->setValue($result->getDisplayName()); ->setValue($result->getDisplayName())
->setTokenType($result->getTokenType());
} }
public static function newFromHandle( public static function newFromHandle(
PhabricatorObjectHandle $handle) { PhabricatorObjectHandle $handle) {
return id(new PhabricatorTypeaheadTokenView()) $token = id(new PhabricatorTypeaheadTokenView())
->setKey($handle->getPHID()) ->setKey($handle->getPHID())
->setValue($handle->getFullName()) ->setValue($handle->getFullName())
->setIcon($handle->getIcon()); ->setIcon(rtrim($handle->getIcon().' '.$handle->getIconColor()));
if ($handle->isDisabled() ||
$handle->getStatus() == PhabricatorObjectHandleStatus::STATUS_CLOSED) {
$token->setTokenType(self::TYPE_DISABLED);
}
return $token;
} }
public function setKey($key) { public function setKey($key) {
@ -37,6 +49,15 @@ final class PhabricatorTypeaheadTokenView
return $this->key; return $this->key;
} }
public function setTokenType($token_type) {
$this->tokenType = $token_type;
return $this;
}
public function getTokenType() {
return $this->tokenType;
}
public function setInputName($input_name) { public function setInputName($input_name) {
$this->inputName = $input_name; $this->inputName = $input_name;
return $this; return $this;
@ -69,8 +90,25 @@ final class PhabricatorTypeaheadTokenView
} }
protected function getTagAttributes() { protected function getTagAttributes() {
$classes = array();
$classes[] = 'jx-tokenizer-token';
switch ($this->getTokenType()) {
case self::TYPE_FUNCTION:
$classes[] = 'jx-tokenizer-token-function';
break;
case self::TYPE_INVALID:
$classes[] = 'jx-tokenizer-token-invalid';
break;
case self::TYPE_DISABLED:
$classes[] = 'jx-tokenizer-token-disabled';
break;
case self::TYPE_OBJECT:
default:
break;
}
return array( return array(
'class' => 'jx-tokenizer-token', 'class' => $classes,
); );
} }

View file

@ -0,0 +1,57 @@
<?php
final class PHUITypeaheadExample extends PhabricatorUIExample {
public function getName() {
return 'Typeaheads';
}
public function getDescription() {
return pht('Typeaheads, tokenizers and tokens.');
}
public function renderExample() {
$token_list = array();
$token_list[] = id(new PhabricatorTypeaheadTokenView())
->setValue(pht('Normal Object'))
->setIcon('fa-user');
$token_list[] = id(new PhabricatorTypeaheadTokenView())
->setValue(pht('Disabled Object'))
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_DISABLED)
->setIcon('fa-user');
$token_list[] = id(new PhabricatorTypeaheadTokenView())
->setValue(pht('Custom Object'))
->setIcon('fa-tag green');
$token_list[] = id(new PhabricatorTypeaheadTokenView())
->setValue(pht('Function Token'))
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setIcon('fa-users');
$token_list[] = id(new PhabricatorTypeaheadTokenView())
->setValue(pht('Invalid Token'))
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID)
->setIcon('fa-exclamation-circle');
$token_list = phutil_tag(
'div',
array(
'class' => 'grouped',
'style' => 'padding: 8px',
),
$token_list);
$output = array();
$output[] = id(new PHUIObjectBoxView())
->setHeaderText('Tokens')
->appendChild($token_list);
return $output;
}
}

View file

@ -51,7 +51,9 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
} }
$datasource = $this->datasource; $datasource = $this->datasource;
if ($datasource) {
$datasource->setViewer($this->getUser()); $datasource->setViewer($this->getUser());
}
$placeholder = null; $placeholder = null;
if (!strlen($this->placeholder)) { if (!strlen($this->placeholder)) {
@ -84,7 +86,8 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
$token = $datasource->newInvalidToken($name); $token = $datasource->newInvalidToken($name);
} }
if ($token->getKey() == PhabricatorTypeaheadTokenView::KEY_INVALID) { $type = $token->getTokenType();
if ($type == PhabricatorTypeaheadTokenView::TYPE_INVALID) {
$token->setKey($value); $token->setKey($value);
} }
} }
@ -121,6 +124,7 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
'src' => $datasource_uri, 'src' => $datasource_uri,
'value' => mpull($tokens, 'getValue', 'getKey'), 'value' => mpull($tokens, 'getValue', 'getKey'),
'icons' => mpull($tokens, 'getIcon', 'getKey'), 'icons' => mpull($tokens, 'getIcon', 'getKey'),
'types' => mpull($tokens, 'getTokenType', 'getKey'),
'limit' => $this->limit, 'limit' => $this->limit,
'username' => $username, 'username' => $username,
'placeholder' => $placeholder, 'placeholder' => $placeholder,

View file

@ -83,6 +83,51 @@ a.jx-tokenizer-token:hover {
color: {$bluetext}; color: {$bluetext};
} }
a.jx-tokenizer-token-function {
border-color: {$sh-lightyellowborder};
background: {$sh-yellowbackground};
color: {$sh-yellowtext};
}
a.jx-tokenizer-token-function:hover {
border-color: {$sh-yellowborder};
background: {$lightyellow};
}
.jx-tokenizer-token-function .phui-icon-view {
color: {$sh-yellowicon};
}
a.jx-tokenizer-token-disabled {
border-color: {$sh-lightgreyborder};
background: {$sh-greybackground};
color: {$sh-greytext};
}
a.jx-tokenizer-token-disabled:hover {
border-color: {$sh-greyborder};
background: {$greybackground};
}
.jx-tokenizer-token-disabled .phui-icon-view {
color: {$sh-greyicon};
}
a.jx-tokenizer-token-invalid {
border-color: {$sh-lightredborder};
background: {$sh-redbackground};
color: {$sh-redtext};
}
a.jx-tokenizer-token-invalid:hover {
border-color: {$sh-redborder};
background: {$lightred};
}
.jx-tokenizer-token-invalid .phui-icon-view {
color: {$sh-redicon};
}
.tokenizer-result { .tokenizer-result {
position: relative; position: relative;
padding: 5px 8px 5px 28px; padding: 5px 8px 5px 28px;

View file

@ -348,16 +348,22 @@ JX.install('Tokenizer', {
}, '\u00d7'); // U+00D7 multiplication sign }, '\u00d7'); // U+00D7 multiplication sign
var display_token = value; var display_token = value;
var render_callback = this.getRenderTokenCallback();
if (render_callback) {
display_token = render_callback(value, key);
}
return JX.$N('a', { var attrs = {
className: 'jx-tokenizer-token', className: 'jx-tokenizer-token',
sigil: 'token', sigil: 'token',
meta: {key: key} meta: {key: key}
}, [display_token, input, remove]); };
var container = JX.$N('a', attrs);
var render_callback = this.getRenderTokenCallback();
if (render_callback) {
display_token = render_callback(value, key, container);
}
JX.DOM.setContent(container, [display_token, input, remove]);
return container;
}, },
getTokens : function() { getTokens : function() {

View file

@ -172,23 +172,27 @@ JX.install('Prefab', {
var tokenizer = new JX.Tokenizer(root); var tokenizer = new JX.Tokenizer(root);
tokenizer.setTypeahead(typeahead); tokenizer.setTypeahead(typeahead);
tokenizer.setRenderTokenCallback(function(value, key) { tokenizer.setRenderTokenCallback(function(value, key, container) {
var result = datasource.getResult(key); var result = datasource.getResult(key);
var icon; var icon;
var type;
if (result) { if (result) {
icon = result.icon; icon = result.icon;
value = result.displayName; value = result.displayName;
type = result.tokenType;
} else { } else {
icon = config.icons[key]; icon = config.icons[key];
type = config.types[key];
} }
if (icon) { if (icon) {
icon = JX.Prefab._renderIcon(icon); icon = JX.Prefab._renderIcon(icon);
} }
// TODO: Maybe we should render these closed tags in grey? Figure out if (type) {
// how we're going to use color. JX.DOM.alterClass(container, 'jx-tokenizer-token-' + type, true);
}
return [icon, value]; return [icon, value];
}); });
@ -288,7 +292,8 @@ JX.install('Prefab', {
closed: closed, closed: closed,
type: fields[5], type: fields[5],
sprite: fields[10], sprite: fields[10],
unique: fields[11] || false tokenType: fields[11],
unique: fields[12] || false
}; };
}, },