1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Rescheduling events by dragging them in day view

Summary: Ref T8300, Rescheduling events by dragging them in day view

Test Plan: Open day view, drag events, observe them reschedule.

Reviewers: chad, epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin, epriestley

Maniphest Tasks: T8300

Differential Revision: https://secure.phabricator.com/D12988
This commit is contained in:
lkassianik 2015-05-23 19:47:23 -07:00
parent 7d757483a0
commit 963485a3da
8 changed files with 274 additions and 90 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => '36142bff',
'core.pkg.css' => '439658b5',
'core.pkg.js' => '328799d0',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'bb338e4b',
@ -112,7 +112,7 @@ return array(
'rsrc/css/core/core.css' => 'aaea7a7a',
'rsrc/css/core/remarkup.css' => '07b7dc54',
'rsrc/css/core/syntax.css' => '6b7b24d9',
'rsrc/css/core/z-index.css' => '8414a09b',
'rsrc/css/core/z-index.css' => 'c4732d32',
'rsrc/css/diviner/diviner-shared.css' => '38813222',
'rsrc/css/font/font-awesome.css' => 'e2e712fe',
'rsrc/css/font/font-source-sans-pro.css' => '8906c07b',
@ -121,7 +121,7 @@ return array(
'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' => 'c0cf782a',
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'feba82c5',
'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 @@ return array(
'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' => 'f4f4ad80',
'rsrc/js/application/calendar/behavior-day-view.js' => 'dc0065ab',
'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 @@ return array(
'javelin-behavior-dashboard-move-panels' => '82439934',
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
'javelin-behavior-day-view' => 'f4f4ad80',
'javelin-behavior-day-view' => 'dc0065ab',
'javelin-behavior-device' => 'a205cf28',
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
'javelin-behavior-differential-comment-jump' => '4fdb476d',
@ -752,7 +752,7 @@ return array(
'phabricator-uiexample-reactor-select' => 'a155550f',
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '8414a09b',
'phabricator-zindex-css' => 'c4732d32',
'phame-css' => '88bd4705',
'pholio-css' => '95174bdd',
'pholio-edit-css' => '3ad9d1ee',
@ -767,7 +767,7 @@ return array(
'phui-box-css' => '7b3a2eed',
'phui-button-css' => 'de610129',
'phui-calendar-css' => 'ccabe893',
'phui-calendar-day-css' => 'c0cf782a',
'phui-calendar-day-css' => 'feba82c5',
'phui-calendar-list-css' => 'c1c7f338',
'phui-calendar-month-css' => '476be7e0',
'phui-crumbs-view-css' => '594d719e',

View file

@ -1498,6 +1498,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php',
'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
'PhabricatorCalendarEventEditIconController' => 'applications/calendar/controller/PhabricatorCalendarEventEditIconController.php',
'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php',
@ -4853,6 +4854,7 @@ phutil_register_library_map(array(
),
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditIconController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor',

View file

@ -54,6 +54,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
=> 'PhabricatorCalendarEventEditController',
'edit/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventEditController',
'drag/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventDragController',
'cancel/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventCancelController',
'(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/'

View file

@ -0,0 +1,66 @@
<?php
final class PhabricatorCalendarEventDragController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$event) {
return new Aphront404Response();
}
if (!$request->validateCSRF()) {
return new Aphront400Response();
}
if ($event->getIsAllDay()) {
return new Aphront400Response();
}
$xactions = array();
$duration = $event->getDateTo() - $event->getDateFrom();
$start = $request->getInt('start');
$start_value = id(AphrontFormDateControlValue::newFromEpoch(
$viewer,
$start));
$end = $start + $duration;
$end_value = id(AphrontFormDateControlValue::newFromEpoch(
$viewer,
$end));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_START_DATE)
->setNewValue($start_value);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_END_DATE)
->setNewValue($end_value);
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
$xactions = $editor->applyTransactions($event, $xactions);
return id(new AphrontReloadResponse());
}
}

View file

@ -9,7 +9,6 @@ final class PHUICalendarDayView extends AphrontView {
private $year;
private $browseURI;
private $events = array();
private $jsTodayEvents = array();
private $allDayEvents = array();
@ -44,8 +43,11 @@ final class PHUICalendarDayView extends AphrontView {
public function render() {
require_celerity_resource('phui-calendar-day-css');
$viewer = $this->getUser();
$hours = $this->getHoursOfDay();
$js_hours = array();
$js_today_events = array();
foreach ($hours as $hour) {
$js_hours[] = array(
@ -55,10 +57,7 @@ final class PHUICalendarDayView extends AphrontView {
}
$first_event_hour = null;
$js_hourly_events = array();
$js_today_all_day_events = array();
$all_day_events = $this->getAllDayEvents();
$day_start = $this->getDateTime();
@ -81,60 +80,54 @@ final class PHUICalendarDayView extends AphrontView {
}
}
foreach ($hours as $hour) {
$current_hour_events = array();
$hour_start = $hour->format('U');
$hour_end = id(clone $hour)->modify('+1 hour')->format('U');
$this->events = msort($this->events, 'getEpochStart');
foreach ($this->events as $event) {
if ($event->getIsAllDay()) {
continue;
}
if (($hour == $day_start &&
$event->getEpochStart() <= $hour_start &&
$event->getEpochEnd() > $day_start_epoch) ||
($event->getEpochStart() >= $hour_start
&& $event->getEpochStart() < $hour_end)) {
$current_hour_events[] = $event;
$this->jsTodayEvents[] = array(
'eventStartEpoch' => $event->getEpochStart(),
'eventEndEpoch' => $event->getEpochEnd(),
'eventName' => $event->getName(),
'eventID' => $event->getEventID(),
'viewerIsInvited' => $event->getViewerIsInvited(),
'uri' => $event->getURI(),
);
}
if (!$this->events) {
$first_event_hour = $this->getDateTime()->setTime(8, 0, 0);
}
foreach ($this->events as $event) {
if ($event->getIsAllDay()) {
continue;
}
foreach ($current_hour_events as $event) {
$day_start_epoch = $this->getDateTime()->format('U');
if ($event->getEpochStart() <= $day_end_epoch &&
$event->getEpochEnd() > $day_start_epoch) {
if ($first_event_hour === null) {
$first_event_hour = new DateTime('@'.$event->getEpochStart());
$first_event_hour->setTimeZone($viewer->getTimeZone());
$eight_am = $this->getDateTime()->setTime(8, 0, 0);
if ($eight_am->format('U') < $first_event_hour->format('U')) {
$first_event_hour = clone $eight_am;
}
}
$event_start = max($event->getEpochStart(), $day_start_epoch);
$event_end = min($event->getEpochEnd(), $day_end_epoch);
$top = (($event_start - $hour_start) / ($hour_end - $hour_start))
* 100;
$day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60;
$top = (($event_start - $first_event_hour->format('U'))
/ ($day_end_epoch - $first_event_hour->format('U')))
* $day_duration;
$top = max(0, $top);
$height = (($event_end - $event_start) / ($hour_end - $hour_start))
* 100;
$height = min(2400, $height);
$height = (($event_end - $event_start)
/ ($day_end_epoch - $first_event_hour->format('U')))
* $day_duration;
$height = min($day_duration, $height);
if ($first_event_hour === null) {
$first_event_hour = $hour;
}
$js_hourly_events[] = array(
$js_today_events[] = array(
'eventStartEpoch' => $event->getEpochStart(),
'eventEndEpoch' => $event->getEpochEnd(),
'eventName' => $event->getName(),
'eventID' => $event->getEventID(),
'viewerIsInvited' => $event->getViewerIsInvited(),
'uri' => $event->getURI(),
'hour' => $hour->format('G'),
'offset' => '0',
'width' => '100%',
'top' => $top.'%',
'height' => $height.'%',
'top' => $top.'px',
'height' => $height.'px',
);
}
}
@ -156,10 +149,10 @@ final class PHUICalendarDayView extends AphrontView {
'day-view',
array(
'allDayEvents' => $js_today_all_day_events,
'todayEvents' => $this->jsTodayEvents,
'hourlyEvents' => $js_hourly_events,
'todayEvents' => $js_today_events,
'hours' => $js_hours,
'firstEventHour' => $first_event_hour->format('G'),
'firstEventHourEpoch' => $first_event_hour->format('U'),
'tableID' => $table_id,
));

View file

@ -36,6 +36,10 @@
z-index: 2;
}
div.phui-calendar-day-event {
z-index: 2;
}
.slowvote-above-the-bar {
z-index: 3;
}

View file

@ -35,7 +35,11 @@
border-top: 1px solid {$lightgreyborder};
}
.phui-calendar-day-view td div.phui-calendar-day-event {
.phui-drag {
opacity: .25;
}
div.phui-calendar-day-event {
width: 100%;
position: absolute;
top: 0;
@ -43,11 +47,15 @@
min-height: 30px;
}
div.phui-calendar-day-event.all-day {
position: relative;
}
.phui-calendar-day-event-link {
padding: 8px;
border: 1px solid {$greyborder};
background-color: {$darkgreybackground};
margin: 0 4px;
margin: 0 1px;
position: absolute;
left: 0;
right: 0;

View file

@ -4,13 +4,6 @@
JX.behavior('day-view', function(config) {
var hours = config.hours;
var first_event_hour = config.firstEventHour;
var hourly_events = config.hourlyEvents;
var today_events = config.todayEvents;
var today_all_day_events = config.allDayEvents;
var table_wrapper = JX.$(config.tableID);
function findTodayClusters() {
var events = today_events.sort(function(x, y){
@ -23,8 +16,8 @@ JX.behavior('day-view', function(config) {
var today_event = events[i];
var destination_cluster_index = null;
var event_start = today_event.eventStartEpoch - (30*60);
var event_end = today_event.eventEndEpoch + (30*60);
var event_start = today_event.eventStartEpoch - (60);
var event_end = today_event.eventEndEpoch + (60);
for (var j=0; j < clusters.length; j++) {
var cluster = clusters[j];
@ -59,7 +52,7 @@ JX.behavior('day-view', function(config) {
return clusters;
}
function updateEventsFromCluster(cluster, hourly_events) {
function updateEventsFromCluster(cluster) {
var cluster_size = cluster.length;
var n = 0;
for(var i=0; i < cluster.length; i++) {
@ -69,28 +62,30 @@ JX.behavior('day-view', function(config) {
var offset = ((n / cluster_size) * 100) + '%';
var width = ((1 / cluster_size) * 100) + '%';
for (var j=0; j < hourly_events.length; j++) {
if (hourly_events[j].eventID == event_id) {
for (var j=0; j < today_events.length; j++) {
if (today_events[j].eventID == event_id) {
hourly_events[j]['offset'] = offset;
hourly_events[j]['width'] = width;
today_events[j]['offset'] = offset;
today_events[j]['width'] = width;
}
}
n++;
}
return hourly_events;
return today_events;
}
function drawEvent(hourly_event) {
var name = hourly_event['eventName'];
var viewerIsInvited = hourly_event['viewerIsInvited'];
var offset = hourly_event['offset'];
var width = hourly_event['width'];
var top = hourly_event['top'];
var height = hourly_event['height'];
var uri = hourly_events['uri'];
function drawEvent(e) {
var name = e['eventName'];
var eventID = e['eventID'];
var viewerIsInvited = e['viewerIsInvited'];
var offset = e['offset'];
var width = e['width'];
var top = e['top'];
var height = e['height'];
var uri = e['uri'];
var sigil = 'phui-calendar-day-event';
var link_class = 'phui-calendar-day-event-link';
if (viewerIsInvited) {
@ -109,6 +104,8 @@ JX.behavior('day-view', function(config) {
'div',
{
className: 'phui-calendar-day-event',
sigil: sigil,
meta: {eventID: eventID, record: e, uri: uri},
style: {
left: offset,
width: width,
@ -145,7 +142,7 @@ JX.behavior('day-view', function(config) {
var div_all_day = JX.$N(
'div',
{className: 'phui-calendar-day-event'},
{className: 'phui-calendar-day-event all-day'},
[all_day_label, name]);
return div_all_day;
@ -164,24 +161,17 @@ JX.behavior('day-view', function(config) {
if (hours[i]['hour'] < min_early_hour) {
continue;
}
var drawn_hourly_events = [];
var cell_time = JX.$N(
'td',
{className: 'phui-calendar-day-hour'},
hours[i]['hour_meridian']);
for (var j=0; j < hourly_events.length; j++) {
if (hourly_events[j]['hour'] == hours[i]['hour']) {
drawn_hourly_events.push(drawEvent(hourly_events[j]));
}
}
var cell_event = JX.$N(
'td',
{
className: 'phui-calendar-day-events'
},
drawn_hourly_events);
});
var row = JX.$N(
'tr',
{},
@ -191,10 +181,26 @@ JX.behavior('day-view', function(config) {
return rows;
}
var today_clusters = findTodayClusters();
for(var i=0; i < today_clusters.length; i++) {
hourly_events = updateEventsFromCluster(today_clusters[i], hourly_events);
function clusterAndDrawEvents() {
var today_clusters = findTodayClusters();
for(var i=0; i < today_clusters.length; i++) {
today_events = updateEventsFromCluster(today_clusters[i]);
}
var drawn_hourly_events = [];
for (i=0; i < today_events.length; i++) {
drawn_hourly_events.push(drawEvent(today_events[i]));
}
JX.DOM.setContent(hourly_events_wrapper, drawn_hourly_events);
}
var hours = config.hours;
var first_event_hour = config.firstEventHour;
var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10);
var today_events = config.todayEvents;
var today_all_day_events = config.allDayEvents;
var table_wrapper = JX.$(config.tableID);
var rows = drawRows();
var all_day_events = [];
@ -211,5 +217,108 @@ JX.behavior('day-view', function(config) {
{className: 'phui-calendar-day-view'},
rows);
JX.DOM.setContent(table_wrapper, [all_day_events, table]);
var dragging = false;
var origin = null;
var offset_top = null;
var new_top = null;
var click_time = null;
JX.DOM.listen(
table_wrapper,
'mousedown',
'phui-calendar-day-event',
function(e){
if (!e.isNormalMouseEvent()) {
return;
}
e.kill();
dragging = e.getNode('phui-calendar-day-event');
JX.DOM.alterClass(dragging, 'phui-drag', true);
click_time = new Date();
origin = JX.$V(e);
var outer = JX.Vector.getPos(table);
var inner = JX.Vector.getPos(dragging);
offset_top = inner.y - outer.y;
new_top = offset_top;
dragging.style.top = offset_top + 'px';
});
JX.Stratcom.listen('mousemove', null, function(e){
if (!dragging) {
return;
}
var cursor = JX.$V(e);
new_top = cursor.y - origin.y + offset_top;
new_top = Math.min(new_top, 1320);
new_top = Math.max(new_top, 0);
new_top = Math.floor(new_top/15) * 15;
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;
}
if (new_top == offset_top) {
var now = new Date();
if (now.getTime() - click_time.getTime() < 250) {
JX.$U(record.uri).go();
}
JX.DOM.alterClass(dragging, 'phui-drag', false);
dragging = false;
return;
}
var new_time = first_event_hour_epoch + (new_top * 60);
var id = data.eventID;
var duration = record.eventEndEpoch - record.eventStartEpoch;
record.eventStartEpoch = new_time;
record.eventEndEpoch = new_time + duration;
record.top = new_top + 'px';
new JX.Workflow(
'/calendar/event/drag/' + id + '/',
{start: new_time})
.start();
JX.DOM.alterClass(dragging, 'phui-drag', false);
dragging = false;
clusterAndDrawEvents();
});
JX.DOM.listen(table_wrapper, 'click', 'phui-calendar-day-event', function(e){
if (e.isNormalClick()) {
e.kill();
}
});
var hourly_events_wrapper = JX.$N(
'div',
{style: {
position: 'absolute',
left: '69px',
right: 0
}});
clusterAndDrawEvents();
var daily_wrapper = JX.$N(
'div',
{style: {position: 'relative'}},
[hourly_events_wrapper, table]);
JX.DOM.setContent(table_wrapper, [all_day_events, daily_wrapper]);
});