From 89ee928a51c72d9fda17a59f73b060a92cc0c665 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 Jul 2013 13:18:50 -0700 Subject: [PATCH] Share rendering code for embedded votes and vote detail Summary: We have two separate pieces of rendering code and both are pretty ugly. Move them toward being more reasonable. This could no doubt be improved: - Getting a text style which was readable on both the dark and light bars was hard, maybe we should change the colors or maybe I am just bad. - Could probably benefit from actual competent design in general. - JS magic is temporarily ineffective, I'll restore that in the future. - Embed style is a little funky (margin/centering). - Could use a little cleanup. Test Plan: {F50226} {F50227} {F50228} Reviewers: chad, btrahan Reviewed By: btrahan CC: aran Differential Revision: https://secure.phabricator.com/D6465 --- src/__celerity_resource_map__.php | 92 ++--- .../PhabricatorSlowvoteEditController.php | 2 +- .../PhabricatorSlowvotePollController.php | 321 +-------------- .../editor/PhabricatorSlowvoteEditor.php | 6 + .../remarkup/SlowvoteRemarkupRule.php | 11 +- .../storage/PhabricatorSlowvotePoll.php | 2 +- .../slowvote/view/SlowvoteEmbedView.php | 378 ++++++++++++++---- .../css/application/slowvote/slowvote.css | 162 +++++--- webroot/rsrc/css/core/z-index.css | 8 + 9 files changed, 485 insertions(+), 497 deletions(-) diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 3a0682422b..daa115c9ec 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -3475,7 +3475,7 @@ celerity_register_resource_map(array( ), 'phabricator-slowvote-css' => array( - 'uri' => '/res/d1c2e05a/rsrc/css/application/slowvote/slowvote.css', + 'uri' => '/res/11373549/rsrc/css/application/slowvote/slowvote.css', 'type' => 'css', 'requires' => array( @@ -3702,7 +3702,7 @@ celerity_register_resource_map(array( ), 'phabricator-zindex-css' => array( - 'uri' => '/res/99a9447b/rsrc/css/core/z-index.css', + 'uri' => '/res/a50437bf/rsrc/css/core/z-index.css', 'type' => 'css', 'requires' => array( @@ -4149,7 +4149,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - 'c01cebae' => + 'f32a863a' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -4197,7 +4197,7 @@ celerity_register_resource_map(array( 40 => 'phabricator-property-list-view-css', 41 => 'phabricator-tag-view-css', ), - 'uri' => '/res/pkg/c01cebae/core.pkg.css', + 'uri' => '/res/pkg/f32a863a/core.pkg.css', 'type' => 'css', ), '75ccea43' => @@ -4391,16 +4391,16 @@ celerity_register_resource_map(array( 'reverse' => array( 'aphront-attached-file-view-css' => 'adc3c36d', - 'aphront-dialog-view-css' => 'c01cebae', - 'aphront-error-view-css' => 'c01cebae', - 'aphront-form-view-css' => 'c01cebae', - 'aphront-list-filter-view-css' => 'c01cebae', - 'aphront-pager-view-css' => 'c01cebae', - 'aphront-panel-view-css' => 'c01cebae', - 'aphront-table-view-css' => 'c01cebae', - 'aphront-tokenizer-control-css' => 'c01cebae', - 'aphront-tooltip-css' => 'c01cebae', - 'aphront-typeahead-control-css' => 'c01cebae', + 'aphront-dialog-view-css' => 'f32a863a', + 'aphront-error-view-css' => 'f32a863a', + 'aphront-form-view-css' => 'f32a863a', + 'aphront-list-filter-view-css' => 'f32a863a', + 'aphront-pager-view-css' => 'f32a863a', + 'aphront-panel-view-css' => 'f32a863a', + 'aphront-table-view-css' => 'f32a863a', + 'aphront-tokenizer-control-css' => 'f32a863a', + 'aphront-tooltip-css' => 'f32a863a', + 'aphront-typeahead-control-css' => 'f32a863a', 'differential-changeset-view-css' => 'dd27a69b', 'differential-core-view-css' => 'dd27a69b', 'differential-inline-comment-editor' => '504ca7d2', @@ -4414,7 +4414,7 @@ celerity_register_resource_map(array( 'differential-table-of-contents-css' => 'dd27a69b', 'diffusion-commit-view-css' => 'c8ce2d88', 'diffusion-icons-css' => 'c8ce2d88', - 'global-drag-and-drop-css' => 'c01cebae', + 'global-drag-and-drop-css' => 'f32a863a', 'inline-comment-summary-css' => 'dd27a69b', 'javelin-aphlict' => '75ccea43', 'javelin-behavior' => 'a9f14d76', @@ -4488,55 +4488,55 @@ celerity_register_resource_map(array( 'javelin-util' => 'a9f14d76', 'javelin-vector' => 'a9f14d76', 'javelin-workflow' => 'a9f14d76', - 'lightbox-attachment-css' => 'c01cebae', + 'lightbox-attachment-css' => 'f32a863a', 'maniphest-task-summary-css' => 'adc3c36d', 'maniphest-transaction-detail-css' => 'adc3c36d', - 'phabricator-action-list-view-css' => 'c01cebae', - 'phabricator-application-launch-view-css' => 'c01cebae', + 'phabricator-action-list-view-css' => 'f32a863a', + 'phabricator-application-launch-view-css' => 'f32a863a', 'phabricator-busy' => '75ccea43', 'phabricator-content-source-view-css' => 'dd27a69b', - 'phabricator-core-css' => 'c01cebae', - 'phabricator-crumbs-view-css' => 'c01cebae', + 'phabricator-core-css' => 'f32a863a', + 'phabricator-crumbs-view-css' => 'f32a863a', 'phabricator-drag-and-drop-file-upload' => '504ca7d2', 'phabricator-dropdown-menu' => '75ccea43', 'phabricator-file-upload' => '75ccea43', - 'phabricator-filetree-view-css' => 'c01cebae', - 'phabricator-flag-css' => 'c01cebae', - 'phabricator-form-view-css' => 'c01cebae', - 'phabricator-header-view-css' => 'c01cebae', + 'phabricator-filetree-view-css' => 'f32a863a', + 'phabricator-flag-css' => 'f32a863a', + 'phabricator-form-view-css' => 'f32a863a', + 'phabricator-header-view-css' => 'f32a863a', 'phabricator-hovercard' => '75ccea43', - 'phabricator-jump-nav' => 'c01cebae', + 'phabricator-jump-nav' => 'f32a863a', 'phabricator-keyboard-shortcut' => '75ccea43', 'phabricator-keyboard-shortcut-manager' => '75ccea43', - 'phabricator-main-menu-view' => 'c01cebae', + 'phabricator-main-menu-view' => 'f32a863a', 'phabricator-menu-item' => '75ccea43', - 'phabricator-nav-view-css' => 'c01cebae', + 'phabricator-nav-view-css' => 'f32a863a', 'phabricator-notification' => '75ccea43', - 'phabricator-notification-css' => 'c01cebae', - 'phabricator-notification-menu-css' => 'c01cebae', - 'phabricator-object-item-list-view-css' => 'c01cebae', + 'phabricator-notification-css' => 'f32a863a', + 'phabricator-notification-menu-css' => 'f32a863a', + 'phabricator-object-item-list-view-css' => 'f32a863a', 'phabricator-object-selector-css' => 'dd27a69b', 'phabricator-phtize' => '75ccea43', 'phabricator-prefab' => '75ccea43', 'phabricator-project-tag-css' => 'adc3c36d', - 'phabricator-property-list-view-css' => 'c01cebae', - 'phabricator-remarkup-css' => 'c01cebae', + 'phabricator-property-list-view-css' => 'f32a863a', + 'phabricator-remarkup-css' => 'f32a863a', 'phabricator-shaped-request' => '504ca7d2', - 'phabricator-side-menu-view-css' => 'c01cebae', - 'phabricator-standard-page-view' => 'c01cebae', - 'phabricator-tag-view-css' => 'c01cebae', + 'phabricator-side-menu-view-css' => 'f32a863a', + 'phabricator-standard-page-view' => 'f32a863a', + 'phabricator-tag-view-css' => 'f32a863a', 'phabricator-textareautils' => '75ccea43', 'phabricator-tooltip' => '75ccea43', - 'phabricator-transaction-view-css' => 'c01cebae', - 'phabricator-zindex-css' => 'c01cebae', - 'phui-button-css' => 'c01cebae', - 'phui-form-css' => 'c01cebae', - 'phui-icon-view-css' => 'c01cebae', - 'phui-spacing-css' => 'c01cebae', - 'sprite-apps-large-css' => 'c01cebae', - 'sprite-gradient-css' => 'c01cebae', - 'sprite-icons-css' => 'c01cebae', - 'sprite-menu-css' => 'c01cebae', - 'syntax-highlighting-css' => 'c01cebae', + 'phabricator-transaction-view-css' => 'f32a863a', + 'phabricator-zindex-css' => 'f32a863a', + 'phui-button-css' => 'f32a863a', + 'phui-form-css' => 'f32a863a', + 'phui-icon-view-css' => 'f32a863a', + 'phui-spacing-css' => 'f32a863a', + 'sprite-apps-large-css' => 'f32a863a', + 'sprite-gradient-css' => 'f32a863a', + 'sprite-icons-css' => 'f32a863a', + 'sprite-menu-css' => 'f32a863a', + 'syntax-highlighting-css' => 'f32a863a', ), )); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index a7be2d3a02..75c7740dcf 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -51,7 +51,7 @@ final class PhabricatorSlowvoteEditController if ($request->isFormPost()) { $v_question = $request->getStr('question'); $v_description = $request->getStr('description'); - $v_responses = $request->getInt('responses'); + $v_responses = (int)$request->getInt('responses'); $v_shuffle = (int)$request->getBool('shuffle'); if ($is_new) { diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 8d8980be18..a61577c22b 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -13,116 +13,40 @@ final class PhabricatorSlowvotePollController } public function processRequest() { - $request = $this->getRequest(); $user = $request->getUser(); - $viewer_phid = $user->getPHID(); $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needOptions(true) ->needChoices(true) + ->needViewerChoices(true) ->executeOne(); if (!$poll) { return new Aphront404Response(); } - $options = $poll->getOptions(); - $choices = $poll->getChoices(); - - $choices_by_option = mgroup($choices, 'getOptionID'); - $choices_by_user = mgroup($choices, 'getAuthorPHID'); - $viewer_choices = idx($choices_by_user, $viewer_phid, array()); + $poll_view = id(new SlowvoteEmbedView()) + ->setHeadless(true) + ->setUser($user) + ->setPoll($poll); if ($request->isAjax()) { - $embed = id(new SlowvoteEmbedView()) - ->setPoll($poll) - ->setOptions($options) - ->setViewerChoices($viewer_choices); - return id(new AphrontAjaxResponse()) - ->setContent(array( - 'pollID' => $poll->getID(), - 'contentHTML' => $embed->render())); + ->setContent( + array( + 'pollID' => $poll->getID(), + 'contentHTML' => $poll_view->render(), + )); } - require_celerity_resource('phabricator-slowvote-css'); - - $phids = array_merge( - mpull($choices, 'getAuthorPHID'), - array( - $poll->getAuthorPHID(), - )); - - $query = new PhabricatorObjectHandleData($phids); - $query->setViewer($user); - $handles = $query->loadHandles(); - $objects = $query->loadObjects(); - - if ($poll->getShuffle()) { - shuffle($options); - } - - $option_markup = array(); - foreach ($options as $option) { - $option_markup[] = $this->renderPollOption( - $poll, - $viewer_choices, - $option); - } - - switch ($poll->getMethod()) { - case PhabricatorSlowvotePoll::METHOD_PLURALITY: - $choice_ids = array(); - foreach ($choices_by_user as $user_phid => $user_choices) { - $choice_ids[$user_phid] = head($user_choices)->getOptionID(); - } - break; - case PhabricatorSlowvotePoll::METHOD_APPROVAL: - break; - default: - throw new Exception("Unknown poll method!"); - } - - $result_markup = $this->renderResultMarkup( - $poll, - $options, - $choices, - $viewer_choices, - $choices_by_option, - $handles, - $objects); - - if ($viewer_choices) { - $instructions = - pht('Your vote has been recorded... but there is still ample time to '. - 'rethink your position. Have you thoroughly considered all possible '. - 'eventualities?'); - } else { - $instructions = - pht('This is a weighty matter indeed. Consider your choices with the '. - 'greatest of care.'); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->setFlexible(true) - ->setAction(sprintf('/vote/%d/', $poll->getID())) - ->appendChild(hsprintf( - '

