1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 23:02:42 +01:00

Calendar upgrades

Summary:
Does a handful of things to make Calendar significantly more useful

- Enabled overlapping events
- Profile has a 'week view' of the user
- Profile has a 'month view' of the users
- Multiple users on a calendar are color coded
- Browse view slightly more useful

This stops short of implementing the new 'home' view on Calendar, mostly this is a big step though to make that happen next.

Test Plan: Make lots of events on diffent users.

Reviewers: epriestley, btrahan

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T2897, T4375

Differential Revision: https://secure.phabricator.com/D8317
This commit is contained in:
Chad Little 2014-02-24 10:04:23 -08:00
parent e7254944ec
commit 396e8ba82c
23 changed files with 696 additions and 157 deletions

View file

@ -94,7 +94,7 @@ return array(
'rsrc/css/application/ponder/feed.css' => 'e62615b6',
'rsrc/css/application/ponder/post.css' => 'ebab8a70',
'rsrc/css/application/ponder/vote.css' => '8ed6ed8b',
'rsrc/css/application/profile/profile-view.css' => '3a7e04ca',
'rsrc/css/application/profile/profile-view.css' => '9bdb9804',
'rsrc/css/application/projects/phabricator-object-list-view.css' => '1a1ea560',
'rsrc/css/application/projects/project-tag.css' => '095c9404',
'rsrc/css/application/releeph/releeph-branch.css' => 'b8821d2d',
@ -123,9 +123,12 @@ return array(
'rsrc/css/layout/phabricator-hovercard-view.css' => '67c12b16',
'rsrc/css/layout/phabricator-side-menu-view.css' => '503699d0',
'rsrc/css/layout/phabricator-source-code-view.css' => '62a99814',
'rsrc/css/phui/phui-box.css' => '1a82a4ae',
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59',
'rsrc/css/phui/calendar/phui-calendar-month.css' => '5e762971',
'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989',
'rsrc/css/phui/phui-box.css' => 'a36cf3a5',
'rsrc/css/phui/phui-button.css' => '8784a966',
'rsrc/css/phui/phui-calendar-month.css' => '3474d15a',
'rsrc/css/phui/phui-document.css' => '143b2ac8',
'rsrc/css/phui/phui-feed-story.css' => '3a59c2cf',
'rsrc/css/phui/phui-form-view.css' => '0efd3326',
@ -134,7 +137,7 @@ return array(
'rsrc/css/phui/phui-icon.css' => 'fcb145a7',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-list.css' => '2edb76cf',
'rsrc/css/phui/phui-object-box.css' => '95767d08',
'rsrc/css/phui/phui-object-box.css' => 'ce92d8ec',
'rsrc/css/phui/phui-object-item-list-view.css' => 'eb579d6c',
'rsrc/css/phui/phui-pinboard-view.css' => '4b346c2a',
'rsrc/css/phui/phui-property-list-view.css' => 'dbf53b12',
@ -702,7 +705,7 @@ return array(
'phabricator-object-selector-css' => '029a133d',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '0326e5d0',
'phabricator-profile-css' => '3a7e04ca',
'phabricator-profile-css' => '9bdb9804',
'phabricator-project-tag-css' => '095c9404',
'phabricator-remarkup-css' => 'ca7f2265',
'phabricator-search-results-css' => 'f240504c',
@ -735,9 +738,12 @@ return array(
'phortune-credit-card-form-css' => 'b25b4beb',
'phrequent-css' => 'ffc185ad',
'phriction-document-css' => 'b0309d8e',
'phui-box-css' => '1a82a4ae',
'phui-box-css' => 'a36cf3a5',
'phui-button-css' => '8784a966',
'phui-calendar-month-css' => '3474d15a',
'phui-calendar-css' => '5e1ad989',
'phui-calendar-day-css' => 'de035c8a',
'phui-calendar-list-css' => 'c1d0ca59',
'phui-calendar-month-css' => '5e762971',
'phui-document-view-css' => '143b2ac8',
'phui-feed-story-css' => '3a59c2cf',
'phui-form-css' => 'b78ec020',
@ -746,7 +752,7 @@ return array(
'phui-icon-view-css' => 'fcb145a7',
'phui-info-panel-css' => '27ea50a1',
'phui-list-view-css' => '2edb76cf',
'phui-object-box-css' => '95767d08',
'phui-object-box-css' => 'ce92d8ec',
'phui-object-item-list-view-css' => 'eb579d6c',
'phui-pinboard-view-css' => '4b346c2a',
'phui-property-list-view-css' => 'dbf53b12',

View file

@ -93,6 +93,8 @@ phutil_register_library_map(array(
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
'AuditActionMenuEventListener' => 'applications/audit/events/AuditActionMenuEventListener.php',
'BuildStepImplementation' => 'applications/harbormaster/step/BuildStepImplementation.php',
'CalendarColors' => 'applications/calendar/constants/CalendarColors.php',
'CalendarConstants' => 'applications/calendar/constants/CalendarConstants.php',
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
'CelerityManagementMapWorkflow' => 'infrastructure/celerity/management/CelerityManagementMapWorkflow.php',
'CelerityManagementWorkflow' => 'infrastructure/celerity/management/CelerityManagementWorkflow.php',
@ -1000,7 +1002,9 @@ phutil_register_library_map(array(
'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php',
'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php',
'PHUIButtonView' => 'view/phui/PHUIButtonView.php',
'PHUICalendarMonthView' => 'applications/calendar/view/PHUICalendarMonthView.php',
'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php',
'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php',
'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php',
'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php',
'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php',
'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php',
@ -1291,7 +1295,6 @@ phutil_register_library_map(array(
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
'PhabricatorCalendarEventInvalidEpochException' => 'applications/calendar/exception/PhabricatorCalendarEventInvalidEpochException.php',
'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php',
'PhabricatorCalendarEventOverlapException' => 'applications/calendar/exception/PhabricatorCalendarEventOverlapException.php',
'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php',
'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php',
'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
@ -1800,6 +1803,7 @@ phutil_register_library_map(array(
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php',
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php',
@ -2652,6 +2656,7 @@ phutil_register_library_map(array(
),
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
'AuditActionMenuEventListener' => 'PhabricatorEventListener',
'CalendarColors' => 'CalendarConstants',
'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow',
'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow',
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
@ -3666,7 +3671,9 @@ phutil_register_library_map(array(
'PHUIButtonBarView' => 'AphrontTagView',
'PHUIButtonExample' => 'PhabricatorUIExample',
'PHUIButtonView' => 'AphrontTagView',
'PHUICalendarListView' => 'AphrontTagView',
'PHUICalendarMonthView' => 'AphrontView',
'PHUICalendarWidgetView' => 'AphrontTagView',
'PHUIColorPalletteExample' => 'PhabricatorUIExample',
'PHUIDocumentExample' => 'PhabricatorUIExample',
'PHUIDocumentView' => 'AphrontTagView',
@ -3987,7 +3994,6 @@ phutil_register_library_map(array(
0 => 'PhabricatorCalendarController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
),
'PhabricatorCalendarEventOverlapException' => 'Exception',
'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
@ -4563,6 +4569,7 @@ phutil_register_library_map(array(
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleController',
'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',

View file

@ -0,0 +1,30 @@
<?php
/**
* @group calendar
*/
final class CalendarColors extends CalendarConstants {
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';
const COLOR_YELLOW = 'yellow';
const COLOR_GREEN = 'green';
const COLOR_BLUE = 'blue';
const COLOR_SKY = 'sky';
const COLOR_INDIGO = 'indigo';
const COLOR_VIOLET = 'violet';
public static function getColors() {
return array(
self::COLOR_SKY,
self::COLOR_GREEN,
self::COLOR_VIOLET,
self::COLOR_ORANGE,
self::COLOR_BLUE,
self::COLOR_INDIGO,
self::COLOR_RED,
self::COLOR_YELLOW,
);
}
}

View file

@ -0,0 +1,8 @@
<?php
/**
* @group calendar
*/
abstract class CalendarConstants {
}

View file

@ -39,6 +39,24 @@ final class PhabricatorCalendarBrowseController
$phids = mpull($statuses, 'getUserPHID');
$handles = $this->loadViewerHandles($phids);
/* 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());
@ -46,14 +64,10 @@ final class PhabricatorCalendarBrowseController
$name_text = $handles[$status->getUserPHID()]->getName();
$status_text = $status->getHumanStatus();
$event->setUserPHID($status->getUserPHID());
$event->setName("{$name_text} ({$status_text})");
$details = '';
if ($status->getDescription()) {
$details = "\n\n".rtrim($status->getDescription());
}
$event->setDescription(
$status->getTerseSummary($user).$details);
$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);
}

View file

@ -73,9 +73,6 @@ final class PhabricatorCalendarEventEditController
->save();
} catch (PhabricatorCalendarEventInvalidEpochException $e) {
$errors[] = pht('Start must be before end.');
} catch (PhabricatorCalendarEventOverlapException $e) {
$errors[] = pht('There is already a status within the specified '.
'timeframe. Edit or delete this existing status.');
}
if (!$errors) {

View file

@ -1,4 +0,0 @@
<?php
final class PhabricatorCalendarEventOverlapException extends Exception {
}

View file

@ -73,7 +73,7 @@ final class PhabricatorCalendarEvent
/**
* Validates data and throws exceptions for non-sensical status
* windows and attempts to create an overlapping status.
* windows
*/
public function save() {
@ -81,28 +81,7 @@ final class PhabricatorCalendarEvent
throw new PhabricatorCalendarEventInvalidEpochException();
}
$this->openTransaction();
$this->beginWriteLocking();
if ($this->shouldInsertWhenSaved()) {
$overlap = $this->loadAllWhere(
'userPHID = %s AND dateFrom < %d AND dateTo > %d',
$this->getUserPHID(),
$this->getDateTo(),
$this->getDateFrom());
if ($overlap) {
$this->endWriteLocking();
$this->killTransaction();
throw new PhabricatorCalendarEventOverlapException();
}
}
parent::save();
$this->endWriteLocking();
return $this->saveTransaction();
return parent::save();
}

View file

@ -8,6 +8,7 @@ final class AphrontCalendarEventView extends AphrontView {
private $epochEnd;
private $description;
private $eventID;
private $color;
public function setEventID($event_id) {
$this->eventID = $event_id;
@ -58,6 +59,18 @@ final class AphrontCalendarEventView extends AphrontView {
return $this->description;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
public function getColor() {
if ($this->color) {
return $this->color;
} else {
return CalendarColors::COLOR_SKY;
}
}
public function render() {
throw new Exception("Events are only rendered indirectly.");
}

View file

@ -3,7 +3,7 @@
final class PhabricatorApplicationPeople extends PhabricatorApplication {
public function getShortDescription() {
return 'User Accounts';
return pht('User Accounts');
}
public function getBaseURI() {
@ -53,6 +53,8 @@ final class PhabricatorApplicationPeople extends PhabricatorApplication {
),
'/p/(?P<username>[\w._-]+)/'
=> 'PhabricatorPeopleProfileController',
'/p/(?P<username>[\w._-]+)/calendar/'
=> 'PhabricatorPeopleCalendarController',
);
}

View file

@ -51,8 +51,6 @@ final class ConduitAPI_user_addstatus_Method extends ConduitAPI_user_Method {
->save();
} catch (PhabricatorCalendarEventInvalidEpochException $e) {
throw new ConduitException('ERR-BAD-EPOCH');
} catch (PhabricatorCalendarEventOverlapException $e) {
throw new ConduitException('ERR-OVERLAP');
}
}

View file

@ -0,0 +1,92 @@
<?php
final class PhabricatorPeopleCalendarController
extends PhabricatorPeopleController {
private $username;
public function shouldRequireAdmin() {
return false;
}
public function willProcessRequest(array $data) {
$this->username = idx($data, 'username');
}
public function processRequest() {
$viewer = $this->getRequest()->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($this->username))
->needProfileImage(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$picture = $user->loadProfileImageURI();
$now = time();
$request = $this->getRequest();
$year_d = phabricator_format_local_time($now, $user, 'Y');
$year = $request->getInt('year', $year_d);
$month_d = phabricator_format_local_time($now, $user, 'm');
$month = $request->getInt('month', $month_d);
$day = phabricator_format_local_time($now, $user, 'j');
$holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere(
'day BETWEEN %s AND %s',
"{$year}-{$month}-01",
"{$year}-{$month}-31");
$statuses = id(new PhabricatorCalendarEventQuery())
->setViewer($user)
->withInvitedPHIDs(array($user->getPHID()))
->withDateRange(
strtotime("{$year}-{$month}-01"),
strtotime("{$year}-{$month}-01 next month"))
->execute();
if ($month == $month_d && $year == $year_d) {
$month_view = new PHUICalendarMonthView($month, $year, $day);
} else {
$month_view = new PHUICalendarMonthView($month, $year);
}
$month_view->setBrowseURI($request->getRequestURI());
$month_view->setUser($user);
$month_view->setHolidays($holidays);
$month_view->setImage($picture);
$phids = mpull($statuses, 'getUserPHID');
$handles = $this->loadViewerHandles($phids);
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setUserPHID($status->getUserPHID());
$event->setName($status->getHumanStatus());
$event->setDescription($status->getDescription());
$event->setEventID($status->getID());
$month_view->addEvent($event);
}
$date = new DateTime("{$year}-{$month}-01");
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
$user->getUsername(),
'/p/'.$user->getUsername().'/');
$crumbs->addTextCrumb($date->format('F Y'));
return $this->buildApplicationPage(
array(
$crumbs,
$month_view),
array(
'title' => pht('Calendar'),
'device' => true,
));
}
}

View file

@ -73,6 +73,16 @@ final class PhabricatorPeopleProfileController
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($user->getUsername());
$feed = $this->renderUserFeed($user);
$calendar = $this->renderUserCalendar($user);
$activity = phutil_tag(
'div',
array(
'class' => 'profile-activity-view grouped'
),
array(
$calendar,
$feed
));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
@ -82,7 +92,7 @@ final class PhabricatorPeopleProfileController
array(
$crumbs,
$object_box,
$feed,
$activity,
),
array(
'title' => $user->getUsername(),
@ -129,4 +139,72 @@ final class PhabricatorPeopleProfileController
'profile-feed',
$view->render());
}
private function renderUserCalendar(PhabricatorUser $user) {
$now = time();
$year = phabricator_format_local_time($now, $user, 'Y');
$month = phabricator_format_local_time($now, $user, 'm');
$day = phabricator_format_local_time($now, $user, 'j');
$statuses = id(new PhabricatorCalendarEventQuery())
->setViewer($user)
->withInvitedPHIDs(array($user->getPHID()))
->withDateRange(
strtotime("{$year}-{$month}-{$day}"),
strtotime("{$year}-{$month}-{$day} +7 days"))
->execute();
$events = array();
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$status_text = $status->getHumanStatus();
$event->setUserPHID($status->getUserPHID());
$event->setName($status_text);
$event->setDescription($status->getDescription());
$event->setEventID($status->getID());
$key = date('Y-m-d', $event->getEpochStart());
$events[$key][] = $event;
// Populate multiday events
// Better means?
$next_day = strtotime("{$key} +1 day");
if ($event->getEpochEnd() >= $next_day) {
$nextkey = date('Y-m-d', $next_day);
$events[$nextkey][] = $event;
}
}
$i = 0;
$week = array();
for ($i = 0;$i <= 6;$i++) {
$datetime = strtotime("{$year}-{$month}-{$day} +{$i} days");
$headertext = phabricator_format_local_time($datetime, $user, 'l, M d');
$this_day = date('Y-m-d', $datetime);
$list = new PHUICalendarListView();
$list->setUser($user);
$list->showBlankState(true);
if (isset($events[$this_day])) {
foreach ($events[$this_day] as $event) {
$list->addEvent($event);
}
}
$header = phutil_tag(
'a',
array(
'href' => $this->getRequest()->getRequestURI().'calendar/'
),
$headertext);
$calendar = new PHUICalendarWidgetView();
$calendar->setHeader($header);
$calendar->setCalendarList($list);
$week[] = $calendar;
}
return phutil_tag_div(
'profile-calendar',
$week);
}
}

View file

@ -0,0 +1,138 @@
<?php
final class PHUICalendarListView extends AphrontTagView {
private $events = array();
private $blankState;
protected $user;
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function showBlankState($state) {
$this->blankState = $state;
return $this;
}
public function getTagName() {
return 'div';
}
public function getTagAttributes() {
require_celerity_resource('phui-calendar-css');
require_celerity_resource('phui-calendar-list-css');
return array('class' => 'phui-calendar-day-list');
}
protected function getTagContent() {
if (!$this->blankState && empty($this->events)) {
return '';
}
$events = msort($this->events, 'getEpochStart');
// All Day Event (well, 23 hours, 59 minutes worth)
$timespan = ((3600 * 24) - 60);
$singletons = array();
$allday = false;
foreach ($events as $event) {
$color = $event->getColor();
$length = ($event->getEpochEnd() - $event->getEpochStart());
if ($length >= $timespan) {
$timelabel = pht('All Day');
} else {
$timelabel = phabricator_time($event->getEpochStart(), $this->user);
}
$dot = phutil_tag(
'span',
array(
'class' => 'phui-calendar-list-dot'),
'');
$title = phutil_tag(
'span',
array(
'class' => 'phui-calendar-list-title'),
$this->renderEventLink($event, $allday));
$time = phutil_tag(
'span',
array(
'class' => 'phui-calendar-list-time'),
$timelabel);
$singletons[] = phutil_tag(
'li',
array(
'class' => 'phui-calendar-list-item phui-calendar-'.$color
),
array(
$dot,
$title,
$time));
}
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 renderEventLink($event) {
Javelin::initBehavior('phabricator-tooltips');
// Multiple Days
$timespan = ((3600 * 24) + 60);
$length = ($event->getEpochEnd() - $event->getEpochStart());
if ($length >= $timespan) {
$tip = pht('%s, Until: %s', $event->getName(),
phabricator_date($event->getEpochEnd(), $this->user));
} else {
$tip = pht('%s, Until: %s', $event->getName(),
phabricator_time($event->getEpochEnd(), $this->user));
}
$description = $event->getDescription();
if (strlen($description) == 0) {
$description = pht('(%s)', $event->getName());
}
$anchor = javelin_tag(
'a',
array(
'sigil' => 'has-tooltip',
'class' => 'phui-calendar-item-link',
'href' => '/calendar/event/view/'.$event->getEventID().'/',
'meta' => array(
'tip' => $tip,
'size' => 200,
),
),
$description);
return $anchor;
}
}

View file

@ -8,6 +8,7 @@ final class PHUICalendarMonthView extends AphrontView {
private $holidays = array();
private $events = array();
private $browseURI;
private $image;
public function setBrowseURI($browse_uri) {
$this->browseURI = $browse_uri;
@ -22,6 +23,11 @@ final class PHUICalendarMonthView extends AphrontView {
return $this;
}
public function setImage($uri) {
$this->image = $uri;
return $this;
}
public function setHolidays(array $holidays) {
assert_instances_of($holidays, 'PhabricatorCalendarHoliday');
$this->holidays = mpull($holidays, null, 'getDay');
@ -92,6 +98,7 @@ final class PHUICalendarMonthView extends AphrontView {
"\xC2\xA0")); // &nbsp;
}
$list_events = array();
foreach ($events as $event) {
if ($event->getEpochStart() >= $epoch_end) {
// This list is sorted, so we can stop looking.
@ -99,13 +106,16 @@ final class PHUICalendarMonthView extends AphrontView {
}
if ($event->getEpochStart() < $epoch_end &&
$event->getEpochEnd() > $epoch_start) {
$show_events[$event->getUserPHID()] = $this->renderEvent(
$event,
$epoch_start,
$epoch_end);
$list_events[] = $event;
}
}
$list = new PHUICalendarListView();
$list->setUser($this->user);
foreach ($list_events as $item) {
$list->addEvent($item);
}
$holiday_markup = null;
if ($holiday) {
$name = $holiday->getName();
@ -123,7 +133,7 @@ final class PHUICalendarMonthView extends AphrontView {
array(
phutil_tag_div('phui-calendar-date-number', $day_number),
$holiday_markup,
phutil_implode_html("\n", $show_events),
$list,
));
}
@ -227,6 +237,10 @@ final class PHUICalendarMonthView extends AphrontView {
$header->setButtonBar($button_bar);
}
if ($this->image) {
$header->setImage($this->image);
}
return $header;
}
@ -293,60 +307,4 @@ final class PHUICalendarMonthView extends AphrontView {
return $days;
}
private function renderEvent(
AphrontCalendarEventView $event,
$epoch_start,
$epoch_end) {
$user = $this->user;
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
$classes = array();
$when = array();
$classes[] = 'phui-calendar-event';
if ($event_start < $epoch_start) {
$classes[] = 'phui-calendar-event-continues-before';
$when[] = 'Started '.phabricator_datetime($event_start, $user);
} else {
$when[] = 'Starts at '.phabricator_time($event_start, $user);
}
if ($event_end > $epoch_end) {
$classes[] = 'phui-calendar-event-continues-after';
$when[] = 'Ends '.phabricator_datetime($event_end, $user);
} else {
$when[] = 'Ends at '.phabricator_time($event_end, $user);
}
Javelin::initBehavior('phabricator-tooltips');
$info = $event->getName();
if ($event->getDescription()) {
$info .= "\n\n".$event->getDescription();
}
$text_div = javelin_tag(
'a',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $info."\n\n".implode("\n", $when),
'size' => 240,
),
'class' => 'phui-calendar-event-text',
'href' => '/calendar/event/view/'.$event->getEventID().'/',
),
phutil_utf8_shorten($event->getName(), 32));
return javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$text_div);
}
}

View file

@ -0,0 +1,39 @@
<?php
final class PHUICalendarWidgetView extends AphrontTagView {
private $header;
private $list;
public function setHeader($date) {
$this->header = $date;
return $this;
}
public function setCalendarList(PHUICalendarListView $list) {
$this->list = $list;
return $this;
}
public function getTagName() {
return 'div';
}
public function getTagAttributes() {
require_celerity_resource('phui-calendar-list-css');
return array('class' => 'phui-calendar-list-container');
}
protected function getTagContent() {
$header = id(new PHUIHeaderView())
->setHeader($this->header);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setFlush(true)
->appendChild($this->list);
return $box;
}
}

View file

@ -4,8 +4,7 @@
.device-desktop .profile-feed,
.device-tablet .profile-feed {
max-width: 640px;
padding: 12px 16px;
padding: 0 16px 16px 0;
}
.device-phone .profile-feed {
@ -16,3 +15,26 @@
font-size: 16px;
margin-bottom: 5px;
}
.profile-activity-view {
padding-top: 16px;
}
.profile-activity-view .profile-calendar {
float: left;
margin: 0 16px;
}
.profile-activity-view .profile-feed {
margin-left: 332px;
}
.device-phone .profile-activity-view .profile-calendar {
float: none;
margin: 0;
}
.device-phone .profile-activity-view .profile-feed {
float: none;
margin: 0 8px;
}

View file

@ -0,0 +1,3 @@
/**
* @provides phui-calendar-day-css
*/

View file

@ -0,0 +1,64 @@
/**
* @provides phui-calendar-list-css
*/
.phui-calendar-list-container {
width: 300px;
}
.device-phone .phui-calendar-list-container {
width: auto;
}
.phui-calendar-list-container .phui-object-box {
border-bottom: none;
margin: 0;
}
.phui-calendar-list-container:last-child .phui-object-box {
border-bottom: 1px solid {$blueborder};
}
.phui-calendar-list-container .phui-object-box .phui-header-shell h1 {
padding: 6px 0;
}
.phui-calendar-list {
padding: 16px 12px;
}
.phui-calendar-list-item {
position: relative;
}
.phui-calendar-list-dot {
position: relative;
display: inline-block;
width: 5px;
height: 5px;
margin-right: 6px;
top: -5px;
border-radius: 10px;
border: 1px solid transparent;
}
.phui-calendar-list-title {
width: 200px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.phui-calendar-list-item .phui-calendar-list-time {
position: absolute;
width: 60px;
right: 0;
top: 0;
color: {$lightgreytext};
text-align: right;
}
.phui-calendar-list-item-empty {
color: {$lightgreytext};
}

View file

@ -23,6 +23,7 @@ table.phui-calendar-view td {
table.phui-calendar-view td div.phui-calendar-day {
min-height: 125px;
position: relative;
}
.phui-calendar-holiday {
@ -43,12 +44,14 @@ table.phui-calendar-view td.phui-calendar-month-weekstart {
border-color: {$thinblueborder};
border-style: solid;
border-width: 0 0 1px 1px;
float: right;
position: absolute;
background: #ffffff;
width: 16px;
height: 16px;
text-align: center;
margin-bottom: 3px;
top: 0;
right: 0;
z-index: 10;
}
.phui-calendar-not-work-day {
@ -63,40 +66,36 @@ table.phui-calendar-view td.phui-calendar-month-weekstart {
background-color: {$greybackground};
}
.phui-calendar-event {
clear: both;
background: {$sky};
font-size: 11px;
margin: 2px 0;
border-radius: 3px;
padding: 3px 5%;
width: 90%;
overflow: hidden;
}
.phui-calendar-event a:link {
color: #fff;
}
.phui-calendar-event-empty {
border-color: transparent;
background: transparent;
}
.phui-calendar-event-text {
color: #fff;
overflow: hidden;
white-space: nowrap;
.phui-calendar-view .phui-calendar-list {
padding: 8px;
}
.phui-calendar-event-continues-before {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-left-width: 0px;
.phui-calendar-view .phui-calendar-list li:first-child {
margin-right: 16px;
}
.phui-calendar-event-continues-after {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-right-width: 0px;
.phui-calendar-view .phui-calendar-list-dot {
width: 3px;
height: 3px;
margin-right: 4px;
border-radius: 10px;
position: absolute;
top: 5px;
left: 0;
}
.phui-calendar-view .phui-calendar-list-title {
width: auto;
margin-left: 10px;
white-space: normal;
word-break: break-word;
}
.phui-calendar-view .phui-calendar-list-time {
display: none;
}

View file

@ -0,0 +1,107 @@
/**
* @provides phui-calendar-css
*/
.phui-calendar-red a {
color: {$red};
}
.phui-calendar-orange a {
color: {$orange};
}
.phui-calendar-yellow a {
color: {$yellow};
}
.phui-calendar-green a {
color: {$green}
}
.phui-calendar-blue a {
color: {$blue};
}
.phui-calendar-sky a {
color: {$sky};
}
.phui-calendar-indigo a {
color: {$indigo};
}
.phui-calendar-violet a {
color: {$violet};
}
.phui-calendar-bg-red {
background-color: {$lightred};
}
.phui-calendar-bg-orange {
background-color: {$lightorange};
}
.phui-calendar-bg-yellow {
background-color: {$lightyellow};
}
.phui-calendar-bg-green {
background-color: {$lightgreen}
}
.phui-calendar-bg-blue {
background-color: {$lightblue};
}
.phui-calendar-bg-sky {
background-color: {$lightsky};
}
.phui-calendar-bg-indigo {
background-color: {$lightindigo};
}
.phui-calendar-bg-violet {
background-color: {$lightviolet};
}
.phui-calendar-red .phui-calendar-list-dot {
background-color: {$red};
border-color: {$red};
}
.phui-calendar-orange .phui-calendar-list-dot {
background-color: {$orange};
border-color: {$orange};
}
.phui-calendar-yellow .phui-calendar-list-dot {
background-color: {$orange};
border-color: {$orange};
}
.phui-calendar-green .phui-calendar-list-dot {
background-color: {$green};
border-color: {$green};
}
.phui-calendar-blue .phui-calendar-list-dot {
background-color: {$blue};
border-color: {$blue};
}
.phui-calendar-sky .phui-calendar-list-dot {
background-color: {$sky};
border-color: {$sky};
}
.phui-calendar-indigo .phui-calendar-list-dot {
background-color: {$indigo};
border-color: {$indigo};
}
.phui-calendar-violet .phui-calendar-list-dot {
background-color: {$violet};
border-color: {$violet};
}

View file

@ -7,10 +7,3 @@
border-bottom: 1px solid {$blueborder};
background-color: #fff;
}
.device-phone .phui-box {
border-left: none;
border-right: none;
margin-left: 0;
margin-right: 0;
}

View file

@ -2,7 +2,7 @@
* @provides phui-object-box-css
*/
.phui-object-box.phui-object-box-flush {
div.phui-object-box.phui-object-box-flush {
margin-top: 0;
}
@ -29,9 +29,5 @@
}
.device-phone .phui-object-box {
margin-top: 0;
}
.device-phone .phui-object-box + .phui-object-box {
border-top: none;
margin: 8px 8px 0 8px;
}