@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.

AphrontFormCountedToggleButtonsControl

Summary: Like AphrontFormToggleButtonsControl, but with mouseover counters. These counters let you know how many of each thing there are in each category, which is useful when using this control for filtering a list of things in multiple dimensions.

Test Plan: `/uiexample/view/PhabricatorCountedToggleButtonsExample/`

Reviewers: epriestley

CC: aran, Korvin

Maniphest Tasks: T2094

Differential Revision: https://secure.phabricator.com/D5118

+311 -40
+40 -40
src/__celerity_resource_map__.php
··· 683 683 ), 684 684 'aphront-form-view-css' => 685 685 array( 686 - 'uri' => '/res/c1cf5cce/rsrc/css/aphront/form-view.css', 686 + 'uri' => '/res/efaecc2d/rsrc/css/aphront/form-view.css', 687 687 'type' => 'css', 688 688 'requires' => 689 689 array( ··· 3562 3562 ), array( 3563 3563 'packages' => 3564 3564 array( 3565 - '0f595159' => 3565 + 'b481b309' => 3566 3566 array( 3567 3567 'name' => 'core.pkg.css', 3568 3568 'symbols' => ··· 3605 3605 35 => 'phabricator-object-item-list-view-css', 3606 3606 36 => 'global-drag-and-drop-css', 3607 3607 ), 3608 - 'uri' => '/res/pkg/0f595159/core.pkg.css', 3608 + 'uri' => '/res/pkg/b481b309/core.pkg.css', 3609 3609 'type' => 'css', 3610 3610 ), 3611 3611 '95ceba95' => ··· 3796 3796 'reverse' => 3797 3797 array( 3798 3798 'aphront-attached-file-view-css' => 'eb35a026', 3799 - 'aphront-crumbs-view-css' => '0f595159', 3800 - 'aphront-dialog-view-css' => '0f595159', 3801 - 'aphront-error-view-css' => '0f595159', 3802 - 'aphront-form-view-css' => '0f595159', 3803 - 'aphront-list-filter-view-css' => '0f595159', 3804 - 'aphront-pager-view-css' => '0f595159', 3805 - 'aphront-panel-view-css' => '0f595159', 3806 - 'aphront-table-view-css' => '0f595159', 3807 - 'aphront-tokenizer-control-css' => '0f595159', 3808 - 'aphront-tooltip-css' => '0f595159', 3809 - 'aphront-typeahead-control-css' => '0f595159', 3799 + 'aphront-crumbs-view-css' => 'b481b309', 3800 + 'aphront-dialog-view-css' => 'b481b309', 3801 + 'aphront-error-view-css' => 'b481b309', 3802 + 'aphront-form-view-css' => 'b481b309', 3803 + 'aphront-list-filter-view-css' => 'b481b309', 3804 + 'aphront-pager-view-css' => 'b481b309', 3805 + 'aphront-panel-view-css' => 'b481b309', 3806 + 'aphront-table-view-css' => 'b481b309', 3807 + 'aphront-tokenizer-control-css' => 'b481b309', 3808 + 'aphront-tooltip-css' => 'b481b309', 3809 + 'aphront-typeahead-control-css' => 'b481b309', 3810 3810 'differential-changeset-view-css' => '8aaacd1b', 3811 3811 'differential-core-view-css' => '8aaacd1b', 3812 3812 'differential-inline-comment-editor' => '322728f3', ··· 3820 3820 'differential-table-of-contents-css' => '8aaacd1b', 3821 3821 'diffusion-commit-view-css' => 'c8ce2d88', 3822 3822 'diffusion-icons-css' => 'c8ce2d88', 3823 - 'global-drag-and-drop-css' => '0f595159', 3823 + 'global-drag-and-drop-css' => 'b481b309', 3824 3824 'inline-comment-summary-css' => '8aaacd1b', 3825 3825 'javelin-aphlict' => '95ceba95', 3826 3826 'javelin-behavior' => 'cd1d650a', ··· 3892 3892 'javelin-util' => 'cd1d650a', 3893 3893 'javelin-vector' => 'cd1d650a', 3894 3894 'javelin-workflow' => 'cd1d650a', 3895 - 'lightbox-attachment-css' => '0f595159', 3895 + 'lightbox-attachment-css' => 'b481b309', 3896 3896 'maniphest-task-summary-css' => 'eb35a026', 3897 3897 'maniphest-transaction-detail-css' => 'eb35a026', 3898 3898 'phabricator-busy' => '95ceba95', 3899 3899 'phabricator-content-source-view-css' => '8aaacd1b', 3900 - 'phabricator-core-buttons-css' => '0f595159', 3901 - 'phabricator-core-css' => '0f595159', 3902 - 'phabricator-crumbs-view-css' => '0f595159', 3903 - 'phabricator-directory-css' => '0f595159', 3900 + 'phabricator-core-buttons-css' => 'b481b309', 3901 + 'phabricator-core-css' => 'b481b309', 3902 + 'phabricator-crumbs-view-css' => 'b481b309', 3903 + 'phabricator-directory-css' => 'b481b309', 3904 3904 'phabricator-drag-and-drop-file-upload' => '322728f3', 3905 3905 'phabricator-dropdown-menu' => '95ceba95', 3906 3906 'phabricator-file-upload' => '95ceba95', 3907 - 'phabricator-filetree-view-css' => '0f595159', 3908 - 'phabricator-flag-css' => '0f595159', 3909 - 'phabricator-form-view-css' => '0f595159', 3910 - 'phabricator-header-view-css' => '0f595159', 3911 - 'phabricator-jump-nav' => '0f595159', 3907 + 'phabricator-filetree-view-css' => 'b481b309', 3908 + 'phabricator-flag-css' => 'b481b309', 3909 + 'phabricator-form-view-css' => 'b481b309', 3910 + 'phabricator-header-view-css' => 'b481b309', 3911 + 'phabricator-jump-nav' => 'b481b309', 3912 3912 'phabricator-keyboard-shortcut' => '95ceba95', 3913 3913 'phabricator-keyboard-shortcut-manager' => '95ceba95', 3914 - 'phabricator-main-menu-view' => '0f595159', 3914 + 'phabricator-main-menu-view' => 'b481b309', 3915 3915 'phabricator-menu-item' => '95ceba95', 3916 - 'phabricator-nav-view-css' => '0f595159', 3916 + 'phabricator-nav-view-css' => 'b481b309', 3917 3917 'phabricator-notification' => '95ceba95', 3918 - 'phabricator-notification-css' => '0f595159', 3919 - 'phabricator-notification-menu-css' => '0f595159', 3920 - 'phabricator-object-item-list-view-css' => '0f595159', 3918 + 'phabricator-notification-css' => 'b481b309', 3919 + 'phabricator-notification-menu-css' => 'b481b309', 3920 + 'phabricator-object-item-list-view-css' => 'b481b309', 3921 3921 'phabricator-object-selector-css' => '8aaacd1b', 3922 3922 'phabricator-paste-file-upload' => '95ceba95', 3923 3923 'phabricator-prefab' => '95ceba95', 3924 3924 'phabricator-project-tag-css' => 'eb35a026', 3925 - 'phabricator-remarkup-css' => '0f595159', 3925 + 'phabricator-remarkup-css' => 'b481b309', 3926 3926 'phabricator-shaped-request' => '322728f3', 3927 - 'phabricator-side-menu-view-css' => '0f595159', 3928 - 'phabricator-standard-page-view' => '0f595159', 3927 + 'phabricator-side-menu-view-css' => 'b481b309', 3928 + 'phabricator-standard-page-view' => 'b481b309', 3929 3929 'phabricator-textareautils' => '95ceba95', 3930 3930 'phabricator-tooltip' => '95ceba95', 3931 - 'phabricator-transaction-view-css' => '0f595159', 3932 - 'phabricator-zindex-css' => '0f595159', 3933 - 'sprite-apps-large-css' => '0f595159', 3934 - 'sprite-gradient-css' => '0f595159', 3935 - 'sprite-icon-css' => '0f595159', 3936 - 'sprite-menu-css' => '0f595159', 3937 - 'syntax-highlighting-css' => '0f595159', 3931 + 'phabricator-transaction-view-css' => 'b481b309', 3932 + 'phabricator-zindex-css' => 'b481b309', 3933 + 'sprite-apps-large-css' => 'b481b309', 3934 + 'sprite-gradient-css' => 'b481b309', 3935 + 'sprite-icon-css' => 'b481b309', 3936 + 'sprite-menu-css' => 'b481b309', 3937 + 'syntax-highlighting-css' => 'b481b309', 3938 3938 ), 3939 3939 ));
+4
src/__phutil_library_map__.php
··· 33 33 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 34 34 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 35 35 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', 36 + 'AphrontFormCountedToggleButtonsControl' => 'view/form/control/AphrontFormCountedToggleButtonsControl.php', 36 37 'AphrontFormCropControl' => 'view/form/control/AphrontFormCropControl.php', 37 38 'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php', 38 39 'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php', ··· 824 825 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 825 826 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', 826 827 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 828 + 'PhabricatorCountedToggleButtonsExample' => 'applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php', 827 829 'PhabricatorCrumbView' => 'view/layout/PhabricatorCrumbView.php', 828 830 'PhabricatorCrumbsView' => 'view/layout/PhabricatorCrumbsView.php', 829 831 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', ··· 1604 1606 'AphrontFileResponse' => 'AphrontResponse', 1605 1607 'AphrontFormCheckboxControl' => 'AphrontFormControl', 1606 1608 'AphrontFormControl' => 'AphrontView', 1609 + 'AphrontFormCountedToggleButtonsControl' => 'AphrontFormControl', 1607 1610 'AphrontFormCropControl' => 'AphrontFormControl', 1608 1611 'AphrontFormDateControl' => 'AphrontFormControl', 1609 1612 'AphrontFormDividerControl' => 'AphrontFormControl', ··· 2356 2359 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 2357 2360 'PhabricatorCountdownRemarkupRule' => 'PhutilRemarkupRule', 2358 2361 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 2362 + 'PhabricatorCountedToggleButtonsExample' => 'PhabricatorUIExample', 2359 2363 'PhabricatorCrumbView' => 'AphrontView', 2360 2364 'PhabricatorCrumbsView' => 'AphrontView', 2361 2365 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
+148
src/applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountedToggleButtonsExample 4 + extends PhabricatorUIExample { 5 + 6 + public function getName() { 7 + return 'Counted Toggle Buttons'; 8 + } 9 + 10 + public function getDescription() { 11 + return 'Like AphrontFormToggleButtonsControl, but with counters.'; 12 + } 13 + 14 + private static function buildTreesList() { 15 + $all_trees = array( 16 + array( 17 + 'name' => 'Oak', 18 + 'leaves' => 'deciduous', 19 + 'wood' => 'hard', 20 + 'branches' => 'climbable', 21 + ), 22 + array( 23 + 'name' => 'Pine', 24 + 'leaves' => 'coniferous', 25 + 'wood' => 'soft', 26 + 'branches' => 'spindly', 27 + ), 28 + array( 29 + 'name' => 'Spruce', 30 + 'leaves' => 'coniferous', 31 + 'wood' => 'soft', 32 + 'branches' => 'sticky', 33 + ), 34 + array( 35 + 'name' => 'Ash', 36 + 'leaves' => 'deciduous', 37 + 'wood' => 'hard', 38 + 'branches' => 'climbable', 39 + ), 40 + array( 41 + 'name' => 'Holly', 42 + 'leaves' => 'waxy', 43 + 'wood' => 'hard', 44 + 'branches' => 'prickly', 45 + ), 46 + ); 47 + 48 + for ($ii = 0; $ii < 345; $ii++) { 49 + $name = sprintf("Soylent UltraTree \xE2\x84\xA2 Mutation 0xPD%03x", $ii); 50 + $all_trees[] = array( 51 + 'name' => $name, 52 + 'leaves' => 'carcinogenic', 53 + 'wood' => 'metallic', 54 + 'branches' => 'sentient', 55 + ); 56 + } 57 + 58 + return $all_trees; 59 + } 60 + 61 + public function renderExample() { 62 + $request = $this->getRequest(); 63 + 64 + $form = id(new AphrontFormView()) 65 + ->setUser($request->getUser()); 66 + 67 + $attributes = array('leaves', 'wood', 'branches'); 68 + 69 + $all_trees = self::buildTreesList(); 70 + $trees = $all_trees; 71 + 72 + foreach ($attributes as $attribute) { 73 + $form_value = $request->getStr($attribute); 74 + 75 + $buttons = array(null => 'all'); 76 + foreach ($all_trees as $dict) { 77 + $value = $dict[$attribute]; 78 + $buttons[$value] = $value; 79 + } 80 + 81 + // The trees filtered by other attributes, before we filter by this 82 + // attribute 83 + $trees_before = $all_trees; 84 + foreach ($attributes as $other_attribute) { 85 + if ($other_attribute != $attribute) { 86 + $trees_before = $this->filterTrees($trees_before, $other_attribute); 87 + } 88 + } 89 + 90 + $counters = array(null => count($trees_before)); 91 + foreach ($trees_before as $dict) { 92 + $value = $dict[$attribute]; 93 + if (!isset($counters[$value])) { 94 + $counters[$value] = 0; 95 + } 96 + $counters[$value]++; 97 + } 98 + 99 + $trees = $this->filterTrees($trees, $attribute); 100 + 101 + $control = id(new AphrontFormCountedToggleButtonsControl()) 102 + ->setLabel(ucfirst($attribute)) 103 + ->setName($attribute) 104 + ->setValue($form_value) 105 + ->setBaseURI($request->getRequestURI(), $attribute) 106 + ->setButtons($buttons) 107 + ->setCounters($counters); 108 + 109 + $form->appendChild($control); 110 + } 111 + 112 + $rows = array(); 113 + foreach ($trees as $dict) { 114 + $row = array_select_keys($dict, $attributes); 115 + array_unshift($row, $dict['name']); 116 + $rows[] = $row; 117 + } 118 + 119 + $headers = $attributes; 120 + array_unshift($headers, 'name'); 121 + $table = id(new AphrontTableView($rows)) 122 + ->setHeaders($headers); 123 + 124 + $panel = id(new AphrontPanelView()) 125 + ->setHeader('Counters!') 126 + ->setWidth(AphrontPanelView::WIDTH_FULL) 127 + ->appendChild($form) 128 + ->appendChild($table); 129 + 130 + return $panel; 131 + } 132 + 133 + private function filterTrees($trees, $attribute) { 134 + $form_value = $this->getRequest()->getStr($attribute); 135 + if (!$form_value) { 136 + return $trees; 137 + } 138 + 139 + $new = array(); 140 + foreach ($trees as $dict) { 141 + if ($dict[$attribute] == $form_value) { 142 + $new[] = $dict; 143 + } 144 + } 145 + return $new; 146 + } 147 + 148 + }
+80
src/view/form/control/AphrontFormCountedToggleButtonsControl.php
··· 1 + <?php 2 + 3 + final class AphrontFormCountedToggleButtonsControl extends AphrontFormControl { 4 + 5 + private $baseURI; 6 + private $param; 7 + 8 + private $buttons; 9 + private $counters = array(); 10 + 11 + public function setBaseURI(PhutilURI $uri, $param) { 12 + $this->baseURI = $uri; 13 + $this->param = $param; 14 + return $this; 15 + } 16 + 17 + public function setButtons(array $buttons) { 18 + $this->buttons = $buttons; 19 + return $this; 20 + } 21 + 22 + public function setCounters(array $counters) { 23 + $this->counters = $counters; 24 + return $this; 25 + } 26 + 27 + protected function getCustomControlClass() { 28 + return 'aphront-form-control-counted-togglebuttons'; 29 + } 30 + 31 + protected function renderInput() { 32 + if (!$this->baseURI) { 33 + throw new Exception('Call setBaseURI() before render()!'); 34 + } 35 + 36 + $selected = $this->getValue(); 37 + 38 + $out = array(); 39 + foreach ($this->buttons as $value => $label) { 40 + if ($value == $selected) { 41 + $more = ' toggle-selected toggle-fixed'; 42 + } else { 43 + $more = null; 44 + } 45 + 46 + $counter = idx($this->counters, $value); 47 + 48 + if ($counter > 0) { 49 + $href = $this->baseURI->alter($this->param, $value); 50 + $counter_markup = phutil_tag( 51 + 'div', 52 + array( 53 + 'class' => 'counter', 54 + ), 55 + $counter); 56 + } else { 57 + $href = null; 58 + $counter_markup = ''; 59 + $more .= ' disabled'; 60 + } 61 + 62 + $attributes = array( 63 + 'class' => 'toggle'.$more, 64 + ); 65 + if ($href) { 66 + $attributes['href'] = $href; 67 + } 68 + 69 + $out[] = phutil_tag( 70 + 'a', 71 + $attributes, 72 + array( 73 + $counter_markup, 74 + $label)); 75 + } 76 + 77 + return $out; 78 + } 79 + 80 + }
+39
webroot/rsrc/css/aphront/form-view.css
··· 368 368 padding: 12px; 369 369 float: right; 370 370 } 371 + 372 + .aphront-form-control-counted-togglebuttons { 373 + padding-top: 7px; 374 + } 375 + 376 + .aphront-form-control-counted-togglebuttons .toggle { 377 + position: relative; 378 + } 379 + 380 + .aphront-form-control-counted-togglebuttons .toggle-fixed { 381 + cursor: pointer; 382 + } 383 + 384 + .aphront-form-control-counted-togglebuttons .toggle .counter { 385 + font-size: smaller; 386 + display: none; 387 + position: absolute; 388 + top: -9px; 389 + right: -8px; 390 + padding: 0px 3px; 391 + border-radius: 3px; 392 + } 393 + 394 + .aphront-form-control-counted-togglebuttons:hover .toggle .counter { 395 + display: block; 396 + } 397 + 398 + .aphront-form-control-counted-togglebuttons .toggle .counter { 399 + background: gray; 400 + color: #ddd; 401 + } 402 + 403 + .aphront-form-control-counted-togglebuttons .toggle-selected .counter { 404 + color: white; 405 + } 406 + 407 + .aphront-form-control-counted-togglebuttons .toggle.disabled:hover { 408 + background-color: #a7a7a7; 409 + }