1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +01:00

Use the new "CurtainObjectRefList" UI element for subscribers

Summary:
Depends on D20966. Ref T13486. Curtains currently render subscribers in a plain text list, but the new ref list element is a good fit for this.

Also, improve the sorting and ordering behavior.

This makes the subscriber list take up a bit more space, but it should make it a lot easier to read at a glance.

Test Plan: Viewed object subscriber lists at varying limits and subscriber counts, saw sensible subscriber lists.

Maniphest Tasks: T13486

Differential Revision: https://secure.phabricator.com/D20967
This commit is contained in:
epriestley 2020-02-04 12:05:11 -08:00
parent 2a92fef879
commit 0e82bd024a
9 changed files with 286 additions and 25 deletions

View file

@ -146,7 +146,7 @@ return array(
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf',
'rsrc/css/phui/phui-curtain-object-ref-view.css' => 'e3331b60',
'rsrc/css/phui/phui-curtain-object-ref-view.css' => '12404744',
'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6',
'rsrc/css/phui/phui-document-pro.css' => 'b9613a10',
'rsrc/css/phui/phui-document-summary.css' => 'b068eed1',
@ -834,7 +834,7 @@ return array(
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
'phui-crumbs-view-css' => '614f43cf',
'phui-curtain-object-ref-view-css' => 'e3331b60',
'phui-curtain-object-ref-view-css' => '12404744',
'phui-curtain-view-css' => '68c5efb6',
'phui-document-summary-view-css' => 'b068eed1',
'phui-document-view-css' => '52b748a5',

View file

@ -2042,6 +2042,7 @@ phutil_register_library_map(array(
'PHUILauncherView' => 'view/phui/PHUILauncherView.php',
'PHUILeftRightExample' => 'applications/uiexample/examples/PHUILeftRightExample.php',
'PHUILeftRightView' => 'view/phui/PHUILeftRightView.php',
'PHUILinkView' => 'view/phui/PHUILinkView.php',
'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
'PHUIListView' => 'view/phui/PHUIListView.php',
@ -8242,6 +8243,7 @@ phutil_register_library_map(array(
'PHUILauncherView' => 'AphrontTagView',
'PHUILeftRightExample' => 'PhabricatorUIExample',
'PHUILeftRightView' => 'AphrontTagView',
'PHUILinkView' => 'AphrontTagView',
'PHUIListExample' => 'PhabricatorUIExample',
'PHUIListItemView' => 'AphrontTagView',
'PHUIListView' => 'AphrontTagView',

View file

@ -336,6 +336,7 @@ final class ManiphestTaskDetailController extends ManiphestController {
$curtain->addAction($relationship_submenu);
}
$viewer_phid = $viewer->getPHID();
$owner_phid = $task->getOwnerPHID();
$author_phid = $task->getAuthorPHID();
$handles = $viewer->loadHandles(array($owner_phid, $author_phid));
@ -346,7 +347,8 @@ final class ManiphestTaskDetailController extends ManiphestController {
if ($owner_phid) {
$assigned_ref = $assigned_refs->newObjectRefView()
->setHandle($handles[$owner_phid]);
->setHandle($handles[$owner_phid])
->setHighlighted($owner_phid === $viewer_phid);
}
$curtain->newPanel()
@ -358,7 +360,8 @@ final class ManiphestTaskDetailController extends ManiphestController {
$author_ref = $author_refs->newObjectRefView()
->setHandle($handles[$author_phid])
->setEpoch($task->getDateCreated());
->setEpoch($task->getDateCreated())
->setHighlighted($author_phid === $viewer_phid);
$curtain->newPanel()
->setHeaderText(pht('Authored By'))

View file

@ -15,25 +15,129 @@ final class PhabricatorSubscriptionsCurtainExtension
public function buildCurtainPanel($object) {
$viewer = $this->getViewer();
$viewer_phid = $viewer->getPHID();
$object_phid = $object->getPHID();
$max_handles = 100;
$max_visible = 8;
// TODO: We should limit the number of subscriber PHIDs we'll load, so
// we degrade gracefully when objects have thousands of subscribers.
$subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object_phid);
$subscriber_count = count($subscriber_phids);
$handles = $viewer->loadHandles($subscriber_phids);
$subscriber_phids = $this->sortSubscriberPHIDs(
$subscriber_phids,
null);
// TODO: This class can't accept a HandleList yet.
$handles = iterator_to_array($handles);
// If we have fewer subscribers than the maximum number of handles we're
// willing to load, load all the handles and then sort the list based on
// complete handle data.
$susbscribers_view = id(new SubscriptionListStringBuilder())
->setObjectPHID($object_phid)
->setHandles($handles)
->buildPropertyString();
// If we have too many PHIDs, we'll skip this step and accept a less
// useful ordering.
$handles = null;
if ($subscriber_count <= $max_handles) {
$handles = $viewer->loadHandles($subscriber_phids);
$subscriber_phids = $this->sortSubscriberPHIDs(
$subscriber_phids,
$handles);
}
// If we have more PHIDs to show than visible slots, slice the list.
if ($subscriber_count > $max_visible) {
$visible_phids = array_slice($subscriber_phids, 0, $max_visible - 1);
$show_all = true;
} else {
$visible_phids = $subscriber_phids;
$show_all = false;
}
// If we didn't load handles earlier because we had too many PHIDs,
// load them now.
if ($handles === null) {
$handles = $viewer->loadHandles($visible_phids);
}
$ref_list = id(new PHUICurtainObjectRefListView())
->setViewer($viewer)
->setEmptyMessage(pht('None'));
foreach ($visible_phids as $phid) {
$ref = $ref_list->newObjectRefView()
->setHandle($handles[$phid]);
if ($phid === $viewer_phid) {
$ref->setHighlighted(true);
}
}
if ($show_all) {
$view_all_uri = urisprintf(
'/subscriptions/list/%s/',
$object_phid);
$ref_list->newTailLink()
->setURI($view_all_uri)
->setText(pht('View All %d Subscriber(s)', $subscriber_count))
->setWorkflow(true);
}
return $this->newPanel()
->setHeaderText(pht('Subscribers'))
->setOrder(20000)
->appendChild($susbscribers_view);
->appendChild($ref_list);
}
private function sortSubscriberPHIDs(array $subscriber_phids, $handles) {
// Sort subscriber PHIDs with or without handle data. If we have handles,
// we can sort results more comprehensively.
$viewer = $this->getViewer();
$user_type = PhabricatorPeopleUserPHIDType::TYPECONST;
$viewer_phid = $viewer->getPHID();
$type_order_map = array(
PhabricatorPeopleUserPHIDType::TYPECONST => 0,
PhabricatorProjectProjectPHIDType::TYPECONST => 1,
PhabricatorOwnersPackagePHIDType::TYPECONST => 2,
);
$default_type_order = count($type_order_map);
$subscriber_map = array();
foreach ($subscriber_phids as $subscriber_phid) {
$is_viewer = ($viewer_phid === $subscriber_phid);
$subscriber_type = phid_get_type($subscriber_phid);
$type_order = idx($type_order_map, $subscriber_type, $default_type_order);
$sort_name = '';
$is_complete = false;
if ($handles) {
if (isset($handles[$subscriber_phid])) {
$handle = $handles[$subscriber_phid];
if ($handle->isComplete()) {
$is_complete = true;
$sort_name = $handle->getLinkName();
}
}
}
$subscriber_map[$subscriber_phid] = id(new PhutilSortVector())
->addInt($is_viewer ? 0 : 1)
->addInt($is_complete ? 0 : 1)
->addInt($type_order)
->addString($sort_name);
}
$subscriber_map = msortv($subscriber_map, 'getSelf');
return array_keys($subscriber_map);
}
}

View file

@ -1718,6 +1718,11 @@ final class PhabricatorUSEnglishTranslation
'then try again.',
),
'View All %d Subscriber(s)' => array(
'View Subscriber',
'View All %d Subscribers',
),
);
}

View file

@ -5,6 +5,7 @@ final class PHUICurtainObjectRefListView
private $refs = array();
private $emptyMessage;
private $tail = array();
protected function getTagAttributes() {
return array(
@ -20,18 +21,31 @@ final class PHUICurtainObjectRefListView
protected function getTagContent() {
$refs = $this->refs;
if (!$refs) {
if ($this->emptyMessage) {
return phutil_tag(
'div',
array(
'class' => 'phui-curtain-object-ref-list-view-empty',
),
$this->emptyMessage);
}
if (!$refs && ($this->emptyMessage !== null)) {
$view = phutil_tag(
'div',
array(
'class' => 'phui-curtain-object-ref-list-view-empty',
),
$this->emptyMessage);
} else {
$view = $refs;
}
return $refs;
$tail = null;
if ($this->tail) {
$tail = phutil_tag(
'div',
array(
'class' => 'phui-curtain-object-ref-list-view-tail',
),
$this->tail);
}
return array(
$view,
$tail,
);
}
public function newObjectRefView() {
@ -43,4 +57,12 @@ final class PHUICurtainObjectRefListView
return $ref_view;
}
public function newTailLink() {
$link = new PHUILinkView();
$this->tail[] = $link;
return $link;
}
}

View file

@ -5,6 +5,7 @@ final class PHUICurtainObjectRefView
private $handle;
private $epoch;
private $highlighted;
public function setHandle(PhabricatorObjectHandle $handle) {
$this->handle = $handle;
@ -16,9 +17,22 @@ final class PHUICurtainObjectRefView
return $this;
}
public function setHighlighted($highlighted) {
$this->highlighted = $highlighted;
return $this;
}
protected function getTagAttributes() {
$classes = array();
$classes[] = 'phui-curtain-object-ref-view';
if ($this->highlighted) {
$classes[] = 'phui-curtain-object-ref-view-highlighted';
}
$classes = implode(' ', $classes);
return array(
'class' => 'phui-curtain-object-ref-view',
'class' => $classes,
);
}
@ -114,6 +128,11 @@ final class PHUICurtainObjectRefView
$image_uri = $this->getImageURI();
$target_uri = $this->getTargetURI();
$icon_view = null;
if ($image_uri == null) {
$icon_view = $this->newIconView();
}
if ($image_uri !== null) {
$image_view = javelin_tag(
'a',
@ -122,6 +141,15 @@ final class PHUICurtainObjectRefView
'href' => $target_uri,
'aural' => false,
));
} else if ($icon_view !== null) {
$image_view = javelin_tag(
'a',
array(
'href' => $target_uri,
'class' => 'phui-curtain-object-ref-view-icon-image',
'aural' => false,
),
$icon_view);
} else {
$image_view = null;
}
@ -151,5 +179,16 @@ final class PHUICurtainObjectRefView
return $image_uri;
}
private function newIconView() {
$handle = $this->handle;
if ($handle) {
$icon_view = id(new PHUIIconView())
->setIcon($handle->getIcon());
}
return $icon_view;
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PHUILinkView
extends AphrontTagView {
private $uri;
private $text;
private $workflow;
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function getURI() {
return $this->uri;
}
public function setText($text) {
$this->text = $text;
return $this;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
protected function getTagName() {
return 'a';
}
protected function getTagAttributes() {
$sigil = array();
if ($this->workflow) {
$sigil[] = 'workflow';
}
return array(
'href' => $this->getURI(),
'sigil' => $sigil,
);
}
protected function getTagContent() {
return $this->text;
}
}

View file

@ -7,9 +7,14 @@
color: {$greytext};
}
.phui-curtain-object-ref-view {
padding: 4px 6px;
border-radius: 3px;
}
.phui-curtain-object-ref-view-image-cell {
min-width: 32px;
min-height: 32px;
padding-bottom: 24px;
}
.phui-curtain-object-ref-view-image-cell > a {
@ -18,6 +23,24 @@
background-size: 100%;
border-radius: 3px;
display: block;
position: absolute;
}
.phui-curtain-object-ref-view-image-cell .phui-icon-view {
font-size: 16px;
line-height: 16px;
vertical-align: middle;
text-align: center;
width: 24px;
height: 24px;
top: 3px;
display: block;
position: absolute;
color: #ffffff;
}
.phui-curtain-object-ref-view-icon-image {
background-color: {$backdrop};
}
.phui-curtain-object-ref-view-title-cell {
@ -26,7 +49,7 @@
overflow: hidden;
/* This is forcing "text-overflow: ellipsis" to actually work. */
max-width: 225px;
max-width: 210px;
}
.phui-curtain-object-ref-view-without-content >
@ -46,3 +69,16 @@
.phui-curtain-object-ref-view-epoch-cell {
color: {$greytext};
}
.phui-curtain-object-ref-list-view-tail {
text-align: center;
margin-top: 8px;
padding: 4px;
background: {$lightgreybackground};
border-top: 1px dashed {$thinblueborder};
box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.04);
}
.phui-curtain-object-ref-view-highlighted {
background: {$bluebackground};
}