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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,7 @@ final class PhabricatorTypeaheadResult {
private $imageSprite;
private $icon;
private $closed;
private $tokenType;
private $unique;
public function setIcon($icon) {
@ -91,6 +92,18 @@ final class PhabricatorTypeaheadResult {
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() {
// Put unique results (special parameter functions) ahead of other
// results.
@ -116,6 +129,7 @@ final class PhabricatorTypeaheadResult {
$this->getIcon(),
$this->closed,
$this->imageSprite ? (string)$this->imageSprite : null,
$this->tokenType,
$this->unique ? 1 : null,
);
while (end($data) === null) {

View file

@ -3,12 +3,16 @@
final class PhabricatorTypeaheadTokenView
extends AphrontTagView {
const TYPE_OBJECT = 'object';
const TYPE_DISABLED = 'disabled';
const TYPE_FUNCTION = 'function';
const TYPE_INVALID = 'invalid';
private $key;
private $icon;
private $inputName;
private $value;
const KEY_INVALID = '<invalid>';
private $tokenType = self::TYPE_OBJECT;
public static function newFromTypeaheadResult(
PhabricatorTypeaheadResult $result) {
@ -16,16 +20,24 @@ final class PhabricatorTypeaheadTokenView
return id(new PhabricatorTypeaheadTokenView())
->setKey($result->getPHID())
->setIcon($result->getIcon())
->setValue($result->getDisplayName());
->setValue($result->getDisplayName())
->setTokenType($result->getTokenType());
}
public static function newFromHandle(
PhabricatorObjectHandle $handle) {
return id(new PhabricatorTypeaheadTokenView())
$token = id(new PhabricatorTypeaheadTokenView())
->setKey($handle->getPHID())
->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) {
@ -37,6 +49,15 @@ final class PhabricatorTypeaheadTokenView
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) {
$this->inputName = $input_name;
return $this;
@ -69,8 +90,25 @@ final class PhabricatorTypeaheadTokenView
}
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(
'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->setViewer($this->getUser());
if ($datasource) {
$datasource->setViewer($this->getUser());
}
$placeholder = null;
if (!strlen($this->placeholder)) {
@ -84,7 +86,8 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
$token = $datasource->newInvalidToken($name);
}
if ($token->getKey() == PhabricatorTypeaheadTokenView::KEY_INVALID) {
$type = $token->getTokenType();
if ($type == PhabricatorTypeaheadTokenView::TYPE_INVALID) {
$token->setKey($value);
}
}
@ -117,12 +120,13 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
if (!$this->disableBehavior) {
Javelin::initBehavior('aphront-basic-tokenizer', array(
'id' => $id,
'src' => $datasource_uri,
'value' => mpull($tokens, 'getValue', 'getKey'),
'icons' => mpull($tokens, 'getIcon', 'getKey'),
'limit' => $this->limit,
'username' => $username,
'id' => $id,
'src' => $datasource_uri,
'value' => mpull($tokens, 'getValue', 'getKey'),
'icons' => mpull($tokens, 'getIcon', 'getKey'),
'types' => mpull($tokens, 'getTokenType', 'getKey'),
'limit' => $this->limit,
'username' => $username,
'placeholder' => $placeholder,
'browseURI' => $browse_uri,
));

View file

@ -83,6 +83,51 @@ a.jx-tokenizer-token:hover {
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 {
position: relative;
padding: 5px 8px 5px 28px;

View file

@ -348,16 +348,22 @@ JX.install('Tokenizer', {
}, '\u00d7'); // U+00D7 multiplication sign
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',
sigil: 'token',
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() {

View file

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