1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 16:52:41 +01:00

Provide result type information in tokenizers

Summary:
Ref T1279. The new dual-mode user/project tokenizers are a bit disorienting. Provide content type hints.

Very open to any suggestions here, most of this patch is just getting the right data in the right places. We can change things up pretty easily.

  - I like the little icons in the tokens themselves, I think they look good and are useful.
  - I'm less sold on the '(Project)' thing I did in the dropdown. We can easily make this richer if you have thoughts on it -- we could put icons in the left column maybe? Or right-justify the types?
  - I made it always sort users above projects.

Test Plan: See screenshot.

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: chad, aran, carl

Maniphest Tasks: T4420, T1279

Differential Revision: https://secure.phabricator.com/D7250
This commit is contained in:
epriestley 2014-02-14 10:23:56 -08:00
parent 75c4a185a9
commit 3c19f363bf
12 changed files with 133 additions and 43 deletions

View file

@ -7,14 +7,14 @@
return array( return array(
'names' => 'names' =>
array( array(
'core.pkg.css' => '607946ba', 'core.pkg.css' => 'd0936e05',
'core.pkg.js' => 'c7854cc5', 'core.pkg.js' => '51eb4e66',
'darkconsole.pkg.js' => 'ca8671ce', 'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '6aef439e', 'differential.pkg.css' => '6aef439e',
'differential.pkg.js' => '322ea941', 'differential.pkg.js' => '322ea941',
'diffusion.pkg.css' => '3783278d', 'diffusion.pkg.css' => '3783278d',
'diffusion.pkg.js' => '7b51e80a', 'diffusion.pkg.js' => '7b51e80a',
'javelin.pkg.js' => '6d430a66', 'javelin.pkg.js' => '896bb02e',
'maniphest.pkg.css' => 'f1887d71', 'maniphest.pkg.css' => 'f1887d71',
'maniphest.pkg.js' => '1e8f11af', 'maniphest.pkg.js' => '1e8f11af',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
@ -33,7 +33,7 @@ return array(
'rsrc/css/aphront/phabricator-nav-view.css' => 'd0d4a509', 'rsrc/css/aphront/phabricator-nav-view.css' => 'd0d4a509',
'rsrc/css/aphront/request-failure-view.css' => 'da14df31', 'rsrc/css/aphront/request-failure-view.css' => 'da14df31',
'rsrc/css/aphront/table-view.css' => '92a719ca', 'rsrc/css/aphront/table-view.css' => '92a719ca',
'rsrc/css/aphront/tokenizer.css' => 'd888465e', 'rsrc/css/aphront/tokenizer.css' => '8e7ae263',
'rsrc/css/aphront/tooltip.css' => '9c90229d', 'rsrc/css/aphront/tooltip.css' => '9c90229d',
'rsrc/css/aphront/transaction.css' => 'ce491938', 'rsrc/css/aphront/transaction.css' => 'ce491938',
'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/two-column.css' => '16ab3ad2',
@ -207,7 +207,7 @@ return array(
'rsrc/externals/javelin/lib/__tests__/URI.js' => 'ece3ddb3', 'rsrc/externals/javelin/lib/__tests__/URI.js' => 'ece3ddb3',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => 'c1d75ee6', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => 'c1d75ee6',
'rsrc/externals/javelin/lib/behavior.js' => '8a3ed18b', 'rsrc/externals/javelin/lib/behavior.js' => '8a3ed18b',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'e9e18227', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'e7c21fb3',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'c22f4c01', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'c22f4c01',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '5f850b5c', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '5f850b5c',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => 'dbd9cd11', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => 'dbd9cd11',
@ -431,7 +431,7 @@ return array(
'rsrc/js/core/KeyboardShortcutManager.js' => 'ad7a69ca', 'rsrc/js/core/KeyboardShortcutManager.js' => 'ad7a69ca',
'rsrc/js/core/MultirowRowManager.js' => 'e7076916', 'rsrc/js/core/MultirowRowManager.js' => 'e7076916',
'rsrc/js/core/Notification.js' => '95944043', 'rsrc/js/core/Notification.js' => '95944043',
'rsrc/js/core/Prefab.js' => '9eaf0bfa', 'rsrc/js/core/Prefab.js' => '6c78666a',
'rsrc/js/core/ShapedRequest.js' => 'dfa181a4', 'rsrc/js/core/ShapedRequest.js' => 'dfa181a4',
'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc', 'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc',
'rsrc/js/core/ToolTip.js' => '0a81ea29', 'rsrc/js/core/ToolTip.js' => '0a81ea29',
@ -489,7 +489,7 @@ return array(
'aphront-panel-view-css' => '5846dfa2', 'aphront-panel-view-css' => '5846dfa2',
'aphront-request-failure-view-css' => 'da14df31', 'aphront-request-failure-view-css' => 'da14df31',
'aphront-table-view-css' => '92a719ca', 'aphront-table-view-css' => '92a719ca',
'aphront-tokenizer-control-css' => 'd888465e', 'aphront-tokenizer-control-css' => '8e7ae263',
'aphront-tooltip-css' => '9c90229d', 'aphront-tooltip-css' => '9c90229d',
'aphront-two-column-view-css' => '16ab3ad2', 'aphront-two-column-view-css' => '16ab3ad2',
'aphront-typeahead-control-css' => '00c9a200', 'aphront-typeahead-control-css' => '00c9a200',
@ -641,7 +641,7 @@ return array(
'javelin-request' => '23f9bb8d', 'javelin-request' => '23f9bb8d',
'javelin-resource' => '356de121', 'javelin-resource' => '356de121',
'javelin-stratcom' => 'c293f7b9', 'javelin-stratcom' => 'c293f7b9',
'javelin-tokenizer' => 'e9e18227', 'javelin-tokenizer' => 'e7c21fb3',
'javelin-typeahead' => 'c22f4c01', 'javelin-typeahead' => 'c22f4c01',
'javelin-typeahead-composite-source' => 'dbd9cd11', 'javelin-typeahead-composite-source' => 'dbd9cd11',
'javelin-typeahead-normalizer' => '5f850b5c', 'javelin-typeahead-normalizer' => '5f850b5c',
@ -701,7 +701,7 @@ return array(
'phabricator-object-list-view-css' => '1a1ea560', 'phabricator-object-list-view-css' => '1a1ea560',
'phabricator-object-selector-css' => '029a133d', 'phabricator-object-selector-css' => '029a133d',
'phabricator-phtize' => 'd254d646', 'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '9eaf0bfa', 'phabricator-prefab' => '6c78666a',
'phabricator-profile-css' => '3a7e04ca', 'phabricator-profile-css' => '3a7e04ca',
'phabricator-project-tag-css' => '095c9404', 'phabricator-project-tag-css' => '095c9404',
'phabricator-remarkup-css' => 'ca7f2265', 'phabricator-remarkup-css' => 'ca7f2265',
@ -1205,6 +1205,19 @@ return array(
0 => 'javelin-install', 0 => 'javelin-install',
1 => 'javelin-util', 1 => 'javelin-util',
), ),
'6c78666a' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead',
4 => 'javelin-tokenizer',
5 => 'javelin-typeahead-preloaded-source',
6 => 'javelin-typeahead-ondemand-source',
7 => 'javelin-dom',
8 => 'javelin-stratcom',
9 => 'javelin-util',
),
'6ec125a0' => '6ec125a0' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -1348,6 +1361,10 @@ return array(
2 => 'javelin-stratcom', 2 => 'javelin-stratcom',
3 => 'javelin-uri', 3 => 'javelin-uri',
), ),
'8e7ae263' =>
array(
0 => 'aphront-typeahead-control-css',
),
'8f24abfc' => '8f24abfc' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -1414,19 +1431,6 @@ return array(
1 => 'javelin-vector', 1 => 'javelin-vector',
2 => 'javelin-dom', 2 => 'javelin-dom',
), ),
'9eaf0bfa' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead',
4 => 'javelin-tokenizer',
5 => 'javelin-typeahead-preloaded-source',
6 => 'javelin-typeahead-ondemand-source',
7 => 'javelin-dom',
8 => 'javelin-stratcom',
9 => 'javelin-util',
),
'9eb2cedb' => '9eb2cedb' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -1736,10 +1740,6 @@ return array(
3 => 'javelin-dom', 3 => 'javelin-dom',
4 => 'phabricator-keyboard-shortcut', 4 => 'phabricator-keyboard-shortcut',
), ),
'd888465e' =>
array(
0 => 'aphront-typeahead-control-css',
),
'd8ef8659' => 'd8ef8659' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -1815,6 +1815,13 @@ return array(
2 => 'javelin-dom', 2 => 'javelin-dom',
3 => 'javelin-util', 3 => 'javelin-util',
), ),
'e7c21fb3' =>
array(
0 => 'javelin-dom',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-install',
),
'e9b95df3' => 'e9b95df3' =>
array( array(
0 => 'javelin-install', 0 => 'javelin-install',
@ -1822,13 +1829,6 @@ return array(
2 => 'javelin-request', 2 => 'javelin-request',
3 => 'javelin-typeahead-source', 3 => 'javelin-typeahead-source',
), ),
'e9e18227' =>
array(
0 => 'javelin-dom',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-install',
),
'e9fdb5e5' => 'e9fdb5e5' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',

View file

@ -12,6 +12,10 @@ final class PhabricatorPeoplePHIDTypeUser extends PhabricatorPHIDType {
return pht('Phabricator User'); return pht('Phabricator User');
} }
public function getTypeIcon() {
return 'policy-all';
}
public function newObject() { public function newObject() {
return new PhabricatorUser(); return new PhabricatorUser();
} }

View file

@ -17,6 +17,13 @@ final class PhabricatorObjectHandle
private $objectName; private $objectName;
private $policyFiltered; private $policyFiltered;
public function getTypeIcon() {
if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeIcon();
}
return null;
}
public function setPolicyFiltered($policy_filered) { public function setPolicyFiltered($policy_filered) {
$this->policyFiltered = $policy_filered; $this->policyFiltered = $policy_filered;
return $this; return $this;

View file

@ -9,6 +9,10 @@ abstract class PhabricatorPHIDType {
return null; return null;
} }
public function getTypeIcon() {
return null;
}
/** /**
* Build a @{class:PhabricatorPolicyAwareQuery} to load objects of this type * Build a @{class:PhabricatorPolicyAwareQuery} to load objects of this type
* by PHID. * by PHID.

View file

@ -12,6 +12,10 @@ final class PhabricatorProjectPHIDTypeProject extends PhabricatorPHIDType {
return pht('Project'); return pht('Project');
} }
public function getTypeIcon() {
return 'policy-project';
}
public function newObject() { public function newObject() {
return new PhabricatorProject(); return new PhabricatorProject();
} }

View file

@ -208,7 +208,9 @@ final class PhabricatorTypeaheadCommonDatasourceController
->setName($user->getFullName()) ->setName($user->getFullName())
->setURI('/p/'.$user->getUsername()) ->setURI('/p/'.$user->getUsername())
->setPHID($user->getPHID()) ->setPHID($user->getPHID())
->setPriorityString($user->getUsername()); ->setPriorityString($user->getUsername())
->setIcon('policy-all')
->setPriorityType('user');
if ($need_rich_data) { if ($need_rich_data) {
$display_type = 'User'; $display_type = 'User';
@ -217,7 +219,6 @@ final class PhabricatorTypeaheadCommonDatasourceController
} }
$result->setDisplayType($display_type); $result->setDisplayType($display_type);
$result->setImageURI($handles[$user->getPHID()]->getImageURI()); $result->setImageURI($handles[$user->getPHID()]->getImageURI());
$result->setPriorityType('user');
} }
$results[] = $result; $results[] = $result;
} }
@ -292,7 +293,8 @@ final class PhabricatorTypeaheadCommonDatasourceController
->setName($proj->getName()) ->setName($proj->getName())
->setDisplayType("Project") ->setDisplayType("Project")
->setURI('/project/view/'.$proj->getID().'/') ->setURI('/project/view/'.$proj->getID().'/')
->setPHID($proj->getPHID()); ->setPHID($proj->getPHID())
->setIcon('policy-project');
$proj_result->setImageURI($proj->getProfileImageURI()); $proj_result->setImageURI($proj->getProfileImageURI());

View file

@ -10,6 +10,12 @@ final class PhabricatorTypeaheadResult {
private $displayType; private $displayType;
private $imageURI; private $imageURI;
private $priorityType; private $priorityType;
private $icon;
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setName($name) { public function setName($name) {
$this->name = $name; $this->name = $name;
@ -61,6 +67,7 @@ final class PhabricatorTypeaheadResult {
$this->displayType, $this->displayType,
$this->imageURI ? (string)$this->imageURI : null, $this->imageURI ? (string)$this->imageURI : null,
$this->priorityType, $this->priorityType,
$this->icon,
); );
while (end($data) === null) { while (end($data) === null) {
array_pop($data); array_pop($data);

View file

@ -12,6 +12,7 @@ final class AphrontTokenizerTemplateView extends AphrontView {
} }
public function setValue(array $value) { public function setValue(array $value) {
assert_instances_of($value, 'PhabricatorObjectHandle');
$this->value = $value; $this->value = $value;
return $this; return $this;
} }
@ -38,7 +39,10 @@ final class AphrontTokenizerTemplateView extends AphrontView {
$tokens = array(); $tokens = array();
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
$tokens[] = $this->renderToken($key, $value); $tokens[] = $this->renderToken(
$value->getPHID(),
$value->getFullName(),
$value->getTypeIcon());
} }
$input = javelin_tag( $input = javelin_tag(
@ -66,11 +70,22 @@ final class AphrontTokenizerTemplateView extends AphrontView {
$content); $content);
} }
private function renderToken($key, $value) { private function renderToken($key, $value, $icon) {
$input_name = $this->getName(); $input_name = $this->getName();
if ($input_name) { if ($input_name) {
$input_name .= '[]'; $input_name .= '[]';
} }
if ($icon) {
$value = array(
phutil_tag(
'span',
array(
'class' => 'phui-icon-view sprite-status status-'.$icon,
)),
$value);
}
return phutil_tag( return phutil_tag(
'a', 'a',
array( array(

View file

@ -36,7 +36,6 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
$values = nonempty($this->getValue(), array()); $values = nonempty($this->getValue(), array());
assert_instances_of($values, 'PhabricatorObjectHandle'); assert_instances_of($values, 'PhabricatorObjectHandle');
$values = mpull($values, 'getFullName', 'getPHID');
if ($this->getID()) { if ($this->getID()) {
$id = $this->getID(); $id = $this->getID();
@ -62,7 +61,8 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
Javelin::initBehavior('aphront-basic-tokenizer', array( Javelin::initBehavior('aphront-basic-tokenizer', array(
'id' => $id, 'id' => $id,
'src' => $this->datasource, 'src' => $this->datasource,
'value' => $values, 'value' => mpull($values, 'getFullName', 'getPHID'),
'icons' => mpull($values, 'getTypeIcon', 'getPHID'),
'limit' => $this->limit, 'limit' => $this->limit,
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'username' => $username, 'username' => $username,

View file

@ -74,3 +74,13 @@ a.jx-tokenizer-token:hover {
text-decoration: none; text-decoration: none;
background: #bbcef1; background: #bbcef1;
} }
.jx-tokenizer-token .phui-icon-view {
display: inline-block;
margin: 2px 4px -2px 0;
}
.tokenizer-result-type {
margin-left: 4px;
color: {$lightgreytext};
}

View file

@ -46,7 +46,8 @@ JX.install('Tokenizer', {
'change'], 'change'],
properties : { properties : {
limit : null limit : null,
renderTokenCallback : null
}, },
members : { members : {
@ -329,11 +330,17 @@ JX.install('Tokenizer', {
sigil: 'remove' sigil: 'remove'
}, '\u00d7'); // U+00D7 multiplication sign }, '\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', { return JX.$N('a', {
className: 'jx-tokenizer-token', className: 'jx-tokenizer-token',
sigil: 'token', sigil: 'token',
meta: {key: key} meta: {key: key}
}, [value, input, remove]); }, [display_token, input, remove]);
}, },
getTokens : function() { getTokens : function() {

View file

@ -99,6 +99,16 @@ JX.install('Prefab', {
return priority_hits[v.id] ? 1 : -1; return priority_hits[v.id] ? 1 : -1;
} }
// Sort users ahead of other result types.
if (u.priorityType != v.priorityType) {
if (u.priorityType == 'user') {
return -1;
}
if (v.priorityType == 'user') {
return 1;
}
}
return cmp(u, v); return cmp(u, v);
}); });
}; };
@ -111,7 +121,9 @@ JX.install('Prefab', {
display: object[0], display: object[0],
uri: object[1], uri: object[1],
id: object[2], id: object[2],
priority: object[3] priority: object[3],
priorityType: object[7],
icon: object[8]
}; };
}); });
@ -122,6 +134,24 @@ 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) {
var icon = datasource.getResult(key);
if (icon) {
icon = icon.icon;
} else {
icon = config.icons[key];
}
if (!icon) {
return value;
}
icon = JX.$N(
'span',
{className: 'phui-icon-view sprite-status status-' + icon});
return [icon, value];
});
if (config.placeholder) { if (config.placeholder) {
tokenizer.setPlaceholder(config.placeholder); tokenizer.setPlaceholder(config.placeholder);