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

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
This commit is contained in:
epriestley 2013-07-15 13:18:50 -07:00
parent 0a3c03d5b6
commit 89ee928a51
9 changed files with 485 additions and 497 deletions

View file

@ -3475,7 +3475,7 @@ celerity_register_resource_map(array(
), ),
'phabricator-slowvote-css' => 'phabricator-slowvote-css' =>
array( array(
'uri' => '/res/d1c2e05a/rsrc/css/application/slowvote/slowvote.css', 'uri' => '/res/11373549/rsrc/css/application/slowvote/slowvote.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(
@ -3702,7 +3702,7 @@ celerity_register_resource_map(array(
), ),
'phabricator-zindex-css' => 'phabricator-zindex-css' =>
array( array(
'uri' => '/res/99a9447b/rsrc/css/core/z-index.css', 'uri' => '/res/a50437bf/rsrc/css/core/z-index.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(
@ -4149,7 +4149,7 @@ celerity_register_resource_map(array(
), array( ), array(
'packages' => 'packages' =>
array( array(
'c01cebae' => 'f32a863a' =>
array( array(
'name' => 'core.pkg.css', 'name' => 'core.pkg.css',
'symbols' => 'symbols' =>
@ -4197,7 +4197,7 @@ celerity_register_resource_map(array(
40 => 'phabricator-property-list-view-css', 40 => 'phabricator-property-list-view-css',
41 => 'phabricator-tag-view-css', 41 => 'phabricator-tag-view-css',
), ),
'uri' => '/res/pkg/c01cebae/core.pkg.css', 'uri' => '/res/pkg/f32a863a/core.pkg.css',
'type' => 'css', 'type' => 'css',
), ),
'75ccea43' => '75ccea43' =>
@ -4391,16 +4391,16 @@ celerity_register_resource_map(array(
'reverse' => 'reverse' =>
array( array(
'aphront-attached-file-view-css' => 'adc3c36d', 'aphront-attached-file-view-css' => 'adc3c36d',
'aphront-dialog-view-css' => 'c01cebae', 'aphront-dialog-view-css' => 'f32a863a',
'aphront-error-view-css' => 'c01cebae', 'aphront-error-view-css' => 'f32a863a',
'aphront-form-view-css' => 'c01cebae', 'aphront-form-view-css' => 'f32a863a',
'aphront-list-filter-view-css' => 'c01cebae', 'aphront-list-filter-view-css' => 'f32a863a',
'aphront-pager-view-css' => 'c01cebae', 'aphront-pager-view-css' => 'f32a863a',
'aphront-panel-view-css' => 'c01cebae', 'aphront-panel-view-css' => 'f32a863a',
'aphront-table-view-css' => 'c01cebae', 'aphront-table-view-css' => 'f32a863a',
'aphront-tokenizer-control-css' => 'c01cebae', 'aphront-tokenizer-control-css' => 'f32a863a',
'aphront-tooltip-css' => 'c01cebae', 'aphront-tooltip-css' => 'f32a863a',
'aphront-typeahead-control-css' => 'c01cebae', 'aphront-typeahead-control-css' => 'f32a863a',
'differential-changeset-view-css' => 'dd27a69b', 'differential-changeset-view-css' => 'dd27a69b',
'differential-core-view-css' => 'dd27a69b', 'differential-core-view-css' => 'dd27a69b',
'differential-inline-comment-editor' => '504ca7d2', 'differential-inline-comment-editor' => '504ca7d2',
@ -4414,7 +4414,7 @@ celerity_register_resource_map(array(
'differential-table-of-contents-css' => 'dd27a69b', 'differential-table-of-contents-css' => 'dd27a69b',
'diffusion-commit-view-css' => 'c8ce2d88', 'diffusion-commit-view-css' => 'c8ce2d88',
'diffusion-icons-css' => 'c8ce2d88', 'diffusion-icons-css' => 'c8ce2d88',
'global-drag-and-drop-css' => 'c01cebae', 'global-drag-and-drop-css' => 'f32a863a',
'inline-comment-summary-css' => 'dd27a69b', 'inline-comment-summary-css' => 'dd27a69b',
'javelin-aphlict' => '75ccea43', 'javelin-aphlict' => '75ccea43',
'javelin-behavior' => 'a9f14d76', 'javelin-behavior' => 'a9f14d76',
@ -4488,55 +4488,55 @@ celerity_register_resource_map(array(
'javelin-util' => 'a9f14d76', 'javelin-util' => 'a9f14d76',
'javelin-vector' => 'a9f14d76', 'javelin-vector' => 'a9f14d76',
'javelin-workflow' => 'a9f14d76', 'javelin-workflow' => 'a9f14d76',
'lightbox-attachment-css' => 'c01cebae', 'lightbox-attachment-css' => 'f32a863a',
'maniphest-task-summary-css' => 'adc3c36d', 'maniphest-task-summary-css' => 'adc3c36d',
'maniphest-transaction-detail-css' => 'adc3c36d', 'maniphest-transaction-detail-css' => 'adc3c36d',
'phabricator-action-list-view-css' => 'c01cebae', 'phabricator-action-list-view-css' => 'f32a863a',
'phabricator-application-launch-view-css' => 'c01cebae', 'phabricator-application-launch-view-css' => 'f32a863a',
'phabricator-busy' => '75ccea43', 'phabricator-busy' => '75ccea43',
'phabricator-content-source-view-css' => 'dd27a69b', 'phabricator-content-source-view-css' => 'dd27a69b',
'phabricator-core-css' => 'c01cebae', 'phabricator-core-css' => 'f32a863a',
'phabricator-crumbs-view-css' => 'c01cebae', 'phabricator-crumbs-view-css' => 'f32a863a',
'phabricator-drag-and-drop-file-upload' => '504ca7d2', 'phabricator-drag-and-drop-file-upload' => '504ca7d2',
'phabricator-dropdown-menu' => '75ccea43', 'phabricator-dropdown-menu' => '75ccea43',
'phabricator-file-upload' => '75ccea43', 'phabricator-file-upload' => '75ccea43',
'phabricator-filetree-view-css' => 'c01cebae', 'phabricator-filetree-view-css' => 'f32a863a',
'phabricator-flag-css' => 'c01cebae', 'phabricator-flag-css' => 'f32a863a',
'phabricator-form-view-css' => 'c01cebae', 'phabricator-form-view-css' => 'f32a863a',
'phabricator-header-view-css' => 'c01cebae', 'phabricator-header-view-css' => 'f32a863a',
'phabricator-hovercard' => '75ccea43', 'phabricator-hovercard' => '75ccea43',
'phabricator-jump-nav' => 'c01cebae', 'phabricator-jump-nav' => 'f32a863a',
'phabricator-keyboard-shortcut' => '75ccea43', 'phabricator-keyboard-shortcut' => '75ccea43',
'phabricator-keyboard-shortcut-manager' => '75ccea43', 'phabricator-keyboard-shortcut-manager' => '75ccea43',
'phabricator-main-menu-view' => 'c01cebae', 'phabricator-main-menu-view' => 'f32a863a',
'phabricator-menu-item' => '75ccea43', 'phabricator-menu-item' => '75ccea43',
'phabricator-nav-view-css' => 'c01cebae', 'phabricator-nav-view-css' => 'f32a863a',
'phabricator-notification' => '75ccea43', 'phabricator-notification' => '75ccea43',
'phabricator-notification-css' => 'c01cebae', 'phabricator-notification-css' => 'f32a863a',
'phabricator-notification-menu-css' => 'c01cebae', 'phabricator-notification-menu-css' => 'f32a863a',
'phabricator-object-item-list-view-css' => 'c01cebae', 'phabricator-object-item-list-view-css' => 'f32a863a',
'phabricator-object-selector-css' => 'dd27a69b', 'phabricator-object-selector-css' => 'dd27a69b',
'phabricator-phtize' => '75ccea43', 'phabricator-phtize' => '75ccea43',
'phabricator-prefab' => '75ccea43', 'phabricator-prefab' => '75ccea43',
'phabricator-project-tag-css' => 'adc3c36d', 'phabricator-project-tag-css' => 'adc3c36d',
'phabricator-property-list-view-css' => 'c01cebae', 'phabricator-property-list-view-css' => 'f32a863a',
'phabricator-remarkup-css' => 'c01cebae', 'phabricator-remarkup-css' => 'f32a863a',
'phabricator-shaped-request' => '504ca7d2', 'phabricator-shaped-request' => '504ca7d2',
'phabricator-side-menu-view-css' => 'c01cebae', 'phabricator-side-menu-view-css' => 'f32a863a',
'phabricator-standard-page-view' => 'c01cebae', 'phabricator-standard-page-view' => 'f32a863a',
'phabricator-tag-view-css' => 'c01cebae', 'phabricator-tag-view-css' => 'f32a863a',
'phabricator-textareautils' => '75ccea43', 'phabricator-textareautils' => '75ccea43',
'phabricator-tooltip' => '75ccea43', 'phabricator-tooltip' => '75ccea43',
'phabricator-transaction-view-css' => 'c01cebae', 'phabricator-transaction-view-css' => 'f32a863a',
'phabricator-zindex-css' => 'c01cebae', 'phabricator-zindex-css' => 'f32a863a',
'phui-button-css' => 'c01cebae', 'phui-button-css' => 'f32a863a',
'phui-form-css' => 'c01cebae', 'phui-form-css' => 'f32a863a',
'phui-icon-view-css' => 'c01cebae', 'phui-icon-view-css' => 'f32a863a',
'phui-spacing-css' => 'c01cebae', 'phui-spacing-css' => 'f32a863a',
'sprite-apps-large-css' => 'c01cebae', 'sprite-apps-large-css' => 'f32a863a',
'sprite-gradient-css' => 'c01cebae', 'sprite-gradient-css' => 'f32a863a',
'sprite-icons-css' => 'c01cebae', 'sprite-icons-css' => 'f32a863a',
'sprite-menu-css' => 'c01cebae', 'sprite-menu-css' => 'f32a863a',
'syntax-highlighting-css' => 'c01cebae', 'syntax-highlighting-css' => 'f32a863a',
), ),
)); ));

View file

@ -51,7 +51,7 @@ final class PhabricatorSlowvoteEditController
if ($request->isFormPost()) { if ($request->isFormPost()) {
$v_question = $request->getStr('question'); $v_question = $request->getStr('question');
$v_description = $request->getStr('description'); $v_description = $request->getStr('description');
$v_responses = $request->getInt('responses'); $v_responses = (int)$request->getInt('responses');
$v_shuffle = (int)$request->getBool('shuffle'); $v_shuffle = (int)$request->getBool('shuffle');
if ($is_new) { if ($is_new) {

View file

@ -13,116 +13,40 @@ final class PhabricatorSlowvotePollController
} }
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $user = $request->getUser();
$viewer_phid = $user->getPHID();
$poll = id(new PhabricatorSlowvoteQuery()) $poll = id(new PhabricatorSlowvoteQuery())
->setViewer($user) ->setViewer($user)
->withIDs(array($this->id)) ->withIDs(array($this->id))
->needOptions(true) ->needOptions(true)
->needChoices(true) ->needChoices(true)
->needViewerChoices(true)
->executeOne(); ->executeOne();
if (!$poll) { if (!$poll) {
return new Aphront404Response(); return new Aphront404Response();
} }
$options = $poll->getOptions(); $poll_view = id(new SlowvoteEmbedView())
$choices = $poll->getChoices(); ->setHeadless(true)
->setUser($user)
$choices_by_option = mgroup($choices, 'getOptionID'); ->setPoll($poll);
$choices_by_user = mgroup($choices, 'getAuthorPHID');
$viewer_choices = idx($choices_by_user, $viewer_phid, array());
if ($request->isAjax()) { if ($request->isAjax()) {
$embed = id(new SlowvoteEmbedView())
->setPoll($poll)
->setOptions($options)
->setViewerChoices($viewer_choices);
return id(new AphrontAjaxResponse()) return id(new AphrontAjaxResponse())
->setContent(array( ->setContent(
'pollID' => $poll->getID(), array(
'contentHTML' => $embed->render())); '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(
'<p class="aphront-form-instructions">%s</p>',
$instructions))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Vote'))
->setValue($option_markup))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Engage in Deliberations')));
$header = id(new PhabricatorHeaderView()) $header = id(new PhabricatorHeaderView())
->setHeader($poll->getQuestion()); ->setHeader($poll->getQuestion());
$xaction_header = id(new PhabricatorHeaderView())
->setHeader(pht('Ongoing Deliberations'));
$actions = $this->buildActionView($poll); $actions = $this->buildActionView($poll);
$properties = $this->buildPropertyView($poll); $properties = $this->buildPropertyView($poll);
@ -131,15 +55,6 @@ final class PhabricatorSlowvotePollController
id(new PhabricatorCrumbView()) id(new PhabricatorCrumbView())
->setName('V'.$poll->getID())); ->setName('V'.$poll->getID()));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->appendChild($result_markup);
$content = array(
$form,
hsprintf('<br /><br />'),
$panel);
$xactions = $this->buildTransactions($poll); $xactions = $this->buildTransactions($poll);
$add_comment = $this->buildCommentForm($poll); $add_comment = $this->buildCommentForm($poll);
@ -149,7 +64,13 @@ final class PhabricatorSlowvotePollController
$header, $header,
$actions, $actions,
$properties, $properties,
$content, phutil_tag(
'div',
array(
'class' => 'ml',
),
$poll_view),
$xaction_header,
$xactions, $xactions,
$add_comment, $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(
'<p class="aphront-form-instructions"><em>%s</em></p>',
$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(
'<div>'.
'<div class="phabricator-slowvote-count">%s</div>'.
'<h1>%s</h1>'.
'<hr class="phabricator-slowvote-hr" />'.
'%s'.
'<div style="clear: both;"></div>'.
'<hr class="phabricator-slowvote-hr" />'.
'</div>',
$vote_count,
$option->getName(),
phutil_tag('div', array(), $user_markup)));
}
return $result_markup;
}
private function buildActionView(PhabricatorSlowvotePoll $poll) { private function buildActionView(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();

View file

@ -26,8 +26,14 @@ final class PhabricatorSlowvoteEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
if ($old === null) {
return true;
}
return ((int)$old !== (int)$new); return ((int)$old !== (int)$new);
case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
if ($old === null) {
return true;
}
return ((bool)$old !== (bool)$new); return ((bool)$old !== (bool)$new);
} }

View file

@ -25,16 +25,11 @@ final class SlowvoteRemarkupRule
protected function renderObjectEmbed($object, $handle, $options) { protected function renderObjectEmbed($object, $handle, $options) {
$viewer = $this->getEngine()->getConfig('viewer'); $viewer = $this->getEngine()->getConfig('viewer');
$options = $object->getOptions();
$choices = $object->getChoices();
$viewer_choices = $object->getViewerChoices($viewer);
$embed = id(new SlowvoteEmbedView()) $embed = id(new SlowvoteEmbedView())
->setPoll($object) ->setUser($viewer)
->setOptions($options) ->setPoll($object);
->setViewerChoices($viewer_choices);
return $embed->render(); return $embed;
} }
} }

View file

@ -74,7 +74,7 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO
} }
public function attachViewerChoices(PhabricatorUser $viewer, array $choices) { public function attachViewerChoices(PhabricatorUser $viewer, array $choices) {
assert_instances_of($choices, 'PhabricatorSlowvoteOption'); assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
$this->viewerChoices[$viewer->getPHID()] = $choices; $this->viewerChoices[$viewer->getPHID()] = $choices;
return $this; return $this;
} }

View file

@ -6,123 +6,349 @@
final class SlowvoteEmbedView extends AphrontView { final class SlowvoteEmbedView extends AphrontView {
private $poll; private $poll;
private $options; private $handles;
private $viewerChoices; private $headless;
public function setHeadless($headless) {
$this->headless = $headless;
return $this;
}
public function setPoll(PhabricatorSlowvotePoll $poll) { public function setPoll(PhabricatorSlowvotePoll $poll) {
$this->poll = $poll; $this->poll = $poll;
return $this; return $this;
} }
public function setOptions(array $options) { public function getPoll() {
$this->options = $options; return $this->poll;
return $this;
}
public function setViewerChoices(array $viewer_choices) {
$this->viewerChoices = $viewer_choices;
return $this;
} }
public function render() { public function render() {
if (!$this->poll) { if (!$this->poll) {
throw new Exception("Call setPoll() before render()!"); throw new Exception("Call setPoll() before render()!");
} }
if (!$this->options) { $poll = $this->poll;
throw new Exception("Call setOptions() before render()!");
}
if ($this->poll->getShuffle()) { $phids = array();
shuffle($this->options); 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('phabricator-slowvote-css');
require_celerity_resource('javelin-behavior-slowvote-embed'); require_celerity_resource('javelin-behavior-slowvote-embed');
$config = array( $config = array(
'pollID' => $this->poll->getID()); 'pollID' => $poll->getID());
Javelin::initBehavior('slowvote-embed', $config); Javelin::initBehavior('slowvote-embed', $config);
$user_choices = array(); $user_choices = $poll->getViewerChoices($this->getUser());
if (!empty($this->viewerChoices)) { $user_choices = mpull($user_choices, 'getOptionID', 'getOptionID');
$user_choices = mpull($this->viewerChoices, null, 'getOptionID');
}
$options = array(); $out = array();
$ribbon_colors = array('#DF0101', '#DF7401', '#D7DF01', '#74DF00', foreach ($options as $option) {
'#01DF01', '#01DF74', '#01DFD7', '#0174DF', '#0101DF', '#5F04B4', $is_selected = isset($user_choices[$option->getID()]);
'#B404AE'); $out[] = $this->renderLabel($option, $is_selected);
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));
} }
$link_to_slowvote = phutil_tag( $link_to_slowvote = phutil_tag(
'a', 'a',
array( array(
'href' => '/V'.$this->poll->getID() 'href' => '/V'.$poll->getID()
), ),
$this->poll->getQuestion()); $poll->getQuestion());
$header = phutil_tag( if ($this->headless) {
'div', $header = null;
array(), } else {
array('V'.$this->poll->getID().': ', $link_to_slowvote)); $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', 'div',
array(), array(
$options); '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( return javelin_tag(
'div', 'div',
array( array(
'class' => 'phabricator-slowvote-embed', 'class' => 'slowvote-embed',
'sigil' => 'slowvote-embed', 'sigil' => 'slowvote-embed',
'meta' => array( 'meta' => array(
'pollID' => $this->poll->getID() 'pollID' => $poll->getID()
) )
), ),
array($header, $body)); 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());
}
}
} }

View file

@ -2,90 +2,124 @@
* @provides phabricator-slowvote-css * @provides phabricator-slowvote-css
*/ */
.phabricator-slowvote-comments { .slowvote-embed {
width: 100%; 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 { .slowvote-header {
width: 150px; font-weight: bold;
text-align: right; line-height: 16px;
padding: 6px 4px 6px; border-bottom: 1px solid #bbbbbb;
white-space: nowrap;
} }
.phabricator-slowvote-comments td { .slowvote-description {
vertical-align: top;
padding: 6px 2px;
border-bottom: 1px solid #d0d0d0;
}
.phabricator-slowvote-datestamp {
font-size: 9px;
font-family: "Verdana";
color: #666666; color: #666666;
margin-top: 1px; padding: 8px;
border-bottom: 1px solid #bbbbbb;
} }
.phabricator-slowvote-hr { .slowvote-header-content {
border: none; padding: 8px;
height: 1px;
position: relative;
background: #c0c0c0;
} }
.phabricator-slowvote-count { .slowvote-body {
float: right;
font-size: 13px;
font-weight: bold;
} }
.phabricator-slowvote-label { .slowvote-body-content {
padding: 4px 16px;
}
.slowvote-option-label {
border: 1px solid #666666;
display: block; 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; position: relative;
float: left; padding: 8px 4px;
margin: 0px 4px 6px 0px; cursor: pointer;
font-weight: bold;
overflow: hidden;
background-color: {$lightblue};
} }
.phabricator-slowvote-embed { .slowvote-bar {
width: 400px; position: absolute;
background: #f7f7f7; top: 0;
border: 1px solid #dbdbdb; left: 0;
padding: 5px; bottom: 0;
overflow: hidden;
background-color: {$blue};
} }
.phabricator-slowvote-embed-option { .slowvote-control-offset {
margin: 1px; white-space: nowrap;
background: #F0FFFF; 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; display: inline-block;
width: 1em; width: 25px;
height: 25px;
background: #f3f3f3;
background-size: 25px 25px;
} }
.phabricator-slowvote-embed-option-text { .slowvote-footer {
border: 1px solid #dbdbdb; border-top-width: 1px;
border-left: 0px; 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;
} }

View file

@ -22,6 +22,14 @@
z-index: 2; z-index: 2;
} }
.slowvote-bar {
z-index: 2;
}
.slowvote-above-the-bar {
z-index: 3;
}
.phabricator-timeline-icon-fill { .phabricator-timeline-icon-fill {
z-index: 3; z-index: 3;
} }