mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 16:52:41 +01:00
First pass at hovercard design
Summary: Refs T1048 - I'm pretty happy and happy to tell you Savepoint CR Everything's dummy right now. If you are testing locally, don't forget to edit the PHIDs. Or populate your own handles. Doesn't really matter. I'm mainly sending it in for the CSS, not the messy PHP code. Ignore the `margin: auto`, that's just for looking nice. Test Plan: UI Example » Hovercard Reviewers: epriestley, chad CC: aran, Korvin Maniphest Tasks: T1048 Differential Revision: https://secure.phabricator.com/D5519
This commit is contained in:
parent
b0d408c5d3
commit
5bd54e35bc
6 changed files with 355 additions and 0 deletions
|
@ -2973,6 +2973,15 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/css/layout/phabricator-header-view.css',
|
||||
),
|
||||
'phabricator-hovercard-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/061e66df/rsrc/css/layout/phabricator-hovercard-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
),
|
||||
'disk' => '/rsrc/css/layout/phabricator-hovercard-view.css',
|
||||
),
|
||||
'phabricator-jump-nav' =>
|
||||
array(
|
||||
'uri' => '/res/745c0e89/rsrc/css/application/directory/phabricator-jump-nav.css',
|
||||
|
|
|
@ -1001,6 +1001,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
||||
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
||||
'PhabricatorHovercardExample' => 'applications/uiexample/examples/PhabricatorHovercardExample.php',
|
||||
'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php',
|
||||
'PhabricatorIRCBot' => 'infrastructure/daemon/bot/PhabricatorIRCBot.php',
|
||||
'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php',
|
||||
'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorIRCProtocolHandler.php',
|
||||
|
@ -2677,6 +2679,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHeaderView' => 'AphrontView',
|
||||
'PhabricatorHelpController' => 'PhabricatorController',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||
'PhabricatorHovercardExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorHovercardView' => 'AphrontView',
|
||||
'PhabricatorIRCBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorIRCProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||
'PhabricatorIRCProtocolHandler' => 'PhabricatorBotHandler',
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorHovercardExample extends PhabricatorUIExample {
|
||||
|
||||
public function getName() {
|
||||
return 'Hovercard';
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return hsprintf('Use <tt>PhabricatorHovercardView</tt> to render '.
|
||||
'hovercards. Aren\'t I genius?');
|
||||
}
|
||||
|
||||
public function renderExample() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$elements = array();
|
||||
|
||||
$diff_handle = $this->createBasicDummyHandle(
|
||||
"Introduce cooler Differential Revisions",
|
||||
PhabricatorPHIDConstants::PHID_TYPE_DREV);
|
||||
|
||||
$panel = $this->createPanel("Differential Hovercard");
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
->setObjectHandle($diff_handle)
|
||||
->addField(pht('Author'), $user->getUsername())
|
||||
->addField(pht('Updated'), phabricator_datetime(time(), $user))
|
||||
->addAction(pht('Subscribe'), '/dev/random')
|
||||
->setUser($user));
|
||||
$elements[] = $panel;
|
||||
|
||||
$task_handle = $this->createBasicDummyHandle(
|
||||
"Improve Mobile Experience for Phabricator",
|
||||
PhabricatorPHIDConstants::PHID_TYPE_TASK);
|
||||
|
||||
$tag = id(new PhabricatorTagView())
|
||||
->setType(PhabricatorTagView::TYPE_STATE)
|
||||
->setBackgroundColor(PhabricatorTagView::COLOR_BLACK)
|
||||
->setName('Abandoned (Really)');
|
||||
$panel = $this->createPanel("Maniphest Hovercard");
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
->setObjectHandle($task_handle)
|
||||
->setUser($user)
|
||||
->addField(pht('Assigned to'), $user->getUsername())
|
||||
->addField(pht('Dependent Tasks'), 'T123, T124, T125')
|
||||
->addAction(pht('Subscribe'), '/dev/random')
|
||||
->addAction(pht('Create Subtask'), '/dev/urandom')
|
||||
->addTag($tag));
|
||||
$elements[] = $panel;
|
||||
|
||||
$user_handle = $this->createBasicDummyHandle(
|
||||
'gwashington',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_USER,
|
||||
'George Washington');
|
||||
$user_handle->setImageURI(
|
||||
celerity_get_resource_uri('/rsrc/image/people/washington.png'));
|
||||
$panel = $this->createPanel("Whatevery Hovercard");
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
->setObjectHandle($user_handle)
|
||||
->addField(pht('Status'), 'Available')
|
||||
->addField(pht('Member since'), '30. February 1750')
|
||||
->addAction(pht('Start a Conpherence'), '/dev/null')
|
||||
->setUser($user));
|
||||
$elements[] = $panel;
|
||||
|
||||
return phutil_implode_html("", $elements);
|
||||
}
|
||||
|
||||
private function createPanel($header) {
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setNoBackground();
|
||||
$panel->setHeader($header);
|
||||
return $panel;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,4 +17,30 @@ abstract class PhabricatorUIExample {
|
|||
abstract public function getDescription();
|
||||
abstract public function renderExample();
|
||||
|
||||
protected function createBasicDummyHandle($name, $type, $fullname = null,
|
||||
$uri = null) {
|
||||
|
||||
$id = mt_rand(15, 9999);
|
||||
$handle = new PhabricatorObjectHandle();
|
||||
$handle->setAlternateID(mt_rand(15, 9999));
|
||||
$handle->setName($name);
|
||||
$handle->setType($type);
|
||||
$handle->setPHID(PhabricatorPHID::generateNewPHID($type));
|
||||
|
||||
if ($fullname) {
|
||||
$handle->setFullName($fullname);
|
||||
} else {
|
||||
$handle->setFullName(sprintf('%s%d: %s',
|
||||
substr($type, 0, 1),
|
||||
$id,
|
||||
$name));
|
||||
}
|
||||
|
||||
if ($uri) {
|
||||
$handle->setURI($uri);
|
||||
}
|
||||
|
||||
return $handle;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
170
src/view/widget/hovercard/PhabricatorHovercardView.php
Normal file
170
src/view/widget/hovercard/PhabricatorHovercardView.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The default one-for-all hovercard. We may derive from this one to create
|
||||
* more specialized ones
|
||||
*/
|
||||
final class PhabricatorHovercardView extends AphrontView {
|
||||
|
||||
/**
|
||||
* @var PhabricatorObjectHandle
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
private $title = array();
|
||||
private $detail;
|
||||
private $tags = array();
|
||||
private $fields = array();
|
||||
private $actions = array();
|
||||
|
||||
/**
|
||||
* For overriding in case of Countdown, Paste, Pholio
|
||||
*/
|
||||
private $body;
|
||||
|
||||
private $color = 'grey';
|
||||
|
||||
public function setObjectHandle(PhabricatorObjectHandle $handle) {
|
||||
$this->handle = $handle;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDetail($detail) {
|
||||
$this->detail = $detail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addField($label, $value) {
|
||||
$this->fields[] = array(
|
||||
'label' => $label,
|
||||
'value' => $value,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addAction($label, $uri, $workflow = false) {
|
||||
$this->actions[] = array(
|
||||
'label' => $label,
|
||||
'uri' => $uri,
|
||||
'workflow' => $workflow,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTag(PhabricatorTagView $tag) {
|
||||
$this->tags[] = $tag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setColor($color) {
|
||||
$this->color = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$handle = $this->handle;
|
||||
$user = $this->getUser();
|
||||
|
||||
$id = $handle->getAlternateID();
|
||||
$type = $handle->getType();
|
||||
|
||||
require_celerity_resource("phabricator-hovercard-view-css");
|
||||
|
||||
$title = array();
|
||||
if ($this->tags) {
|
||||
$title[] = ' ';
|
||||
$title[] = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phabricator-hovercard-tags',
|
||||
),
|
||||
array_interleave(' ', $this->tags));
|
||||
}
|
||||
$title[] = pht("%s: %s", $handle->getTypeName(), substr($type, 0, 1) . $id);
|
||||
|
||||
$body = array();
|
||||
if ($this->detail) {
|
||||
$body[] = hsprintf('<strong>%s</strong>', $this->detail);
|
||||
} else {
|
||||
// Fallback for object handles
|
||||
$body[] = hsprintf('<strong>%s</strong>', $handle->getFullName());
|
||||
}
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
$body[] = hsprintf('<b>%s:</b> <span>%s</span>',
|
||||
$field['label'], $field['value']);
|
||||
}
|
||||
|
||||
$body = phutil_implode_html(phutil_tag('br'), $body);
|
||||
|
||||
if ($handle->getImageURI()) {
|
||||
// Probably a user, we don't need to assume something else
|
||||
// "Prepend" the image by appending $body
|
||||
$body = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'profile-header-picture-frame',
|
||||
'style' => 'background-image: url('.$handle->getImageURI().');',
|
||||
),
|
||||
'')
|
||||
->appendHTML($body);
|
||||
}
|
||||
|
||||
$buttons = array();
|
||||
|
||||
foreach ($this->actions as $action) {
|
||||
$options = array(
|
||||
'class' => 'button grey',
|
||||
'href' => $action['uri'],
|
||||
);
|
||||
|
||||
if ($action['workflow']) {
|
||||
$options['sigil'] = 'workflow';
|
||||
$buttons[] = javelin_tag(
|
||||
'a',
|
||||
$options,
|
||||
$action['label']);
|
||||
} else {
|
||||
$buttons[] = phutil_tag(
|
||||
'a',
|
||||
$options,
|
||||
$action['label']);
|
||||
}
|
||||
}
|
||||
|
||||
$tail = null;
|
||||
if ($buttons) {
|
||||
$tail = phutil_tag('div',
|
||||
array('class' => 'phabricator-hovercard-tail'),
|
||||
$buttons);
|
||||
}
|
||||
|
||||
// Assemble container
|
||||
// TODO: Add color support
|
||||
$content = hsprintf(
|
||||
'%s%s%s',
|
||||
phutil_tag('div', array('class' => 'phabricator-hovercard-head'), $title),
|
||||
phutil_tag('div', array('class' => 'phabricator-hovercard-body'), $body),
|
||||
$tail);
|
||||
|
||||
$hovercard = phutil_tag("div",
|
||||
array(
|
||||
"class" => "phabricator-hovercard-container",
|
||||
),
|
||||
$content);
|
||||
|
||||
// Wrap for thick border
|
||||
// and later the tip at the bottom
|
||||
return phutil_tag('div',
|
||||
array(
|
||||
'class' => 'phabricator-hovercard-wrapper',
|
||||
),
|
||||
$hovercard);
|
||||
}
|
||||
|
||||
}
|
69
webroot/rsrc/css/layout/phabricator-hovercard-view.css
Normal file
69
webroot/rsrc/css/layout/phabricator-hovercard-view.css
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* @provides phabricator-hovercard-view-css
|
||||
*/
|
||||
|
||||
.phabricator-hovercard-wrapper {
|
||||
border-radius: 4px;
|
||||
width: 400px;
|
||||
margin: auto;
|
||||
padding: 4px;
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.device-phone .phabricator-hovercard-wrapper {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-container {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #666666;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-head {
|
||||
padding: 10px 15px;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
text-shadow: 0 1px 0 #333333;
|
||||
font-weight: bold;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #559911;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-tags {
|
||||
font-size: 13px;
|
||||
float: right;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-body {
|
||||
padding: 15px;
|
||||
background-color: white;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-body .profile-header-picture-frame {
|
||||
float: left;
|
||||
margin: 0;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 5px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-tail {
|
||||
padding: 3px 2px;
|
||||
background-color: #eeeeee;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
.phabricator-hovercard-tail button,
|
||||
.phabricator-hovercard-tail a.button {
|
||||
margin: 3px;
|
||||
}
|
Loading…
Reference in a new issue