517 lines
19 KiB
JavaScript
517 lines
19 KiB
JavaScript
|
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
|
|
||
|
(function() {
|
||
|
'use strict';
|
||
|
|
||
|
/*
|
||
|
* sgDraggableCalendarBlock - Make an element draggable
|
||
|
* @memberof SOGo.SchedulerUI
|
||
|
* @restrict class or attribute
|
||
|
*
|
||
|
* @example:
|
||
|
|
||
|
<div class="sg-draggable-calendar-block"/>
|
||
|
*/
|
||
|
sgDraggableCalendarBlock.$inject = ['$rootScope', '$timeout', '$log', 'Calendar', 'CalendarSettings', 'Component'];
|
||
|
function sgDraggableCalendarBlock($rootScope, $timeout, $log, Calendar, CalendarSettings, Component) {
|
||
|
return {
|
||
|
restrict: 'CA',
|
||
|
require: '^sgCalendarDay',
|
||
|
link: link
|
||
|
};
|
||
|
|
||
|
function link(scope, element, attrs, calendarDayCtrl) {
|
||
|
if (scope.block)
|
||
|
// Add dragging grips to existing event block
|
||
|
initGrips();
|
||
|
|
||
|
// Start dragging on mousedown
|
||
|
element.on('mousedown', onDragStart);
|
||
|
|
||
|
// Deregister mousedown when removing the element from the DOM
|
||
|
scope.$on('$destroy', function() {
|
||
|
element.off('mousedown', onDragStart);
|
||
|
});
|
||
|
|
||
|
function initGrips() {
|
||
|
var component, dayNumber, blockIndex, isFirstBlock, isLastBlock,
|
||
|
dragGrip, leftGrip, rightGrip, topGrip, bottomGrip;
|
||
|
|
||
|
component = scope.block.component;
|
||
|
dayNumber = scope.block.dayNumber;
|
||
|
blockIndex = _.findIndex(component.blocks, _.matchesProperty('dayNumber', dayNumber));
|
||
|
isFirstBlock = (blockIndex === 0);
|
||
|
isLastBlock = (blockIndex === component.blocks.length - 1);
|
||
|
|
||
|
dragGrip = angular.element('<div class="dragGrip"></div>');
|
||
|
dragGrip.addClass('bdr-folder' + component.pid);
|
||
|
|
||
|
if (component.c_isallday) {
|
||
|
if (isFirstBlock) {
|
||
|
leftGrip = angular.element('<div class="dragGrip-left"></div>').append(dragGrip);
|
||
|
element.append(leftGrip);
|
||
|
}
|
||
|
if (isLastBlock) {
|
||
|
rightGrip = angular.element('<div class="dragGrip-right"></div>').append(dragGrip.clone());
|
||
|
element.append(rightGrip);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (isFirstBlock) {
|
||
|
topGrip = angular.element('<div class="dragGrip-top"></div>').append(dragGrip);
|
||
|
element.append(topGrip);
|
||
|
}
|
||
|
if (isLastBlock) {
|
||
|
bottomGrip = angular.element('<div class="dragGrip-bottom"></div>').append(dragGrip.clone());
|
||
|
element.append(bottomGrip);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onDragStart(ev) {
|
||
|
var block, dragMode, eventType, startDate, newData, newComponent, pointerHandler;
|
||
|
|
||
|
dragMode = 'move-event';
|
||
|
eventType = 'multiday';
|
||
|
|
||
|
// Stop dragging on the next "mouseup"
|
||
|
angular.element(document).one('mouseup', onDragEnd);
|
||
|
|
||
|
if (scope.block && scope.block.component) {
|
||
|
// Move or resize existing component
|
||
|
block = scope.block;
|
||
|
if (ev.target.className == 'dragGrip-top' ||
|
||
|
ev.target.className == 'dragGrip-left')
|
||
|
dragMode = 'change-start';
|
||
|
else if (ev.target.className == 'dragGrip-bottom' ||
|
||
|
ev.target.className == 'dragGrip-right' )
|
||
|
dragMode = 'change-end';
|
||
|
}
|
||
|
else {
|
||
|
// Create new component from dragging
|
||
|
dragMode = 'change-end';
|
||
|
startDate = new Date(calendarDayCtrl.dayString.substring(0,10) +
|
||
|
' ' +
|
||
|
calendarDayCtrl.dayString.substring(11,16));
|
||
|
startDate.setHours(parseInt(element.attr('sg-hour')));
|
||
|
newData = {
|
||
|
type: 'appointment',
|
||
|
pid: 'personal', // TODO respect SOGoDefaultCalendar
|
||
|
summary: l('New Event'),
|
||
|
startDate: startDate
|
||
|
};
|
||
|
newComponent = new Component(newData);
|
||
|
block = {
|
||
|
component: newComponent,
|
||
|
start: parseInt(element.attr('sg-hour')) * 4,
|
||
|
dayNumber: calendarDayCtrl.dayNumber,
|
||
|
length: 0
|
||
|
};
|
||
|
block.component.blocks = [block];
|
||
|
}
|
||
|
|
||
|
// Mark all blocks as being dragged
|
||
|
_.forEach(block.component.blocks, function(b) {
|
||
|
b.dragging = true;
|
||
|
});
|
||
|
|
||
|
if (block.component.c_isallday)
|
||
|
eventType = 'multiday-allday';
|
||
|
|
||
|
// Initialize pointer handler
|
||
|
pointerHandler = new SOGoEventDragPointerHandler(dragMode);
|
||
|
pointerHandler.prepareWithEventType(eventType);
|
||
|
pointerHandler.initFromEvent(ev);
|
||
|
pointerHandler.initFromBlock(block);
|
||
|
|
||
|
// Update Component.$ghost
|
||
|
Component.$ghost.component = block.component;
|
||
|
Component.$ghost.pointerHandler = pointerHandler;
|
||
|
|
||
|
angular.element(document).on('mousemove', onDrag);
|
||
|
}
|
||
|
|
||
|
function onDrag(ev) {
|
||
|
var pointerHandler = Component.$ghost.pointerHandler;
|
||
|
|
||
|
// Update
|
||
|
// - currentCoordinates
|
||
|
// - currentViewCoordinates
|
||
|
// - currentEventCoordinates
|
||
|
$timeout(function() {
|
||
|
pointerHandler.updateFromEvent(ev);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function onDragEnd(ev) {
|
||
|
var block, pointer;
|
||
|
|
||
|
block = scope.block;
|
||
|
pointer = Component.$ghost.pointerHandler;
|
||
|
|
||
|
// Deregister mouse events
|
||
|
angular.element(document).off('mousemove', onDrag);
|
||
|
angular.element(document).off('mouseup', onDragEnd);
|
||
|
|
||
|
if (pointer.dragHasStarted) {
|
||
|
$rootScope.$emit('calendar:dragend');
|
||
|
pointer.dragHasStarted = false;
|
||
|
}
|
||
|
|
||
|
// Unmark all blocks as being dragged
|
||
|
if (block)
|
||
|
_.forEach(block.component.blocks, function(b) {
|
||
|
b.dragging = false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* SOGoCoordinates
|
||
|
*/
|
||
|
function SOGoCoordinates() {
|
||
|
}
|
||
|
|
||
|
SOGoCoordinates.prototype = {
|
||
|
x: -1,
|
||
|
y: -1,
|
||
|
|
||
|
getDelta: function SC_getDelta(otherCoordinates) {
|
||
|
var delta = new SOGoCoordinates();
|
||
|
delta.x = this.x - otherCoordinates.x;
|
||
|
delta.y = this.y - otherCoordinates.y;
|
||
|
|
||
|
return delta;
|
||
|
},
|
||
|
|
||
|
getDistance: function SC_getDistance(otherCoordinates) {
|
||
|
var delta = this.getDelta(otherCoordinates);
|
||
|
|
||
|
return Math.sqrt(delta.x * delta.x + delta.y * delta.y);
|
||
|
},
|
||
|
|
||
|
clone: function SC_clone() {
|
||
|
var coordinates = new SOGoCoordinates();
|
||
|
coordinates.x = this.x;
|
||
|
coordinates.y = this.y;
|
||
|
|
||
|
return coordinates;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* SOGoEventDragEventCoordinates
|
||
|
*/
|
||
|
function SOGoEventDragEventCoordinates() {
|
||
|
}
|
||
|
|
||
|
SOGoEventDragEventCoordinates.prototype = {
|
||
|
dayNumber: -1,
|
||
|
start: -1,
|
||
|
duration: -1,
|
||
|
|
||
|
eventType: null,
|
||
|
|
||
|
setEventType: function(eventType) {
|
||
|
this.eventType = eventType;
|
||
|
},
|
||
|
|
||
|
initFromBlock: function(block) {
|
||
|
// Get the start (first quarter) from the event's first block
|
||
|
this.start = block.component.blocks[0].start;
|
||
|
|
||
|
// Compute overall length
|
||
|
this.duration = _.sum(block.component.blocks, function(b) {
|
||
|
return b.length;
|
||
|
});
|
||
|
|
||
|
// Get the dayNumber from the event's first block
|
||
|
this.dayNumber = block.component.blocks[0].dayNumber;
|
||
|
},
|
||
|
|
||
|
getDelta: function(otherCoordinates) {
|
||
|
var delta = new SOGoEventDragEventCoordinates();
|
||
|
delta.dayNumber = (this.dayNumber - otherCoordinates.dayNumber);
|
||
|
delta.start = (this.start - otherCoordinates.start);
|
||
|
delta.duration = (this.duration - otherCoordinates.duration);
|
||
|
|
||
|
return delta;
|
||
|
},
|
||
|
|
||
|
_quartersToHM: function(quarters) {
|
||
|
var minutes = quarters * 15;
|
||
|
var hours = Math.floor(minutes / 60);
|
||
|
if (hours < 10)
|
||
|
hours = "0" + hours;
|
||
|
var mins = minutes % 60;
|
||
|
if (mins < 10)
|
||
|
mins = "0" + mins;
|
||
|
|
||
|
return "" + hours + ":" + mins;
|
||
|
},
|
||
|
|
||
|
getStartTime: function() {
|
||
|
return this._quartersToHM(this.start);
|
||
|
},
|
||
|
|
||
|
getEndTime: function() {
|
||
|
var end = (this.start + this.duration) % CalendarSettings.EventDragDayLength;
|
||
|
return this._quartersToHM(end);
|
||
|
},
|
||
|
|
||
|
clone: function() {
|
||
|
var coordinates = new SOGoEventDragEventCoordinates();
|
||
|
coordinates.dayNumber = this.dayNumber;
|
||
|
coordinates.start = this.start;
|
||
|
coordinates.duration = this.duration;
|
||
|
|
||
|
return coordinates;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* SOGoEventDragPointerHandler
|
||
|
*/
|
||
|
function SOGoEventDragPointerHandler(dragMode) {
|
||
|
this.dragMode = dragMode;
|
||
|
}
|
||
|
|
||
|
SOGoEventDragPointerHandler.prototype = {
|
||
|
// Pointer absolute xy coordinates within page
|
||
|
originalCoordinates: null,
|
||
|
currentCoordinates: null,
|
||
|
|
||
|
// Pointer relative xy coordinates within view
|
||
|
originalViewCoordinates: null,
|
||
|
currentViewCoordinates: null,
|
||
|
|
||
|
// Event start-duration coordinates
|
||
|
originalEventCoordinates: null,
|
||
|
currentEventCoordinates: null,
|
||
|
|
||
|
dragHasStarted: false,
|
||
|
|
||
|
// Function to return the day and quarter coordinates of the pointer cursor
|
||
|
// within the day view
|
||
|
getEventViewCoordinates: null,
|
||
|
|
||
|
initFromEvent: function SEDPH_initFromEvent(event) {
|
||
|
this.currentCoordinates = new SOGoCoordinates();
|
||
|
this.updateFromEvent(event);
|
||
|
this.originalCoordinates = this.currentCoordinates.clone();
|
||
|
},
|
||
|
|
||
|
initFromBlock: function SEDPH_initFromBlock(block) {
|
||
|
this.currentEventCoordinates = new SOGoEventDragEventCoordinates();
|
||
|
this.originalEventCoordinates = new SOGoEventDragEventCoordinates();
|
||
|
this.originalEventCoordinates.initFromBlock(block);
|
||
|
},
|
||
|
|
||
|
// Method continuously called while dragging
|
||
|
updateFromEvent: function SEDPH_updateFromEvent(event) {
|
||
|
// Event here is a DOM event, not a calendar event!
|
||
|
this.currentCoordinates.x = event.pageX;
|
||
|
this.currentCoordinates.y = event.pageY;
|
||
|
|
||
|
// From SOGoEventDragGhostController.updateFromPointerHandler
|
||
|
if (this.dragHasStarted && Calendar.$view) {
|
||
|
var newEventCoordinates = this.getEventViewCoordinates(Calendar.$view);
|
||
|
if (!this.originalViewCoordinates) {
|
||
|
this.originalViewCoordinates = this.getEventViewCoordinates(Calendar.$view, this.originalCoordinates);
|
||
|
}
|
||
|
if (!this.currentViewCoordinates ||
|
||
|
!newEventCoordinates ||
|
||
|
newEventCoordinates.x != this.currentViewCoordinates.x ||
|
||
|
newEventCoordinates.y != this.currentViewCoordinates.y) {
|
||
|
this.currentViewCoordinates = newEventCoordinates;
|
||
|
if (this.originalViewCoordinates) {
|
||
|
if (!newEventCoordinates) {
|
||
|
this.currentViewCoordinates = this.originalViewCoordinates.clone();
|
||
|
}
|
||
|
this.updateEventCoordinates();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (this.originalCoordinates &&
|
||
|
this.currentCoordinates &&
|
||
|
!this.dragHasStarted) {
|
||
|
var distance = this.getDistance();
|
||
|
if (distance > 3) {
|
||
|
// Emit 'dragstart' event only if pointer has moved from at least 3 pixels
|
||
|
this.dragHasStarted = true;
|
||
|
$rootScope.$emit('calendar:dragstart');
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// SOGoEventDragGhostController._updateCoordinates
|
||
|
// Extend this.currentCoordinates with start, dayNumber and duration
|
||
|
updateEventCoordinates: function SEDGC__updateCoordinates() {
|
||
|
var newDuration;
|
||
|
|
||
|
// Compute delta wrt to position of mouse at dragstart on the day/quarter grid
|
||
|
var delta = this.currentViewCoordinates.getDelta(this.originalViewCoordinates);
|
||
|
var deltaQuarters = delta.x * CalendarSettings.EventDragDayLength + delta.y;
|
||
|
$log.debug('quarters delta ' + deltaQuarters);
|
||
|
|
||
|
// if (currentView == "multicolumndayview")
|
||
|
// this._updateMulticolumnViewDayNumber_SEDGC();
|
||
|
// else
|
||
|
this.currentEventCoordinates.dayNumber = this.originalEventCoordinates.dayNumber;
|
||
|
|
||
|
if (this.dragMode == "move-event") {
|
||
|
this.currentEventCoordinates.start = this.originalEventCoordinates.start + deltaQuarters;
|
||
|
this.currentEventCoordinates.duration = this.originalEventCoordinates.duration;
|
||
|
}
|
||
|
else {
|
||
|
if (this.dragMode == "change-start") {
|
||
|
newDuration = this.originalEventCoordinates.duration - deltaQuarters;
|
||
|
if (newDuration > 0) {
|
||
|
this.currentEventCoordinates.start = this.originalEventCoordinates.start + deltaQuarters;
|
||
|
this.currentEventCoordinates.duration = newDuration;
|
||
|
}
|
||
|
else if (newDuration < 0) {
|
||
|
this.currentEventCoordinates.start = (this.originalEventCoordinates.start + this.originalEventCoordinates.duration);
|
||
|
this.currentEventCoordinates.duration = -newDuration;
|
||
|
}
|
||
|
}
|
||
|
else if (this.dragMode == "change-end") {
|
||
|
newDuration = this.originalEventCoordinates.duration + deltaQuarters;
|
||
|
if (newDuration > 0) {
|
||
|
this.currentEventCoordinates.start = this.originalEventCoordinates.start;
|
||
|
this.currentEventCoordinates.duration = newDuration;
|
||
|
}
|
||
|
else if (newDuration < 0) {
|
||
|
this.currentEventCoordinates.start = this.originalEventCoordinates.start + newDuration;
|
||
|
this.currentEventCoordinates.duration = -newDuration;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var deltaDays;
|
||
|
if (this.currentEventCoordinates.start < 0) {
|
||
|
deltaDays = Math.ceil(-this.currentEventCoordinates.start / CalendarSettings.EventDragDayLength);
|
||
|
this.currentEventCoordinates.start += deltaDays * CalendarSettings.EventDragDayLength;
|
||
|
this.currentEventCoordinates.dayNumber -= deltaDays;
|
||
|
}
|
||
|
else if (this.currentEventCoordinates.start >= CalendarSettings.EventDragDayLength) {
|
||
|
deltaDays = Math.floor(this.currentEventCoordinates.start / CalendarSettings.EventDragDayLength);
|
||
|
this.currentEventCoordinates.start -= deltaDays * CalendarSettings.EventDragDayLength;
|
||
|
|
||
|
// This dayNumber needs to be updated with the calendar number.
|
||
|
// if (currentView == "multicolumndayview")
|
||
|
// this._updateMulticolumnViewDayNumber_SEDGC();
|
||
|
this.currentEventCoordinates.dayNumber += deltaDays;
|
||
|
}
|
||
|
$log.debug('event coordinates ' + JSON.stringify(this.currentEventCoordinates));
|
||
|
$rootScope.$emit('calendar:drag');
|
||
|
},
|
||
|
|
||
|
// SOGoEventDragPointerHandler.getContainerBasedCoordinates
|
||
|
getContainerBasedCoordinates: function SEDPH_getCBC(view, pointerCoordinates) {
|
||
|
var currentCoordinates = pointerCoordinates || this.currentCoordinates;
|
||
|
var coordinates = currentCoordinates.getDelta(view.coordinates);
|
||
|
var container = view.element;
|
||
|
|
||
|
if (coordinates.x < view.daysOffset || coordinates.x > container.clientWidth ||
|
||
|
coordinates.y < 0 || coordinates.y > container.clientHeight)
|
||
|
coordinates = null;
|
||
|
|
||
|
return coordinates;
|
||
|
},
|
||
|
|
||
|
prepareWithEventType: function SEDPH_prepareWithEventType(eventType) {
|
||
|
var methods = { "multiday": this.getEventMultiDayViewCoordinates,
|
||
|
"multiday-allday": this.getEventMultiDayAllDayViewCoordinates,
|
||
|
"monthly": this.getEventMonthlyViewCoordinates,
|
||
|
"unknown": null };
|
||
|
var method = methods[eventType];
|
||
|
this.eventType = eventType;
|
||
|
this.getEventViewCoordinates = method;
|
||
|
},
|
||
|
|
||
|
getEventMultiDayViewCoordinates: function SEDPH_gEMultiDayViewC(view, pointerCoordinates) {
|
||
|
/* x = day; y = quarter */
|
||
|
var coordinates = this.getEventMultiDayAllDayViewCoordinates(view, pointerCoordinates); // get the x coordinate
|
||
|
if (coordinates) {
|
||
|
var quarterHeight = view.quarterHeight;
|
||
|
var pxCoordinates = this.getContainerBasedCoordinates(view, pointerCoordinates);
|
||
|
pxCoordinates.y += view.element.scrollTop;
|
||
|
|
||
|
coordinates.y = Math.floor((pxCoordinates.y - CalendarSettings.EventDragHorizontalOffset) / quarterHeight);
|
||
|
var maxY = CalendarSettings.EventDragDayLength - 1;
|
||
|
if (coordinates.y < 0)
|
||
|
coordinates.y = 0;
|
||
|
else if (coordinates.y > maxY)
|
||
|
coordinates.y = maxY;
|
||
|
}
|
||
|
|
||
|
return coordinates;
|
||
|
},
|
||
|
getEventMultiDayAllDayViewCoordinates: function SEDPH_gEMultiDayADVC(view, pointerCoordinates) {
|
||
|
/* x = day; y = quarter */
|
||
|
var coordinates;
|
||
|
|
||
|
var pxCoordinates = this.getContainerBasedCoordinates(view, pointerCoordinates);
|
||
|
if (pxCoordinates) {
|
||
|
coordinates = new SOGoCoordinates();
|
||
|
|
||
|
var dayWidth = view.dayWidth;
|
||
|
var daysOffset = view.daysOffset;
|
||
|
|
||
|
coordinates.x = Math.floor((pxCoordinates.x - daysOffset) / dayWidth);
|
||
|
var maxX = Calendar.$view.maxX;
|
||
|
if (coordinates.x < 0)
|
||
|
coordinates.x = 0;
|
||
|
else if (coordinates.x > maxX)
|
||
|
coordinates.x = maxX;
|
||
|
coordinates.y = 0;
|
||
|
}
|
||
|
else {
|
||
|
coordinates = null;
|
||
|
}
|
||
|
|
||
|
return coordinates;
|
||
|
},
|
||
|
// getEventMonthlyViewCoordinates: function SEDPH_gEMonthlyViewC() {
|
||
|
// /* x = day; y = quarter */
|
||
|
// var coordinates;
|
||
|
|
||
|
// var pxCoordinates = this.getContainerBasedCoordinates();
|
||
|
// if (pxCoordinates) {
|
||
|
// coordinates = new SOGoCoordinates();
|
||
|
// var utilities = SOGoEventDragUtilities();
|
||
|
// var daysOffset = utilities.getDaysOffset();
|
||
|
// var daysTopOffset = daysOffset; /* change later */
|
||
|
// var dayHeight = utilities.getDayHeight();
|
||
|
// var daysY = Math.floor((pxCoordinates.y - daysTopOffset) / dayHeight);
|
||
|
// if (daysY < 0)
|
||
|
// daysY = 0;
|
||
|
// var dayWidth = utilities.getDayWidth();
|
||
|
|
||
|
// coordinates.x = Math.floor((pxCoordinates.x - daysOffset) / dayWidth);
|
||
|
// if (coordinates.x < 0)
|
||
|
// coordinates.x = 0;
|
||
|
// else if (coordinates.x > 6)
|
||
|
// coordinates.x = 6;
|
||
|
// coordinates.x += 7 * daysY;
|
||
|
// coordinates.y = 0;
|
||
|
// } else {
|
||
|
// coordinates = null;
|
||
|
// }
|
||
|
|
||
|
// return coordinates;
|
||
|
// },
|
||
|
|
||
|
getDistance: function SEDPH_getDistance() {
|
||
|
return this.currentCoordinates.getDistance(this.originalCoordinates);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
angular
|
||
|
.module('SOGo.SchedulerUI')
|
||
|
.directive('sgDraggableCalendarBlock', sgDraggableCalendarBlock);
|
||
|
})();
|
||
|
|