diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 62d470c934..8bb0f74731 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -683,7 +683,7 @@ celerity_register_resource_map(array( ), 'aphront-form-view-css' => array( - 'uri' => '/res/c1cf5cce/rsrc/css/aphront/form-view.css', + 'uri' => '/res/efaecc2d/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( @@ -3562,7 +3562,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - '0f595159' => + 'b481b309' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -3605,7 +3605,7 @@ celerity_register_resource_map(array( 35 => 'phabricator-object-item-list-view-css', 36 => 'global-drag-and-drop-css', ), - 'uri' => '/res/pkg/0f595159/core.pkg.css', + 'uri' => '/res/pkg/b481b309/core.pkg.css', 'type' => 'css', ), '95ceba95' => @@ -3796,17 +3796,17 @@ celerity_register_resource_map(array( 'reverse' => array( 'aphront-attached-file-view-css' => 'eb35a026', - 'aphront-crumbs-view-css' => '0f595159', - 'aphront-dialog-view-css' => '0f595159', - 'aphront-error-view-css' => '0f595159', - 'aphront-form-view-css' => '0f595159', - 'aphront-list-filter-view-css' => '0f595159', - 'aphront-pager-view-css' => '0f595159', - 'aphront-panel-view-css' => '0f595159', - 'aphront-table-view-css' => '0f595159', - 'aphront-tokenizer-control-css' => '0f595159', - 'aphront-tooltip-css' => '0f595159', - 'aphront-typeahead-control-css' => '0f595159', + 'aphront-crumbs-view-css' => 'b481b309', + 'aphront-dialog-view-css' => 'b481b309', + 'aphront-error-view-css' => 'b481b309', + 'aphront-form-view-css' => 'b481b309', + 'aphront-list-filter-view-css' => 'b481b309', + 'aphront-pager-view-css' => 'b481b309', + 'aphront-panel-view-css' => 'b481b309', + 'aphront-table-view-css' => 'b481b309', + 'aphront-tokenizer-control-css' => 'b481b309', + 'aphront-tooltip-css' => 'b481b309', + 'aphront-typeahead-control-css' => 'b481b309', 'differential-changeset-view-css' => '8aaacd1b', 'differential-core-view-css' => '8aaacd1b', 'differential-inline-comment-editor' => '322728f3', @@ -3820,7 +3820,7 @@ celerity_register_resource_map(array( 'differential-table-of-contents-css' => '8aaacd1b', 'diffusion-commit-view-css' => 'c8ce2d88', 'diffusion-icons-css' => 'c8ce2d88', - 'global-drag-and-drop-css' => '0f595159', + 'global-drag-and-drop-css' => 'b481b309', 'inline-comment-summary-css' => '8aaacd1b', 'javelin-aphlict' => '95ceba95', 'javelin-behavior' => 'cd1d650a', @@ -3892,48 +3892,48 @@ celerity_register_resource_map(array( 'javelin-util' => 'cd1d650a', 'javelin-vector' => 'cd1d650a', 'javelin-workflow' => 'cd1d650a', - 'lightbox-attachment-css' => '0f595159', + 'lightbox-attachment-css' => 'b481b309', 'maniphest-task-summary-css' => 'eb35a026', 'maniphest-transaction-detail-css' => 'eb35a026', 'phabricator-busy' => '95ceba95', 'phabricator-content-source-view-css' => '8aaacd1b', - 'phabricator-core-buttons-css' => '0f595159', - 'phabricator-core-css' => '0f595159', - 'phabricator-crumbs-view-css' => '0f595159', - 'phabricator-directory-css' => '0f595159', + 'phabricator-core-buttons-css' => 'b481b309', + 'phabricator-core-css' => 'b481b309', + 'phabricator-crumbs-view-css' => 'b481b309', + 'phabricator-directory-css' => 'b481b309', 'phabricator-drag-and-drop-file-upload' => '322728f3', 'phabricator-dropdown-menu' => '95ceba95', 'phabricator-file-upload' => '95ceba95', - 'phabricator-filetree-view-css' => '0f595159', - 'phabricator-flag-css' => '0f595159', - 'phabricator-form-view-css' => '0f595159', - 'phabricator-header-view-css' => '0f595159', - 'phabricator-jump-nav' => '0f595159', + 'phabricator-filetree-view-css' => 'b481b309', + 'phabricator-flag-css' => 'b481b309', + 'phabricator-form-view-css' => 'b481b309', + 'phabricator-header-view-css' => 'b481b309', + 'phabricator-jump-nav' => 'b481b309', 'phabricator-keyboard-shortcut' => '95ceba95', 'phabricator-keyboard-shortcut-manager' => '95ceba95', - 'phabricator-main-menu-view' => '0f595159', + 'phabricator-main-menu-view' => 'b481b309', 'phabricator-menu-item' => '95ceba95', - 'phabricator-nav-view-css' => '0f595159', + 'phabricator-nav-view-css' => 'b481b309', 'phabricator-notification' => '95ceba95', - 'phabricator-notification-css' => '0f595159', - 'phabricator-notification-menu-css' => '0f595159', - 'phabricator-object-item-list-view-css' => '0f595159', + 'phabricator-notification-css' => 'b481b309', + 'phabricator-notification-menu-css' => 'b481b309', + 'phabricator-object-item-list-view-css' => 'b481b309', 'phabricator-object-selector-css' => '8aaacd1b', 'phabricator-paste-file-upload' => '95ceba95', 'phabricator-prefab' => '95ceba95', 'phabricator-project-tag-css' => 'eb35a026', - 'phabricator-remarkup-css' => '0f595159', + 'phabricator-remarkup-css' => 'b481b309', 'phabricator-shaped-request' => '322728f3', - 'phabricator-side-menu-view-css' => '0f595159', - 'phabricator-standard-page-view' => '0f595159', + 'phabricator-side-menu-view-css' => 'b481b309', + 'phabricator-standard-page-view' => 'b481b309', 'phabricator-textareautils' => '95ceba95', 'phabricator-tooltip' => '95ceba95', - 'phabricator-transaction-view-css' => '0f595159', - 'phabricator-zindex-css' => '0f595159', - 'sprite-apps-large-css' => '0f595159', - 'sprite-gradient-css' => '0f595159', - 'sprite-icon-css' => '0f595159', - 'sprite-menu-css' => '0f595159', - 'syntax-highlighting-css' => '0f595159', + 'phabricator-transaction-view-css' => 'b481b309', + 'phabricator-zindex-css' => 'b481b309', + 'sprite-apps-large-css' => 'b481b309', + 'sprite-gradient-css' => 'b481b309', + 'sprite-icon-css' => 'b481b309', + 'sprite-menu-css' => 'b481b309', + 'syntax-highlighting-css' => 'b481b309', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 29b5f8e59b..cdda45de05 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -33,6 +33,7 @@ phutil_register_library_map(array( 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', + 'AphrontFormCountedToggleButtonsControl' => 'view/form/control/AphrontFormCountedToggleButtonsControl.php', 'AphrontFormCropControl' => 'view/form/control/AphrontFormCropControl.php', 'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php', 'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php', @@ -824,6 +825,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', + 'PhabricatorCountedToggleButtonsExample' => 'applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php', 'PhabricatorCrumbView' => 'view/layout/PhabricatorCrumbView.php', 'PhabricatorCrumbsView' => 'view/layout/PhabricatorCrumbsView.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', @@ -1604,6 +1606,7 @@ phutil_register_library_map(array( 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', + 'AphrontFormCountedToggleButtonsControl' => 'AphrontFormControl', 'AphrontFormCropControl' => 'AphrontFormControl', 'AphrontFormDateControl' => 'AphrontFormControl', 'AphrontFormDividerControl' => 'AphrontFormControl', @@ -2356,6 +2359,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', + 'PhabricatorCountedToggleButtonsExample' => 'PhabricatorUIExample', 'PhabricatorCrumbView' => 'AphrontView', 'PhabricatorCrumbsView' => 'AphrontView', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', diff --git a/src/applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php b/src/applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php new file mode 100644 index 0000000000..e06c5d10bb --- /dev/null +++ b/src/applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php @@ -0,0 +1,148 @@ + 'Oak', + 'leaves' => 'deciduous', + 'wood' => 'hard', + 'branches' => 'climbable', + ), + array( + 'name' => 'Pine', + 'leaves' => 'coniferous', + 'wood' => 'soft', + 'branches' => 'spindly', + ), + array( + 'name' => 'Spruce', + 'leaves' => 'coniferous', + 'wood' => 'soft', + 'branches' => 'sticky', + ), + array( + 'name' => 'Ash', + 'leaves' => 'deciduous', + 'wood' => 'hard', + 'branches' => 'climbable', + ), + array( + 'name' => 'Holly', + 'leaves' => 'waxy', + 'wood' => 'hard', + 'branches' => 'prickly', + ), + ); + + for ($ii = 0; $ii < 345; $ii++) { + $name = sprintf("Soylent UltraTree \xE2\x84\xA2 Mutation 0xPD%03x", $ii); + $all_trees[] = array( + 'name' => $name, + 'leaves' => 'carcinogenic', + 'wood' => 'metallic', + 'branches' => 'sentient', + ); + } + + return $all_trees; + } + + public function renderExample() { + $request = $this->getRequest(); + + $form = id(new AphrontFormView()) + ->setUser($request->getUser()); + + $attributes = array('leaves', 'wood', 'branches'); + + $all_trees = self::buildTreesList(); + $trees = $all_trees; + + foreach ($attributes as $attribute) { + $form_value = $request->getStr($attribute); + + $buttons = array(null => 'all'); + foreach ($all_trees as $dict) { + $value = $dict[$attribute]; + $buttons[$value] = $value; + } + + // The trees filtered by other attributes, before we filter by this + // attribute + $trees_before = $all_trees; + foreach ($attributes as $other_attribute) { + if ($other_attribute != $attribute) { + $trees_before = $this->filterTrees($trees_before, $other_attribute); + } + } + + $counters = array(null => count($trees_before)); + foreach ($trees_before as $dict) { + $value = $dict[$attribute]; + if (!isset($counters[$value])) { + $counters[$value] = 0; + } + $counters[$value]++; + } + + $trees = $this->filterTrees($trees, $attribute); + + $control = id(new AphrontFormCountedToggleButtonsControl()) + ->setLabel(ucfirst($attribute)) + ->setName($attribute) + ->setValue($form_value) + ->setBaseURI($request->getRequestURI(), $attribute) + ->setButtons($buttons) + ->setCounters($counters); + + $form->appendChild($control); + } + + $rows = array(); + foreach ($trees as $dict) { + $row = array_select_keys($dict, $attributes); + array_unshift($row, $dict['name']); + $rows[] = $row; + } + + $headers = $attributes; + array_unshift($headers, 'name'); + $table = id(new AphrontTableView($rows)) + ->setHeaders($headers); + + $panel = id(new AphrontPanelView()) + ->setHeader('Counters!') + ->setWidth(AphrontPanelView::WIDTH_FULL) + ->appendChild($form) + ->appendChild($table); + + return $panel; + } + + private function filterTrees($trees, $attribute) { + $form_value = $this->getRequest()->getStr($attribute); + if (!$form_value) { + return $trees; + } + + $new = array(); + foreach ($trees as $dict) { + if ($dict[$attribute] == $form_value) { + $new[] = $dict; + } + } + return $new; + } + +} diff --git a/src/view/form/control/AphrontFormCountedToggleButtonsControl.php b/src/view/form/control/AphrontFormCountedToggleButtonsControl.php new file mode 100644 index 0000000000..4a2100d1ec --- /dev/null +++ b/src/view/form/control/AphrontFormCountedToggleButtonsControl.php @@ -0,0 +1,80 @@ +baseURI = $uri; + $this->param = $param; + return $this; + } + + public function setButtons(array $buttons) { + $this->buttons = $buttons; + return $this; + } + + public function setCounters(array $counters) { + $this->counters = $counters; + return $this; + } + + protected function getCustomControlClass() { + return 'aphront-form-control-counted-togglebuttons'; + } + + protected function renderInput() { + if (!$this->baseURI) { + throw new Exception('Call setBaseURI() before render()!'); + } + + $selected = $this->getValue(); + + $out = array(); + foreach ($this->buttons as $value => $label) { + if ($value == $selected) { + $more = ' toggle-selected toggle-fixed'; + } else { + $more = null; + } + + $counter = idx($this->counters, $value); + + if ($counter > 0) { + $href = $this->baseURI->alter($this->param, $value); + $counter_markup = phutil_tag( + 'div', + array( + 'class' => 'counter', + ), + $counter); + } else { + $href = null; + $counter_markup = ''; + $more .= ' disabled'; + } + + $attributes = array( + 'class' => 'toggle'.$more, + ); + if ($href) { + $attributes['href'] = $href; + } + + $out[] = phutil_tag( + 'a', + $attributes, + array( + $counter_markup, + $label)); + } + + return $out; + } + +} diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css index 40eb7b0226..299502a0fc 100644 --- a/webroot/rsrc/css/aphront/form-view.css +++ b/webroot/rsrc/css/aphront/form-view.css @@ -368,3 +368,42 @@ table.aphront-form-control-checkbox-layout th { padding: 12px; float: right; } + +.aphront-form-control-counted-togglebuttons { + padding-top: 7px; +} + +.aphront-form-control-counted-togglebuttons .toggle { + position: relative; +} + +.aphront-form-control-counted-togglebuttons .toggle-fixed { + cursor: pointer; +} + +.aphront-form-control-counted-togglebuttons .toggle .counter { + font-size: smaller; + display: none; + position: absolute; + top: -9px; + right: -8px; + padding: 0px 3px; + border-radius: 3px; +} + +.aphront-form-control-counted-togglebuttons:hover .toggle .counter { + display: block; +} + +.aphront-form-control-counted-togglebuttons .toggle .counter { + background: gray; + color: #ddd; +} + +.aphront-form-control-counted-togglebuttons .toggle-selected .counter { + color: white; +} + +.aphront-form-control-counted-togglebuttons .toggle.disabled:hover { + background-color: #a7a7a7; +}