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

Show user status on calendar

Summary:
This is a rough cut, but gets some of the basics at least. Here's what it looks like:

{F11690}

Some things that would be nice for future diffs:

  - Different colors for different event types (tasks? MEETINGS?!)
  - When events span across multiple days, keep them in the same row.
  - Switch which month you're looking at.
  - Show specific users instead of all.
  - etc etc etc

Test Plan: See screenshot.

Reviewers: btrahan, vrana

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D2514
This commit is contained in:
epriestley 2012-05-21 10:24:23 -07:00
parent c2d2f6ded1
commit 0548dc4c4c
10 changed files with 314 additions and 50 deletions

View file

@ -389,7 +389,7 @@ celerity_register_resource_map(array(
),
'aphront-calendar-view-css' =>
array(
'uri' => '/res/57b017ec/rsrc/css/aphront/calendar-view.css',
'uri' => '/res/4fd79240/rsrc/css/aphront/calendar-view.css',
'type' => 'css',
'requires' =>
array(
@ -534,7 +534,7 @@ celerity_register_resource_map(array(
),
'aphront-tooltip-css' =>
array(
'uri' => '/res/6bbfd9dd/rsrc/css/aphront/tooltip.css',
'uri' => '/res/b46170b3/rsrc/css/aphront/tooltip.css',
'type' => 'css',
'requires' =>
array(
@ -2223,7 +2223,7 @@ celerity_register_resource_map(array(
),
'phabricator-standard-page-view' =>
array(
'uri' => '/res/1e786be6/rsrc/css/application/base/standard-page-view.css',
'uri' => '/res/82608dbf/rsrc/css/application/base/standard-page-view.css',
'type' => 'css',
'requires' =>
array(
@ -2478,7 +2478,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'c0b5efc6' =>
'2eec2a11' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -2507,7 +2507,7 @@ celerity_register_resource_map(array(
21 => 'phabricator-flag-css',
22 => 'aphront-error-view-css',
),
'uri' => '/res/pkg/c0b5efc6/core.pkg.css',
'uri' => '/res/pkg/2eec2a11/core.pkg.css',
'type' => 'css',
),
'0c96375e' =>
@ -2674,20 +2674,20 @@ celerity_register_resource_map(array(
'reverse' =>
array(
'aphront-attached-file-view-css' => '7839ae2d',
'aphront-crumbs-view-css' => 'c0b5efc6',
'aphront-dialog-view-css' => 'c0b5efc6',
'aphront-error-view-css' => 'c0b5efc6',
'aphront-form-view-css' => 'c0b5efc6',
'aphront-crumbs-view-css' => '2eec2a11',
'aphront-dialog-view-css' => '2eec2a11',
'aphront-error-view-css' => '2eec2a11',
'aphront-form-view-css' => '2eec2a11',
'aphront-headsup-action-list-view-css' => 'd9299c35',
'aphront-headsup-view-css' => 'c0b5efc6',
'aphront-list-filter-view-css' => 'c0b5efc6',
'aphront-pager-view-css' => 'c0b5efc6',
'aphront-panel-view-css' => 'c0b5efc6',
'aphront-side-nav-view-css' => 'c0b5efc6',
'aphront-table-view-css' => 'c0b5efc6',
'aphront-tokenizer-control-css' => 'c0b5efc6',
'aphront-tooltip-css' => 'c0b5efc6',
'aphront-typeahead-control-css' => 'c0b5efc6',
'aphront-headsup-view-css' => '2eec2a11',
'aphront-list-filter-view-css' => '2eec2a11',
'aphront-pager-view-css' => '2eec2a11',
'aphront-panel-view-css' => '2eec2a11',
'aphront-side-nav-view-css' => '2eec2a11',
'aphront-table-view-css' => '2eec2a11',
'aphront-tokenizer-control-css' => '2eec2a11',
'aphront-tooltip-css' => '2eec2a11',
'aphront-typeahead-control-css' => '2eec2a11',
'differential-changeset-view-css' => 'd9299c35',
'differential-core-view-css' => 'd9299c35',
'differential-inline-comment-editor' => '5ef7da0b',
@ -2753,15 +2753,15 @@ celerity_register_resource_map(array(
'javelin-workflow' => '0c96375e',
'maniphest-task-summary-css' => '7839ae2d',
'maniphest-transaction-detail-css' => '7839ae2d',
'phabricator-app-buttons-css' => 'c0b5efc6',
'phabricator-app-buttons-css' => '2eec2a11',
'phabricator-content-source-view-css' => 'd9299c35',
'phabricator-core-buttons-css' => 'c0b5efc6',
'phabricator-core-css' => 'c0b5efc6',
'phabricator-directory-css' => 'c0b5efc6',
'phabricator-core-buttons-css' => '2eec2a11',
'phabricator-core-css' => '2eec2a11',
'phabricator-directory-css' => '2eec2a11',
'phabricator-drag-and-drop-file-upload' => '5ef7da0b',
'phabricator-dropdown-menu' => '0c96375e',
'phabricator-flag-css' => 'c0b5efc6',
'phabricator-jump-nav' => 'c0b5efc6',
'phabricator-flag-css' => '2eec2a11',
'phabricator-jump-nav' => '2eec2a11',
'phabricator-keyboard-shortcut' => '0c96375e',
'phabricator-keyboard-shortcut-manager' => '0c96375e',
'phabricator-menu-item' => '0c96375e',
@ -2769,11 +2769,11 @@ celerity_register_resource_map(array(
'phabricator-paste-file-upload' => '0c96375e',
'phabricator-prefab' => '0c96375e',
'phabricator-project-tag-css' => '7839ae2d',
'phabricator-remarkup-css' => 'c0b5efc6',
'phabricator-remarkup-css' => '2eec2a11',
'phabricator-shaped-request' => '5ef7da0b',
'phabricator-standard-page-view' => 'c0b5efc6',
'phabricator-standard-page-view' => '2eec2a11',
'phabricator-tooltip' => '0c96375e',
'phabricator-transaction-view-css' => 'c0b5efc6',
'syntax-highlighting-css' => 'c0b5efc6',
'phabricator-transaction-view-css' => '2eec2a11',
'syntax-highlighting-css' => '2eec2a11',
),
));

View file

@ -16,6 +16,7 @@ phutil_register_library_map(array(
'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration',
'AphrontAttachedFileView' => 'view/control/attachedfile',
'AphrontCSRFException' => 'aphront/exception/csrf',
'AphrontCalendarEventView' => 'applications/calendar/view/event',
'AphrontCalendarMonthView' => 'applications/calendar/view/month',
'AphrontContextBarView' => 'view/layout/contextbar',
'AphrontController' => 'aphront/controller',
@ -1069,6 +1070,7 @@ phutil_register_library_map(array(
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontAttachedFileView' => 'AphrontView',
'AphrontCSRFException' => 'AphrontException',
'AphrontCalendarEventView' => 'AphrontView',
'AphrontCalendarMonthView' => 'AphrontView',
'AphrontContextBarView' => 'AphrontView',
'AphrontCrumbsView' => 'AphrontView',

View file

@ -22,25 +22,46 @@ final class PhabricatorCalendarBrowseController
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
// TODO: These should be user-based and navigable in the interface.
$year = idate('Y');
$month = idate('m');
$holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere(
'day BETWEEN %s AND %s',
"{$year}-01-01",
"{$year}-12-31");
"{$year}-{$month}-01",
"{$year}-{$month}-31");
$months = array();
for ($ii = 1; $ii <= 12; $ii++) {
$month_view = new AphrontCalendarMonthView($ii, $year);
$month_view->setUser($user);
$month_view->setHolidays($holidays);
$months[] = '<div style="padding: 2em;">';
$months[] = $month_view;
$months[] = '</div>';
$statuses = id(new PhabricatorUserStatus())
->loadAllWhere(
'dateTo >= %d AND dateFrom <= %d',
strtotime("{$year}-{$month}-01"),
strtotime("{$year}-{$month}-01 next month"));
$month_view = new AphrontCalendarMonthView($month, $year);
$month_view->setUser($user);
$month_view->setHolidays($holidays);
$phids = mpull($statuses, 'getUserPHID');
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$name_text = $handles[$status->getUserPHID()]->getName();
$status_text = $status->getTextStatus();
$event->setName("{$name_text} ({$status_text})");
$event->setDescription($status->getStatusDescription($user));
$month_view->addEvent($event);
}
return $this->buildStandardPageResponse(
$months,
array(
'<div style="padding: 2em;">',
$month_view,
'</div>',
),
array(
'title' => 'Calendar',
));

View file

@ -8,7 +8,10 @@
phutil_require_module('phabricator', 'applications/calendar/controller/base');
phutil_require_module('phabricator', 'applications/calendar/storage/holiday');
phutil_require_module('phabricator', 'applications/calendar/view/event');
phutil_require_module('phabricator', 'applications/calendar/view/month');
phutil_require_module('phabricator', 'applications/people/storage/userstatus');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phutil', 'utils');

View file

@ -0,0 +1,62 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class AphrontCalendarEventView extends AphrontView {
private $name;
private $epochStart;
private $epochEnd;
private $description;
public function setName($name) {
$this->name = $name;
return $this;
}
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 getName() {
return $this->name;
}
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function render() {
throw new Exception("Events are only rendered indirectly.");
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'view/base');
phutil_require_source('AphrontCalendarEventView.php');

View file

@ -22,12 +22,18 @@ final class AphrontCalendarMonthView extends AphrontView {
private $month;
private $year;
private $holidays = array();
private $events = array();
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function setHolidays(array $holidays) {
assert_instances_of($holidays, 'PhabricatorCalendarHoliday');
$this->holidays = mpull($holidays, null, 'getDay');
@ -44,6 +50,8 @@ final class AphrontCalendarMonthView extends AphrontView {
throw new Exception("Call setUser() before render()!");
}
$events = msort($this->events, 'getEpochStart');
$days = $this->getDatesInMonth();
require_celerity_resource('aphront-calendar-view-css');
@ -53,21 +61,59 @@ final class AphrontCalendarMonthView extends AphrontView {
$markup = array();
$empty_box =
'<div class="aphront-calendar-day aphront-calendar-empty">'.
'</div>';
for ($ii = 0; $ii < $empty; $ii++) {
$markup[] = null;
$markup[] = $empty_box;
}
foreach ($days as $day) {
$holiday = idx($this->holidays, $day->format('Y-m-d'));
$class = 'aphront-calendar-day-of-month';
$class = 'aphront-calendar-day';
$weekday = $day->format('w');
if ($holiday || $weekday == 0 || $weekday == 6) {
$class .= ' aphront-calendar-not-work-day';
}
$day->setTime(0, 0, 0);
$epoch_start = $day->format('U');
$day->setTime(23, 59, 59);
$epoch_end = $day->format('U');
$show_events = array();
foreach ($events as $event) {
if ($event->getEpochStart() > $epoch_end) {
// This list is sorted, so we can stop looking.
break;
}
if ($event->getEpochStart() <= $epoch_end &&
$event->getEpochEnd() >= $epoch_start) {
$show_events[] = $this->renderEvent(
$event,
$day,
$epoch_start,
$epoch_end);
}
}
$holiday_markup = null;
if ($holiday) {
$holiday_markup =
'<div class="aphront-calendar-holiday">'.
phutil_escape_html($holiday->getName()).
'</div>';
}
$markup[] =
'<div class="'.$class.'">'.
$day->format('j').
($holiday ? '<br />'.phutil_escape_html($holiday->getName()) : '').
$holiday_markup.
'<div class="aphront-calendar-date-number">'.
$day->format('j').
'</div>'.
implode("\n", $show_events).
'</div>';
}
@ -76,7 +122,7 @@ final class AphrontCalendarMonthView extends AphrontView {
foreach ($rows as $row) {
$table[] = '<tr>';
while (count($row) < 7) {
$row[] = null;
$row[] = $empty_box;
}
foreach ($row as $cell) {
$table[] = '<td>'.$cell.'</td>';
@ -142,4 +188,61 @@ final class AphrontCalendarMonthView extends AphrontView {
return $days;
}
private function renderEvent(
AphrontCalendarEventView $event,
DateTime $day,
$epoch_start,
$epoch_end) {
$user = $this->user;
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
$classes = array();
$when = array();
$classes[] = 'aphront-calendar-event';
if ($event_start < $epoch_start) {
$classes[] = 'aphront-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[] = 'aphront-calendar-event-continues-after';
$when[] = 'Ends '.phabricator_datetime($event_start, $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_render_tag(
'div',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $info."\n\n".implode("\n", $when),
'size' => 240,
),
'class' => 'aphront-calendar-event-text',
),
phutil_escape_html(phutil_utf8_shorten($event->getName(), 32)));
return javelin_render_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$text_div);
}
}

View file

@ -7,7 +7,10 @@
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');

View file

@ -28,18 +28,75 @@ tr.aphront-calendar-day-of-week-header th {
table.aphront-calendar-view td {
border: 1px solid #dfdfdf;
width: 14.2857%; /* This is one seventh, approximately. */
}
table.aphront-calendar-view td div.aphront-calendar-day {
min-height: 110px;
}
.aphront-calendar-holiday {
float: left;
color: #666666;
padding: .5em;
}
table.aphront-calendar-view td div {
min-height: 70px;
}
.aphront-calendar-day-of-month {
text-align: right;
.aphront-calendar-date-number {
font-weight: bold;
color: #666666;
padding: .5em;
border-color: #dfdfdf;
border-style: solid;
border-width: 0 0 1px 1px;
float: right;
background: #ffffff;
width: 1.25em;
height: 1.25em;
text-align: center;
margin-bottom: 3px;
}
.aphront-calendar-not-work-day {
background-color: #fdfae7;
}
.aphront-calendar-empty {
background-color: #ededed;
}
.aphront-calendar-event {
clear: both;
border-style: solid;
border-width: 1px;
border-color: #6666ff;
background: #ddddff;
font-size: 11px;
margin: 2px 0;
border-radius: 8px;
-moz-border-radius: 8px;
padding: 3px 5%;
color: #222266;
width: 90%;
overflow: hidden;
}
.aphront-calendar-event-text {
overflow: hidden;
white-space: nowrap;
}
.aphront-calendar-event-continues-before {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-left-width: 0px;
}
.aphront-calendar-event-continues-after {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-right-width: 0px;
}

View file

@ -13,6 +13,7 @@
font-size: 11px;
padding: 4px;
overflow: hidden;
white-space: pre-wrap;
}
.jx-tooltip-align-E {