Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index d829a29093..ca6d87b26c 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -1,405 +1,334 @@
<?php
final class AphrontFormDateControl extends AphrontFormControl {
private $initialTime;
private $zone;
- private $valueDay;
- private $valueMonth;
- private $valueYear;
+ private $valueDate;
private $valueTime;
private $allowNull;
private $continueOnInvalidDate = false;
private $isTimeDisabled;
private $isDisabled;
private $endDateID;
public function setAllowNull($allow_null) {
$this->allowNull = $allow_null;
return $this;
}
public function setIsTimeDisabled($is_disabled) {
$this->isTimeDisabled = $is_disabled;
return $this;
}
public function setEndDateID($value) {
$this->endDateID = $value;
return $this;
}
const TIME_START_OF_DAY = 'start-of-day';
const TIME_END_OF_DAY = 'end-of-day';
const TIME_START_OF_BUSINESS = 'start-of-business';
const TIME_END_OF_BUSINESS = 'end-of-business';
public function setInitialTime($time) {
$this->initialTime = $time;
return $this;
}
public function readValueFromRequest(AphrontRequest $request) {
- $day = $request->getInt($this->getDayInputName());
- $month = $request->getInt($this->getMonthInputName());
- $year = $request->getInt($this->getYearInputName());
+ $date = $request->getStr($this->getDateInputName());
$time = $request->getStr($this->getTimeInputName());
$enabled = $request->getBool($this->getCheckboxInputName());
if ($this->allowNull && !$enabled) {
$this->setError(null);
$this->setValue(null);
return;
}
$err = $this->getError();
- if ($day || $month || $year || $time) {
- $this->valueDay = $day;
- $this->valueMonth = $month;
- $this->valueYear = $year;
+ if ($date || $time) {
+ $this->valueDate = $date;
$this->valueTime = $time;
// Assume invalid.
$err = 'Invalid';
$zone = $this->getTimezone();
try {
- $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone);
- $value = $date->format('U');
+ $datetime = new DateTime("{$date} {$time}", $zone);
+ $value = $datetime->format('U');
} catch (Exception $ex) {
$value = null;
}
if ($value) {
$this->setValue($value);
$err = null;
} else {
$this->setValue(null);
}
} else {
$value = $this->getInitialValue();
if ($value) {
$this->setValue($value);
} else {
$this->setValue(null);
}
}
$this->setError($err);
return $this->getValue();
}
protected function getCustomControlClass() {
return 'aphront-form-control-date';
}
public function setValue($epoch) {
if ($epoch instanceof AphrontFormDateControlValue) {
$this->continueOnInvalidDate = true;
- $this->valueYear = $epoch->getValueYear();
- $this->valueMonth = $epoch->getValueMonth();
- $this->valueDay = $epoch->getValueDay();
+ $this->valueDate = $epoch->getValueDate();
$this->valueTime = $epoch->getValueTime();
$this->allowNull = $epoch->getOptional();
$this->isDisabled = $epoch->isDisabled();
return parent::setValue($epoch->getEpoch());
}
$result = parent::setValue($epoch);
if ($epoch === null) {
return $result;
}
$readable = $this->formatTime($epoch, 'Y!m!d!g:i A');
$readable = explode('!', $readable, 4);
- $this->valueYear = $readable[0];
- $this->valueMonth = $readable[1];
- $this->valueDay = $readable[2];
+ $year = $readable[0];
+ $month = $readable[1];
+ $day = $readable[2];
+
+ $this->valueDate = $month.'/'.$day.'/'.$year;
$this->valueTime = $readable[3];
return $result;
}
- private function getMinYear() {
- $cur_year = $this->formatTime(
- time(),
- 'Y');
- $val_year = $this->getYearInputValue();
-
- return min($cur_year, $val_year) - 3;
- }
-
- private function getMaxYear() {
- $cur_year = $this->formatTime(
- time(),
- 'Y');
- $val_year = $this->getYearInputValue();
-
- return max($cur_year, $val_year) + 3;
- }
-
- private function getDayInputValue() {
- return $this->valueDay;
- }
-
- private function getMonthInputValue() {
- return $this->valueMonth;
- }
-
- private function getYearInputValue() {
- return $this->valueYear;
+ private function getDateInputValue() {
+ return $this->valueDate;
}
private function getTimeInputValue() {
return $this->valueTime;
}
private function formatTime($epoch, $fmt) {
return phabricator_format_local_time(
$epoch,
$this->user,
$fmt);
}
- private function getDayInputName() {
+ private function getDateInputName() {
return $this->getName().'_d';
}
- private function getMonthInputName() {
- return $this->getName().'_m';
- }
-
- private function getYearInputName() {
- return $this->getName().'_y';
- }
-
private function getTimeInputName() {
return $this->getName().'_t';
}
private function getCheckboxInputName() {
return $this->getName().'_e';
}
protected function renderInput() {
$disabled = null;
if ($this->getValue() === null && !$this->continueOnInvalidDate) {
$this->setValue($this->getInitialValue());
if ($this->allowNull) {
$disabled = 'disabled';
}
}
if ($this->isDisabled) {
$disabled = 'disabled';
}
- $min_year = $this->getMinYear();
- $max_year = $this->getMaxYear();
-
- $days = range(1, 31);
- $days = array_fuse($days);
-
- $months = array(
- 1 => pht('Jan'),
- 2 => pht('Feb'),
- 3 => pht('Mar'),
- 4 => pht('Apr'),
- 5 => pht('May'),
- 6 => pht('Jun'),
- 7 => pht('Jul'),
- 8 => pht('Aug'),
- 9 => pht('Sep'),
- 10 => pht('Oct'),
- 11 => pht('Nov'),
- 12 => pht('Dec'),
- );
-
$checkbox = null;
if ($this->allowNull) {
$checkbox = javelin_tag(
'input',
array(
'type' => 'checkbox',
'name' => $this->getCheckboxInputName(),
'sigil' => 'calendar-enable',
'class' => 'aphront-form-date-enabled-input',
'value' => 1,
'checked' => ($disabled === null ? 'checked' : null),
));
}
- $years = range($this->getMinYear(), $this->getMaxYear());
- $years = array_fuse($years);
-
- $days_sel = AphrontFormSelectControl::renderSelectTag(
- $this->getDayInputValue(),
- $days,
- array(
- 'name' => $this->getDayInputName(),
- 'sigil' => 'day-input',
- ));
-
- $months_sel = AphrontFormSelectControl::renderSelectTag(
- $this->getMonthInputValue(),
- $months,
+ $date_sel = javelin_tag(
+ 'input',
array(
- 'name' => $this->getMonthInputName(),
- 'sigil' => 'month-input',
- ));
+ 'autocomplete' => 'off',
+ 'name' => $this->getDateInputName(),
+ 'sigil' => 'date-input',
+ 'value' => $this->getDateInputValue(),
+ 'type' => 'text',
+ 'class' => 'aphront-form-date-time-input',
+ ),
+ '');
- $years_sel = AphrontFormSelectControl::renderSelectTag(
- $this->getYearInputValue(),
- $years,
+ $date_div = javelin_tag(
+ 'div',
array(
- 'name' => $this->getYearInputName(),
- 'sigil' => 'year-input',
- ));
+ 'class' => 'aphront-form-date-time-input-container',
+ ),
+ $date_sel);
$cicon = id(new PHUIIconView())
->setIconFont('fa-calendar');
$cal_icon = javelin_tag(
'a',
array(
'href' => '#',
'class' => 'calendar-button',
'sigil' => 'calendar-button',
),
$cicon);
$values = $this->getTimeTypeaheadValues();
$time_id = celerity_generate_unique_node_id();
Javelin::initBehavior('time-typeahead', array(
'startTimeID' => $time_id,
'endTimeID' => $this->endDateID,
'timeValues' => $values,
));
$time_sel = javelin_tag(
'input',
array(
'autocomplete' => 'off',
'name' => $this->getTimeInputName(),
'sigil' => 'time-input',
'value' => $this->getTimeInputValue(),
'type' => 'text',
'class' => 'aphront-form-date-time-input',
),
'');
$time_div = javelin_tag(
'div',
array(
'id' => $time_id,
'class' => 'aphront-form-date-time-input-container',
),
$time_sel);
Javelin::initBehavior('fancy-datepicker', array());
$classes = array();
$classes[] = 'aphront-form-date-container';
if ($disabled) {
$classes[] = 'datepicker-disabled';
}
if ($this->isTimeDisabled) {
$classes[] = 'no-time';
}
return javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
'sigil' => 'phabricator-date-control',
'meta' => array(
'disabled' => (bool)$disabled,
),
'id' => $this->getID(),
),
array(
$checkbox,
- $days_sel,
- $months_sel,
- $years_sel,
+ $date_div,
$cal_icon,
$time_div,
));
}
private function getTimezone() {
if ($this->zone) {
return $this->zone;
}
$user = $this->getUser();
if (!$this->getUser()) {
throw new PhutilInvalidStateException('setUser');
}
$user_zone = $user->getTimezoneIdentifier();
$this->zone = new DateTimeZone($user_zone);
return $this->zone;
}
private function getInitialValue() {
$zone = $this->getTimezone();
// TODO: We could eventually allow these to be customized per install or
// per user or both, but let's wait and see.
switch ($this->initialTime) {
case self::TIME_START_OF_DAY:
default:
$time = '12:00 AM';
break;
case self::TIME_START_OF_BUSINESS:
$time = '9:00 AM';
break;
case self::TIME_END_OF_BUSINESS:
$time = '5:00 PM';
break;
case self::TIME_END_OF_DAY:
$time = '11:59 PM';
break;
}
$today = $this->formatTime(time(), 'Y-m-d');
try {
$date = new DateTime("{$today} {$time}", $zone);
$value = $date->format('U');
} catch (Exception $ex) {
$value = null;
}
return $value;
}
private function getTimeTypeaheadValues() {
$times = array();
$am_pm_list = array('AM', 'PM');
foreach ($am_pm_list as $am_pm) {
for ($hour = 0; $hour < 12; $hour++) {
$actual_hour = ($hour == 0) ? 12 : $hour;
$times[] = $actual_hour.':00 '.$am_pm;
$times[] = $actual_hour.':30 '.$am_pm;
}
}
foreach ($times as $key => $time) {
$times[$key] = array($key, $time);
}
return $times;
}
}
diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php
index 28d52371a9..43fb42664e 100644
--- a/src/view/form/control/AphrontFormDateControlValue.php
+++ b/src/view/form/control/AphrontFormDateControlValue.php
@@ -1,238 +1,212 @@
<?php
final class AphrontFormDateControlValue extends Phobject {
- private $valueDay;
- private $valueMonth;
- private $valueYear;
+ private $valueDate;
private $valueTime;
private $valueEnabled;
private $viewer;
private $zone;
private $optional;
- public function getValueDay() {
- return $this->valueDay;
- }
-
- public function getValueMonth() {
- return $this->valueMonth;
- }
-
- public function getValueYear() {
- return $this->valueYear;
+ public function getValueDate() {
+ return $this->valueDate;
}
public function getValueTime() {
return $this->valueTime;
}
public function isValid() {
if ($this->isDisabled()) {
return true;
}
return ($this->getEpoch() !== null);
}
public function isEmpty() {
- if ($this->valueDay) {
- return false;
- }
-
- if ($this->valueMonth) {
- return false;
- }
-
- if ($this->valueYear) {
+ if ($this->valueDate) {
return false;
}
if ($this->valueTime) {
return false;
}
return true;
}
public function isDisabled() {
return ($this->optional && !$this->valueEnabled);
}
public function setEnabled($enabled) {
$this->valueEnabled = $enabled;
return $this;
}
public function setOptional($optional) {
$this->optional = $optional;
return $this;
}
public function getOptional() {
return $this->optional;
}
public static function newFromParts(
PhabricatorUser $viewer,
$year,
$month,
$day,
$time = null,
$enabled = true) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
- $value->valueYear = $year;
- $value->valueMonth = $month;
- $value->valueDay = $day;
+ $value->valueDate = $month.'/'.$day.'/'.$year;
$value->valueTime = coalesce($time, '12:00 AM');
$value->valueEnabled = $enabled;
return $value;
}
public static function newFromRequest(AphrontRequest $request, $key) {
$value = new AphrontFormDateControlValue();
$value->viewer = $request->getViewer();
-
- $value->valueDay = $request->getInt($key.'_d');
- $value->valueMonth = $request->getInt($key.'_m');
- $value->valueYear = $request->getInt($key.'_y');
+ $value->valueDate = $request->getStr($key.'_d');
$value->valueTime = $request->getStr($key.'_t');
$value->valueEnabled = $request->getStr($key.'_e');
return $value;
}
public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
$readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
$readable = explode('!', $readable, 4);
- $value->valueYear = $readable[0];
- $value->valueMonth = $readable[1];
- $value->valueDay = $readable[2];
+ $year = $readable[0];
+ $month = $readable[1];
+ $day = $readable[2];
+
+ $value->valueDate = $month.'/'.$day.'/'.$year;
$value->valueTime = $readable[3];
+
return $value;
}
public static function newFromDictionary(
PhabricatorUser $viewer,
array $dictionary) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
- $value->valueYear = idx($dictionary, 'y');
- $value->valueMonth = idx($dictionary, 'm');
- $value->valueDay = idx($dictionary, 'd');
+ $value->valueDate = idx($dictionary, 'd');
$value->valueTime = idx($dictionary, 't');
$value->valueEnabled = idx($dictionary, 'e');
return $value;
}
public static function newFromWild(PhabricatorUser $viewer, $wild) {
if (is_array($wild)) {
return self::newFromDictionary($viewer, $wild);
} else if (is_numeric($wild)) {
return self::newFromEpoch($viewer, $wild);
} else {
throw new Exception(
pht(
'Unable to construct a date value from value of type "%s".',
gettype($wild)));
}
}
public function getDictionary() {
return array(
- 'y' => $this->valueYear,
- 'm' => $this->valueMonth,
- 'd' => $this->valueDay,
+ 'd' => $this->valueDate,
't' => $this->valueTime,
'e' => $this->valueEnabled,
);
}
public function getValueAsFormat($format) {
return phabricator_format_local_time(
$this->getEpoch(),
$this->viewer,
$format);
}
private function formatTime($epoch, $format) {
return phabricator_format_local_time(
$epoch,
$this->viewer,
$format);
}
public function getEpoch() {
if ($this->isDisabled()) {
return null;
}
- $year = $this->valueYear;
- $month = $this->valueMonth;
- $day = $this->valueDay;
+ $date = $this->valueDate;
$time = $this->valueTime;
$zone = $this->getTimezone();
if (!strlen($time)) {
return null;
}
$colloquial = array(
'elevenses' => '11:00 AM',
'morning tea' => '11:00 AM',
'noon' => '12:00 PM',
'high noon' => '12:00 PM',
'lunch' => '12:00 PM',
'tea time' => '3:00 PM',
'witching hour' => '12:00 AM',
'midnight' => '12:00 AM',
);
$normalized = phutil_utf8_strtolower($time);
if (isset($colloquial[$normalized])) {
$time = $colloquial[$normalized];
}
try {
- $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone);
- $value = $date->format('U');
+ $datetime = new DateTime("{$date} {$time}", $zone);
+ $value = $datetime->format('U');
} catch (Exception $ex) {
$value = null;
}
return $value;
}
public function getDateTime() {
$epoch = $this->getEpoch();
$date = null;
if ($epoch) {
$zone = $this->getTimezone();
$date = new DateTime('@'.$epoch);
$date->setTimeZone($zone);
}
return $date;
}
private function getTimezone() {
if ($this->zone) {
return $this->zone;
}
$viewer_zone = $this->viewer->getTimezoneIdentifier();
$this->zone = new DateTimeZone($viewer_zone);
return $this->zone;
}
}
diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php
index 9bdab3fc4b..1975d6610a 100644
--- a/src/view/phui/calendar/PHUICalendarDayView.php
+++ b/src/view/phui/calendar/PHUICalendarDayView.php
@@ -1,484 +1,481 @@
<?php
final class PHUICalendarDayView extends AphrontView {
private $rangeStart;
private $rangeEnd;
private $day;
private $month;
private $year;
private $browseURI;
private $query;
private $events = array();
private $allDayEvents = array();
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function setBrowseURI($browse_uri) {
$this->browseURI = $browse_uri;
return $this;
}
private function getBrowseURI() {
return $this->browseURI;
}
public function setQuery($query) {
$this->query = $query;
return $this;
}
private function getQuery() {
return $this->query;
}
public function __construct(
$range_start,
$range_end,
$year,
$month,
$day = null) {
$this->rangeStart = $range_start;
$this->rangeEnd = $range_end;
$this->day = $day;
$this->month = $month;
$this->year = $year;
}
public function render() {
require_celerity_resource('phui-calendar-day-css');
$viewer = $this->getUser();
$hours = $this->getHoursOfDay();
$js_hours = array();
$js_today_events = array();
foreach ($hours as $hour) {
$js_hours[] = array(
'hour' => $hour->format('G'),
'hour_meridian' => $hour->format('g A'),
);
}
$first_event_hour = null;
$js_today_all_day_events = array();
$all_day_events = $this->getAllDayEvents();
$day_start = $this->getDateTime();
$day_end = id(clone $day_start)->modify('+1 day');
$day_start_epoch = $day_start->format('U');
$day_end_epoch = $day_end->format('U') - 1;
foreach ($all_day_events as $all_day_event) {
$all_day_start = $all_day_event->getEpochStart();
$all_day_end = $all_day_event->getEpochEnd();
if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) {
$js_today_all_day_events[] = array(
'name' => $all_day_event->getName(),
'id' => $all_day_event->getEventID(),
'viewerIsInvited' => $all_day_event->getViewerIsInvited(),
'uri' => $all_day_event->getURI(),
);
}
}
$this->events = msort($this->events, 'getEpochStart');
-
- if (!$this->events) {
- $first_event_hour = $this->getDateTime()->setTime(8, 0, 0);
- }
+ $first_event_hour = $this->getDateTime()->setTime(8, 0, 0);
foreach ($this->events as $event) {
if ($event->getIsAllDay()) {
continue;
}
if ($event->getEpochStart() <= $day_end_epoch &&
$event->getEpochEnd() > $day_start_epoch) {
if ($first_event_hour === null) {
$first_event_hour = PhabricatorTime::getDateTimeFromEpoch(
$event->getEpochStart(),
$viewer);
$midnight = $this->getDateTime()->setTime(0, 0, 0);
if ($first_event_hour->format('U') < $midnight->format('U')) {
$first_event_hour = clone $midnight;
}
$eight_am = $this->getDateTime()->setTime(8, 0, 0);
if ($eight_am->format('U') < $first_event_hour->format('U')) {
$first_event_hour = clone $eight_am;
}
}
$event_start = max($event->getEpochStart(), $day_start_epoch);
$event_end = min($event->getEpochEnd(), $day_end_epoch);
$day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60;
$top = (($event_start - $first_event_hour->format('U'))
/ ($day_end_epoch - $first_event_hour->format('U')))
* $day_duration;
$top = max(0, $top);
$height = (($event_end - $event_start)
/ ($day_end_epoch - $first_event_hour->format('U')))
* $day_duration;
$height = min($day_duration, $height);
$js_today_events[] = array(
'eventStartEpoch' => $event->getEpochStart(),
'eventEndEpoch' => $event->getEpochEnd(),
'eventName' => $event->getName(),
'eventID' => $event->getEventID(),
'viewerIsInvited' => $event->getViewerIsInvited(),
'uri' => $event->getURI(),
'offset' => '0',
'width' => '100%',
'top' => $top.'px',
'height' => $height.'px',
'canEdit' => $event->getCanEdit(),
);
}
}
$header = $this->renderDayViewHeader();
$sidebar = $this->renderSidebar();
$warnings = $this->getQueryRangeWarning();
$table_id = celerity_generate_unique_node_id();
$table_wrapper = phutil_tag(
'div',
array(
'id' => $table_id,
),
'');
Javelin::initBehavior(
'day-view',
array(
'year' => $first_event_hour->format('Y'),
'month' => $first_event_hour->format('m'),
'day' => $first_event_hour->format('d'),
'query' => $this->getQuery(),
'allDayEvents' => $js_today_all_day_events,
'todayEvents' => $js_today_events,
'hours' => $js_hours,
'firstEventHour' => $first_event_hour->format('G'),
'firstEventHourEpoch' => $first_event_hour->format('U'),
'tableID' => $table_id,
));
$table_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($table_wrapper)
->setFormErrors($warnings)
->setFlush(true);
$layout = id(new AphrontMultiColumnView())
->addColumn($sidebar, 'third')
->addColumn($table_box, 'thirds phui-day-view-column')
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
return phutil_tag(
'div',
array(
'class' => 'ml',
),
$layout);
}
private function getAllDayEvents() {
$all_day_events = array();
foreach ($this->events as $event) {
if ($event->getIsAllDay()) {
$all_day_events[] = $event;
}
}
$all_day_events = array_values(msort($all_day_events, 'getEpochStart'));
return $all_day_events;
}
private function getQueryRangeWarning() {
$errors = array();
$range_start_epoch = $this->rangeStart->getEpoch();
$range_end_epoch = $this->rangeEnd->getEpoch();
$day_start = $this->getDateTime();
$day_end = id(clone $day_start)->modify('+1 day');
$day_start = $day_start->format('U');
$day_end = $day_end->format('U') - 1;
if (($range_start_epoch != null &&
$range_start_epoch < $day_end &&
$range_start_epoch > $day_start) ||
($range_end_epoch != null &&
$range_end_epoch < $day_end &&
$range_end_epoch > $day_start)) {
$errors[] = pht('Part of the day is out of range');
}
if (($this->rangeEnd->getEpoch() != null &&
$this->rangeEnd->getEpoch() < $day_start) ||
($this->rangeStart->getEpoch() != null &&
$this->rangeStart->getEpoch() > $day_end)) {
$errors[] = pht('Day is out of query range');
}
return $errors;
}
private function renderSidebar() {
$this->events = msort($this->events, 'getEpochStart');
$week_of_boxes = $this->getWeekOfBoxes();
$filled_boxes = array();
foreach ($week_of_boxes as $day_box) {
$box_start = $day_box['start'];
$box_end = id(clone $box_start)->modify('+1 day');
$box_start = $box_start->format('U');
$box_end = $box_end->format('U');
$box_events = array();
foreach ($this->events as $event) {
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
if ($event_start < $box_end && $event_end > $box_start) {
$box_events[] = $event;
}
}
$filled_boxes[] = $this->renderSidebarBox(
$box_events,
$day_box['title']);
}
return $filled_boxes;
}
private function renderSidebarBox($events, $title) {
$widget = id(new PHUICalendarWidgetView())
->addClass('calendar-day-view-sidebar');
$list = id(new PHUICalendarListView())
->setUser($this->user)
->setView('day');
if (count($events) == 0) {
$list->showBlankState(true);
} else {
$sorted_events = msort($events, 'getEpochStart');
foreach ($sorted_events as $event) {
$list->addEvent($event);
}
}
$widget
->setCalendarList($list)
->setHeader($title);
return $widget;
}
private function getWeekOfBoxes() {
$sidebar_day_boxes = array();
$display_start_day = $this->getDateTime();
$display_end_day = id(clone $display_start_day)->modify('+6 day');
$box_start_time = clone $display_start_day;
$today_time = PhabricatorTime::getTodayMidnightDateTime($this->user);
$tomorrow_time = clone $today_time;
$tomorrow_time->modify('+1 day');
while ($box_start_time <= $display_end_day) {
if ($box_start_time == $today_time) {
$title = pht('Today');
} else if ($box_start_time == $tomorrow_time) {
$title = pht('Tomorrow');
} else {
$title = $box_start_time->format('l');
}
$sidebar_day_boxes[] = array(
'title' => $title,
'start' => clone $box_start_time,
);
$box_start_time->modify('+1 day');
}
return $sidebar_day_boxes;
}
private function renderDayViewHeader() {
$button_bar = null;
$uri = $this->getBrowseURI();
if ($uri) {
list($prev_year, $prev_month, $prev_day) = $this->getPrevDay();
$prev_uri = $uri.$prev_year.'/'.$prev_month.'/'.$prev_day.'/';
list($next_year, $next_month, $next_day) = $this->getNextDay();
$next_uri = $uri.$next_year.'/'.$next_month.'/'.$next_day.'/';
$button_bar = new PHUIButtonBarView();
$left_icon = id(new PHUIIconView())
->setIconFont('fa-chevron-left bluegrey');
$left = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setHref($prev_uri)
->setTitle(pht('Previous Day'))
->setIcon($left_icon);
$right_icon = id(new PHUIIconView())
->setIconFont('fa-chevron-right bluegrey');
$right = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setHref($next_uri)
->setTitle(pht('Next Day'))
->setIcon($right_icon);
$button_bar->addButton($left);
$button_bar->addButton($right);
}
$display_day = $this->getDateTime();
$header_text = $display_day->format('l, F j, Y');
$header = id(new PHUIHeaderView())
->setHeader($header_text);
if ($button_bar) {
$header->setButtonBar($button_bar);
}
return $header;
}
private function updateEventsFromCluster($cluster, $hourly_events) {
$cluster_size = count($cluster);
$n = 0;
foreach ($cluster as $cluster_member) {
$event_id = $cluster_member->getEventID();
$offset = (($n / $cluster_size) * 100).'%';
$width = ((1 / $cluster_size) * 100).'%';
if (isset($hourly_events[$event_id])) {
$hourly_events[$event_id]['offset'] = $offset;
$hourly_events[$event_id]['width'] = $width;
}
$n++;
}
return $hourly_events;
}
// returns DateTime of each hour in the day
private function getHoursOfDay() {
$included_datetimes = array();
$day_datetime = $this->getDateTime();
$day_epoch = $day_datetime->format('U');
$day_datetime->modify('+1 day');
$next_day_epoch = $day_datetime->format('U');
$included_time = $day_epoch;
$included_datetime = $this->getDateTime();
while ($included_time < $next_day_epoch) {
$included_datetimes[] = clone $included_datetime;
$included_datetime->modify('+1 hour');
$included_time = $included_datetime->format('U');
}
return $included_datetimes;
}
private function getPrevDay() {
$prev = $this->getDateTime();
$prev->modify('-1 day');
return array(
$prev->format('Y'),
$prev->format('m'),
$prev->format('d'),
);
}
private function getNextDay() {
$next = $this->getDateTime();
$next->modify('+1 day');
return array(
$next->format('Y'),
$next->format('m'),
$next->format('d'),
);
}
private function getDateTime() {
$user = $this->user;
$timezone = new DateTimeZone($user->getTimezoneIdentifier());
$day = $this->day;
$month = $this->month;
$year = $this->year;
$date = new DateTime("{$year}-{$month}-{$day} ", $timezone);
return $date;
}
private function findTodayClusters() {
$events = msort($this->todayEvents, 'getEpochStart');
$clusters = array();
foreach ($events as $event) {
$destination_cluster_key = null;
$event_start = $event->getEpochStart() - (30 * 60);
$event_end = $event->getEpochEnd() + (30 * 60);
foreach ($clusters as $key => $cluster) {
foreach ($cluster as $clustered_event) {
$compare_event_start = $clustered_event->getEpochStart();
$compare_event_end = $clustered_event->getEpochEnd();
if ($event_start < $compare_event_end
&& $event_end > $compare_event_start) {
$destination_cluster_key = $key;
break;
}
}
}
if ($destination_cluster_key !== null) {
$clusters[$destination_cluster_key][] = $event;
} else {
$next_cluster = array();
$next_cluster[] = $event;
$clusters[] = $next_cluster;
}
}
return $clusters;
}
}
diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php
index e1f37fa1ae..fa998f11a6 100644
--- a/src/view/phui/calendar/PHUICalendarListView.php
+++ b/src/view/phui/calendar/PHUICalendarListView.php
@@ -1,188 +1,189 @@
<?php
final class PHUICalendarListView extends AphrontTagView {
private $events = array();
private $blankState;
private $view;
private function getView() {
return $this->view;
}
public function setView($view) {
$this->view = $view;
return $this;
}
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function showBlankState($state) {
$this->blankState = $state;
return $this;
}
protected function getTagName() {
return 'div';
}
protected function getTagAttributes() {
require_celerity_resource('phui-calendar-css');
require_celerity_resource('phui-calendar-list-css');
return array('class' => 'phui-calendar-event-list');
}
protected function getTagContent() {
if (!$this->blankState && empty($this->events)) {
return '';
}
$singletons = array();
$allday = false;
foreach ($this->events as $event) {
$start_epoch = $event->getEpochStart();
if ($event->getIsAllDay()) {
$timelabel = pht('All Day');
} else {
$timelabel = phabricator_time(
$event->getEpochStart(),
$this->getUser());
}
if ($event->getViewerIsInvited()) {
$icon_color = 'green';
} else {
$icon_color = null;
}
$dot = id(new PHUIIconView())
->setIconFont($event->getIcon(), $icon_color)
->addClass('phui-calendar-list-item-icon');
$title = phutil_tag(
'span',
array(
'class' => 'phui-calendar-list-title',
),
$this->getEventTitle($event, $allday));
$time = phutil_tag(
'span',
array(
'class' => 'phui-calendar-list-time',
),
$timelabel);
$class = 'phui-calendar-list-item';
if ($event->getViewerIsInvited()) {
$class = $class.' phui-calendar-viewer-invited';
}
if ($event->getIsAllDay()) {
$class = $class.' all-day';
}
$tip = $this->getEventTooltip($event);
$tip_align = ($this->getView() == 'day') ? 'E' : 'N';
$content = javelin_tag(
'a',
array(
'href' => '/E'.$event->getEventID(),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tip,
'size' => 200,
'align' => $tip_align,
),
),
array(
$dot,
$time,
$title,
));
$singletons[] = phutil_tag(
'li',
array(
'class' => $class,
),
$content);
}
if (empty($singletons)) {
$singletons[] = phutil_tag(
'li',
array(
'class' => 'phui-calendar-list-item-empty',
),
pht('Clear sailing ahead.'));
}
$list = phutil_tag(
'ul',
array(
'class' => 'phui-calendar-list',
),
$singletons);
return $list;
}
private function getEventTitle($event) {
$class = 'phui-calendar-item';
return phutil_tag(
'span',
array(
'class' => $class,
),
$event->getName());
}
private function getEventTooltip(AphrontCalendarEventView $event) {
Javelin::initBehavior('phabricator-tooltips');
$start = id(AphrontFormDateControlValue::newFromEpoch(
$this->getUser(),
$event->getEpochStart()));
$end = id(AphrontFormDateControlValue::newFromEpoch(
$this->getUser(),
$event->getEpochEnd()));
+ $start_date = $start->getDateTime()->format('m d Y');
+ $end_date = $end->getDateTime()->format('m d Y');
+
if ($event->getIsAllDay()) {
- if ($start->getValueDay() == $end->getValueDay()) {
+ if ($start_date == $end_date) {
$tip = pht('All day');
} else {
$tip = pht(
'All day, %s - %s',
$start->getValueAsFormat('M j, Y'),
$end->getValueAsFormat('M j, Y'));
}
} else {
- if ($start->getValueDay() == $end->getValueDay() &&
- $start->getValueMonth() == $end->getValueMonth() &&
- $start->getValueYear() == $end->getValueYear()) {
+ if ($start->getValueDate() == $end->getValueDate()) {
$tip = pht(
'%s - %s',
$start->getValueAsFormat('g:i A'),
$end->getValueAsFormat('g:i A'));
} else {
$tip = pht(
'%s - %s',
$start->getValueAsFormat('M j, Y, g:i A'),
$end->getValueAsFormat('M j, Y, g:i A'));
}
}
return $tip;
}
public function getIsViewerInvitedOnList() {
foreach ($this->events as $event) {
if ($event->getViewerIsInvited()) {
return true;
}
}
return false;
}
}
diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js
index f2d092faee..c4b335aefd 100644
--- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js
+++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js
@@ -1,266 +1,295 @@
/**
* @provides javelin-behavior-fancy-datepicker
* @requires javelin-behavior
* javelin-util
* javelin-dom
* javelin-stratcom
* javelin-vector
*/
JX.behavior('fancy-datepicker', function() {
var picker;
var root;
var value_y;
var value_m;
var value_d;
var onopen = function(e) {
e.kill();
// If you click the calendar icon while the date picker is open, close it
// without writing the change.
if (picker) {
if (root == e.getNode('phabricator-date-control')) {
// If the user clicked the same control, just close it.
onclose(e);
return;
} else {
// If the user clicked a different control, close the old one but then
// open the new one.
onclose(e);
}
}
root = e.getNode('phabricator-date-control');
picker = JX.$N(
'div',
{className: 'fancy-datepicker', sigil: 'phabricator-datepicker'},
JX.$N('div', {className: 'fancy-datepicker-core'}));
document.body.appendChild(picker);
var button = e.getNode('calendar-button');
var p = JX.$V(button);
var d = JX.Vector.getDim(picker);
picker.style.left = (p.x - d.x - 2) + 'px';
picker.style.top = (p.y) + 'px';
JX.DOM.alterClass(root, 'picker-open', true);
read_date();
render();
};
var onclose = function(e) {
if (!picker) {
return;
}
JX.DOM.remove(picker);
picker = null;
JX.DOM.alterClass(root, 'picker-open', false);
e.kill();
root = null;
};
var ontoggle = function(e) {
var box = e.getTarget();
root = e.getNode('phabricator-date-control');
JX.Stratcom.getData(root).disabled = !box.checked;
redraw_inputs();
};
var get_inputs = function() {
return {
- y: JX.DOM.find(root, 'select', 'year-input'),
- m: JX.DOM.find(root, 'select', 'month-input'),
- d: JX.DOM.find(root, 'select', 'day-input'),
+ d: JX.DOM.find(root, 'input', 'date-input'),
t: JX.DOM.find(root, 'input', 'time-input')
};
};
var read_date = function() {
var i = get_inputs();
- value_y = +i.y.value;
- value_m = +i.m.value;
- value_d = +i.d.value;
+ var date = i.d.value;
+ var parts = date.split('/');
+ value_y = +parts[2];
+ value_m = +parts[0];
+ value_d = +parts[1];
};
var write_date = function() {
var i = get_inputs();
- i.y.value = value_y;
- i.m.value = value_m;
- i.d.value = value_d;
+ i.d.value = value_m + '/' + value_d + '/' + value_y;
};
var render = function() {
JX.DOM.setContent(
picker.firstChild,
[
render_month(),
render_day()
]);
};
var redraw_inputs = function() {
var disabled = JX.Stratcom.getData(root).disabled;
JX.DOM.alterClass(root, 'datepicker-disabled', disabled);
var box = JX.DOM.scry(root, 'input', 'calendar-enable');
if (box.length) {
box[0].checked = !disabled;
}
};
// Render a cell for the date picker.
var cell = function(label, value, selected, class_name) {
class_name = class_name || '';
if (selected) {
class_name += ' datepicker-selected';
}
if (!value) {
class_name += ' novalue';
}
return JX.$N('td', {meta: {value: value}, className: class_name}, label);
};
-
// Render the top bar which allows you to pick a month and year.
var render_month = function() {
+ var valid_date = getValidDate();
+ var month = valid_date.getMonth();
+ var year = valid_date.getYear() + 1900;
+
var months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'];
var buttons = [
cell('\u25C0', 'm:-1', false, 'lrbutton'),
- cell(months[value_m - 1] + ' ' + value_y, null),
+ cell(months[month] + ' ' + year, null),
cell('\u25B6', 'm:1', false, 'lrbutton')];
return JX.$N(
'table',
{className: 'month-table'},
JX.$N('tr', {}, buttons));
};
+ function getValidDate() {
+ var written_date = new Date(value_y, value_m-1, value_d);
+ if (isNaN(written_date.getTime())) {
+ return new Date();
+ } else {
+ return written_date;
+ }
+ }
+
// Render the day-of-week and calendar views.
var render_day = function() {
+ var today = new Date();
+ var valid_date = getValidDate();
+
var weeks = [];
// First, render the weekday names.
var weekdays = 'SMTWTFS';
var weekday_names = [];
var ii;
for (ii = 0; ii < weekdays.length; ii++) {
weekday_names.push(cell(weekdays.charAt(ii), null, false, 'day-name'));
}
weeks.push(JX.$N('tr', {}, weekday_names));
// Render the calendar itself. NOTE: Javascript uses 0-based month indexes
// while we use 1-based month indexes, so we have to adjust for that.
var days = [];
- var start = new Date(value_y, value_m - 1, 1).getDay();
+ var start = new Date(
+ valid_date.getYear() + 1900,
+ valid_date.getMonth(),
+ 1).getDay();
+
while (start--) {
days.push(cell('', null, false, 'day-placeholder'));
}
- var today = new Date();
-
for (ii = 1; ii <= 31; ii++) {
- var date = new Date(value_y, value_m - 1, ii);
- if (date.getMonth() != (value_m - 1)) {
+ var date = new Date(
+ valid_date.getYear() + 1900,
+ valid_date.getMonth(),
+ ii);
+ if (date.getMonth() != (valid_date.getMonth())) {
// We've spilled over into the next month, so stop rendering.
break;
}
var is_today = (today.getYear() == date.getYear() &&
today.getMonth() == date.getMonth() &&
today.getDate() == date.getDate());
var classes = [];
classes.push('day');
if (is_today) {
classes.push('today');
}
if (date.getDay() === 0 || date.getDay() == 6) {
classes.push('weekend');
}
- days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' ')));
+ days.push(cell(
+ ii,
+ 'd:'+ii,
+ valid_date.getDate() == ii,
+ classes.join(' ')));
}
// Slice the days into weeks.
for (ii = 0; ii < days.length; ii += 7) {
weeks.push(JX.$N('tr', {}, days.slice(ii, ii + 7)));
}
return JX.$N('table', {className: 'day-table'}, weeks);
};
JX.Stratcom.listen('click', 'calendar-button', onopen);
JX.Stratcom.listen('change', 'calendar-enable', ontoggle);
JX.Stratcom.listen(
'click',
['phabricator-datepicker', 'tag:td'],
function(e) {
e.kill();
var data = e.getNodeData('tag:td');
if (!data.value) {
return;
}
var p = data.value.split(':');
switch (p[0]) {
case 'm':
// User clicked left or right month selection buttons.
value_m = value_m + parseInt(p[1], 10);
if (value_m > 12) {
value_m -= 12;
value_y++;
} else if (value_m <= 0) {
value_m += 12;
value_y--;
}
break;
case 'd':
// User clicked a day.
value_d = parseInt(p[1], 10);
write_date();
// Wait a moment to close the selector so they can see the effect
// of their action.
setTimeout(JX.bind(null, onclose, e), 150);
break;
}
// Enable the control.
JX.Stratcom.getData(root).disabled = false;
redraw_inputs();
render();
});
+ JX.Stratcom.listen('click', null, function(e){
+ if (e.getNode('phabricator-datepicker')) {
+ return;
+ }
+ onclose();
+ });
+
});

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 7:25 AM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431720
Default Alt Text
(44 KB)

Event Timeline