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

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

+695 -156
+14 -8
resources/celerity/map.php
··· 94 94 'rsrc/css/application/ponder/feed.css' => 'e62615b6', 95 95 'rsrc/css/application/ponder/post.css' => 'ebab8a70', 96 96 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 97 - 'rsrc/css/application/profile/profile-view.css' => '3a7e04ca', 97 + 'rsrc/css/application/profile/profile-view.css' => '9bdb9804', 98 98 'rsrc/css/application/projects/phabricator-object-list-view.css' => '1a1ea560', 99 99 'rsrc/css/application/projects/project-tag.css' => '095c9404', 100 100 'rsrc/css/application/releeph/releeph-branch.css' => 'b8821d2d', ··· 123 123 'rsrc/css/layout/phabricator-hovercard-view.css' => '67c12b16', 124 124 'rsrc/css/layout/phabricator-side-menu-view.css' => '503699d0', 125 125 'rsrc/css/layout/phabricator-source-code-view.css' => '62a99814', 126 - 'rsrc/css/phui/phui-box.css' => '1a82a4ae', 126 + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a', 127 + 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 128 + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '5e762971', 129 + 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', 130 + 'rsrc/css/phui/phui-box.css' => 'a36cf3a5', 127 131 'rsrc/css/phui/phui-button.css' => '8784a966', 128 - 'rsrc/css/phui/phui-calendar-month.css' => '3474d15a', 129 132 'rsrc/css/phui/phui-document.css' => '143b2ac8', 130 133 'rsrc/css/phui/phui-feed-story.css' => '3a59c2cf', 131 134 'rsrc/css/phui/phui-form-view.css' => '0efd3326', ··· 134 137 'rsrc/css/phui/phui-icon.css' => 'fcb145a7', 135 138 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 136 139 'rsrc/css/phui/phui-list.css' => '2edb76cf', 137 - 'rsrc/css/phui/phui-object-box.css' => '95767d08', 140 + 'rsrc/css/phui/phui-object-box.css' => 'ce92d8ec', 138 141 'rsrc/css/phui/phui-object-item-list-view.css' => 'eb579d6c', 139 142 'rsrc/css/phui/phui-pinboard-view.css' => '4b346c2a', 140 143 'rsrc/css/phui/phui-property-list-view.css' => 'dbf53b12', ··· 702 705 'phabricator-object-selector-css' => '029a133d', 703 706 'phabricator-phtize' => 'd254d646', 704 707 'phabricator-prefab' => '0326e5d0', 705 - 'phabricator-profile-css' => '3a7e04ca', 708 + 'phabricator-profile-css' => '9bdb9804', 706 709 'phabricator-project-tag-css' => '095c9404', 707 710 'phabricator-remarkup-css' => 'ca7f2265', 708 711 'phabricator-search-results-css' => 'f240504c', ··· 735 738 'phortune-credit-card-form-css' => 'b25b4beb', 736 739 'phrequent-css' => 'ffc185ad', 737 740 'phriction-document-css' => 'b0309d8e', 738 - 'phui-box-css' => '1a82a4ae', 741 + 'phui-box-css' => 'a36cf3a5', 739 742 'phui-button-css' => '8784a966', 740 - 'phui-calendar-month-css' => '3474d15a', 743 + 'phui-calendar-css' => '5e1ad989', 744 + 'phui-calendar-day-css' => 'de035c8a', 745 + 'phui-calendar-list-css' => 'c1d0ca59', 746 + 'phui-calendar-month-css' => '5e762971', 741 747 'phui-document-view-css' => '143b2ac8', 742 748 'phui-feed-story-css' => '3a59c2cf', 743 749 'phui-form-css' => 'b78ec020', ··· 746 752 'phui-icon-view-css' => 'fcb145a7', 747 753 'phui-info-panel-css' => '27ea50a1', 748 754 'phui-list-view-css' => '2edb76cf', 749 - 'phui-object-box-css' => '95767d08', 755 + 'phui-object-box-css' => 'ce92d8ec', 750 756 'phui-object-item-list-view-css' => 'eb579d6c', 751 757 'phui-pinboard-view-css' => '4b346c2a', 752 758 'phui-property-list-view-css' => 'dbf53b12',
+10 -3
src/__phutil_library_map__.php
··· 93 93 'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php', 94 94 'AuditActionMenuEventListener' => 'applications/audit/events/AuditActionMenuEventListener.php', 95 95 'BuildStepImplementation' => 'applications/harbormaster/step/BuildStepImplementation.php', 96 + 'CalendarColors' => 'applications/calendar/constants/CalendarColors.php', 97 + 'CalendarConstants' => 'applications/calendar/constants/CalendarConstants.php', 96 98 'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php', 97 99 'CelerityManagementMapWorkflow' => 'infrastructure/celerity/management/CelerityManagementMapWorkflow.php', 98 100 'CelerityManagementWorkflow' => 'infrastructure/celerity/management/CelerityManagementWorkflow.php', ··· 1000 1002 'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php', 1001 1003 'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php', 1002 1004 'PHUIButtonView' => 'view/phui/PHUIButtonView.php', 1003 - 'PHUICalendarMonthView' => 'applications/calendar/view/PHUICalendarMonthView.php', 1005 + 'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php', 1006 + 'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php', 1007 + 'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php', 1004 1008 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 1005 1009 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 1006 1010 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', ··· 1291 1295 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 1292 1296 'PhabricatorCalendarEventInvalidEpochException' => 'applications/calendar/exception/PhabricatorCalendarEventInvalidEpochException.php', 1293 1297 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 1294 - 'PhabricatorCalendarEventOverlapException' => 'applications/calendar/exception/PhabricatorCalendarEventOverlapException.php', 1295 1298 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 1296 1299 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', 1297 1300 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', ··· 1800 1803 'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php', 1801 1804 'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php', 1802 1805 'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php', 1806 + 'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php', 1803 1807 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 1804 1808 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', 1805 1809 'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php', ··· 2652 2656 ), 2653 2657 'AphrontWebpageResponse' => 'AphrontHTMLResponse', 2654 2658 'AuditActionMenuEventListener' => 'PhabricatorEventListener', 2659 + 'CalendarColors' => 'CalendarConstants', 2655 2660 'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow', 2656 2661 'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow', 2657 2662 'CelerityPhabricatorResourceController' => 'CelerityResourceController', ··· 3666 3671 'PHUIButtonBarView' => 'AphrontTagView', 3667 3672 'PHUIButtonExample' => 'PhabricatorUIExample', 3668 3673 'PHUIButtonView' => 'AphrontTagView', 3674 + 'PHUICalendarListView' => 'AphrontTagView', 3669 3675 'PHUICalendarMonthView' => 'AphrontView', 3676 + 'PHUICalendarWidgetView' => 'AphrontTagView', 3670 3677 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 3671 3678 'PHUIDocumentExample' => 'PhabricatorUIExample', 3672 3679 'PHUIDocumentView' => 'AphrontTagView', ··· 3987 3994 0 => 'PhabricatorCalendarController', 3988 3995 1 => 'PhabricatorApplicationSearchResultsControllerInterface', 3989 3996 ), 3990 - 'PhabricatorCalendarEventOverlapException' => 'Exception', 3991 3997 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3992 3998 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', 3993 3999 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', ··· 4563 4569 'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4564 4570 'PhabricatorPasteViewController' => 'PhabricatorPasteController', 4565 4571 'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController', 4572 + 'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleController', 4566 4573 'PhabricatorPeopleController' => 'PhabricatorController', 4567 4574 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', 4568 4575 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
+30
src/applications/calendar/constants/CalendarColors.php
··· 1 + <?php 2 + 3 + /** 4 + * @group calendar 5 + */ 6 + final class CalendarColors extends CalendarConstants { 7 + 8 + const COLOR_RED = 'red'; 9 + const COLOR_ORANGE = 'orange'; 10 + const COLOR_YELLOW = 'yellow'; 11 + const COLOR_GREEN = 'green'; 12 + const COLOR_BLUE = 'blue'; 13 + const COLOR_SKY = 'sky'; 14 + const COLOR_INDIGO = 'indigo'; 15 + const COLOR_VIOLET = 'violet'; 16 + 17 + public static function getColors() { 18 + return array( 19 + self::COLOR_SKY, 20 + self::COLOR_GREEN, 21 + self::COLOR_VIOLET, 22 + self::COLOR_ORANGE, 23 + self::COLOR_BLUE, 24 + self::COLOR_INDIGO, 25 + self::COLOR_RED, 26 + self::COLOR_YELLOW, 27 + ); 28 + } 29 + 30 + }
+8
src/applications/calendar/constants/CalendarConstants.php
··· 1 + <?php 2 + 3 + /** 4 + * @group calendar 5 + */ 6 + abstract class CalendarConstants { 7 + 8 + }
+21 -7
src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
··· 39 39 $phids = mpull($statuses, 'getUserPHID'); 40 40 $handles = $this->loadViewerHandles($phids); 41 41 42 + /* Assign Colors */ 43 + $unique = array_unique($phids); 44 + $allblue = false; 45 + $calcolors = CalendarColors::getColors(); 46 + if (count($unique) > count($calcolors)) { 47 + $allblue = true; 48 + } 49 + $i = 0; 50 + $eventcolor = array(); 51 + foreach ($unique as $phid) { 52 + if ($allblue) { 53 + $eventcolor[$phid] = CalendarColors::COLOR_SKY; 54 + } else { 55 + $eventcolor[$phid] = $calcolors[$i]; 56 + } 57 + $i++; 58 + } 59 + 42 60 foreach ($statuses as $status) { 43 61 $event = new AphrontCalendarEventView(); 44 62 $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); ··· 46 64 $name_text = $handles[$status->getUserPHID()]->getName(); 47 65 $status_text = $status->getHumanStatus(); 48 66 $event->setUserPHID($status->getUserPHID()); 49 - $event->setName("{$name_text} ({$status_text})"); 50 - $details = ''; 51 - if ($status->getDescription()) { 52 - $details = "\n\n".rtrim($status->getDescription()); 53 - } 54 - $event->setDescription( 55 - $status->getTerseSummary($user).$details); 67 + $event->setDescription(pht('%s (%s)', $name_text, $status_text)); 68 + $event->setName($status_text); 56 69 $event->setEventID($status->getID()); 70 + $event->setColor($eventcolor[$status->getUserPHID()]); 57 71 $month_view->addEvent($event); 58 72 } 59 73
-3
src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
··· 73 73 ->save(); 74 74 } catch (PhabricatorCalendarEventInvalidEpochException $e) { 75 75 $errors[] = pht('Start must be before end.'); 76 - } catch (PhabricatorCalendarEventOverlapException $e) { 77 - $errors[] = pht('There is already a status within the specified '. 78 - 'timeframe. Edit or delete this existing status.'); 79 76 } 80 77 81 78 if (!$errors) {
-4
src/applications/calendar/exception/PhabricatorCalendarEventOverlapException.php
··· 1 - <?php 2 - 3 - final class PhabricatorCalendarEventOverlapException extends Exception { 4 - }
+2 -23
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 73 73 74 74 /** 75 75 * Validates data and throws exceptions for non-sensical status 76 - * windows and attempts to create an overlapping status. 76 + * windows 77 77 */ 78 78 public function save() { 79 79 ··· 81 81 throw new PhabricatorCalendarEventInvalidEpochException(); 82 82 } 83 83 84 - $this->openTransaction(); 85 - $this->beginWriteLocking(); 86 - 87 - if ($this->shouldInsertWhenSaved()) { 88 - 89 - $overlap = $this->loadAllWhere( 90 - 'userPHID = %s AND dateFrom < %d AND dateTo > %d', 91 - $this->getUserPHID(), 92 - $this->getDateTo(), 93 - $this->getDateFrom()); 94 - 95 - if ($overlap) { 96 - $this->endWriteLocking(); 97 - $this->killTransaction(); 98 - throw new PhabricatorCalendarEventOverlapException(); 99 - } 100 - } 101 - 102 - parent::save(); 103 - 104 - $this->endWriteLocking(); 105 - return $this->saveTransaction(); 84 + return parent::save(); 106 85 } 107 86 108 87
+13
src/applications/calendar/view/AphrontCalendarEventView.php
··· 8 8 private $epochEnd; 9 9 private $description; 10 10 private $eventID; 11 + private $color; 11 12 12 13 public function setEventID($event_id) { 13 14 $this->eventID = $event_id; ··· 56 57 57 58 public function getDescription() { 58 59 return $this->description; 60 + } 61 + 62 + public function setColor($color) { 63 + $this->color = $color; 64 + return $this; 65 + } 66 + public function getColor() { 67 + if ($this->color) { 68 + return $this->color; 69 + } else { 70 + return CalendarColors::COLOR_SKY; 71 + } 59 72 } 60 73 61 74 public function render() {
+19 -61
src/applications/calendar/view/PHUICalendarMonthView.php src/view/phui/calendar/PHUICalendarMonthView.php
··· 8 8 private $holidays = array(); 9 9 private $events = array(); 10 10 private $browseURI; 11 + private $image; 11 12 12 13 public function setBrowseURI($browse_uri) { 13 14 $this->browseURI = $browse_uri; ··· 19 20 20 21 public function addEvent(AphrontCalendarEventView $event) { 21 22 $this->events[] = $event; 23 + return $this; 24 + } 25 + 26 + public function setImage($uri) { 27 + $this->image = $uri; 22 28 return $this; 23 29 } 24 30 ··· 92 98 "\xC2\xA0")); // &nbsp; 93 99 } 94 100 101 + $list_events = array(); 95 102 foreach ($events as $event) { 96 103 if ($event->getEpochStart() >= $epoch_end) { 97 104 // This list is sorted, so we can stop looking. ··· 99 106 } 100 107 if ($event->getEpochStart() < $epoch_end && 101 108 $event->getEpochEnd() > $epoch_start) { 102 - $show_events[$event->getUserPHID()] = $this->renderEvent( 103 - $event, 104 - $epoch_start, 105 - $epoch_end); 109 + $list_events[] = $event; 106 110 } 111 + } 112 + 113 + $list = new PHUICalendarListView(); 114 + $list->setUser($this->user); 115 + foreach ($list_events as $item) { 116 + $list->addEvent($item); 107 117 } 108 118 109 119 $holiday_markup = null; ··· 123 133 array( 124 134 phutil_tag_div('phui-calendar-date-number', $day_number), 125 135 $holiday_markup, 126 - phutil_implode_html("\n", $show_events), 136 + $list, 127 137 )); 128 138 } 129 139 ··· 227 237 $header->setButtonBar($button_bar); 228 238 } 229 239 240 + if ($this->image) { 241 + $header->setImage($this->image); 242 + } 243 + 230 244 return $header; 231 245 } 232 246 ··· 291 305 } 292 306 293 307 return $days; 294 - } 295 - 296 - private function renderEvent( 297 - AphrontCalendarEventView $event, 298 - $epoch_start, 299 - $epoch_end) { 300 - 301 - $user = $this->user; 302 - 303 - $event_start = $event->getEpochStart(); 304 - $event_end = $event->getEpochEnd(); 305 - 306 - $classes = array(); 307 - $when = array(); 308 - 309 - $classes[] = 'phui-calendar-event'; 310 - if ($event_start < $epoch_start) { 311 - $classes[] = 'phui-calendar-event-continues-before'; 312 - $when[] = 'Started '.phabricator_datetime($event_start, $user); 313 - } else { 314 - $when[] = 'Starts at '.phabricator_time($event_start, $user); 315 - } 316 - 317 - if ($event_end > $epoch_end) { 318 - $classes[] = 'phui-calendar-event-continues-after'; 319 - $when[] = 'Ends '.phabricator_datetime($event_end, $user); 320 - } else { 321 - $when[] = 'Ends at '.phabricator_time($event_end, $user); 322 - } 323 - 324 - Javelin::initBehavior('phabricator-tooltips'); 325 - 326 - $info = $event->getName(); 327 - if ($event->getDescription()) { 328 - $info .= "\n\n".$event->getDescription(); 329 - } 330 - 331 - $text_div = javelin_tag( 332 - 'a', 333 - array( 334 - 'sigil' => 'has-tooltip', 335 - 'meta' => array( 336 - 'tip' => $info."\n\n".implode("\n", $when), 337 - 'size' => 240, 338 - ), 339 - 'class' => 'phui-calendar-event-text', 340 - 'href' => '/calendar/event/view/'.$event->getEventID().'/', 341 - ), 342 - phutil_utf8_shorten($event->getName(), 32)); 343 - 344 - return javelin_tag( 345 - 'div', 346 - array( 347 - 'class' => implode(' ', $classes), 348 - ), 349 - $text_div); 350 308 } 351 309 352 310 }
+3 -1
src/applications/people/application/PhabricatorApplicationPeople.php
··· 3 3 final class PhabricatorApplicationPeople extends PhabricatorApplication { 4 4 5 5 public function getShortDescription() { 6 - return 'User Accounts'; 6 + return pht('User Accounts'); 7 7 } 8 8 9 9 public function getBaseURI() { ··· 53 53 ), 54 54 '/p/(?P<username>[\w._-]+)/' 55 55 => 'PhabricatorPeopleProfileController', 56 + '/p/(?P<username>[\w._-]+)/calendar/' 57 + => 'PhabricatorPeopleCalendarController', 56 58 ); 57 59 } 58 60
-2
src/applications/people/conduit/ConduitAPI_user_addstatus_Method.php
··· 51 51 ->save(); 52 52 } catch (PhabricatorCalendarEventInvalidEpochException $e) { 53 53 throw new ConduitException('ERR-BAD-EPOCH'); 54 - } catch (PhabricatorCalendarEventOverlapException $e) { 55 - throw new ConduitException('ERR-OVERLAP'); 56 54 } 57 55 } 58 56
+92
src/applications/people/controller/PhabricatorPeopleCalendarController.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleCalendarController 4 + extends PhabricatorPeopleController { 5 + 6 + private $username; 7 + 8 + public function shouldRequireAdmin() { 9 + return false; 10 + } 11 + 12 + public function willProcessRequest(array $data) { 13 + $this->username = idx($data, 'username'); 14 + } 15 + 16 + public function processRequest() { 17 + $viewer = $this->getRequest()->getUser(); 18 + $user = id(new PhabricatorPeopleQuery()) 19 + ->setViewer($viewer) 20 + ->withUsernames(array($this->username)) 21 + ->needProfileImage(true) 22 + ->executeOne(); 23 + 24 + if (!$user) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $picture = $user->loadProfileImageURI(); 29 + 30 + $now = time(); 31 + $request = $this->getRequest(); 32 + $year_d = phabricator_format_local_time($now, $user, 'Y'); 33 + $year = $request->getInt('year', $year_d); 34 + $month_d = phabricator_format_local_time($now, $user, 'm'); 35 + $month = $request->getInt('month', $month_d); 36 + $day = phabricator_format_local_time($now, $user, 'j'); 37 + 38 + 39 + $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere( 40 + 'day BETWEEN %s AND %s', 41 + "{$year}-{$month}-01", 42 + "{$year}-{$month}-31"); 43 + 44 + $statuses = id(new PhabricatorCalendarEventQuery()) 45 + ->setViewer($user) 46 + ->withInvitedPHIDs(array($user->getPHID())) 47 + ->withDateRange( 48 + strtotime("{$year}-{$month}-01"), 49 + strtotime("{$year}-{$month}-01 next month")) 50 + ->execute(); 51 + 52 + if ($month == $month_d && $year == $year_d) { 53 + $month_view = new PHUICalendarMonthView($month, $year, $day); 54 + } else { 55 + $month_view = new PHUICalendarMonthView($month, $year); 56 + } 57 + 58 + $month_view->setBrowseURI($request->getRequestURI()); 59 + $month_view->setUser($user); 60 + $month_view->setHolidays($holidays); 61 + $month_view->setImage($picture); 62 + 63 + $phids = mpull($statuses, 'getUserPHID'); 64 + $handles = $this->loadViewerHandles($phids); 65 + 66 + foreach ($statuses as $status) { 67 + $event = new AphrontCalendarEventView(); 68 + $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); 69 + $event->setUserPHID($status->getUserPHID()); 70 + $event->setName($status->getHumanStatus()); 71 + $event->setDescription($status->getDescription()); 72 + $event->setEventID($status->getID()); 73 + $month_view->addEvent($event); 74 + } 75 + 76 + $date = new DateTime("{$year}-{$month}-01"); 77 + $crumbs = $this->buildApplicationCrumbs(); 78 + $crumbs->addTextCrumb( 79 + $user->getUsername(), 80 + '/p/'.$user->getUsername().'/'); 81 + $crumbs->addTextCrumb($date->format('F Y')); 82 + 83 + return $this->buildApplicationPage( 84 + array( 85 + $crumbs, 86 + $month_view), 87 + array( 88 + 'title' => pht('Calendar'), 89 + 'device' => true, 90 + )); 91 + } 92 + }
+79 -1
src/applications/people/controller/PhabricatorPeopleProfileController.php
··· 73 73 $crumbs = $this->buildApplicationCrumbs(); 74 74 $crumbs->addTextCrumb($user->getUsername()); 75 75 $feed = $this->renderUserFeed($user); 76 + $calendar = $this->renderUserCalendar($user); 77 + $activity = phutil_tag( 78 + 'div', 79 + array( 80 + 'class' => 'profile-activity-view grouped' 81 + ), 82 + array( 83 + $calendar, 84 + $feed 85 + )); 76 86 77 87 $object_box = id(new PHUIObjectBoxView()) 78 88 ->setHeader($header) ··· 82 92 array( 83 93 $crumbs, 84 94 $object_box, 85 - $feed, 95 + $activity, 86 96 ), 87 97 array( 88 98 'title' => $user->getUsername(), ··· 128 138 return phutil_tag_div( 129 139 'profile-feed', 130 140 $view->render()); 141 + } 142 + 143 + private function renderUserCalendar(PhabricatorUser $user) { 144 + $now = time(); 145 + $year = phabricator_format_local_time($now, $user, 'Y'); 146 + $month = phabricator_format_local_time($now, $user, 'm'); 147 + $day = phabricator_format_local_time($now, $user, 'j'); 148 + $statuses = id(new PhabricatorCalendarEventQuery()) 149 + ->setViewer($user) 150 + ->withInvitedPHIDs(array($user->getPHID())) 151 + ->withDateRange( 152 + strtotime("{$year}-{$month}-{$day}"), 153 + strtotime("{$year}-{$month}-{$day} +7 days")) 154 + ->execute(); 155 + 156 + $events = array(); 157 + foreach ($statuses as $status) { 158 + $event = new AphrontCalendarEventView(); 159 + $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); 160 + 161 + $status_text = $status->getHumanStatus(); 162 + $event->setUserPHID($status->getUserPHID()); 163 + $event->setName($status_text); 164 + $event->setDescription($status->getDescription()); 165 + $event->setEventID($status->getID()); 166 + $key = date('Y-m-d', $event->getEpochStart()); 167 + $events[$key][] = $event; 168 + // Populate multiday events 169 + // Better means? 170 + $next_day = strtotime("{$key} +1 day"); 171 + if ($event->getEpochEnd() >= $next_day) { 172 + $nextkey = date('Y-m-d', $next_day); 173 + $events[$nextkey][] = $event; 174 + } 175 + } 176 + 177 + $i = 0; 178 + $week = array(); 179 + for ($i = 0;$i <= 6;$i++) { 180 + $datetime = strtotime("{$year}-{$month}-{$day} +{$i} days"); 181 + $headertext = phabricator_format_local_time($datetime, $user, 'l, M d'); 182 + $this_day = date('Y-m-d', $datetime); 183 + 184 + $list = new PHUICalendarListView(); 185 + $list->setUser($user); 186 + $list->showBlankState(true); 187 + if (isset($events[$this_day])) { 188 + foreach ($events[$this_day] as $event) { 189 + $list->addEvent($event); 190 + } 191 + } 192 + 193 + $header = phutil_tag( 194 + 'a', 195 + array( 196 + 'href' => $this->getRequest()->getRequestURI().'calendar/' 197 + ), 198 + $headertext); 199 + 200 + $calendar = new PHUICalendarWidgetView(); 201 + $calendar->setHeader($header); 202 + $calendar->setCalendarList($list); 203 + $week[] = $calendar; 204 + } 205 + 206 + return phutil_tag_div( 207 + 'profile-calendar', 208 + $week); 131 209 } 132 210 }
+138
src/view/phui/calendar/PHUICalendarListView.php
··· 1 + <?php 2 + 3 + final class PHUICalendarListView extends AphrontTagView { 4 + 5 + private $events = array(); 6 + private $blankState; 7 + 8 + protected $user; 9 + 10 + public function addEvent(AphrontCalendarEventView $event) { 11 + $this->events[] = $event; 12 + return $this; 13 + } 14 + 15 + public function setUser($user) { 16 + $this->user = $user; 17 + return $this; 18 + } 19 + 20 + public function showBlankState($state) { 21 + $this->blankState = $state; 22 + return $this; 23 + } 24 + 25 + public function getTagName() { 26 + return 'div'; 27 + } 28 + 29 + public function getTagAttributes() { 30 + require_celerity_resource('phui-calendar-css'); 31 + require_celerity_resource('phui-calendar-list-css'); 32 + return array('class' => 'phui-calendar-day-list'); 33 + } 34 + 35 + protected function getTagContent() { 36 + if (!$this->blankState && empty($this->events)) { 37 + return ''; 38 + } 39 + 40 + $events = msort($this->events, 'getEpochStart'); 41 + 42 + // All Day Event (well, 23 hours, 59 minutes worth) 43 + $timespan = ((3600 * 24) - 60); 44 + 45 + $singletons = array(); 46 + $allday = false; 47 + foreach ($events as $event) { 48 + $color = $event->getColor(); 49 + 50 + $length = ($event->getEpochEnd() - $event->getEpochStart()); 51 + if ($length >= $timespan) { 52 + $timelabel = pht('All Day'); 53 + } else { 54 + $timelabel = phabricator_time($event->getEpochStart(), $this->user); 55 + } 56 + 57 + $dot = phutil_tag( 58 + 'span', 59 + array( 60 + 'class' => 'phui-calendar-list-dot'), 61 + ''); 62 + $title = phutil_tag( 63 + 'span', 64 + array( 65 + 'class' => 'phui-calendar-list-title'), 66 + $this->renderEventLink($event, $allday)); 67 + $time = phutil_tag( 68 + 'span', 69 + array( 70 + 'class' => 'phui-calendar-list-time'), 71 + $timelabel); 72 + 73 + $singletons[] = phutil_tag( 74 + 'li', 75 + array( 76 + 'class' => 'phui-calendar-list-item phui-calendar-'.$color 77 + ), 78 + array( 79 + $dot, 80 + $title, 81 + $time)); 82 + } 83 + 84 + if (empty($singletons)) { 85 + $singletons[] = phutil_tag( 86 + 'li', 87 + array( 88 + 'class' => 'phui-calendar-list-item-empty' 89 + ), 90 + pht('Clear sailing ahead.')); 91 + } 92 + 93 + $list = phutil_tag( 94 + 'ul', 95 + array( 96 + 'class' => 'phui-calendar-list' 97 + ), 98 + $singletons); 99 + 100 + return $list; 101 + } 102 + 103 + private function renderEventLink($event) { 104 + 105 + Javelin::initBehavior('phabricator-tooltips'); 106 + 107 + // Multiple Days 108 + $timespan = ((3600 * 24) + 60); 109 + $length = ($event->getEpochEnd() - $event->getEpochStart()); 110 + if ($length >= $timespan) { 111 + $tip = pht('%s, Until: %s', $event->getName(), 112 + phabricator_date($event->getEpochEnd(), $this->user)); 113 + } else { 114 + $tip = pht('%s, Until: %s', $event->getName(), 115 + phabricator_time($event->getEpochEnd(), $this->user)); 116 + } 117 + 118 + $description = $event->getDescription(); 119 + if (strlen($description) == 0) { 120 + $description = pht('(%s)', $event->getName()); 121 + } 122 + 123 + $anchor = javelin_tag( 124 + 'a', 125 + array( 126 + 'sigil' => 'has-tooltip', 127 + 'class' => 'phui-calendar-item-link', 128 + 'href' => '/calendar/event/view/'.$event->getEventID().'/', 129 + 'meta' => array( 130 + 'tip' => $tip, 131 + 'size' => 200, 132 + ), 133 + ), 134 + $description); 135 + 136 + return $anchor; 137 + } 138 + }
+39
src/view/phui/calendar/PHUICalendarWidgetView.php
··· 1 + <?php 2 + 3 + final class PHUICalendarWidgetView extends AphrontTagView { 4 + 5 + private $header; 6 + private $list; 7 + 8 + public function setHeader($date) { 9 + $this->header = $date; 10 + return $this; 11 + } 12 + 13 + public function setCalendarList(PHUICalendarListView $list) { 14 + $this->list = $list; 15 + return $this; 16 + } 17 + 18 + public function getTagName() { 19 + return 'div'; 20 + } 21 + 22 + public function getTagAttributes() { 23 + require_celerity_resource('phui-calendar-list-css'); 24 + return array('class' => 'phui-calendar-list-container'); 25 + } 26 + 27 + protected function getTagContent() { 28 + 29 + $header = id(new PHUIHeaderView()) 30 + ->setHeader($this->header); 31 + 32 + $box = id(new PHUIObjectBoxView()) 33 + ->setHeader($header) 34 + ->setFlush(true) 35 + ->appendChild($this->list); 36 + 37 + return $box; 38 + } 39 + }
+24 -2
webroot/rsrc/css/application/profile/profile-view.css
··· 4 4 5 5 .device-desktop .profile-feed, 6 6 .device-tablet .profile-feed { 7 - max-width: 640px; 8 - padding: 12px 16px; 7 + padding: 0 16px 16px 0; 9 8 } 10 9 11 10 .device-phone .profile-feed { ··· 16 15 font-size: 16px; 17 16 margin-bottom: 5px; 18 17 } 18 + 19 + .profile-activity-view { 20 + padding-top: 16px; 21 + } 22 + 23 + .profile-activity-view .profile-calendar { 24 + float: left; 25 + margin: 0 16px; 26 + } 27 + 28 + .profile-activity-view .profile-feed { 29 + margin-left: 332px; 30 + } 31 + 32 + .device-phone .profile-activity-view .profile-calendar { 33 + float: none; 34 + margin: 0; 35 + } 36 + 37 + .device-phone .profile-activity-view .profile-feed { 38 + float: none; 39 + margin: 0 8px; 40 + }
+3
webroot/rsrc/css/phui/calendar/phui-calendar-day.css
··· 1 + /** 2 + * @provides phui-calendar-day-css 3 + */
+64
webroot/rsrc/css/phui/calendar/phui-calendar-list.css
··· 1 + /** 2 + * @provides phui-calendar-list-css 3 + */ 4 + 5 + .phui-calendar-list-container { 6 + width: 300px; 7 + } 8 + 9 + .device-phone .phui-calendar-list-container { 10 + width: auto; 11 + } 12 + 13 + .phui-calendar-list-container .phui-object-box { 14 + border-bottom: none; 15 + margin: 0; 16 + } 17 + 18 + .phui-calendar-list-container:last-child .phui-object-box { 19 + border-bottom: 1px solid {$blueborder}; 20 + } 21 + 22 + .phui-calendar-list-container .phui-object-box .phui-header-shell h1 { 23 + padding: 6px 0; 24 + } 25 + 26 + .phui-calendar-list { 27 + padding: 16px 12px; 28 + } 29 + 30 + .phui-calendar-list-item { 31 + position: relative; 32 + } 33 + 34 + .phui-calendar-list-dot { 35 + position: relative; 36 + display: inline-block; 37 + width: 5px; 38 + height: 5px; 39 + margin-right: 6px; 40 + top: -5px; 41 + border-radius: 10px; 42 + border: 1px solid transparent; 43 + } 44 + 45 + .phui-calendar-list-title { 46 + width: 200px; 47 + display: inline-block; 48 + overflow: hidden; 49 + text-overflow: ellipsis; 50 + white-space: nowrap; 51 + } 52 + 53 + .phui-calendar-list-item .phui-calendar-list-time { 54 + position: absolute; 55 + width: 60px; 56 + right: 0; 57 + top: 0; 58 + color: {$lightgreytext}; 59 + text-align: right; 60 + } 61 + 62 + .phui-calendar-list-item-empty { 63 + color: {$lightgreytext}; 64 + }
+107
webroot/rsrc/css/phui/calendar/phui-calendar.css
··· 1 + /** 2 + * @provides phui-calendar-css 3 + */ 4 + 5 + .phui-calendar-red a { 6 + color: {$red}; 7 + } 8 + 9 + .phui-calendar-orange a { 10 + color: {$orange}; 11 + } 12 + 13 + .phui-calendar-yellow a { 14 + color: {$yellow}; 15 + } 16 + 17 + .phui-calendar-green a { 18 + color: {$green} 19 + } 20 + 21 + .phui-calendar-blue a { 22 + color: {$blue}; 23 + } 24 + 25 + .phui-calendar-sky a { 26 + color: {$sky}; 27 + } 28 + 29 + .phui-calendar-indigo a { 30 + color: {$indigo}; 31 + } 32 + 33 + .phui-calendar-violet a { 34 + color: {$violet}; 35 + } 36 + 37 + .phui-calendar-bg-red { 38 + background-color: {$lightred}; 39 + } 40 + 41 + .phui-calendar-bg-orange { 42 + background-color: {$lightorange}; 43 + } 44 + 45 + .phui-calendar-bg-yellow { 46 + background-color: {$lightyellow}; 47 + } 48 + 49 + .phui-calendar-bg-green { 50 + background-color: {$lightgreen} 51 + } 52 + 53 + .phui-calendar-bg-blue { 54 + background-color: {$lightblue}; 55 + } 56 + 57 + .phui-calendar-bg-sky { 58 + background-color: {$lightsky}; 59 + } 60 + 61 + .phui-calendar-bg-indigo { 62 + background-color: {$lightindigo}; 63 + } 64 + 65 + .phui-calendar-bg-violet { 66 + background-color: {$lightviolet}; 67 + } 68 + 69 + .phui-calendar-red .phui-calendar-list-dot { 70 + background-color: {$red}; 71 + border-color: {$red}; 72 + } 73 + 74 + .phui-calendar-orange .phui-calendar-list-dot { 75 + background-color: {$orange}; 76 + border-color: {$orange}; 77 + } 78 + 79 + .phui-calendar-yellow .phui-calendar-list-dot { 80 + background-color: {$orange}; 81 + border-color: {$orange}; 82 + } 83 + 84 + .phui-calendar-green .phui-calendar-list-dot { 85 + background-color: {$green}; 86 + border-color: {$green}; 87 + } 88 + 89 + .phui-calendar-blue .phui-calendar-list-dot { 90 + background-color: {$blue}; 91 + border-color: {$blue}; 92 + } 93 + 94 + .phui-calendar-sky .phui-calendar-list-dot { 95 + background-color: {$sky}; 96 + border-color: {$sky}; 97 + } 98 + 99 + .phui-calendar-indigo .phui-calendar-list-dot { 100 + background-color: {$indigo}; 101 + border-color: {$indigo}; 102 + } 103 + 104 + .phui-calendar-violet .phui-calendar-list-dot { 105 + background-color: {$violet}; 106 + border-color: {$violet}; 107 + }
-7
webroot/rsrc/css/phui/phui-box.css
··· 7 7 border-bottom: 1px solid {$blueborder}; 8 8 background-color: #fff; 9 9 } 10 - 11 - .device-phone .phui-box { 12 - border-left: none; 13 - border-right: none; 14 - margin-left: 0; 15 - margin-right: 0; 16 - }
+27 -28
webroot/rsrc/css/phui/phui-calendar-month.css webroot/rsrc/css/phui/calendar/phui-calendar-month.css
··· 23 23 24 24 table.phui-calendar-view td div.phui-calendar-day { 25 25 min-height: 125px; 26 + position: relative; 26 27 } 27 28 28 29 .phui-calendar-holiday { ··· 43 44 border-color: {$thinblueborder}; 44 45 border-style: solid; 45 46 border-width: 0 0 1px 1px; 46 - float: right; 47 + position: absolute; 47 48 background: #ffffff; 48 49 width: 16px; 49 50 height: 16px; 50 51 text-align: center; 51 - margin-bottom: 3px; 52 + top: 0; 53 + right: 0; 54 + z-index: 10; 52 55 } 53 56 54 57 .phui-calendar-not-work-day { ··· 63 66 background-color: {$greybackground}; 64 67 } 65 68 66 - .phui-calendar-event { 67 - clear: both; 68 - background: {$sky}; 69 - font-size: 11px; 70 - margin: 2px 0; 71 - border-radius: 3px; 72 - padding: 3px 5%; 73 - width: 90%; 74 - overflow: hidden; 69 + .phui-calendar-event-empty { 70 + border-color: transparent; 71 + background: transparent; 75 72 } 76 73 77 - .phui-calendar-event a:link { 78 - color: #fff; 74 + .phui-calendar-view .phui-calendar-list { 75 + padding: 8px; 79 76 } 80 77 81 - .phui-calendar-event-empty { 82 - border-color: transparent; 83 - background: transparent; 78 + .phui-calendar-view .phui-calendar-list li:first-child { 79 + margin-right: 16px; 84 80 } 85 81 86 - .phui-calendar-event-text { 87 - color: #fff; 88 - overflow: hidden; 89 - white-space: nowrap; 82 + .phui-calendar-view .phui-calendar-list-dot { 83 + width: 3px; 84 + height: 3px; 85 + margin-right: 4px; 86 + border-radius: 10px; 87 + position: absolute; 88 + top: 5px; 89 + left: 0; 90 90 } 91 91 92 - .phui-calendar-event-continues-before { 93 - border-top-left-radius: 0px; 94 - border-bottom-left-radius: 0px; 95 - border-left-width: 0px; 92 + .phui-calendar-view .phui-calendar-list-title { 93 + width: auto; 94 + margin-left: 10px; 95 + white-space: normal; 96 + word-break: break-word; 96 97 } 97 98 98 - .phui-calendar-event-continues-after { 99 - border-top-right-radius: 0px; 100 - border-bottom-right-radius: 0px; 101 - border-right-width: 0px; 99 + .phui-calendar-view .phui-calendar-list-time { 100 + display: none; 102 101 }
+2 -6
webroot/rsrc/css/phui/phui-object-box.css
··· 2 2 * @provides phui-object-box-css 3 3 */ 4 4 5 - .phui-object-box.phui-object-box-flush { 5 + div.phui-object-box.phui-object-box-flush { 6 6 margin-top: 0; 7 7 } 8 8 ··· 29 29 } 30 30 31 31 .device-phone .phui-object-box { 32 - margin-top: 0; 33 - } 34 - 35 - .device-phone .phui-object-box + .phui-object-box { 36 - border-top: none; 32 + margin: 8px 8px 0 8px; 37 33 }