From 5bd54e35bc8485b78779934795de25d074d9e19a Mon Sep 17 00:00:00 2001 From: Anh Nhan Nguyen Date: Tue, 2 Apr 2013 09:15:33 -0700 Subject: [PATCH] First pass at hovercard design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/__celerity_resource_map__.php | 9 + src/__phutil_library_map__.php | 4 + .../examples/PhabricatorHovercardExample.php | 77 ++++++++ .../examples/PhabricatorUIExample.php | 26 +++ .../hovercard/PhabricatorHovercardView.php | 170 ++++++++++++++++++ .../css/layout/phabricator-hovercard-view.css | 69 +++++++ 6 files changed, 355 insertions(+) create mode 100644 src/applications/uiexample/examples/PhabricatorHovercardExample.php create mode 100644 src/view/widget/hovercard/PhabricatorHovercardView.php create mode 100644 webroot/rsrc/css/layout/phabricator-hovercard-view.css diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index ee31b216d4..e986594e78 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -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', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d7542b179c..f5d37cb1f5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/uiexample/examples/PhabricatorHovercardExample.php b/src/applications/uiexample/examples/PhabricatorHovercardExample.php new file mode 100644 index 0000000000..6ceb8083f7 --- /dev/null +++ b/src/applications/uiexample/examples/PhabricatorHovercardExample.php @@ -0,0 +1,77 @@ +PhabricatorHovercardView 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; + } + +} diff --git a/src/applications/uiexample/examples/PhabricatorUIExample.php b/src/applications/uiexample/examples/PhabricatorUIExample.php index 6688e489b3..7889b1ed86 100644 --- a/src/applications/uiexample/examples/PhabricatorUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorUIExample.php @@ -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; + } + } diff --git a/src/view/widget/hovercard/PhabricatorHovercardView.php b/src/view/widget/hovercard/PhabricatorHovercardView.php new file mode 100644 index 0000000000..86b482fd43 --- /dev/null +++ b/src/view/widget/hovercard/PhabricatorHovercardView.php @@ -0,0 +1,170 @@ +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('%s', $this->detail); + } else { + // Fallback for object handles + $body[] = hsprintf('%s', $handle->getFullName()); + } + + foreach ($this->fields as $field) { + $body[] = hsprintf('%s: %s', + $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); + } + +} diff --git a/webroot/rsrc/css/layout/phabricator-hovercard-view.css b/webroot/rsrc/css/layout/phabricator-hovercard-view.css new file mode 100644 index 0000000000..2aeb6499a3 --- /dev/null +++ b/webroot/rsrc/css/layout/phabricator-hovercard-view.css @@ -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; +}