mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 16:30:59 +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:
parent
0a3c03d5b6
commit
89ee928a51
9 changed files with 485 additions and 497 deletions
|
@ -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',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
'<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())
|
||||
->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('<br /><br />'),
|
||||
$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(
|
||||
'<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) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue