Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890793
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
22 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
index b0e0bac6e8..e4a9067a5b 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
@@ -1,475 +1,476 @@
<?php
final class PhabricatorCalendarEventSearchEngine
extends PhabricatorApplicationSearchEngine {
private $calendarYear;
private $calendarMonth;
private $calendarDay;
public function getResultTypeDescription() {
return pht('Calendar Events');
}
public function getApplicationClassName() {
return 'PhabricatorCalendarApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'rangeStart',
$this->readDateFromRequest($request, 'rangeStart'));
$saved->setParameter(
'rangeEnd',
$this->readDateFromRequest($request, 'rangeEnd'));
$saved->setParameter(
'upcoming',
$this->readBoolFromRequest($request, 'upcoming'));
$saved->setParameter(
'invitedPHIDs',
$this->readUsersFromRequest($request, 'invited'));
$saved->setParameter(
'creatorPHIDs',
$this->readUsersFromRequest($request, 'creators'));
$saved->setParameter(
'isCancelled',
$request->getStr('isCancelled'));
$saved->setParameter(
'display',
$request->getStr('display'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorCalendarEventQuery());
$viewer = $this->requireViewer();
$min_range = $this->getDateFrom($saved)->getEpoch();
$max_range = $this->getDateTo($saved)->getEpoch();
if ($saved->getParameter('display') == 'month') {
list($start_month, $start_year) = $this->getDisplayMonthAndYear($saved);
$start_day = 1;
$end_year = ($start_month == 12) ? $start_year + 1 : $start_year;
$end_month = ($start_month == 12) ? 1 : $start_month + 1;
$end_day = 1;
$calendar_start = AphrontFormDateControlValue::newFromParts(
$viewer,
$start_year,
$start_month,
$start_day)->getEpoch();
$calendar_end = AphrontFormDateControlValue::newFromParts(
$viewer,
$end_year,
$end_month,
$end_day)->getEpoch();
if (!$min_range || ($min_range < $calendar_start)) {
$min_range = $calendar_start;
}
if (!$max_range || ($max_range > $calendar_end)) {
$max_range = $calendar_end;
}
}
if ($saved->getParameter('upcoming')) {
if ($min_range) {
$min_range = max(time(), $min_range);
} else {
$min_range = time();
}
}
if ($min_range || $max_range) {
$query->withDateRange($min_range, $max_range);
}
$invited_phids = $saved->getParameter('invitedPHIDs');
if ($invited_phids) {
$query->withInvitedPHIDs($invited_phids);
}
$creator_phids = $saved->getParameter('creatorPHIDs');
if ($creator_phids) {
$query->withCreatorPHIDs($creator_phids);
}
$is_cancelled = $saved->getParameter('isCancelled');
switch ($is_cancelled) {
case 'active':
$query->withIsCancelled(false);
break;
case 'cancelled':
$query->withIsCancelled(true);
break;
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$range_start = $this->getDateFrom($saved);
$e_start = null;
$range_end = $this->getDateTo($saved);
$e_end = null;
if (!$range_start->isValid()) {
$this->addError(pht('Start date is not valid.'));
$e_start = pht('Invalid');
}
if (!$range_end->isValid()) {
$this->addError(pht('End date is not valid.'));
$e_end = pht('Invalid');
}
$start_epoch = $range_start->getEpoch();
$end_epoch = $range_end->getEpoch();
if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) {
$this->addError(pht('End date must be after start date.'));
$e_start = pht('Invalid');
$e_end = pht('Invalid');
}
$upcoming = $saved->getParameter('upcoming');
$is_cancelled = $saved->getParameter('isCancelled', 'active');
$display = $saved->getParameter('display', 'month');
$invited_phids = $saved->getParameter('invitedPHIDs', array());
$creator_phids = $saved->getParameter('creatorPHIDs', array());
$resolution_types = array(
'active' => pht('Active Events Only'),
'cancelled' => pht('Cancelled Events Only'),
'both' => pht('Both Cancelled and Active Events'),
);
$display_options = array(
'month' => pht('Month View'),
'day' => pht('Day View (beta)'),
'list' => pht('List View'),
);
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('creators')
->setLabel(pht('Created By'))
->setValue($creator_phids))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('invited')
->setLabel(pht('Invited'))
->setValue($invited_phids))
->appendChild(
id(new AphrontFormDateControl())
->setLabel(pht('Occurs After'))
->setUser($this->requireViewer())
->setName('rangeStart')
->setError($e_start)
->setValue($range_start))
->appendChild(
id(new AphrontFormDateControl())
->setLabel(pht('Occurs Before'))
->setUser($this->requireViewer())
->setName('rangeEnd')
->setError($e_end)
->setValue($range_end))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'upcoming',
1,
pht('Show only upcoming events.'),
$upcoming))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Cancelled Events'))
->setName('isCancelled')
->setValue($is_cancelled)
->setOptions($resolution_types))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Display Options'))
->setName('display')
->setValue($display)
->setOptions($display_options));
}
protected function getURI($path) {
return '/calendar/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
'month' => pht('Month View'),
'upcoming' => pht('Upcoming Events'),
'all' => pht('All Events'),
);
return $names;
}
public function setCalendarYearAndMonth($year, $month) {
$this->calendarYear = $year;
$this->calendarMonth = $month;
return $this;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'month':
return $query->setParameter('display', 'month');
case 'upcoming':
return $query->setParameter('upcoming', true);
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $objects,
PhabricatorSavedQuery $query) {
$phids = array();
foreach ($objects as $event) {
$phids[$event->getUserPHID()] = 1;
}
return array_keys($phids);
}
protected function renderResultList(
array $events,
PhabricatorSavedQuery $query,
array $handles) {
if ($query->getParameter('display') == 'month') {
return $this->buildCalendarView($events, $query, $handles);
} else if ($query->getParameter('display') == 'day') {
return $this->buildCalendarDayView($events, $query, $handles);
}
assert_instances_of($events, 'PhabricatorCalendarEvent');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
foreach ($events as $event) {
$href = '/E'.$event->getID();
$from = phabricator_datetime($event->getDateFrom(), $viewer);
$to = phabricator_datetime($event->getDateTo(), $viewer);
$creator_handle = $handles[$event->getUserPHID()];
$name = (strlen($event->getName())) ?
$event->getName() : $event->getTerseSummary($viewer);
$color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY)
? 'red'
: 'yellow';
$item = id(new PHUIObjectItemView())
->setHeader($name)
->setHref($href)
->setBarColor($color)
->addByline(pht('Creator: %s', $creator_handle->renderLink()))
->addAttribute(pht('From %s to %s', $from, $to))
->addAttribute(id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(64)
->truncateString($event->getDescription()));
$list->addItem($item);
}
return $list;
}
private function buildCalendarView(
array $statuses,
PhabricatorSavedQuery $query,
array $handles) {
$viewer = $this->requireViewer();
$now = time();
list($start_month, $start_year) = $this->getDisplayMonthAndYear($query);
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
$now_month = phabricator_format_local_time($now, $viewer, 'm');
$now_day = phabricator_format_local_time($now, $viewer, 'j');
if ($start_month == $now_month && $start_year == $now_year) {
$month_view = new PHUICalendarMonthView(
$start_month,
$start_year,
$now_day);
} else {
$month_view = new PHUICalendarMonthView(
$start_month,
$start_year);
}
$month_view->setUser($viewer);
$phids = mpull($statuses, 'getUserPHID');
/* Assign Colors */
$unique = array_unique($phids);
$allblue = false;
$calcolors = CalendarColors::getColors();
if (count($unique) > count($calcolors)) {
$allblue = true;
}
$i = 0;
$eventcolor = array();
foreach ($unique as $phid) {
if ($allblue) {
$eventcolor[$phid] = CalendarColors::COLOR_SKY;
} else {
$eventcolor[$phid] = $calcolors[$i];
}
$i++;
}
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$name_text = $handles[$status->getUserPHID()]->getName();
$status_text = $status->getHumanStatus();
$event->setUserPHID($status->getUserPHID());
$event->setDescription(pht('%s (%s)', $name_text, $status_text));
$event->setName($status_text);
$event->setEventID($status->getID());
$event->setColor($eventcolor[$status->getUserPHID()]);
$month_view->addEvent($event);
}
$month_view->setBrowseURI(
$this->getURI('query/'.$query->getQueryKey().'/'));
return $month_view;
}
private function buildCalendarDayView(
array $statuses,
PhabricatorSavedQuery $query,
array $handles) {
$viewer = $this->requireViewer();
list($start_month, $start_year, $start_day) =
$this->getDisplayMonthAndYearAndDay($query);
$day_view = new PHUICalendarDayView(
$start_month,
$start_year,
$start_day);
$day_view->setUser($viewer);
$phids = mpull($statuses, 'getUserPHID');
foreach ($statuses as $status) {
$event = new AphrontCalendarDayEventView();
+ $event->setEventID($status->getID());
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setName($status->getName());
$event->setURI('/'.$status->getMonogram());
$day_view->addEvent($event);
}
return $day_view;
}
private function getDisplayMonthAndYear(
PhabricatorSavedQuery $query) {
$viewer = $this->requireViewer();
// get month/year from url
if ($this->calendarYear && $this->calendarMonth) {
$start_year = $this->calendarYear;
$start_month = $this->calendarMonth;
} else {
$epoch = $this->getDateFrom($query)->getEpoch();
if (!$epoch) {
$epoch = $this->getDateTo($query)->getEpoch();
if (!$epoch) {
$epoch = time();
}
}
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
}
return array($start_month, $start_year);
}
private function getDisplayMonthAndYearAndDay(
PhabricatorSavedQuery $query) {
$viewer = $this->requireViewer();
if ($this->calendarYear && $this->calendarMonth && $this->calendarDay) {
$start_year = $this->calendarYear;
$start_month = $this->calendarMonth;
$start_day = $this->calendarDay;
} else {
$epoch = $this->getDateFrom($query)->getEpoch();
if (!$epoch) {
$epoch = $this->getDateTo($query)->getEpoch();
if (!$epoch) {
$epoch = time();
}
}
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
$start_day = phabricator_format_local_time($epoch, $viewer, 'd');
}
return array($start_year, $start_month, $start_day);
}
public function getPageSize(PhabricatorSavedQuery $saved) {
return $saved->getParameter('limit', 1000);
}
private function getDateFrom(PhabricatorSavedQuery $saved) {
return $this->getDate($saved, 'rangeStart');
}
private function getDateTo(PhabricatorSavedQuery $saved) {
return $this->getDate($saved, 'rangeEnd');
}
private function getDate(PhabricatorSavedQuery $saved, $key) {
$viewer = $this->requireViewer();
$wild = $saved->getParameter($key);
if ($wild) {
$value = AphrontFormDateControlValue::newFromWild($viewer, $wild);
} else {
$value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
PhabricatorTime::getNow());
$value->setEnabled(false);
}
$value->setOptional(true);
return $value;
}
}
diff --git a/src/applications/calendar/view/AphrontCalendarDayEventView.php b/src/applications/calendar/view/AphrontCalendarDayEventView.php
index 000dbadde2..0989b8fefa 100644
--- a/src/applications/calendar/view/AphrontCalendarDayEventView.php
+++ b/src/applications/calendar/view/AphrontCalendarDayEventView.php
@@ -1,48 +1,58 @@
<?php
final class AphrontCalendarDayEventView extends AphrontView {
+ private $eventID;
private $epochStart;
private $epochEnd;
private $name;
private $uri;
+ public function setEventID($event_id) {
+ $this->eventID = $event_id;
+ return $this;
+ }
+
+ public function getEventID() {
+ return $this->eventID;
+ }
+
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function getURI() {
return $this->uri;
}
public function setEpochRange($start, $end) {
$this->epochStart = $start;
$this->epochEnd = $end;
return $this;
}
public function getEpochStart() {
return $this->epochStart;
}
public function getEpochEnd() {
return $this->epochEnd;
}
public function render() {
$box = new PHUIObjectBoxView();
$box->setHeaderText($this->name);
return $box;
}
}
diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php
index f0abeb3218..61a0b09c7a 100644
--- a/src/view/phui/calendar/PHUICalendarDayView.php
+++ b/src/view/phui/calendar/PHUICalendarDayView.php
@@ -1,174 +1,245 @@
<?php
final class PHUICalendarDayView extends AphrontView {
private $day;
private $month;
private $year;
private $events = array();
public function addEvent(AphrontCalendarDayEventView $event) {
$this->events[] = $event;
return $this;
}
public function __construct($year, $month, $day = null) {
$this->day = $day;
$this->month = $month;
$this->year = $year;
}
public function render() {
require_celerity_resource('phui-calendar-day-css');
$day_box = new PHUIObjectBoxView();
$day_of_week = $this->getDayOfWeek();
$header_text = $this->getDateTime()->format('F j, Y');
$header_text = $day_of_week.', '.$header_text;
$day_box->setHeaderText($header_text);
$hours = $this->getHoursOfDay();
+ $hourly_events = array();
$rows = array();
+ // sort events into buckets by their start time
+ // pretend no events overlap
foreach ($hours as $hour) {
- // time slot
- $cell_time = phutil_tag(
- 'td',
- array('class' => 'phui-calendar-day-hour'),
- $hour->format('g A'));
-
$events = array();
$hour_start = $hour->format('U');
$hour_end = id(clone $hour)->modify('+1 hour')->format('U');
foreach ($this->events as $event) {
- // check if start date is in hour slot
if ($event->getEpochStart() >= $hour_start
&& $event->getEpochStart() < $hour_end) {
$events[] = $event;
}
}
-
$count_events = count($events);
- $event_boxes = array();
$n = 0;
- // draw all events that start in this hour
- // all times as epochs
foreach ($events as $event) {
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
- $offset = (($n / $count_events) * 100).'%';
- $width = ((1 / $count_events) * 100).'%';
$top = ((($event_start - $hour_start) / ($hour_end - $hour_start))
* 100).'%';
$height = ((($event_end - $event_start) / ($hour_end - $hour_start))
* 100).'%';
- $event_boxes[] = $this->drawEvent(
- $event,
- $offset,
- $width,
- $top,
- $height);
+ $hourly_events[$event->getEventID()] = array(
+ 'hour' => $hour,
+ 'event' => $event,
+ 'offset' => '0',
+ 'width' => '100%',
+ 'top' => $top,
+ 'height' => $height,
+ );
+
$n++;
}
+ }
+
+ $clusters = $this->findClusters();
+ foreach ($clusters as $cluster) {
+ $hourly_events = $this->updateEventsFromCluster(
+ $cluster,
+ $hourly_events);
+ }
- // events starting in time slot
+ // actually construct table
+ foreach ($hours as $hour) {
+ $drawn_hourly_events = array();
+ $cell_time = phutil_tag(
+ 'td',
+ array('class' => 'phui-calendar-day-hour'),
+ $hour->format('g A'));
+
+ foreach ($hourly_events as $hourly_event) {
+ if ($hourly_event['hour'] == $hour) {
+ $drawn_hourly_events[] = $this->drawEvent(
+ $hourly_event['event'],
+ $hourly_event['offset'],
+ $hourly_event['width'],
+ $hourly_event['top'],
+ $hourly_event['height']);
+ }
+ }
$cell_event = phutil_tag(
'td',
array('class' => 'phui-calendar-day-events'),
- $event_boxes);
-
+ $drawn_hourly_events);
$row = phutil_tag(
'tr',
array(),
array($cell_time, $cell_event));
$rows[] = $row;
}
$table = phutil_tag(
'table',
array('class' => 'phui-calendar-day-view'),
array(
'',
$rows,
));
$day_box->appendChild($table);
return $day_box;
}
+ 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;
+ }
+
private function drawEvent(
AphrontCalendarDayEventView $event,
$offset,
$width,
$top,
$height) {
$name = phutil_tag(
'a',
array(
'class' => 'phui-calendar-day-event-link',
'href' => $event->getURI(),
),
$event->getName());
$div = phutil_tag(
'div',
array(
'class' => 'phui-calendar-day-event',
'style' => 'left: '.$offset
.'; width: '.$width
.'; top: '.$top
.'; height: '.$height
.';',
),
$name);
return $div;
}
private function getDayOfWeek() {
$date = $this->getDateTime();
$day_of_week = $date->format('l');
return $day_of_week;
}
// 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 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 findClusters() {
+ $events = msort($this->events, 'getEpochStart');
+ $clusters = array();
+
+
+ foreach ($events as $event) {
+ $destination_cluster_key = null;
+ $event_start = $event->getEpochStart();
+ $event_end = $event->getEpochEnd();
+
+ 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;
+ }
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:07 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125355
Default Alt Text
(22 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment