\n */\n sgDraggableCalendarBlock.$inject = ['$rootScope', '$timeout', '$log', 'Preferences', 'Calendar', 'CalendarSettings', 'Component'];\n function sgDraggableCalendarBlock($rootScope, $timeout, $log, Preferences, Calendar, CalendarSettings, Component) {\n return {\n restrict: 'CA',\n require: '^sgCalendarDay',\n link: link\n };\n\n function link(scope, element, attrs, calendarDayCtrl) {\n if (scope.block) {\n if (scope.block.component.editable) {\n // Add dragging grips to existing event block\n initGrips();\n }\n else {\n element.removeClass('sg-draggable-calendar-block');\n return;\n }\n }\n\n // Start dragging on mousedown\n element.on('mousedown', onDragDetect);\n\n // Deregister listeners when removing the element from the DOM\n scope.$on('$destroy', function() {\n element.off('mousedown', onDragDetect);\n element.off('mousemove', onDrag);\n });\n\n function initGrips() {\n var component, dayNumber, blockIndex, isFirstBlock, isLastBlock,\n dragGrip, leftGrip, rightGrip, topGrip, bottomGrip;\n\n component = scope.block.component;\n dayNumber = scope.block.dayNumber;\n blockIndex = _.findIndex(component.blocks, ['dayNumber', dayNumber]);\n isFirstBlock = (blockIndex === 0);\n isLastBlock = (blockIndex === component.blocks.length - 1);\n\n dragGrip = angular.element('
');\n dragGrip.addClass('bdr-folder' + component.pid);\n\n if (component.c_isallday ||\n element[0].parentNode.tagName === 'SG-CALENDAR-MONTH-DAY') {\n if (isFirstBlock) {\n leftGrip = angular.element('
').append(dragGrip);\n element.append(leftGrip);\n }\n if (isLastBlock) {\n rightGrip = angular.element('
').append(dragGrip.clone());\n element.append(rightGrip);\n }\n }\n else {\n if (isFirstBlock) {\n topGrip = angular.element('
').append(dragGrip);\n element.append(topGrip);\n }\n if (isLastBlock) {\n bottomGrip = angular.element('
').append(dragGrip.clone());\n element.append(bottomGrip);\n }\n }\n }\n\n function onDragDetect(ev) {\n var block, dragMode, eventType, startDate, newData, newComponent, pointerHandler;\n\n ev.stopPropagation();\n\n dragMode = 'move-event';\n \n if (scope.block && scope.block.component) {\n // Move or resize existing component\n if (ev.target.className == 'dragGrip-top' ||\n ev.target.className == 'dragGrip-left')\n dragMode = 'change-start';\n else if (ev.target.className == 'dragGrip-bottom' ||\n ev.target.className == 'dragGrip-right' )\n dragMode = 'change-end';\n }\n else {\n // Create new component from dragging\n dragMode = 'change-end';\n }\n\n // Initialize pointer handler\n pointerHandler = new SOGoEventDragPointerHandler(dragMode);\n pointerHandler.initFromEvent(ev);\n\n // Update Component.$ghost\n Component.$ghost.pointerHandler = pointerHandler;\n\n // Stop dragging on the next \"mouseup\"\n angular.element(document).one('mouseup', onDragEnd);\n\n // Listen to mousemove and start dragging when mouse has moved from at least 3 pixels\n angular.element(document).on('mousemove', onDrag);\n }\n\n function dragStart(ev) {\n var block, eventType, isHourCell, isMonthly, startDate, newData, newComponent, pointerHandler, calendarData;\n\n isHourCell = element.hasClass('clickableHourCell');\n isMonthly = (element[0].parentNode.tagName == 'SG-CALENDAR-MONTH-DAY') ||\n element.hasClass('clickableDayCell');\n\n calendarData = calendarDayCtrl.calendarData();\n\n if (scope.block && scope.block.component) {\n // Move or resize existing component\n block = scope.block;\n }\n else {\n // Create new component from dragging\n startDate = calendarDayCtrl.dayString.parseDate(Preferences.$mdDateLocaleProvider, '%Y-%m-%e');\n newData = {\n type: 'appointment',\n pid: calendarData? calendarData.pid : Calendar.$defaultCalendar(),\n summary: l('New Event'),\n startDate: startDate,\n isAllDay: isHourCell? 0 : 1\n };\n newComponent = new Component(newData);\n block = {\n component: newComponent,\n dayNumber: calendarDayCtrl.dayNumber,\n length: 0\n };\n block.component.blocks = [block];\n }\n\n // Determine event type\n eventType = 'multiday';\n if (isMonthly)\n eventType = 'monthly';\n else if (block.component.c_isallday)\n eventType = 'multiday-allday';\n\n // Mark all blocks as being dragged\n _.forEach(block.component.blocks, function(b) {\n b.dragging = true;\n });\n\n // Update pointer handler\n pointerHandler = Component.$ghost.pointerHandler;\n pointerHandler.prepareWithEventType(eventType);\n pointerHandler.initFromBlock(block);\n if (calendarData)\n // When the day is associated to a calendar, the day number becomes the calendar index\n // among the active calendars\n pointerHandler.initFromCalendar(calendarData);\n\n // Update Component.$ghost\n Component.$ghost.component = block.component;\n\n $log.debug('emit calendar:dragstart ' + eventType);\n $rootScope.$emit('calendar:dragstart');\n }\n\n function onDrag(ev) {\n var pointerHandler = Component.$ghost.pointerHandler;\n\n // Update\n // - currentCoordinates\n // - currentViewCoordinates\n // - currentEventCoordinates\n $timeout(function() {\n pointerHandler.updateFromEvent(ev);\n });\n }\n\n function onDragEnd(ev) {\n var block, pointer;\n\n block = scope.block;\n pointer = Component.$ghost.pointerHandler;\n\n // Deregister mouse events\n angular.element(document).off('mousemove', onDrag);\n\n if (pointer.dragHasStarted) {\n $rootScope.$emit('calendar:dragend');\n pointer.dragHasStarted = false;\n }\n\n // Unmark all blocks as being dragged\n if (block && block.component)\n _.forEach(block.component.blocks, function(b) {\n b.dragging = false;\n });\n }\n\n /**\n * SOGoCoordinates\n */\n function SOGoCoordinates() {\n }\n\n SOGoCoordinates.prototype = {\n x: -1,\n y: -1,\n\n getDelta: function SC_getDelta(otherCoordinates) {\n var delta = new SOGoCoordinates();\n delta.x = this.x - otherCoordinates.x;\n delta.y = this.y - otherCoordinates.y;\n\n return delta;\n },\n\n getDistance: function SC_getDistance(otherCoordinates) {\n var delta = this.getDelta(otherCoordinates);\n\n return Math.sqrt(delta.x * delta.x + delta.y * delta.y);\n },\n\n clone: function SC_clone() {\n var coordinates = new SOGoCoordinates();\n coordinates.x = this.x;\n coordinates.y = this.y;\n\n return coordinates;\n }\n };\n\n /**\n * SOGoEventDragEventCoordinates\n */\n function SOGoEventDragEventCoordinates(eventType) {\n this.setEventType(eventType);\n }\n\n SOGoEventDragEventCoordinates.prototype = {\n dayNumber: -1,\n start: -1,\n duration: -1,\n\n eventType: null,\n\n setEventType: function(eventType) {\n this.eventType = eventType;\n },\n\n initFromBlock: function(block) {\n if (this.eventType === 'monthly') {\n this.start = 0;\n this.duration = block.component.blocks.length * 96;\n }\n else {\n // Get the start (first quarter) from the event's first block\n // Compute overall length\n this.start = block.component.blocks[0].start;\n this.duration = _.sumBy(block.component.blocks, function(b) {\n return b.length;\n });\n }\n\n // Get the dayNumber from the event's first block\n this.dayNumber = block.component.blocks[0].dayNumber;\n },\n\n initFromCalendar: function(calendarNumber) {\n this.dayNumber = calendarNumber;\n },\n\n getDelta: function(otherCoordinates) {\n var delta = new SOGoEventDragEventCoordinates();\n delta.dayNumber = (this.dayNumber - otherCoordinates.dayNumber);\n delta.start = (this.start - otherCoordinates.start);\n delta.duration = (this.duration - otherCoordinates.duration);\n\n return delta;\n },\n\n _quartersToHM: function(quarters) {\n var minutes = quarters * 15;\n var hours = Math.floor(minutes / 60);\n if (hours < 10)\n hours = \"0\" + hours;\n var mins = minutes % 60;\n if (mins < 10)\n mins = \"0\" + mins;\n\n return \"\" + hours + \":\" + mins;\n },\n\n getStartTime: function() {\n return this._quartersToHM(this.start);\n },\n\n getEndTime: function() {\n var end = (this.start + this.duration) % CalendarSettings.EventDragDayLength;\n return this._quartersToHM(end);\n },\n\n clone: function() {\n var coordinates = new SOGoEventDragEventCoordinates();\n coordinates.dayNumber = this.dayNumber;\n coordinates.start = this.start;\n coordinates.duration = this.duration;\n\n return coordinates;\n }\n };\n\n /**\n * SOGoEventDragPointerHandler\n */\n function SOGoEventDragPointerHandler(dragMode) {\n this.dragMode = dragMode;\n }\n\n SOGoEventDragPointerHandler.prototype = {\n // Pointer absolute xy coordinates within page\n originalCoordinates: null,\n currentCoordinates: null,\n\n // Pointer relative xy coordinates within view (row-column)\n originalViewCoordinates: null,\n currentViewCoordinates: null,\n\n // Event start-duration coordinates\n originalEventCoordinates: null,\n currentEventCoordinates: null,\n\n originalCalendar: null,\n\n dragHasStarted: false,\n\n // Function to return the day and quarter coordinates of the pointer cursor\n // within the day view\n getEventViewCoordinates: null,\n\n initFromBlock: function SEDPH_initFromBlock(block) {\n this.currentEventCoordinates = new SOGoEventDragEventCoordinates(this.eventType);\n this.originalEventCoordinates = new SOGoEventDragEventCoordinates(this.eventType);\n this.originalEventCoordinates.initFromBlock(block);\n },\n\n initFromEvent: function SEDPH_initFromEvent(event) {\n this.currentCoordinates = new SOGoCoordinates();\n this.updateFromEvent(event);\n this.originalCoordinates = this.currentCoordinates.clone();\n },\n\n initFromCalendar: function SEDPH_initFromCalendar(calendarData) {\n this.originalCalendar = calendarData;\n this.currentEventCoordinates.initFromCalendar(calendarData.index);\n this.originalEventCoordinates.initFromCalendar(calendarData.index);\n },\n\n // Method continuously called while dragging\n updateFromEvent: function SEDPH_updateFromEvent(event) {\n // Event here is a DOM event, not a calendar event!\n this.currentCoordinates.x = event.pageX;\n this.currentCoordinates.y = event.pageY;\n\n // From SOGoEventDragGhostController.updateFromPointerHandler\n if (this.dragHasStarted && Calendar.$view) {\n var newEventCoordinates = this.getEventViewCoordinates(Calendar.$view);\n if (!this.originalViewCoordinates) {\n this.originalViewCoordinates = this.getEventViewCoordinates(Calendar.$view, this.originalCoordinates);\n if (Component.$ghost.component.isNew) {\n this.setTimeFromQuarters(Component.$ghost.component.start, this.originalViewCoordinates.y);\n $log.debug('new event start date ' + Component.$ghost.component.start);\n }\n }\n if (!this.currentViewCoordinates ||\n !newEventCoordinates ||\n newEventCoordinates.x != this.currentViewCoordinates.x ||\n newEventCoordinates.y != this.currentViewCoordinates.y) {\n this.currentViewCoordinates = newEventCoordinates;\n if (this.originalViewCoordinates) {\n if (!newEventCoordinates) {\n this.currentViewCoordinates = this.originalViewCoordinates.clone();\n }\n this.updateEventCoordinates();\n }\n }\n }\n else if (this.originalCoordinates &&\n this.currentCoordinates &&\n !this.dragHasStarted) {\n var distance = this.getDistance();\n if (distance > 3) {\n this.dragHasStarted = true;\n dragStart(event);\n }\n }\n },\n\n // SOGoEventDragGhostController._updateCoordinates\n // Extend this.currentCoordinates with start, dayNumber and duration\n updateEventCoordinates: function SEDGC__updateCoordinates() {\n var newDuration;\n\n // Compute delta wrt to position of mouse at dragstart on the day/quarter grid\n var delta = this.currentViewCoordinates.getDelta(this.originalViewCoordinates);\n var deltaQuarters = delta.x * CalendarSettings.EventDragDayLength + delta.y;\n $log.debug('quarters delta ' + deltaQuarters);\n\n if (angular.isUndefined(this.originalEventCoordinates.start)) {\n this.originalEventCoordinates.dayNumber = this.originalViewCoordinates.x;\n this.originalEventCoordinates.start = this.originalViewCoordinates.y;\n }\n // if (currentView == \"multicolumndayview\")\n // this._updateMulticolumnViewDayNumber_SEDGC();\n // else\n this.currentEventCoordinates.dayNumber = this.originalEventCoordinates.dayNumber;\n\n if (this.dragMode == \"move-event\") {\n this.currentEventCoordinates.start = this.originalEventCoordinates.start + deltaQuarters;\n this.currentEventCoordinates.duration = this.originalEventCoordinates.duration;\n }\n else {\n if (this.dragMode == \"change-start\") {\n newDuration = this.originalEventCoordinates.duration - deltaQuarters;\n if (newDuration > 0) {\n this.currentEventCoordinates.start = this.originalEventCoordinates.start + deltaQuarters;\n this.currentEventCoordinates.duration = newDuration;\n }\n else if (newDuration < 0) {\n this.currentEventCoordinates.start = (this.originalEventCoordinates.start + this.originalEventCoordinates.duration);\n this.currentEventCoordinates.duration = -newDuration;\n }\n }\n else if (this.dragMode == \"change-end\") {\n newDuration = this.originalEventCoordinates.duration + deltaQuarters;\n if (newDuration > 0) {\n this.currentEventCoordinates.start = this.originalEventCoordinates.start;\n this.currentEventCoordinates.duration = newDuration;\n }\n else if (newDuration < 0) {\n this.currentEventCoordinates.start = this.originalEventCoordinates.start + newDuration;\n this.currentEventCoordinates.duration = -newDuration;\n }\n }\n }\n\n var deltaDays;\n if (this.currentEventCoordinates.start < 0) {\n deltaDays = Math.ceil(-this.currentEventCoordinates.start / CalendarSettings.EventDragDayLength);\n this.currentEventCoordinates.start += deltaDays * CalendarSettings.EventDragDayLength;\n this.currentEventCoordinates.dayNumber -= deltaDays;\n }\n else if (this.currentEventCoordinates.start >= CalendarSettings.EventDragDayLength) {\n deltaDays = Math.floor(this.currentEventCoordinates.start / CalendarSettings.EventDragDayLength);\n this.currentEventCoordinates.start -= deltaDays * CalendarSettings.EventDragDayLength;\n\n // This dayNumber needs to be updated with the calendar number.\n // if (currentView == \"multicolumndayview\")\n // this._updateMulticolumnViewDayNumber_SEDGC();\n this.currentEventCoordinates.dayNumber += deltaDays;\n }\n $log.debug('event coordinates ' + JSON.stringify(this.currentEventCoordinates));\n $rootScope.$emit('calendar:drag');\n },\n\n // SOGoEventDragPointerHandler.getContainerBasedCoordinates\n getContainerBasedCoordinates: function SEDPH_getCBC(view, pointerCoordinates) {\n var currentCoordinates = pointerCoordinates || this.currentCoordinates;\n var coordinates = currentCoordinates.getDelta(view.coordinates);\n var container = view.element;\n\n if (coordinates.x < view.daysOffset || coordinates.x > container.clientWidth ||\n coordinates.y < 0 || coordinates.y > container.clientHeight)\n coordinates = null;\n\n return coordinates;\n },\n\n prepareWithEventType: function SEDPH_prepareWithEventType(eventType) {\n var methods = { \"multiday\": this.getEventMultiDayViewCoordinates,\n \"multiday-allday\": this.getEventMultiDayAllDayViewCoordinates,\n \"monthly\": this.getEventMonthlyViewCoordinates,\n \"unknown\": null };\n var method = methods[eventType];\n this.eventType = eventType;\n this.getEventViewCoordinates = method;\n },\n\n getEventMultiDayViewCoordinates: function SEDPH_gEMultiDayViewC(view, pointerCoordinates) {\n /* x = day; y = quarter */\n var coordinates = this.getEventMultiDayAllDayViewCoordinates(view, pointerCoordinates); // get the x coordinate\n if (coordinates) {\n var quarterHeight = view.quarterHeight;\n var pxCoordinates = this.getContainerBasedCoordinates(view, pointerCoordinates);\n pxCoordinates.y += view.element.scrollTop;\n\n coordinates.y = Math.floor((pxCoordinates.y - CalendarSettings.EventDragHorizontalOffset) / quarterHeight);\n var maxY = CalendarSettings.EventDragDayLength - 1;\n if (coordinates.y < 0)\n coordinates.y = 0;\n else if (coordinates.y > maxY)\n coordinates.y = maxY;\n }\n\n return coordinates;\n },\n getEventMultiDayAllDayViewCoordinates: function SEDPH_gEMultiDayADVC(view, pointerCoordinates) {\n /* x = day; y = quarter */\n var coordinates;\n\n var pxCoordinates = this.getContainerBasedCoordinates(view, pointerCoordinates);\n if (pxCoordinates) {\n coordinates = new SOGoCoordinates();\n\n var dayWidth = view.dayWidth;\n var daysOffset = view.daysOffset;\n\n coordinates.x = Math.floor((pxCoordinates.x - daysOffset) / dayWidth);\n var minX = 0;\n var maxX = Calendar.$view.maxX;\n if (this.dragMode != 'move-event') {\n var calendarData = calendarDayCtrl.calendarData();\n if (calendarData)\n // Resizing an event can't span a different day when in multicolumn view\n minX = maxX = calendarData.index;\n }\n if (coordinates.x < minX)\n coordinates.x = minX;\n else if (coordinates.x > maxX)\n coordinates.x = maxX;\n coordinates.y = 0;\n }\n else {\n coordinates = null;\n }\n\n return coordinates;\n },\n getEventMonthlyViewCoordinates: function SEDPH_gEMonthlyViewC(view, pointerCoordinates) {\n /* x = day; y = quarter */\n var coordinates;\n\n var pxCoordinates = this.getContainerBasedCoordinates(view, pointerCoordinates);\n if (pxCoordinates) {\n coordinates = new SOGoCoordinates();\n\n var daysTopOffset = 0;\n var dayWidth = view.dayWidth;\n var daysOffset = view.daysOffset;\n var dayHeight = view.dayHeight;\n var daysY = Math.floor((pxCoordinates.y - daysTopOffset) / dayHeight);\n if (daysY < 0)\n daysY = 0;\n\n coordinates.x = Math.floor((pxCoordinates.x - daysOffset) / dayWidth);\n if (coordinates.x < 0)\n coordinates.x = 0;\n else if (coordinates.x > 6)\n coordinates.x = 6;\n coordinates.x += 7 * daysY;\n coordinates.y = 0;\n }\n else {\n coordinates = null;\n }\n\n return coordinates;\n },\n\n getDistance: function SEDPH_getDistance() {\n return this.currentCoordinates.getDistance(this.originalCoordinates);\n },\n\n setTimeFromQuarters: function SEDPH_setTimeFromQuarters(date, quarters) {\n var hours, minutes;\n hours = Math.floor(quarters / 4);\n minutes = (quarters % 4) * 15;\n date.setHours(hours, minutes);\n }\n };\n }\n }\n\n angular\n .module('SOGo.SchedulerUI')\n .directive('sgDraggableCalendarBlock', sgDraggableCalendarBlock);\n})();\n\n"]}