@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+472 -484
+46 -46
src/__celerity_resource_map__.php
··· 3475 3475 ), 3476 3476 'phabricator-slowvote-css' => 3477 3477 array( 3478 - 'uri' => '/res/d1c2e05a/rsrc/css/application/slowvote/slowvote.css', 3478 + 'uri' => '/res/11373549/rsrc/css/application/slowvote/slowvote.css', 3479 3479 'type' => 'css', 3480 3480 'requires' => 3481 3481 array( ··· 3702 3702 ), 3703 3703 'phabricator-zindex-css' => 3704 3704 array( 3705 - 'uri' => '/res/99a9447b/rsrc/css/core/z-index.css', 3705 + 'uri' => '/res/a50437bf/rsrc/css/core/z-index.css', 3706 3706 'type' => 'css', 3707 3707 'requires' => 3708 3708 array( ··· 4149 4149 ), array( 4150 4150 'packages' => 4151 4151 array( 4152 - 'c01cebae' => 4152 + 'f32a863a' => 4153 4153 array( 4154 4154 'name' => 'core.pkg.css', 4155 4155 'symbols' => ··· 4197 4197 40 => 'phabricator-property-list-view-css', 4198 4198 41 => 'phabricator-tag-view-css', 4199 4199 ), 4200 - 'uri' => '/res/pkg/c01cebae/core.pkg.css', 4200 + 'uri' => '/res/pkg/f32a863a/core.pkg.css', 4201 4201 'type' => 'css', 4202 4202 ), 4203 4203 '75ccea43' => ··· 4391 4391 'reverse' => 4392 4392 array( 4393 4393 'aphront-attached-file-view-css' => 'adc3c36d', 4394 - 'aphront-dialog-view-css' => 'c01cebae', 4395 - 'aphront-error-view-css' => 'c01cebae', 4396 - 'aphront-form-view-css' => 'c01cebae', 4397 - 'aphront-list-filter-view-css' => 'c01cebae', 4398 - 'aphront-pager-view-css' => 'c01cebae', 4399 - 'aphront-panel-view-css' => 'c01cebae', 4400 - 'aphront-table-view-css' => 'c01cebae', 4401 - 'aphront-tokenizer-control-css' => 'c01cebae', 4402 - 'aphront-tooltip-css' => 'c01cebae', 4403 - 'aphront-typeahead-control-css' => 'c01cebae', 4394 + 'aphront-dialog-view-css' => 'f32a863a', 4395 + 'aphront-error-view-css' => 'f32a863a', 4396 + 'aphront-form-view-css' => 'f32a863a', 4397 + 'aphront-list-filter-view-css' => 'f32a863a', 4398 + 'aphront-pager-view-css' => 'f32a863a', 4399 + 'aphront-panel-view-css' => 'f32a863a', 4400 + 'aphront-table-view-css' => 'f32a863a', 4401 + 'aphront-tokenizer-control-css' => 'f32a863a', 4402 + 'aphront-tooltip-css' => 'f32a863a', 4403 + 'aphront-typeahead-control-css' => 'f32a863a', 4404 4404 'differential-changeset-view-css' => 'dd27a69b', 4405 4405 'differential-core-view-css' => 'dd27a69b', 4406 4406 'differential-inline-comment-editor' => '504ca7d2', ··· 4414 4414 'differential-table-of-contents-css' => 'dd27a69b', 4415 4415 'diffusion-commit-view-css' => 'c8ce2d88', 4416 4416 'diffusion-icons-css' => 'c8ce2d88', 4417 - 'global-drag-and-drop-css' => 'c01cebae', 4417 + 'global-drag-and-drop-css' => 'f32a863a', 4418 4418 'inline-comment-summary-css' => 'dd27a69b', 4419 4419 'javelin-aphlict' => '75ccea43', 4420 4420 'javelin-behavior' => 'a9f14d76', ··· 4488 4488 'javelin-util' => 'a9f14d76', 4489 4489 'javelin-vector' => 'a9f14d76', 4490 4490 'javelin-workflow' => 'a9f14d76', 4491 - 'lightbox-attachment-css' => 'c01cebae', 4491 + 'lightbox-attachment-css' => 'f32a863a', 4492 4492 'maniphest-task-summary-css' => 'adc3c36d', 4493 4493 'maniphest-transaction-detail-css' => 'adc3c36d', 4494 - 'phabricator-action-list-view-css' => 'c01cebae', 4495 - 'phabricator-application-launch-view-css' => 'c01cebae', 4494 + 'phabricator-action-list-view-css' => 'f32a863a', 4495 + 'phabricator-application-launch-view-css' => 'f32a863a', 4496 4496 'phabricator-busy' => '75ccea43', 4497 4497 'phabricator-content-source-view-css' => 'dd27a69b', 4498 - 'phabricator-core-css' => 'c01cebae', 4499 - 'phabricator-crumbs-view-css' => 'c01cebae', 4498 + 'phabricator-core-css' => 'f32a863a', 4499 + 'phabricator-crumbs-view-css' => 'f32a863a', 4500 4500 'phabricator-drag-and-drop-file-upload' => '504ca7d2', 4501 4501 'phabricator-dropdown-menu' => '75ccea43', 4502 4502 'phabricator-file-upload' => '75ccea43', 4503 - 'phabricator-filetree-view-css' => 'c01cebae', 4504 - 'phabricator-flag-css' => 'c01cebae', 4505 - 'phabricator-form-view-css' => 'c01cebae', 4506 - 'phabricator-header-view-css' => 'c01cebae', 4503 + 'phabricator-filetree-view-css' => 'f32a863a', 4504 + 'phabricator-flag-css' => 'f32a863a', 4505 + 'phabricator-form-view-css' => 'f32a863a', 4506 + 'phabricator-header-view-css' => 'f32a863a', 4507 4507 'phabricator-hovercard' => '75ccea43', 4508 - 'phabricator-jump-nav' => 'c01cebae', 4508 + 'phabricator-jump-nav' => 'f32a863a', 4509 4509 'phabricator-keyboard-shortcut' => '75ccea43', 4510 4510 'phabricator-keyboard-shortcut-manager' => '75ccea43', 4511 - 'phabricator-main-menu-view' => 'c01cebae', 4511 + 'phabricator-main-menu-view' => 'f32a863a', 4512 4512 'phabricator-menu-item' => '75ccea43', 4513 - 'phabricator-nav-view-css' => 'c01cebae', 4513 + 'phabricator-nav-view-css' => 'f32a863a', 4514 4514 'phabricator-notification' => '75ccea43', 4515 - 'phabricator-notification-css' => 'c01cebae', 4516 - 'phabricator-notification-menu-css' => 'c01cebae', 4517 - 'phabricator-object-item-list-view-css' => 'c01cebae', 4515 + 'phabricator-notification-css' => 'f32a863a', 4516 + 'phabricator-notification-menu-css' => 'f32a863a', 4517 + 'phabricator-object-item-list-view-css' => 'f32a863a', 4518 4518 'phabricator-object-selector-css' => 'dd27a69b', 4519 4519 'phabricator-phtize' => '75ccea43', 4520 4520 'phabricator-prefab' => '75ccea43', 4521 4521 'phabricator-project-tag-css' => 'adc3c36d', 4522 - 'phabricator-property-list-view-css' => 'c01cebae', 4523 - 'phabricator-remarkup-css' => 'c01cebae', 4522 + 'phabricator-property-list-view-css' => 'f32a863a', 4523 + 'phabricator-remarkup-css' => 'f32a863a', 4524 4524 'phabricator-shaped-request' => '504ca7d2', 4525 - 'phabricator-side-menu-view-css' => 'c01cebae', 4526 - 'phabricator-standard-page-view' => 'c01cebae', 4527 - 'phabricator-tag-view-css' => 'c01cebae', 4525 + 'phabricator-side-menu-view-css' => 'f32a863a', 4526 + 'phabricator-standard-page-view' => 'f32a863a', 4527 + 'phabricator-tag-view-css' => 'f32a863a', 4528 4528 'phabricator-textareautils' => '75ccea43', 4529 4529 'phabricator-tooltip' => '75ccea43', 4530 - 'phabricator-transaction-view-css' => 'c01cebae', 4531 - 'phabricator-zindex-css' => 'c01cebae', 4532 - 'phui-button-css' => 'c01cebae', 4533 - 'phui-form-css' => 'c01cebae', 4534 - 'phui-icon-view-css' => 'c01cebae', 4535 - 'phui-spacing-css' => 'c01cebae', 4536 - 'sprite-apps-large-css' => 'c01cebae', 4537 - 'sprite-gradient-css' => 'c01cebae', 4538 - 'sprite-icons-css' => 'c01cebae', 4539 - 'sprite-menu-css' => 'c01cebae', 4540 - 'syntax-highlighting-css' => 'c01cebae', 4530 + 'phabricator-transaction-view-css' => 'f32a863a', 4531 + 'phabricator-zindex-css' => 'f32a863a', 4532 + 'phui-button-css' => 'f32a863a', 4533 + 'phui-form-css' => 'f32a863a', 4534 + 'phui-icon-view-css' => 'f32a863a', 4535 + 'phui-spacing-css' => 'f32a863a', 4536 + 'sprite-apps-large-css' => 'f32a863a', 4537 + 'sprite-gradient-css' => 'f32a863a', 4538 + 'sprite-icons-css' => 'f32a863a', 4539 + 'sprite-menu-css' => 'f32a863a', 4540 + 'syntax-highlighting-css' => 'f32a863a', 4541 4541 ), 4542 4542 ));
+1 -1
src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
··· 51 51 if ($request->isFormPost()) { 52 52 $v_question = $request->getStr('question'); 53 53 $v_description = $request->getStr('description'); 54 - $v_responses = $request->getInt('responses'); 54 + $v_responses = (int)$request->getInt('responses'); 55 55 $v_shuffle = (int)$request->getBool('shuffle'); 56 56 57 57 if ($is_new) {
+20 -301
src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
··· 13 13 } 14 14 15 15 public function processRequest() { 16 - 17 16 $request = $this->getRequest(); 18 17 $user = $request->getUser(); 19 - $viewer_phid = $user->getPHID(); 20 18 21 19 $poll = id(new PhabricatorSlowvoteQuery()) 22 20 ->setViewer($user) 23 21 ->withIDs(array($this->id)) 24 22 ->needOptions(true) 25 23 ->needChoices(true) 24 + ->needViewerChoices(true) 26 25 ->executeOne(); 27 26 if (!$poll) { 28 27 return new Aphront404Response(); 29 28 } 30 29 31 - $options = $poll->getOptions(); 32 - $choices = $poll->getChoices(); 33 - 34 - $choices_by_option = mgroup($choices, 'getOptionID'); 35 - $choices_by_user = mgroup($choices, 'getAuthorPHID'); 36 - $viewer_choices = idx($choices_by_user, $viewer_phid, array()); 30 + $poll_view = id(new SlowvoteEmbedView()) 31 + ->setHeadless(true) 32 + ->setUser($user) 33 + ->setPoll($poll); 37 34 38 35 if ($request->isAjax()) { 39 - $embed = id(new SlowvoteEmbedView()) 40 - ->setPoll($poll) 41 - ->setOptions($options) 42 - ->setViewerChoices($viewer_choices); 43 - 44 36 return id(new AphrontAjaxResponse()) 45 - ->setContent(array( 46 - 'pollID' => $poll->getID(), 47 - 'contentHTML' => $embed->render())); 37 + ->setContent( 38 + array( 39 + 'pollID' => $poll->getID(), 40 + 'contentHTML' => $poll_view->render(), 41 + )); 48 42 } 49 43 50 - require_celerity_resource('phabricator-slowvote-css'); 51 - 52 - $phids = array_merge( 53 - mpull($choices, 'getAuthorPHID'), 54 - array( 55 - $poll->getAuthorPHID(), 56 - )); 57 - 58 - $query = new PhabricatorObjectHandleData($phids); 59 - $query->setViewer($user); 60 - $handles = $query->loadHandles(); 61 - $objects = $query->loadObjects(); 62 - 63 - if ($poll->getShuffle()) { 64 - shuffle($options); 65 - } 66 - 67 - $option_markup = array(); 68 - foreach ($options as $option) { 69 - $option_markup[] = $this->renderPollOption( 70 - $poll, 71 - $viewer_choices, 72 - $option); 73 - } 74 - 75 - switch ($poll->getMethod()) { 76 - case PhabricatorSlowvotePoll::METHOD_PLURALITY: 77 - $choice_ids = array(); 78 - foreach ($choices_by_user as $user_phid => $user_choices) { 79 - $choice_ids[$user_phid] = head($user_choices)->getOptionID(); 80 - } 81 - break; 82 - case PhabricatorSlowvotePoll::METHOD_APPROVAL: 83 - break; 84 - default: 85 - throw new Exception("Unknown poll method!"); 86 - } 87 - 88 - $result_markup = $this->renderResultMarkup( 89 - $poll, 90 - $options, 91 - $choices, 92 - $viewer_choices, 93 - $choices_by_option, 94 - $handles, 95 - $objects); 96 - 97 - if ($viewer_choices) { 98 - $instructions = 99 - pht('Your vote has been recorded... but there is still ample time to '. 100 - 'rethink your position. Have you thoroughly considered all possible '. 101 - 'eventualities?'); 102 - } else { 103 - $instructions = 104 - pht('This is a weighty matter indeed. Consider your choices with the '. 105 - 'greatest of care.'); 106 - } 107 - 108 - $form = id(new AphrontFormView()) 109 - ->setUser($user) 110 - ->setFlexible(true) 111 - ->setAction(sprintf('/vote/%d/', $poll->getID())) 112 - ->appendChild(hsprintf( 113 - '<p class="aphront-form-instructions">%s</p>', 114 - $instructions)) 115 - ->appendChild( 116 - id(new AphrontFormMarkupControl()) 117 - ->setLabel(pht('Vote')) 118 - ->setValue($option_markup)) 119 - ->appendChild( 120 - id(new AphrontFormSubmitControl()) 121 - ->setValue(pht('Engage in Deliberations'))); 122 - 123 44 $header = id(new PhabricatorHeaderView()) 124 45 ->setHeader($poll->getQuestion()); 125 46 47 + $xaction_header = id(new PhabricatorHeaderView()) 48 + ->setHeader(pht('Ongoing Deliberations')); 49 + 126 50 $actions = $this->buildActionView($poll); 127 51 $properties = $this->buildPropertyView($poll); 128 52 ··· 130 54 $crumbs->addCrumb( 131 55 id(new PhabricatorCrumbView()) 132 56 ->setName('V'.$poll->getID())); 133 - 134 - $panel = new AphrontPanelView(); 135 - $panel->setWidth(AphrontPanelView::WIDTH_WIDE); 136 - $panel->appendChild($result_markup); 137 - 138 - $content = array( 139 - $form, 140 - hsprintf('<br /><br />'), 141 - $panel); 142 57 143 58 $xactions = $this->buildTransactions($poll); 144 59 $add_comment = $this->buildCommentForm($poll); ··· 149 64 $header, 150 65 $actions, 151 66 $properties, 152 - $content, 67 + phutil_tag( 68 + 'div', 69 + array( 70 + 'class' => 'ml', 71 + ), 72 + $poll_view), 73 + $xaction_header, 153 74 $xactions, 154 75 $add_comment, 155 76 ), ··· 159 80 'dust' => true, 160 81 'pageObjects' => array($poll->getPHID()), 161 82 )); 162 - } 163 - 164 - 165 - private function renderPollOption( 166 - PhabricatorSlowvotePoll $poll, 167 - array $viewer_choices, 168 - PhabricatorSlowvoteOption $option) { 169 - assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); 170 - 171 - $id = $option->getID(); 172 - switch ($poll->getMethod()) { 173 - case PhabricatorSlowvotePoll::METHOD_PLURALITY: 174 - 175 - // Render a radio button. 176 - 177 - $selected_option = head($viewer_choices); 178 - if ($selected_option) { 179 - $selected = $selected_option->getOptionID(); 180 - } else { 181 - $selected = null; 182 - } 183 - 184 - if ($selected == $id) { 185 - $checked = "checked"; 186 - } else { 187 - $checked = null; 188 - } 189 - 190 - $input = phutil_tag( 191 - 'input', 192 - array( 193 - 'type' => 'radio', 194 - 'name' => 'vote[]', 195 - 'value' => $id, 196 - 'checked' => $checked, 197 - )); 198 - break; 199 - case PhabricatorSlowvotePoll::METHOD_APPROVAL: 200 - 201 - // Render a check box. 202 - 203 - $checked = null; 204 - foreach ($viewer_choices as $choice) { 205 - if ($choice->getOptionID() == $id) { 206 - $checked = 'checked'; 207 - break; 208 - } 209 - } 210 - 211 - $input = phutil_tag( 212 - 'input', 213 - array( 214 - 'type' => 'checkbox', 215 - 'name' => 'vote[]', 216 - 'checked' => $checked, 217 - 'value' => $id, 218 - )); 219 - break; 220 - default: 221 - throw new Exception("Unknown poll method!"); 222 - } 223 - 224 - if ($checked) { 225 - $checked_class = 'phabricator-slowvote-checked'; 226 - } else { 227 - $checked_class = null; 228 - } 229 - 230 - return phutil_tag( 231 - 'label', 232 - array( 233 - 'class' => 'phabricator-slowvote-label '.$checked_class, 234 - ), 235 - array($input, $option->getName())); 236 - } 237 - 238 - private function renderVoteCount( 239 - PhabricatorSlowvotePoll $poll, 240 - array $choices, 241 - array $chosen) { 242 - assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); 243 - assert_instances_of($chosen, 'PhabricatorSlowvoteChoice'); 244 - 245 - switch ($poll->getMethod()) { 246 - case PhabricatorSlowvotePoll::METHOD_PLURALITY: 247 - $out_of_total = count($choices); 248 - break; 249 - case PhabricatorSlowvotePoll::METHOD_APPROVAL: 250 - // Count unique respondents for approval votes. 251 - $out_of_total = count(mpull($choices, null, 'getAuthorPHID')); 252 - break; 253 - default: 254 - throw new Exception("Unknown poll method!"); 255 - } 256 - 257 - return sprintf( 258 - '%d / %d (%d%%)', 259 - number_format(count($chosen)), 260 - number_format($out_of_total), 261 - $out_of_total 262 - ? round(100 * count($chosen) / $out_of_total) 263 - : 0); 264 - } 265 - 266 - private function renderResultMarkup( 267 - PhabricatorSlowvotePoll $poll, 268 - array $options, 269 - array $choices, 270 - array $viewer_choices, 271 - array $choices_by_option, 272 - array $handles, 273 - array $objects) { 274 - assert_instances_of($options, 'PhabricatorSlowvoteOption'); 275 - assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); 276 - assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); 277 - assert_instances_of($handles, 'PhabricatorObjectHandle'); 278 - assert_instances_of($objects, 'PhabricatorLiskDAO'); 279 - 280 - $viewer_phid = $this->getRequest()->getUser()->getPHID(); 281 - 282 - $can_see_responses = false; 283 - $need_vote = false; 284 - switch ($poll->getResponseVisibility()) { 285 - case PhabricatorSlowvotePoll::RESPONSES_VISIBLE: 286 - $can_see_responses = true; 287 - break; 288 - case PhabricatorSlowvotePoll::RESPONSES_VOTERS: 289 - $can_see_responses = (bool)$viewer_choices; 290 - $need_vote = true; 291 - break; 292 - case PhabricatorSlowvotePoll::RESPONSES_OWNER: 293 - $can_see_responses = ($viewer_phid == $poll->getAuthorPHID()); 294 - break; 295 - } 296 - 297 - $result_markup = id(new AphrontFormLayoutView()) 298 - ->appendChild(phutil_tag('h1', array(), pht('Ongoing Deliberation'))); 299 - 300 - if (!$can_see_responses) { 301 - if ($need_vote) { 302 - $reason = pht("You must vote to see the results."); 303 - } else { 304 - $reason = pht("The results are not public."); 305 - } 306 - $result_markup 307 - ->appendChild(hsprintf( 308 - '<p class="aphront-form-instructions"><em>%s</em></p>', 309 - $reason)); 310 - return $result_markup; 311 - } 312 - 313 - foreach ($options as $option) { 314 - $id = $option->getID(); 315 - 316 - $chosen = idx($choices_by_option, $id, array()); 317 - $users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID')); 318 - if ($users) { 319 - $user_markup = array(); 320 - foreach ($users as $handle) { 321 - $object = idx($objects, $handle->getPHID()); 322 - if (!$object) { 323 - continue; 324 - } 325 - 326 - $profile_image = $handle->getImageURI(); 327 - 328 - $user_markup[] = phutil_tag( 329 - 'a', 330 - array( 331 - 'href' => $handle->getURI(), 332 - 'class' => 'phabricator-slowvote-facepile', 333 - ), 334 - phutil_tag( 335 - 'img', 336 - array( 337 - 'src' => $profile_image, 338 - ))); 339 - } 340 - } else { 341 - $user_markup = pht('This option has failed to appeal to anyone.'); 342 - } 343 - 344 - $vote_count = $this->renderVoteCount( 345 - $poll, 346 - $choices, 347 - $chosen); 348 - 349 - $result_markup->appendChild(hsprintf( 350 - '<div>'. 351 - '<div class="phabricator-slowvote-count">%s</div>'. 352 - '<h1>%s</h1>'. 353 - '<hr class="phabricator-slowvote-hr" />'. 354 - '%s'. 355 - '<div style="clear: both;"></div>'. 356 - '<hr class="phabricator-slowvote-hr" />'. 357 - '</div>', 358 - $vote_count, 359 - $option->getName(), 360 - phutil_tag('div', array(), $user_markup))); 361 - } 362 - 363 - return $result_markup; 364 83 } 365 84 366 85 private function buildActionView(PhabricatorSlowvotePoll $poll) {
+6
src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
··· 26 26 27 27 switch ($xaction->getTransactionType()) { 28 28 case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: 29 + if ($old === null) { 30 + return true; 31 + } 29 32 return ((int)$old !== (int)$new); 30 33 case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: 34 + if ($old === null) { 35 + return true; 36 + } 31 37 return ((bool)$old !== (bool)$new); 32 38 } 33 39
+3 -8
src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php
··· 25 25 protected function renderObjectEmbed($object, $handle, $options) { 26 26 $viewer = $this->getEngine()->getConfig('viewer'); 27 27 28 - $options = $object->getOptions(); 29 - $choices = $object->getChoices(); 30 - $viewer_choices = $object->getViewerChoices($viewer); 31 - 32 28 $embed = id(new SlowvoteEmbedView()) 33 - ->setPoll($object) 34 - ->setOptions($options) 35 - ->setViewerChoices($viewer_choices); 29 + ->setUser($viewer) 30 + ->setPoll($object); 36 31 37 - return $embed->render(); 32 + return $embed; 38 33 } 39 34 40 35 }
+1 -1
src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
··· 74 74 } 75 75 76 76 public function attachViewerChoices(PhabricatorUser $viewer, array $choices) { 77 - assert_instances_of($choices, 'PhabricatorSlowvoteOption'); 77 + assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); 78 78 $this->viewerChoices[$viewer->getPHID()] = $choices; 79 79 return $this; 80 80 }
+294 -68
src/applications/slowvote/view/SlowvoteEmbedView.php
··· 6 6 final class SlowvoteEmbedView extends AphrontView { 7 7 8 8 private $poll; 9 - private $options; 10 - private $viewerChoices; 9 + private $handles; 10 + private $headless; 11 11 12 - public function setPoll(PhabricatorSlowvotePoll $poll) { 13 - $this->poll = $poll; 12 + public function setHeadless($headless) { 13 + $this->headless = $headless; 14 14 return $this; 15 15 } 16 16 17 - public function setOptions(array $options) { 18 - $this->options = $options; 17 + public function setPoll(PhabricatorSlowvotePoll $poll) { 18 + $this->poll = $poll; 19 19 return $this; 20 20 } 21 21 22 - public function setViewerChoices(array $viewer_choices) { 23 - $this->viewerChoices = $viewer_choices; 24 - return $this; 22 + public function getPoll() { 23 + return $this->poll; 25 24 } 26 25 27 26 public function render() { 28 - 29 27 if (!$this->poll) { 30 28 throw new Exception("Call setPoll() before render()!"); 31 29 } 32 30 33 - if (!$this->options) { 34 - throw new Exception("Call setOptions() before render()!"); 31 + $poll = $this->poll; 32 + 33 + $phids = array(); 34 + foreach ($poll->getChoices() as $choice) { 35 + $phids[] = $choice->getAuthorPHID(); 35 36 } 37 + $phids[] = $poll->getAuthorPHID(); 36 38 37 - if ($this->poll->getShuffle()) { 38 - shuffle($this->options); 39 + $this->handles = id(new PhabricatorObjectHandleData($phids)) 40 + ->setViewer($this->getUser()) 41 + ->loadHandles(); 42 + 43 + $options = $poll->getOptions(); 44 + 45 + if ($poll->getShuffle()) { 46 + shuffle($options); 39 47 } 40 48 41 49 require_celerity_resource('phabricator-slowvote-css'); 42 50 require_celerity_resource('javelin-behavior-slowvote-embed'); 43 51 44 52 $config = array( 45 - 'pollID' => $this->poll->getID()); 53 + 'pollID' => $poll->getID()); 46 54 Javelin::initBehavior('slowvote-embed', $config); 47 55 48 - $user_choices = array(); 49 - if (!empty($this->viewerChoices)) { 50 - $user_choices = mpull($this->viewerChoices, null, 'getOptionID'); 56 + $user_choices = $poll->getViewerChoices($this->getUser()); 57 + $user_choices = mpull($user_choices, 'getOptionID', 'getOptionID'); 58 + 59 + $out = array(); 60 + foreach ($options as $option) { 61 + $is_selected = isset($user_choices[$option->getID()]); 62 + $out[] = $this->renderLabel($option, $is_selected); 51 63 } 52 64 53 - $options = array(); 54 - $ribbon_colors = array('#DF0101', '#DF7401', '#D7DF01', '#74DF00', 55 - '#01DF01', '#01DF74', '#01DFD7', '#0174DF', '#0101DF', '#5F04B4', 56 - '#B404AE'); 57 - shuffle($ribbon_colors); 65 + $link_to_slowvote = phutil_tag( 66 + 'a', 67 + array( 68 + 'href' => '/V'.$poll->getID() 69 + ), 70 + $poll->getQuestion()); 58 71 59 - foreach ($this->options as $option) { 60 - $classes = 'phabricator-slowvote-embed-option-text'; 72 + if ($this->headless) { 73 + $header = null; 74 + } else { 75 + $header = phutil_tag( 76 + 'div', 77 + array( 78 + 'class' => 'slowvote-header', 79 + ), 80 + phutil_tag( 81 + 'div', 82 + array( 83 + 'class' => 'slowvote-header-content', 84 + ), 85 + array( 86 + 'V'.$poll->getID(), 87 + ' ', 88 + $link_to_slowvote))); 61 89 62 - $selected = ''; 90 + $description = null; 91 + if ($poll->getDescription()) { 92 + $description = PhabricatorMarkupEngine::renderOneObject( 93 + id(new PhabricatorMarkupOneOff())->setContent( 94 + $poll->getDescription()), 95 + 'default', 96 + $this->getUser()); 97 + $description = phutil_tag( 98 + 'div', 99 + array( 100 + 'class' => 'slowvote-description', 101 + ), 102 + $description); 103 + } 63 104 105 + $header = array( 106 + $header, 107 + $description); 108 + } 64 109 65 - if (idx($user_choices, $option->getID(), false)) { 66 - $classes .= ' phabricator-slowvote-embed-option-selected'; 67 - $selected = '@'; 110 + $vis = $poll->getResponseVisibility(); 111 + if ($this->areResultsVisible()) { 112 + if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { 113 + $quip = pht('Only you can see the results.'); 114 + } else { 115 + $quip = pht('Voting improves cardiovascular endurance.'); 68 116 } 117 + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_VOTERS) { 118 + $quip = pht('You must vote to see the results.'); 119 + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { 120 + $quip = pht('Only the author can see the results.'); 121 + } 69 122 70 - $is_selected = javelin_tag( 71 - 'div', 72 - array( 73 - 'class' => 'phabricator-slowvote-embed-option-vote', 74 - 'sigil' => 'slowvote-embed-vote' 75 - ), 76 - $selected); 123 + $hint = phutil_tag( 124 + 'span', 125 + array( 126 + 'class' => 'slowvote-hint', 127 + ), 128 + $quip); 77 129 78 - $option_text = javelin_tag( 130 + $submit = phutil_tag( 131 + 'div', 132 + array( 133 + 'class' => 'slowvote-footer', 134 + ), 135 + phutil_tag( 79 136 'div', 80 137 array( 81 - 'class' => $classes, 82 - 'sigil' => 'slowvote-option', 83 - 'meta' => array( 84 - 'optionID' => $option->getID() 85 - ) 138 + 'class' => 'slowvote-footer-content', 86 139 ), 87 - array($is_selected, $option->getName())); 88 - 89 - $options[] = phutil_tag( 90 - 'div', 91 140 array( 92 - 'class' => 'phabricator-slowvote-embed-option', 93 - 'style' => 94 - sprintf('border-left: 7px solid %s;', array_shift($ribbon_colors)) 95 - ), 96 - array($option_text)); 97 - } 141 + $hint, 142 + phutil_tag( 143 + 'button', 144 + array( 145 + ), 146 + pht('Engage in Deliberations')), 147 + ))); 98 148 99 - $link_to_slowvote = phutil_tag( 100 - 'a', 149 + $body = phabricator_form( 150 + $this->getUser(), 101 151 array( 102 - 'href' => '/V'.$this->poll->getID() 152 + 'action' => '/vote/'.$poll->getID().'/', 153 + 'method' => 'POST', 154 + 'class' => 'slowvote-body', 103 155 ), 104 - $this->poll->getQuestion()); 105 - 106 - $header = phutil_tag( 107 - 'div', 108 - array(), 109 - array('V'.$this->poll->getID().': ', $link_to_slowvote)); 110 - 111 - $body = phutil_tag( 112 - 'div', 113 - array(), 114 - $options); 156 + array( 157 + phutil_tag( 158 + 'div', 159 + array( 160 + 'class' => 'slowvote-body-content', 161 + ), 162 + $out), 163 + $submit, 164 + )); 115 165 116 166 return javelin_tag( 117 167 'div', 118 168 array( 119 - 'class' => 'phabricator-slowvote-embed', 169 + 'class' => 'slowvote-embed', 120 170 'sigil' => 'slowvote-embed', 121 171 'meta' => array( 122 - 'pollID' => $this->poll->getID() 172 + 'pollID' => $poll->getID() 123 173 ) 124 174 ), 125 175 array($header, $body)); 176 + } 177 + 178 + private function renderLabel(PhabricatorSlowvoteOption $option, $selected) { 179 + $classes = array(); 180 + $classes[] = 'slowvote-option-label'; 181 + 182 + $status = $this->renderStatus($option); 183 + $voters = $this->renderVoters($option); 184 + 185 + return phutil_tag( 186 + 'div', 187 + array( 188 + 'class' => 'slowvote-option-label-group', 189 + ), 190 + array( 191 + phutil_tag( 192 + 'label', 193 + array( 194 + 'class' => implode(' ', $classes), 195 + ), 196 + array( 197 + phutil_tag( 198 + 'div', 199 + array( 200 + 'class' => 'slowvote-control-offset', 201 + ), 202 + $option->getName()), 203 + $this->renderBar($option), 204 + phutil_tag( 205 + 'div', 206 + array( 207 + 'class' => 'slowvote-above-the-bar', 208 + ), 209 + array( 210 + $this->renderControl($option, $selected), 211 + )), 212 + )), 213 + $status, 214 + $voters, 215 + )); 216 + } 217 + 218 + private function renderBar(PhabricatorSlowvoteOption $option) { 219 + if (!$this->areResultsVisible()) { 220 + return null; 221 + } 222 + 223 + $poll = $this->getPoll(); 224 + 225 + $choices = mgroup($poll->getChoices(), 'getOptionID'); 226 + $choices = count(idx($choices, $option->getID(), array())); 227 + $count = count(mgroup($poll->getChoices(), 'getAuthorPHID')); 228 + 229 + return phutil_tag( 230 + 'div', 231 + array( 232 + 'class' => 'slowvote-bar', 233 + 'style' => sprintf( 234 + 'width: %.1f%%;', 235 + $count ? 100 * ($choices / $count) : 0), 236 + ), 237 + array( 238 + phutil_tag( 239 + 'div', 240 + array( 241 + 'class' => 'slowvote-control-offset', 242 + ), 243 + $option->getName()), 244 + )); 245 + } 246 + 247 + private function renderControl(PhabricatorSlowvoteOption $option, $selected) { 248 + $types = array( 249 + PhabricatorSlowvotePoll::METHOD_PLURALITY => 'radio', 250 + PhabricatorSlowvotePoll::METHOD_APPROVAL => 'checkbox', 251 + ); 252 + 253 + return phutil_tag( 254 + 'input', 255 + array( 256 + 'type' => idx($types, $this->getPoll()->getMethod()), 257 + 'name' => 'vote[]', 258 + 'value' => $option->getID(), 259 + 'checked' => ($selected ? 'checked' : null), 260 + )); 261 + } 262 + 263 + private function renderVoters(PhabricatorSlowvoteOption $option) { 264 + if (!$this->areResultsVisible()) { 265 + return null; 266 + } 267 + 268 + $poll = $this->getPoll(); 269 + 270 + $choices = mgroup($poll->getChoices(), 'getOptionID'); 271 + $choices = idx($choices, $option->getID(), array()); 272 + 273 + if (!$choices) { 274 + return null; 275 + } 276 + 277 + $handles = $this->handles; 278 + $authors = mpull($choices, 'getAuthorPHID', 'getAuthorPHID'); 279 + 280 + $viewer_phid = $this->getUser()->getPHID(); 281 + 282 + // Put the viewer first if they've voted for this option. 283 + $authors = array_select_keys($authors, array($viewer_phid)) 284 + + $authors; 285 + 286 + $voters = array(); 287 + foreach ($authors as $author_phid) { 288 + $handle = $handles[$author_phid]; 289 + 290 + $voters[] = javelin_tag( 291 + 'div', 292 + array( 293 + 'class' => 'slowvote-voter', 294 + 'style' => 'background-image: url('.$handle->getImageURI().')', 295 + 'sigil' => 'has-tooltip', 296 + 'meta' => array( 297 + 'tip' => $handle->getName(), 298 + ), 299 + )); 300 + } 301 + 302 + return phutil_tag( 303 + 'div', 304 + array( 305 + 'class' => 'slowvote-voters', 306 + ), 307 + $voters); 308 + } 309 + 310 + private function renderStatus(PhabricatorSlowvoteOption $option) { 311 + if (!$this->areResultsVisible()) { 312 + return null; 313 + } 314 + 315 + $poll = $this->getPoll(); 316 + 317 + $choices = mgroup($poll->getChoices(), 'getOptionID'); 318 + $choices = count(idx($choices, $option->getID(), array())); 319 + $count = count(mgroup($poll->getChoices(), 'getAuthorPHID')); 320 + 321 + $percent = sprintf('%d%%', $count ? 100 * $choices / $count : 0); 322 + 323 + switch ($poll->getMethod()) { 324 + case PhabricatorSlowvotePoll::METHOD_PLURALITY: 325 + $status = pht('%s (%d / %d)', $percent, $choices, $count); 326 + break; 327 + case PhabricatorSlowvotePoll::METHOD_APPROVAL: 328 + $status = pht('%s Approval (%d / %d)', $percent, $choices, $count); 329 + break; 330 + } 331 + 332 + return phutil_tag( 333 + 'div', 334 + array( 335 + 'class' => 'slowvote-status', 336 + ), 337 + $status); 338 + } 339 + 340 + private function areResultsVisible() { 341 + $poll = $this->getPoll(); 342 + 343 + $vis = $poll->getResponseVisibility(); 344 + if ($vis == PhabricatorSlowvotePoll::RESPONSES_VISIBLE) { 345 + return true; 346 + } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { 347 + return ($poll->getAuthorPHID() == $this->getUser()->getPHID()); 348 + } else { 349 + $choices = mgroup($poll->getChoices(), 'getAuthorPHID'); 350 + return (bool)idx($choices, $this->getUser()->getPHID()); 351 + } 126 352 } 127 353 128 354 }
+93 -59
webroot/rsrc/css/application/slowvote/slowvote.css
··· 2 2 * @provides phabricator-slowvote-css 3 3 */ 4 4 5 - .phabricator-slowvote-comments { 6 - width: 100%; 5 + .slowvote-embed { 6 + margin: 24px 12px; 7 + background: #ffffff; 8 + border-color: #888888; 9 + border-style: solid; 10 + border-width: 1px; 11 + border-radius: 4px; 12 + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.20), 13 + inset 0 0 2px rgba(0, 0, 0, 0.10); 7 14 } 8 15 9 - .phabricator-slowvote-comments th { 10 - width: 150px; 11 - text-align: right; 12 - padding: 6px 4px 6px; 13 - white-space: nowrap; 16 + .slowvote-header { 17 + font-weight: bold; 18 + line-height: 16px; 19 + border-bottom: 1px solid #bbbbbb; 14 20 } 15 21 16 - .phabricator-slowvote-comments td { 17 - vertical-align: top; 18 - padding: 6px 2px; 19 - border-bottom: 1px solid #d0d0d0; 22 + .slowvote-description { 23 + color: #666666; 24 + padding: 8px; 25 + border-bottom: 1px solid #bbbbbb; 20 26 } 21 27 22 - .phabricator-slowvote-datestamp { 23 - font-size: 9px; 24 - font-family: "Verdana"; 25 - color: #666666; 26 - margin-top: 1px; 28 + .slowvote-header-content { 29 + padding: 8px; 27 30 } 28 31 29 - .phabricator-slowvote-hr { 30 - border: none; 31 - height: 1px; 32 - position: relative; 33 - background: #c0c0c0; 32 + .slowvote-body { 34 33 } 35 34 36 - .phabricator-slowvote-count { 37 - float: right; 38 - font-size: 13px; 39 - font-weight: bold; 35 + .slowvote-body-content { 36 + padding: 4px 16px; 40 37 } 41 38 42 - .phabricator-slowvote-label { 39 + .slowvote-option-label { 40 + border: 1px solid #666666; 43 41 display: block; 44 - width: 100%; 45 - font-size: 14px; 46 - font-weight: bold; 47 - color: #222222; 48 - text-align: left; 49 - margin: 0px 0px 6px; 50 - padding: 6px 4px; 51 - background: #cccccc; 52 - border-bottom: 1px solid #aaaaaa; 42 + position: relative; 43 + padding: 8px 4px; 53 44 cursor: pointer; 45 + font-weight: bold; 46 + overflow: hidden; 47 + background-color: {$lightblue}; 54 48 } 55 49 56 - .aphront-form-input .phabricator-slowvote-label input { 57 - display: inline; 58 - width: auto; 59 - margin-right: 12px; 50 + .slowvote-bar { 51 + position: absolute; 52 + top: 0; 53 + left: 0; 54 + bottom: 0; 55 + overflow: hidden; 56 + background-color: {$blue}; 60 57 } 61 58 62 - .phabricator-slowvote-facepile { 63 - width: 50px; 64 - height: 50px; 65 - overflow: hidden; 66 - position: relative; 67 - float: left; 68 - margin: 0px 4px 6px 0px; 59 + .slowvote-control-offset { 60 + white-space: nowrap; 61 + position: absolute; 62 + left: 32px; 63 + top: 8px; 64 + width: 100%; 65 + color: #000000; 66 + text-shadow: 0 1px 0 #ffffff; 69 67 } 70 68 71 - .phabricator-slowvote-embed { 72 - width: 400px; 73 - background: #f7f7f7; 74 - border: 1px solid #dbdbdb; 75 - padding: 5px; 69 + .slowvote-bar .slowvote-control-offset { 70 + color: #ffffff; 71 + text-shadow: 0 1px 0 #000000; 76 72 } 77 73 78 - .phabricator-slowvote-embed-option { 79 - margin: 1px; 80 - background: #F0FFFF; 74 + .slowvote-option-label-group { 75 + margin: 8px 0 16px; 76 + } 77 + 78 + .slowvote-option-label input[type="radio"], 79 + .slowvote-option-label input[type="checkbox"] { 80 + margin: 0 12px 0 8px; 81 + font-weight: bold; 82 + } 83 + 84 + .slowvote-above-the-bar { 85 + position: relative; 81 86 } 82 87 83 - .phabricator-slowvote-embed-option-vote { 88 + .slowvote-status { 89 + text-align: right; 90 + color: #333333; 91 + font-weight: normal; 92 + padding: 2px 0; 93 + line-height: 15px; 94 + text-align: right; 95 + font-size: 11px; 96 + } 97 + 98 + .slowvote-voter { 84 99 display: inline-block; 85 - width: 1em; 100 + width: 25px; 101 + height: 25px; 102 + background: #f3f3f3; 103 + background-size: 25px 25px; 86 104 } 87 105 88 - .phabricator-slowvote-embed-option-text { 89 - border: 1px solid #dbdbdb; 90 - border-left: 0px; 106 + .slowvote-footer { 107 + border-top-width: 1px; 108 + border-top-style: solid; 109 + border-top-color: #bbbbbb; 110 + position: relative; 111 + } 112 + 113 + .slowvote-footer-content { 114 + padding: 8px; 115 + overflow: hidden; 116 + } 117 + 118 + .slowvote-footer-content .slowvote-hint { 119 + line-height: 24px; 120 + color: #888888; 121 + } 122 + 123 + .slowvote-footer-content button { 124 + float: right; 91 125 }
+8
webroot/rsrc/css/core/z-index.css
··· 22 22 z-index: 2; 23 23 } 24 24 25 + .slowvote-bar { 26 + z-index: 2; 27 + } 28 + 29 + .slowvote-above-the-bar { 30 + z-index: 3; 31 + } 32 + 25 33 .phabricator-timeline-icon-fill { 26 34 z-index: 3; 27 35 }