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

Manage date control enabled state as part of DateControlValue

Summary: Ref T8024. Allow `DateControlValue` to manage enabled/disabled state, so we can eventually delete the copy of this logic in `DateControl`.

Test Plan:
- Used Calendar ApplicationSearch queries to observe improved behaviors:
- Error for invalid start date, if enabled.
- Error for invalid end date, if enabled.
- Error for invalid date range, if both enabled.
- When submitting an invalid date (for example, with the time "Tea Time"), form retains invalid date verbatim instead of discarding information.
- Created an event, using existing date controls to check that I didn't break anything.

Reviewers: chad, lpriestley, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8024

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

+194 -49
+12 -12
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => 'd445f9b8', 10 + 'core.pkg.css' => 'ca3f6a60', 11 11 'core.pkg.js' => '3331b919', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '0a253fbe', ··· 132 132 'rsrc/css/phui/phui-document.css' => '94d5dcd8', 133 133 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 134 134 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', 135 - 'rsrc/css/phui/phui-form-view.css' => 'b147d2ed', 135 + 'rsrc/css/phui/phui-form-view.css' => '17eace76', 136 136 'rsrc/css/phui/phui-form.css' => 'f535f938', 137 137 'rsrc/css/phui/phui-header-view.css' => 'da4586b1', 138 138 'rsrc/css/phui/phui-icon.css' => 'bc766998', ··· 463 463 'rsrc/js/core/behavior-device.js' => 'a205cf28', 464 464 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 465 465 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 466 - 'rsrc/js/core/behavior-fancy-datepicker.js' => 'c51ae228', 466 + 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', 467 467 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 468 468 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 469 469 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', ··· 590 590 'javelin-behavior-doorkeeper-tag' => 'e5822781', 591 591 'javelin-behavior-durable-column' => '657c2b50', 592 592 'javelin-behavior-error-log' => '6882e80a', 593 - 'javelin-behavior-fancy-datepicker' => 'c51ae228', 593 + 'javelin-behavior-fancy-datepicker' => '5c0f680f', 594 594 'javelin-behavior-global-drag-and-drop' => 'c203e6ee', 595 595 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 596 596 'javelin-behavior-high-security-warning' => 'a464fe03', ··· 789 789 'phui-font-icon-base-css' => '3dad2ae3', 790 790 'phui-fontkit-css' => 'dd8ddf27', 791 791 'phui-form-css' => 'f535f938', 792 - 'phui-form-view-css' => 'b147d2ed', 792 + 'phui-form-view-css' => '17eace76', 793 793 'phui-header-view-css' => 'da4586b1', 794 794 'phui-icon-view-css' => 'bc766998', 795 795 'phui-image-mask-css' => '5a8b09c8', ··· 1215 1215 'javelin-uri', 1216 1216 'javelin-routable', 1217 1217 ), 1218 + '5c0f680f' => array( 1219 + 'javelin-behavior', 1220 + 'javelin-util', 1221 + 'javelin-dom', 1222 + 'javelin-stratcom', 1223 + 'javelin-vector', 1224 + ), 1218 1225 '5c54cbf3' => array( 1219 1226 'javelin-behavior', 1220 1227 'javelin-stratcom', ··· 1767 1774 'javelin-uri', 1768 1775 'javelin-mask', 1769 1776 'phabricator-drag-and-drop-file-upload', 1770 - ), 1771 - 'c51ae228' => array( 1772 - 'javelin-behavior', 1773 - 'javelin-util', 1774 - 'javelin-dom', 1775 - 'javelin-stratcom', 1776 - 'javelin-vector', 1777 1777 ), 1778 1778 'c90a04fc' => array( 1779 1779 'javelin-dom',
+57 -16
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 53 53 $query = id(new PhabricatorCalendarEventQuery()); 54 54 $viewer = $this->requireViewer(); 55 55 56 - $min_range = null; 57 - $max_range = null; 58 - 59 - if ($saved->getParameter('rangeStart')) { 60 - $min_range = $saved->getParameter('rangeStart'); 61 - } 62 - 63 - if ($saved->getParameter('rangeEnd')) { 64 - $max_range = $saved->getParameter('rangeEnd'); 65 - } 56 + $min_range = $this->getDateFrom($saved)->getEpoch(); 57 + $max_range = $this->getDateTo($saved)->getEpoch(); 66 58 67 59 if ($saved->getParameter('display') == 'month') { 68 60 list($start_month, $start_year) = $this->getDisplayMonthAndYear($saved); ··· 130 122 AphrontFormView $form, 131 123 PhabricatorSavedQuery $saved) { 132 124 133 - $range_start = $saved->getParameter('rangeStart'); 134 - $range_end = $saved->getParameter('rangeEnd'); 125 + $range_start = $this->getDateFrom($saved); 126 + $e_start = null; 127 + 128 + $range_end = $this->getDateTo($saved); 129 + $e_end = null; 130 + 131 + if (!$range_start->isValid()) { 132 + $this->addError(pht('Start date is not valid.')); 133 + $e_start = pht('Invalid'); 134 + } 135 + 136 + if (!$range_end->isValid()) { 137 + $this->addError(pht('End date is not valid.')); 138 + $e_end = pht('Invalid'); 139 + } 140 + 141 + $start_epoch = $range_start->getEpoch(); 142 + $end_epoch = $range_end->getEpoch(); 143 + 144 + if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) { 145 + $this->addError(pht('End date must be after start date.')); 146 + $e_start = pht('Invalid'); 147 + $e_end = pht('Invalid'); 148 + } 149 + 135 150 $upcoming = $saved->getParameter('upcoming'); 136 151 $is_cancelled = $saved->getParameter('isCancelled', 'active'); 137 152 $display = $saved->getParameter('display', 'month'); ··· 167 182 ->setLabel(pht('Occurs After')) 168 183 ->setUser($this->requireViewer()) 169 184 ->setName('rangeStart') 170 - ->setAllowNull(true) 185 + ->setError($e_start) 171 186 ->setValue($range_start)) 172 187 ->appendChild( 173 188 id(new AphrontFormDateControl()) 174 189 ->setLabel(pht('Occurs Before')) 175 190 ->setUser($this->requireViewer()) 176 191 ->setName('rangeEnd') 177 - ->setAllowNull(true) 192 + ->setError($e_end) 178 193 ->setValue($range_end)) 179 194 ->appendChild( 180 195 id(new AphrontFormCheckboxControl()) ··· 391 406 $start_year = $this->calendarYear; 392 407 $start_month = $this->calendarMonth; 393 408 } else { 394 - $epoch = $query->getParameter('rangeStart'); 409 + $epoch = $this->getDateFrom($query)->getEpoch(); 395 410 if (!$epoch) { 396 - $epoch = $query->getParameter('rangeEnd'); 411 + $epoch = $this->getDateTo($query)->getEpoch(); 397 412 if (!$epoch) { 398 413 $epoch = time(); 399 414 } ··· 429 444 430 445 public function getPageSize(PhabricatorSavedQuery $saved) { 431 446 return $saved->getParameter('limit', 1000); 447 + } 448 + 449 + private function getDateFrom(PhabricatorSavedQuery $saved) { 450 + return $this->getDate($saved, 'rangeStart'); 451 + } 452 + 453 + private function getDateTo(PhabricatorSavedQuery $saved) { 454 + return $this->getDate($saved, 'rangeEnd'); 455 + } 456 + 457 + private function getDate(PhabricatorSavedQuery $saved, $key) { 458 + $viewer = $this->requireViewer(); 459 + 460 + $wild = $saved->getParameter($key); 461 + if ($wild) { 462 + $value = AphrontFormDateControlValue::newFromWild($viewer, $wild); 463 + } else { 464 + $value = AphrontFormDateControlValue::newFromEpoch( 465 + $viewer, 466 + PhabricatorTime::getNow()); 467 + $value->setEnabled(false); 468 + } 469 + 470 + $value->setOptional(true); 471 + 472 + return $value; 432 473 } 433 474 434 475 }
+7 -5
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 566 566 AphrontRequest $request, 567 567 $key) { 568 568 569 - return id(new AphrontFormDateControl()) 570 - ->setUser($this->requireViewer()) 571 - ->setName($key) 572 - ->setAllowNull(true) 573 - ->readValueFromRequest($request); 569 + $value = AphrontFormDateControlValue::newFromRequest($request, $key); 570 + 571 + if ($value->isEmpty()) { 572 + return null; 573 + } 574 + 575 + return $value->getDictionary(); 574 576 } 575 577 576 578 protected function readBoolFromRequest(
+14 -5
src/view/form/control/AphrontFormDateControl.php
··· 11 11 private $valueTime; 12 12 private $allowNull; 13 13 private $continueOnInvalidDate = false; 14 + private $isDisabled; 14 15 15 16 public function setAllowNull($allow_null) { 16 17 $this->allowNull = $allow_null; ··· 91 92 $this->valueMonth = $epoch->getValueMonth(); 92 93 $this->valueDay = $epoch->getValueDay(); 93 94 $this->valueTime = $epoch->getValueTime(); 95 + $this->allowNull = $epoch->getOptional(); 96 + $this->isDisabled = $epoch->isDisabled(); 94 97 95 98 return parent::setValue($epoch->getEpoch()); 96 99 } ··· 183 186 } 184 187 } 185 188 189 + if ($this->isDisabled) { 190 + $disabled = 'disabled'; 191 + } 192 + 186 193 $min_year = $this->getMinYear(); 187 194 $max_year = $this->getMaxYear(); 188 195 ··· 227 234 array( 228 235 'name' => $this->getDayInputName(), 229 236 'sigil' => 'day-input', 230 - 'disabled' => $disabled, 231 237 )); 232 238 233 239 $months_sel = AphrontFormSelectControl::renderSelectTag( ··· 236 242 array( 237 243 'name' => $this->getMonthInputName(), 238 244 'sigil' => 'month-input', 239 - 'disabled' => $disabled, 240 245 )); 241 246 242 247 $years_sel = AphrontFormSelectControl::renderSelectTag( ··· 245 250 array( 246 251 'name' => $this->getYearInputName(), 247 252 'sigil' => 'year-input', 248 - 'disabled' => $disabled, 249 253 )); 250 254 251 255 $cicon = id(new PHUIIconView()) ··· 268 272 'value' => $this->getTimeInputValue(), 269 273 'type' => 'text', 270 274 'class' => 'aphront-form-date-time-input', 271 - 'disabled' => $disabled, 272 275 ), 273 276 ''); 274 277 275 278 Javelin::initBehavior('fancy-datepicker', array()); 276 279 280 + $classes = array(); 281 + $classes[] = 'aphront-form-date-container'; 282 + if ($disabled) { 283 + $classes[] = 'datepicker-disabled'; 284 + } 285 + 277 286 return javelin_tag( 278 287 'div', 279 288 array( 280 - 'class' => 'aphront-form-date-container', 289 + 'class' => implode(' ', $classes), 281 290 'sigil' => 'phabricator-date-control', 282 291 'meta' => array( 283 292 'disabled' => (bool)$disabled,
+91 -3
src/view/form/control/AphrontFormDateControlValue.php
··· 6 6 private $valueMonth; 7 7 private $valueYear; 8 8 private $valueTime; 9 + private $valueEnabled; 9 10 10 11 private $viewer; 11 12 private $zone; 13 + private $optional; 12 14 13 15 public function getValueDay() { 14 16 return $this->valueDay; ··· 27 29 } 28 30 29 31 public function isValid() { 32 + if ($this->isDisabled()) { 33 + return true; 34 + } 30 35 return ($this->getEpoch() !== null); 31 36 } 32 37 38 + public function isEmpty() { 39 + if ($this->valueDay) { 40 + return false; 41 + } 42 + 43 + if ($this->valueMonth) { 44 + return false; 45 + } 46 + 47 + if ($this->valueYear) { 48 + return false; 49 + } 50 + 51 + if ($this->valueTime) { 52 + return false; 53 + } 54 + 55 + return true; 56 + } 57 + 58 + public function isDisabled() { 59 + return ($this->optional && !$this->valueEnabled); 60 + } 61 + 62 + public function setEnabled($enabled) { 63 + $this->valueEnabled = $enabled; 64 + return $this; 65 + } 66 + 67 + public function setOptional($optional) { 68 + $this->optional = $optional; 69 + return $this; 70 + } 71 + 72 + public function getOptional() { 73 + return $this->optional; 74 + } 75 + 33 76 public static function newFromParts( 34 77 PhabricatorUser $viewer, 35 78 $year, 36 79 $month, 37 80 $day, 38 - $time = '12:00 AM') { 81 + $time = null, 82 + $enabled = true) { 39 83 40 84 $value = new AphrontFormDateControlValue(); 41 85 $value->viewer = $viewer; 42 86 $value->valueYear = $year; 43 87 $value->valueMonth = $month; 44 88 $value->valueDay = $day; 45 - $value->valueTime = $time; 89 + $value->valueTime = coalesce($time, '12:00 AM'); 90 + $value->valueEnabled = $enabled; 46 91 47 92 return $value; 48 93 } 49 94 50 - public static function newFromRequest($request, $key) { 95 + public static function newFromRequest(AphrontRequest $request, $key) { 51 96 $value = new AphrontFormDateControlValue(); 52 97 $value->viewer = $request->getViewer(); 53 98 ··· 55 100 $value->valueMonth = $request->getInt($key.'_m'); 56 101 $value->valueYear = $request->getInt($key.'_y'); 57 102 $value->valueTime = $request->getStr($key.'_t'); 103 + $value->valueEnabled = $request->getStr($key.'_e'); 58 104 59 105 return $value; 60 106 } ··· 73 119 return $value; 74 120 } 75 121 122 + public static function newFromDictionary( 123 + PhabricatorUser $viewer, 124 + array $dictionary) { 125 + $value = new AphrontFormDateControlValue(); 126 + $value->viewer = $viewer; 127 + 128 + $value->valueYear = idx($dictionary, 'y'); 129 + $value->valueMonth = idx($dictionary, 'm'); 130 + $value->valueDay = idx($dictionary, 'd'); 131 + $value->valueTime = idx($dictionary, 't'); 132 + $value->valueEnabled = idx($dictionary, 'e'); 133 + 134 + return $value; 135 + } 136 + 137 + public static function newFromWild(PhabricatorUser $viewer, $wild) { 138 + if (is_array($wild)) { 139 + return self::newFromDictionary($viewer, $wild); 140 + } else if (is_numeric($wild)) { 141 + return self::newFromEpoch($viewer, $wild); 142 + } else { 143 + throw new Exception( 144 + pht( 145 + 'Unable to construct a date value from value of type "%s".', 146 + gettype($wild))); 147 + } 148 + } 149 + 150 + public function getDictionary() { 151 + return array( 152 + 'y' => $this->valueYear, 153 + 'm' => $this->valueMonth, 154 + 'd' => $this->valueDay, 155 + 't' => $this->valueTime, 156 + 'e' => $this->valueEnabled, 157 + ); 158 + } 159 + 76 160 private function formatTime($epoch, $format) { 77 161 return phabricator_format_local_time( 78 162 $epoch, ··· 81 165 } 82 166 83 167 public function getEpoch() { 168 + if ($this->isDisabled()) { 169 + return null; 170 + } 171 + 84 172 $year = $this->valueYear; 85 173 $month = $this->valueMonth; 86 174 $day = $this->valueDay;
+12
webroot/rsrc/css/phui/phui-form-view.css
··· 446 446 border-radius: 3px; 447 447 } 448 448 449 + /* When the activation checkbox for the control is toggled off, visually 450 + disable the individual controls. We don't actually use the "disabled" property 451 + because we still want the values to submit. This is just a visual hint that 452 + the controls won't be used. The controls themselves are still live, work 453 + properly, and submit values. */ 454 + .datepicker-disabled select, 455 + .datepicker-disabled .calendar-button, 456 + .datepicker-disabled input[type="text"] { 457 + opacity: 0.5; 458 + } 459 + 460 + 449 461 .login-to-comment { 450 462 margin: 12px; 451 463 }
+1 -8
webroot/rsrc/js/core/behavior-fancy-datepicker.js
··· 109 109 }; 110 110 111 111 var redraw_inputs = function() { 112 - var inputs = get_inputs(); 113 112 var disabled = JX.Stratcom.getData(root).disabled; 114 - for (var k in inputs) { 115 - if (disabled) { 116 - inputs[k].setAttribute('disabled', 'disabled'); 117 - } else { 118 - inputs[k].removeAttribute('disabled'); 119 - } 120 - } 113 + JX.DOM.alterClass(root, 'datepicker-disabled', disabled); 121 114 122 115 var box = JX.DOM.scry(root, 'input', 'calendar-enable'); 123 116 if (box.length) {