%s

', - $instructions)) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Vote')) - ->setValue($option_markup)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Engage in Deliberations'))); - $header = id(new PhabricatorHeaderView()) ->setHeader($poll->getQuestion()); + $xaction_header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Ongoing Deliberations')); + $actions = $this->buildActionView($poll); $properties = $this->buildPropertyView($poll); @@ -131,15 +55,6 @@ final class PhabricatorSlowvotePollController id(new PhabricatorCrumbView()) ->setName('V'.$poll->getID())); - $panel = new AphrontPanelView(); - $panel->setWidth(AphrontPanelView::WIDTH_WIDE); - $panel->appendChild($result_markup); - - $content = array( - $form, - hsprintf('

'), - $panel); - $xactions = $this->buildTransactions($poll); $add_comment = $this->buildCommentForm($poll); @@ -149,7 +64,13 @@ final class PhabricatorSlowvotePollController $header, $actions, $properties, - $content, + phutil_tag( + 'div', + array( + 'class' => 'ml', + ), + $poll_view), + $xaction_header, $xactions, $add_comment, ), @@ -161,208 +82,6 @@ final class PhabricatorSlowvotePollController )); } - - private function renderPollOption( - PhabricatorSlowvotePoll $poll, - array $viewer_choices, - PhabricatorSlowvoteOption $option) { - assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); - - $id = $option->getID(); - switch ($poll->getMethod()) { - case PhabricatorSlowvotePoll::METHOD_PLURALITY: - - // Render a radio button. - - $selected_option = head($viewer_choices); - if ($selected_option) { - $selected = $selected_option->getOptionID(); - } else { - $selected = null; - } - - if ($selected == $id) { - $checked = "checked"; - } else { - $checked = null; - } - - $input = phutil_tag( - 'input', - array( - 'type' => 'radio', - 'name' => 'vote[]', - 'value' => $id, - 'checked' => $checked, - )); - break; - case PhabricatorSlowvotePoll::METHOD_APPROVAL: - - // Render a check box. - - $checked = null; - foreach ($viewer_choices as $choice) { - if ($choice->getOptionID() == $id) { - $checked = 'checked'; - break; - } - } - - $input = phutil_tag( - 'input', - array( - 'type' => 'checkbox', - 'name' => 'vote[]', - 'checked' => $checked, - 'value' => $id, - )); - break; - default: - throw new Exception("Unknown poll method!"); - } - - if ($checked) { - $checked_class = 'phabricator-slowvote-checked'; - } else { - $checked_class = null; - } - - return phutil_tag( - 'label', - array( - 'class' => 'phabricator-slowvote-label '.$checked_class, - ), - array($input, $option->getName())); - } - - private function renderVoteCount( - PhabricatorSlowvotePoll $poll, - array $choices, - array $chosen) { - assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); - assert_instances_of($chosen, 'PhabricatorSlowvoteChoice'); - - switch ($poll->getMethod()) { - case PhabricatorSlowvotePoll::METHOD_PLURALITY: - $out_of_total = count($choices); - break; - case PhabricatorSlowvotePoll::METHOD_APPROVAL: - // Count unique respondents for approval votes. - $out_of_total = count(mpull($choices, null, 'getAuthorPHID')); - break; - default: - throw new Exception("Unknown poll method!"); - } - - return sprintf( - '%d / %d (%d%%)', - number_format(count($chosen)), - number_format($out_of_total), - $out_of_total - ? round(100 * count($chosen) / $out_of_total) - : 0); - } - - private function renderResultMarkup( - PhabricatorSlowvotePoll $poll, - array $options, - array $choices, - array $viewer_choices, - array $choices_by_option, - array $handles, - array $objects) { - assert_instances_of($options, 'PhabricatorSlowvoteOption'); - assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); - assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); - assert_instances_of($handles, 'PhabricatorObjectHandle'); - assert_instances_of($objects, 'PhabricatorLiskDAO'); - - $viewer_phid = $this->getRequest()->getUser()->getPHID(); - - $can_see_responses = false; - $need_vote = false; - switch ($poll->getResponseVisibility()) { - case PhabricatorSlowvotePoll::RESPONSES_VISIBLE: - $can_see_responses = true; - break; - case PhabricatorSlowvotePoll::RESPONSES_VOTERS: - $can_see_responses = (bool)$viewer_choices; - $need_vote = true; - break; - case PhabricatorSlowvotePoll::RESPONSES_OWNER: - $can_see_responses = ($viewer_phid == $poll->getAuthorPHID()); - break; - } - - $result_markup = id(new AphrontFormLayoutView()) - ->appendChild(phutil_tag('h1', array(), pht('Ongoing Deliberation'))); - - if (!$can_see_responses) { - if ($need_vote) { - $reason = pht("You must vote to see the results."); - } else { - $reason = pht("The results are not public."); - } - $result_markup - ->appendChild(hsprintf( - '

%s

', - $reason)); - return $result_markup; - } - - foreach ($options as $option) { - $id = $option->getID(); - - $chosen = idx($choices_by_option, $id, array()); - $users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID')); - if ($users) { - $user_markup = array(); - foreach ($users as $handle) { - $object = idx($objects, $handle->getPHID()); - if (!$object) { - continue; - } - - $profile_image = $handle->getImageURI(); - - $user_markup[] = phutil_tag( - 'a', - array( - 'href' => $handle->getURI(), - 'class' => 'phabricator-slowvote-facepile', - ), - phutil_tag( - 'img', - array( - 'src' => $profile_image, - ))); - } - } else { - $user_markup = pht('This option has failed to appeal to anyone.'); - } - - $vote_count = $this->renderVoteCount( - $poll, - $choices, - $chosen); - - $result_markup->appendChild(hsprintf( - '
'. - '
%s
'. - '

%s

'. - '
'. - '%s'. - '
'. - '
'. - '
', - $vote_count, - $option->getName(), - phutil_tag('div', array(), $user_markup))); - } - - return $result_markup; - } - private function buildActionView(PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index c23a1c5770..44925de7bb 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -26,8 +26,14 @@ final class PhabricatorSlowvoteEditor switch ($xaction->getTransactionType()) { case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: + if ($old === null) { + return true; + } return ((int)$old !== (int)$new); case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: + if ($old === null) { + return true; + } return ((bool)$old !== (bool)$new); } diff --git a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php index c4db192ee7..ad734e3cde 100644 --- a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php +++ b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php @@ -25,16 +25,11 @@ final class SlowvoteRemarkupRule protected function renderObjectEmbed($object, $handle, $options) { $viewer = $this->getEngine()->getConfig('viewer'); - $options = $object->getOptions(); - $choices = $object->getChoices(); - $viewer_choices = $object->getViewerChoices($viewer); - $embed = id(new SlowvoteEmbedView()) - ->setPoll($object) - ->setOptions($options) - ->setViewerChoices($viewer_choices); + ->setUser($viewer) + ->setPoll($object); - return $embed->render(); + return $embed; } } diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 4772ea7c5b..e8d61448e5 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -74,7 +74,7 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO } public function attachViewerChoices(PhabricatorUser $viewer, array $choices) { - assert_instances_of($choices, 'PhabricatorSlowvoteOption'); + assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); $this->viewerChoices[$viewer->getPHID()] = $choices; return $this; } diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php index 626026de70..cbda276f07 100644 --- a/src/applications/slowvote/view/SlowvoteEmbedView.php +++ b/src/applications/slowvote/view/SlowvoteEmbedView.php @@ -6,123 +6,349 @@ final class SlowvoteEmbedView extends AphrontView { private $poll; - private $options; - private $viewerChoices; + private $handles; + private $headless; + + public function setHeadless($headless) { + $this->headless = $headless; + return $this; + } public function setPoll(PhabricatorSlowvotePoll $poll) { $this->poll = $poll; return $this; } - public function setOptions(array $options) { - $this->options = $options; - return $this; - } - - public function setViewerChoices(array $viewer_choices) { - $this->viewerChoices = $viewer_choices; - return $this; + public function getPoll() { + return $this->poll; } public function render() { - if (!$this->poll) { throw new Exception("Call setPoll() before render()!"); } - if (!$this->options) { - throw new Exception("Call setOptions() before render()!"); - } + $poll = $this->poll; - if ($this->poll->getShuffle()) { - shuffle($this->options); + $phids = array(); + foreach ($poll->getChoices() as $choice) { + $phids[] = $choice->getAuthorPHID(); + } + $phids[] = $poll->getAuthorPHID(); + + $this->handles = id(new PhabricatorObjectHandleData($phids)) + ->setViewer($this->getUser()) + ->loadHandles(); + + $options = $poll->getOptions(); + + if ($poll->getShuffle()) { + shuffle($options); } require_celerity_resource('phabricator-slowvote-css'); require_celerity_resource('javelin-behavior-slowvote-embed'); $config = array( - 'pollID' => $this->poll->getID()); + 'pollID' => $poll->getID()); Javelin::initBehavior('slowvote-embed', $config); - $user_choices = array(); - if (!empty($this->viewerChoices)) { - $user_choices = mpull($this->viewerChoices, null, 'getOptionID'); - } + $user_choices = $poll->getViewerChoices($this->getUser()); + $user_choices = mpull($user_choices, 'getOptionID', 'getOptionID'); - $options = array(); - $ribbon_colors = array('#DF0101', '#DF7401', '#D7DF01', '#74DF00', - '#01DF01', '#01DF74', '#01DFD7', '#0174DF', '#0101DF', '#5F04B4', - '#B404AE'); - shuffle($ribbon_colors); - - foreach ($this->options as $option) { - $classes = 'phabricator-slowvote-embed-option-text'; - - $selected = ''; - - - if (idx($user_choices, $option->getID(), false)) { - $classes .= ' phabricator-slowvote-embed-option-selected'; - $selected = '@'; - } - - $is_selected = javelin_tag( - 'div', - array( - 'class' => 'phabricator-slowvote-embed-option-vote', - 'sigil' => 'slowvote-embed-vote' - ), - $selected); - - $option_text = javelin_tag( - 'div', - array( - 'class' => $classes, - 'sigil' => 'slowvote-option', - 'meta' => array( - 'optionID' => $option->getID() - ) - ), - array($is_selected, $option->getName())); - - $options[] = phutil_tag( - 'div', - array( - 'class' => 'phabricator-slowvote-embed-option', - 'style' => - sprintf('border-left: 7px solid %s;', array_shift($ribbon_colors)) - ), - array($option_text)); + $out = array(); + foreach ($options as $option) { + $is_selected = isset($user_choices[$option->getID()]); + $out[] = $this->renderLabel($option, $is_selected); } $link_to_slowvote = phutil_tag( 'a', array( - 'href' => '/V'.$this->poll->getID() + 'href' => '/V'.$poll->getID() ), - $this->poll->getQuestion()); + $poll->getQuestion()); - $header = phutil_tag( - 'div', - array(), - array('V'.$this->poll->getID().': ', $link_to_slowvote)); + if ($this->headless) { + $header = null; + } else { + $header = phutil_tag( + 'div', + array( + 'class' => 'slowvote-header', + ), + phutil_tag( + 'div', + array( + 'class' => 'slowvote-header-content', + ), + array( + 'V'.$poll->getID(), + ' ', + $link_to_slowvote))); - $body = phutil_tag( + $description = null; + if ($poll->getDescription()) { + $description = PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent( + $poll->getDescription()), + 'default', + $this->getUser()); + $description = phutil_tag( + 'div', + array( + 'class' => 'slowvote-description', + ), + $description); + } + + $header = array( + $header, + $description); + } + + $vis = $poll->getResponseVisibility(); + if ($this->areResultsVisible()) { + if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { + $quip = pht('Only you can see the results.'); + } else { + $quip = pht('Voting improves cardiovascular endurance.'); + } + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_VOTERS) { + $quip = pht('You must vote to see the results.'); + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { + $quip = pht('Only the author can see the results.'); + } + + $hint = phutil_tag( + 'span', + array( + 'class' => 'slowvote-hint', + ), + $quip); + + $submit = phutil_tag( 'div', - array(), - $options); + array( + 'class' => 'slowvote-footer', + ), + phutil_tag( + 'div', + array( + 'class' => 'slowvote-footer-content', + ), + array( + $hint, + phutil_tag( + 'button', + array( + ), + pht('Engage in Deliberations')), + ))); + + $body = phabricator_form( + $this->getUser(), + array( + 'action' => '/vote/'.$poll->getID().'/', + 'method' => 'POST', + 'class' => 'slowvote-body', + ), + array( + phutil_tag( + 'div', + array( + 'class' => 'slowvote-body-content', + ), + $out), + $submit, + )); return javelin_tag( 'div', array( - 'class' => 'phabricator-slowvote-embed', + 'class' => 'slowvote-embed', 'sigil' => 'slowvote-embed', 'meta' => array( - 'pollID' => $this->poll->getID() + 'pollID' => $poll->getID() ) ), array($header, $body)); } + private function renderLabel(PhabricatorSlowvoteOption $option, $selected) { + $classes = array(); + $classes[] = 'slowvote-option-label'; + + $status = $this->renderStatus($option); + $voters = $this->renderVoters($option); + + return phutil_tag( + 'div', + array( + 'class' => 'slowvote-option-label-group', + ), + array( + phutil_tag( + 'label', + array( + 'class' => implode(' ', $classes), + ), + array( + phutil_tag( + 'div', + array( + 'class' => 'slowvote-control-offset', + ), + $option->getName()), + $this->renderBar($option), + phutil_tag( + 'div', + array( + 'class' => 'slowvote-above-the-bar', + ), + array( + $this->renderControl($option, $selected), + )), + )), + $status, + $voters, + )); + } + + private function renderBar(PhabricatorSlowvoteOption $option) { + if (!$this->areResultsVisible()) { + return null; + } + + $poll = $this->getPoll(); + + $choices = mgroup($poll->getChoices(), 'getOptionID'); + $choices = count(idx($choices, $option->getID(), array())); + $count = count(mgroup($poll->getChoices(), 'getAuthorPHID')); + + return phutil_tag( + 'div', + array( + 'class' => 'slowvote-bar', + 'style' => sprintf( + 'width: %.1f%%;', + $count ? 100 * ($choices / $count) : 0), + ), + array( + phutil_tag( + 'div', + array( + 'class' => 'slowvote-control-offset', + ), + $option->getName()), + )); + } + + private function renderControl(PhabricatorSlowvoteOption $option, $selected) { + $types = array( + PhabricatorSlowvotePoll::METHOD_PLURALITY => 'radio', + PhabricatorSlowvotePoll::METHOD_APPROVAL => 'checkbox', + ); + + return phutil_tag( + 'input', + array( + 'type' => idx($types, $this->getPoll()->getMethod()), + 'name' => 'vote[]', + 'value' => $option->getID(), + 'checked' => ($selected ? 'checked' : null), + )); + } + + private function renderVoters(PhabricatorSlowvoteOption $option) { + if (!$this->areResultsVisible()) { + return null; + } + + $poll = $this->getPoll(); + + $choices = mgroup($poll->getChoices(), 'getOptionID'); + $choices = idx($choices, $option->getID(), array()); + + if (!$choices) { + return null; + } + + $handles = $this->handles; + $authors = mpull($choices, 'getAuthorPHID', 'getAuthorPHID'); + + $viewer_phid = $this->getUser()->getPHID(); + + // Put the viewer first if they've voted for this option. + $authors = array_select_keys($authors, array($viewer_phid)) + + $authors; + + $voters = array(); + foreach ($authors as $author_phid) { + $handle = $handles[$author_phid]; + + $voters[] = javelin_tag( + 'div', + array( + 'class' => 'slowvote-voter', + 'style' => 'background-image: url('.$handle->getImageURI().')', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $handle->getName(), + ), + )); + } + + return phutil_tag( + 'div', + array( + 'class' => 'slowvote-voters', + ), + $voters); + } + + private function renderStatus(PhabricatorSlowvoteOption $option) { + if (!$this->areResultsVisible()) { + return null; + } + + $poll = $this->getPoll(); + + $choices = mgroup($poll->getChoices(), 'getOptionID'); + $choices = count(idx($choices, $option->getID(), array())); + $count = count(mgroup($poll->getChoices(), 'getAuthorPHID')); + + $percent = sprintf('%d%%', $count ? 100 * $choices / $count : 0); + + switch ($poll->getMethod()) { + case PhabricatorSlowvotePoll::METHOD_PLURALITY: + $status = pht('%s (%d / %d)', $percent, $choices, $count); + break; + case PhabricatorSlowvotePoll::METHOD_APPROVAL: + $status = pht('%s Approval (%d / %d)', $percent, $choices, $count); + break; + } + + return phutil_tag( + 'div', + array( + 'class' => 'slowvote-status', + ), + $status); + } + + private function areResultsVisible() { + $poll = $this->getPoll(); + + $vis = $poll->getResponseVisibility(); + if ($vis == PhabricatorSlowvotePoll::RESPONSES_VISIBLE) { + return true; + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { + return ($poll->getAuthorPHID() == $this->getUser()->getPHID()); + } else { + $choices = mgroup($poll->getChoices(), 'getAuthorPHID'); + return (bool)idx($choices, $this->getUser()->getPHID()); + } + } + } diff --git a/webroot/rsrc/css/application/slowvote/slowvote.css b/webroot/rsrc/css/application/slowvote/slowvote.css index 84470e973f..d82b770440 100644 --- a/webroot/rsrc/css/application/slowvote/slowvote.css +++ b/webroot/rsrc/css/application/slowvote/slowvote.css @@ -2,90 +2,124 @@ * @provides phabricator-slowvote-css */ -.phabricator-slowvote-comments { - width: 100%; +.slowvote-embed { + margin: 24px 12px; + background: #ffffff; + border-color: #888888; + border-style: solid; + border-width: 1px; + border-radius: 4px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.20), + inset 0 0 2px rgba(0, 0, 0, 0.10); } -.phabricator-slowvote-comments th { - width: 150px; - text-align: right; - padding: 6px 4px 6px; - white-space: nowrap; +.slowvote-header { + font-weight: bold; + line-height: 16px; + border-bottom: 1px solid #bbbbbb; } -.phabricator-slowvote-comments td { - vertical-align: top; - padding: 6px 2px; - border-bottom: 1px solid #d0d0d0; -} - -.phabricator-slowvote-datestamp { - font-size: 9px; - font-family: "Verdana"; +.slowvote-description { color: #666666; - margin-top: 1px; + padding: 8px; + border-bottom: 1px solid #bbbbbb; } -.phabricator-slowvote-hr { - border: none; - height: 1px; - position: relative; - background: #c0c0c0; +.slowvote-header-content { + padding: 8px; } -.phabricator-slowvote-count { - float: right; - font-size: 13px; - font-weight: bold; +.slowvote-body { } -.phabricator-slowvote-label { +.slowvote-body-content { + padding: 4px 16px; +} + +.slowvote-option-label { + border: 1px solid #666666; display: block; - width: 100%; - font-size: 14px; - font-weight: bold; - color: #222222; - text-align: left; - margin: 0px 0px 6px; - padding: 6px 4px; - background: #cccccc; - border-bottom: 1px solid #aaaaaa; - cursor: pointer; -} - -.aphront-form-input .phabricator-slowvote-label input { - display: inline; - width: auto; - margin-right: 12px; -} - -.phabricator-slowvote-facepile { - width: 50px; - height: 50px; - overflow: hidden; position: relative; - float: left; - margin: 0px 4px 6px 0px; + padding: 8px 4px; + cursor: pointer; + font-weight: bold; + overflow: hidden; + background-color: {$lightblue}; } -.phabricator-slowvote-embed { - width: 400px; - background: #f7f7f7; - border: 1px solid #dbdbdb; - padding: 5px; +.slowvote-bar { + position: absolute; + top: 0; + left: 0; + bottom: 0; + overflow: hidden; + background-color: {$blue}; } -.phabricator-slowvote-embed-option { - margin: 1px; - background: #F0FFFF; +.slowvote-control-offset { + white-space: nowrap; + position: absolute; + left: 32px; + top: 8px; + width: 100%; + color: #000000; + text-shadow: 0 1px 0 #ffffff; } -.phabricator-slowvote-embed-option-vote { +.slowvote-bar .slowvote-control-offset { + color: #ffffff; + text-shadow: 0 1px 0 #000000; +} + +.slowvote-option-label-group { + margin: 8px 0 16px; +} + +.slowvote-option-label input[type="radio"], +.slowvote-option-label input[type="checkbox"] { + margin: 0 12px 0 8px; + font-weight: bold; +} + +.slowvote-above-the-bar { + position: relative; +} + +.slowvote-status { + text-align: right; + color: #333333; + font-weight: normal; + padding: 2px 0; + line-height: 15px; + text-align: right; + font-size: 11px; +} + +.slowvote-voter { display: inline-block; - width: 1em; + width: 25px; + height: 25px; + background: #f3f3f3; + background-size: 25px 25px; } -.phabricator-slowvote-embed-option-text { - border: 1px solid #dbdbdb; - border-left: 0px; +.slowvote-footer { + border-top-width: 1px; + border-top-style: solid; + border-top-color: #bbbbbb; + position: relative; +} + +.slowvote-footer-content { + padding: 8px; + overflow: hidden; +} + +.slowvote-footer-content .slowvote-hint { + line-height: 24px; + color: #888888; +} + +.slowvote-footer-content button { + float: right; } diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 685e810f9b..56a650bec5 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -22,6 +22,14 @@ z-index: 2; } +.slowvote-bar { + z-index: 2; +} + +.slowvote-above-the-bar { + z-index: 3; +} + .phabricator-timeline-icon-fill { z-index: 3; }