diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -121,7 +121,7 @@ 'rsrc/css/layout/phabricator-hovercard-view.css' => 'dd9121a9', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1db9e9c', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'feba82c5', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', @@ -331,7 +331,7 @@ 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => 'dc0065ab', + 'rsrc/js/application/calendar/behavior-day-view.js' => '28a60488', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', @@ -554,7 +554,7 @@ 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => 'dc0065ab', + 'javelin-behavior-day-view' => '28a60488', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -767,7 +767,7 @@ 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'de610129', 'phui-calendar-css' => 'ccabe893', - 'phui-calendar-day-css' => 'feba82c5', + 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '594d719e', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -14,8 +14,8 @@ } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); - $user_phid = $user->getPHID(); + $viewer = $request->getViewer(); + $user_phid = $viewer->getPHID(); $error_name = true; $error_start_date = true; $error_end_date = true; @@ -25,9 +25,41 @@ $start_date_id = celerity_generate_unique_node_id(); $end_date_id = null; + $next_workflow = $request->getStr('next'); + $uri_query = $request->getStr('query'); + if ($this->isCreate()) { - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); - list($start_value, $end_value) = $this->getDefaultTimeValues($user); + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer); + + $create_start_year = $request->getInt('year'); + $create_start_month = $request->getInt('month'); + $create_start_day = $request->getInt('day'); + $create_start_time = $request->getStr('time'); + + if ($create_start_year) { + $start = AphrontFormDateControlValue::newFromParts( + $viewer, + $create_start_year, + $create_start_month, + $create_start_day, + $create_start_time); + if (!$start->isValid()) { + return new Aphront400Response(); + } + $start_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $start->getEpoch()); + + $end = clone $start_value->getDateTime(); + $end->modify('+1 hour'); + $end_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $end->format('U')); + + } else { + list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); + } + $submit_label = pht('Create'); $page_title = pht('Create Event'); @@ -38,7 +70,7 @@ $end_date_id = celerity_generate_unique_node_id(); } else { $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( @@ -51,10 +83,10 @@ } $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateFrom()); $submit_label = pht('Update'); @@ -81,7 +113,7 @@ $icon = $event->getIcon(); $current_policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($event) ->execute(); @@ -106,9 +138,9 @@ $new_invitees = $this->getNewInviteeList($invitees, $event); $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($this->isCreate()) { - $status = idx($new_invitees, $user->getPHID()); + $status = idx($new_invitees, $viewer->getPHID()); if ($status) { - $new_invitees[$user->getPHID()] = $status_attending; + $new_invitees[$viewer->getPHID()] = $status_attending; } } @@ -161,14 +193,29 @@ ->setNewValue($request->getStr('editPolicy')); $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $xactions = $editor->applyTransactions($event, $xactions); $response = id(new AphrontRedirectResponse()); - return $response->setURI('/E'.$event->getID()); + switch ($next_workflow) { + case 'day': + if (!$uri_query) { + $uri_query = 'month'; + } + $year = $start_value->getDateTime()->format('Y'); + $month = $start_value->getDateTime()->format('m'); + $day = $start_value->getDateTime()->format('d'); + $response->setURI( + '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); + break; + default: + $response->setURI('/E'.$event->getID()); + break; + } + return $response; } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $error_name = $ex->getShortMessage( @@ -204,7 +251,7 @@ $all_day_id); $start_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('start') ->setLabel(pht('Start')) ->setError($error_start_date) @@ -214,7 +261,7 @@ ->setEndDateID($end_date_id); $end_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('end') ->setLabel(pht('End')) ->setError($error_end_date) @@ -228,13 +275,13 @@ ->setValue($description); $view_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($event) ->setPolicies($current_policies) ->setName('viewPolicy'); $edit_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($event) ->setPolicies($current_policies) @@ -244,14 +291,14 @@ ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setValue($subscribers) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $invitees = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Invitees')) ->setName('invitees') ->setValue($invitees) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); if ($this->isCreate()) { @@ -269,7 +316,9 @@ ->setValue($icon); $form = id(new AphrontFormView()) - ->setUser($user) + ->addHiddenInput('next', $next_workflow) + ->addHiddenInput('query', $uri_query) + ->setUser($viewer) ->appendChild($name) ->appendChild($all_day_checkbox) ->appendChild($start_control) @@ -351,19 +400,19 @@ return $new; } - private function getDefaultTimeValues($user) { + private function getDefaultTimeValues($viewer) { $start = new DateTime('@'.time()); - $start->setTimeZone($user->getTimeZone()); + $start->setTimeZone($viewer->getTimeZone()); $start->setTime($start->format('H'), 0, 0); $start->modify('+1 hour'); $end = id(clone $start)->modify('+1 hour'); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $start->format('U')); $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $end->format('U')); return array($start_value, $end_value); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -390,12 +390,13 @@ list($start_year, $start_month, $start_day) = $this->getDisplayYearAndMonthAndDay($query); - $day_view = new PHUICalendarDayView( + $day_view = id(new PHUICalendarDayView( $this->getDateFrom($query), $this->getDateTo($query), $start_year, $start_month, - $start_day); + $start_day)) + ->setQuery($query->getQueryKey()); $day_view->setUser($viewer); @@ -408,7 +409,13 @@ $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $status, + PhabricatorPolicyCapability::CAN_EDIT); + $event = new AphrontCalendarEventView(); + $event->setCanEdit($can_edit); $event->setEventID($status->getID()); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $event->setIsAllDay($status->getIsAllDay()); diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -12,6 +12,7 @@ private $uri; private $isAllDay; private $icon; + private $canEdit; public function setURI($uri) { $this->uri = $uri; @@ -97,6 +98,14 @@ return $this->icon; } + public function setCanEdit($can_edit) { + $this->canEdit = $can_edit; + return $this; + } + + public function getCanEdit() { + return $this->canEdit; + } public function getMultiDay() { $nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart()); diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -211,6 +211,19 @@ return $value; } + public function getDateTime() { + $epoch = $this->getEpoch(); + $date = null; + + if ($epoch) { + $zone = $this->getTimezone(); + $date = new DateTime('@'.$epoch); + $date->setTimeZone($zone); + } + + return $date; + } + private function getTimezone() { if ($this->zone) { return $this->zone; diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -8,6 +8,7 @@ private $month; private $year; private $browseURI; + private $query; private $events = array(); private $allDayEvents = array(); @@ -25,6 +26,14 @@ return $this->browseURI; } + public function setQuery($query) { + $this->query = $query; + return $this; + } + private function getQuery() { + return $this->query; + } + public function __construct( $range_start, $range_end, @@ -128,6 +137,7 @@ 'width' => '100%', 'top' => $top.'px', 'height' => $height.'px', + 'canEdit' => $event->getCanEdit(), ); } } @@ -148,6 +158,10 @@ Javelin::initBehavior( 'day-view', array( + 'year' => $first_event_hour->format('Y'), + 'month' => $first_event_hour->format('m'), + 'day' => $first_event_hour->format('d'), + 'query' => $this->getQuery(), 'allDayEvents' => $js_today_all_day_events, 'todayEvents' => $js_today_events, 'hours' => $js_hours, diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -29,6 +29,11 @@ .phui-calendar-day-view td { position: relative; + cursor: pointer; +} + +.phui-calendar-day-view td:hover { + background: {$lightbluebackground}; } .phui-calendar-day-view tr + tr td.phui-calendar-day-events { @@ -47,6 +52,10 @@ min-height: 30px; } +.can-drag a { + cursor: move; +} + div.phui-calendar-day-event.all-day { position: relative; } diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -100,10 +100,15 @@ }, name); + var class_name = 'phui-calendar-day-event'; + if (e.canEdit) { + class_name = class_name + ' can-drag'; + } + var div = JX.$N( 'div', { - className: 'phui-calendar-day-event', + className: class_name, sigil: sigil, meta: {eventID: eventID, record: e, uri: uri}, style: { @@ -169,7 +174,11 @@ var cell_event = JX.$N( 'td', { - className: 'phui-calendar-day-events' + meta: { + time: hours[i]['hour_meridian'] + }, + className: 'phui-calendar-day-events', + sigil: 'phui-calendar-day-event-cell' }); var row = JX.$N( @@ -195,6 +204,11 @@ } + var year = config.year; + var month = config.month; + var day = config.day; + var query = config.query; + var hours = config.hours; var first_event_hour = config.firstEventHour; var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10); @@ -234,6 +248,10 @@ if (!e.isNormalMouseEvent()) { return; } + var data = e.getNodeData('phui-calendar-day-event'); + if (!data.record.canEdit) { + return; + } e.kill(); dragging = e.getNode('phui-calendar-day-event'); JX.DOM.alterClass(dragging, 'phui-drag', true); @@ -264,12 +282,13 @@ dragging.style.top = new_top + 'px'; }); JX.Stratcom.listen('mouseup', null, function(){ - var data = JX.Stratcom.getData(dragging); - var record = data.record; - if (!dragging) { return; } + + var data = JX.Stratcom.getData(dragging); + var record = data.record; + if (new_top == offset_top) { var now = new Date(); if (now.getTime() - click_time.getTime() < 250) { @@ -304,6 +323,25 @@ } }); + JX.DOM.listen(table, 'click', 'phui-calendar-day-event-cell', function(e){ + if (!e.isNormalClick()) { + return; + } + var data = e.getNodeData('phui-calendar-day-event-cell'); + var time = data.time; + new JX.Workflow( + '/calendar/event/create/', + { + year: year, + month: month, + day: day, + time: time, + next: 'day', + query: query + }) + .start(); + }); + var hourly_events_wrapper = JX.$N( 'div', {style: {