Initial drag'n'drop support in Calendar module

TODO:
- drag'n'drop in month's view;
- drag to create an all-day event;
- drag'n'drop to a calendar in the sidenav;
- touchscreen gestures support.
This commit is contained in:
Francis Lachapelle
2015-11-06 15:12:24 -05:00
parent fc16d6ad90
commit 2e02380a93
18 changed files with 1187 additions and 108 deletions
+13 -11
View File
@@ -38,6 +38,7 @@
#import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <Appointments/iCalEvent+SOGo.h>
@@ -59,23 +60,25 @@
SoSecurityManager *sm;
iCalEvent *event;
NSCalendarDate *start, *newStart, *end, *newEnd;
NSDictionary *params;
NSTimeInterval newDuration;
SOGoUserDefaults *ud;
NSString *daysDelta, *startDelta, *durationDelta, *destionationCalendar;
NSNumber *daysDelta, *startDelta, *durationDelta;
NSString *destionationCalendar;
NSTimeZone *tz;
NSException *ex;
SOGoAppointmentFolder *targetCalendar, *sourceCalendar;
SOGoAppointmentFolders *folders;
rq = [context request];
params = [[rq contentAsString] objectFromJSONString];
daysDelta = [rq formValueForKey: @"days"];
startDelta = [rq formValueForKey: @"start"];
durationDelta = [rq formValueForKey: @"duration"];
destionationCalendar = [rq formValueForKey: @"destination"];
daysDelta = [params objectForKey: @"days"];
startDelta = [params objectForKey: @"start"];
durationDelta = [params objectForKey: @"duration"];
destionationCalendar = [params objectForKey: @"destination"];
if ([daysDelta length] > 0
|| [startDelta length] > 0 || [durationDelta length] > 0)
if (daysDelta || startDelta || durationDelta)
{
co = [self clientObject];
event = (iCalEvent *) [[self clientObject] occurence];
@@ -119,7 +122,7 @@
[event setLastModified: [NSCalendarDate calendarDate]];
ex = [co saveComponent: event];
// This condition will be executed only if the event is moved from a calendar to another. If destionationCalendar == 0; there is no calendar change
if (![destionationCalendar isEqualToString:@"0"])
if ([destionationCalendar length] > 0)
{
folders = [[self->context activeUser] calendarsFolderInContext: self->context];
sourceCalendar = [co container];
@@ -151,9 +154,8 @@
response = [self responseWith204];
}
else
response
= (WOResponse *) [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'days', 'start' and/or 'duration' parameters"];
response = (WOResponse *) [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'days', 'start' and/or 'duration' parameters"];
return response;
}
+2 -2
View File
@@ -284,9 +284,9 @@
return [daysToDisplay indexOfObject: currentTableDay];
}
- (NSString *) currentAppointmentHour
- (NSNumber *) currentAppointmentHour
{
return [NSString stringWithFormat: @"%.2d00", [currentTableHour intValue]];
return [NSNumber numberWithInt: [currentTableHour intValue]];
}
- (NSString *) labelForDay
+21 -12
View File
@@ -51,24 +51,28 @@
<!-- All day cells -->
<div class="allDaysView md-default-theme md-background md-bg">
<div class="days">
<div class="days" sg-calendar-scroll-view="multiday-allday">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses"
<sg-calendar-day var:class="dayClasses"
var:day="currentTableDay.shortDateString"
var:id="currentAllDayId"
hour="allday"
var:sg-day-number="currentDayNumber"
var:sg-day="currentTableDay.shortDateString"
var:sg-day-string="currentTableDay.iso8601DateString"
ng-repeat="view in calendar.views">
<sg-calendar-day-table
sg-blocks="view.allDayBlocks"
sg-click="list.openEvent(event, component)"
var:sg-day="currentTableDay.shortDateString" />
</div>
var:sg-day="currentTableDay.shortDateString"><!-- blocks for current day --></sg-calendar-day-table>
<sg-calendar-day-block-ghost><!-- drag ghost --></sg-calendar-day-block-ghost>
</sg-calendar-day>
</var:foreach>
</div>
</div>
</md-toolbar>
<md-content class="md-flex" md-scroll-y="md-scroll-y">
<md-content class="md-flex" sg-calendar-scroll-view="multiday">
<div class="calendarView">
<!-- The hours -->
@@ -80,18 +84,21 @@
</var:foreach>
</div>
<!-- The hours cells -->
<!-- The quarters grid -->
<div class="daysView">
<div class="days">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses"
<sg-calendar-day var:class="dayClasses"
var:id="currentDayId"
var:day-number="currentDayNumber"
var:day="currentTableDay.shortDateString"
var:sg-day-number="currentDayNumber"
var:sg-day="currentTableDay.shortDateString"
var:sg-day-string="currentTableDay.iso8601DateString"
ng-repeat="view in calendar.views">
<div class="hourCells">
<var:foreach list="hoursToDisplay" item="currentTableHour">
<div var:class="clickableHourCellClass" var:day="currentTableDay.shortDateString" var:hour="currentAppointmentHour">
<div var:class="clickableHourCellClass"
sg-draggable-calendar-block="sg-draggable-calendar-block"
var:sg-hour="currentAppointmentHour">
<span class="minutes15"><!-- space --></span>
<span class="minutes30"><!-- space --></span>
<span class="minutes45"><!-- space --></span>
@@ -100,10 +107,12 @@
<sg-calendar-day-table
sg-click="list.openEvent(event, component)"
sg-blocks="view.blocks"
var:sg-day="currentTableDay.shortDateString" />
var:sg-day-number="currentDayNumber"
var:sg-day="currentTableDay.shortDateString"><!-- blocks for current day --></sg-calendar-day-table>
<sg-calendar-day-block-ghost><!-- drag ghost --></sg-calendar-day-block-ghost>
</div>
<div class="events"><!-- space --></div>
</div>
</sg-calendar-day>
</var:foreach>
</div>
</div>
+1 -1
View File
@@ -551,7 +551,7 @@
</md-menu-item>
</md-menu-content>
</md-menu>
<md-button class="sg-icon-button" ng-click="list.component.$filter(list.componentType)">
<md-button class="sg-icon-button" ng-click="list.reload()">
<md-icon>refresh</md-icon>
</md-button>
</div>
@@ -34,7 +34,8 @@
$$resource: new Resource(Settings.activeUser('folderURL') + 'Calendar', Settings.activeUser()),
$Component: Component,
$$Acl: Acl,
activeUser: Settings.activeUser()
activeUser: Settings.activeUser(),
$view: null
});
return Calendar; // return constructor
@@ -51,6 +52,10 @@
angular.module('SOGo.SchedulerUI', ['SOGo.Common']);
}
angular.module('SOGo.SchedulerUI')
.value('CalendarSettings', {
EventDragDayLength: 24 * 4,
EventDragHorizontalOffset: 3
})
.factory('Calendar', Calendar.$factory);
/**
@@ -6,25 +6,31 @@
/**
* @ngInject
*/
CalendarController.$inject = ['$scope', '$state', '$stateParams', '$timeout', '$interval', '$log', 'sgFocus', 'Calendar', 'Component', 'stateEventsBlocks'];
function CalendarController($scope, $state, $stateParams, $timeout, $interval, $log, focus, Calendar, Component, stateEventsBlocks) {
var vm = this;
CalendarController.$inject = ['$scope', '$rootScope', '$state', '$stateParams', 'Calendar', 'Component', 'stateEventsBlocks'];
function CalendarController($scope, $rootScope, $state, $stateParams, Calendar, Component, stateEventsBlocks) {
var vm = this, deregisterCalendarsList;
vm.views = stateEventsBlocks;
vm.changeView = changeView;
// Refresh current view when the list of calendars is modified
$scope.$on('calendars:list', function() {
deregisterCalendarsList = $rootScope.$on('calendars:list', updateView);
$scope.$on('$destroy', deregisterCalendarsList);
function updateView() {
// See stateEventsBlocks in Scheduler.app.js
Component.$eventsBlocksForView($stateParams.view, $stateParams.day.asDate()).then(function(data) {
vm.views = data;
_.forEach(vm.views, function(view) {
if (view.id) {
// Note: this can't be done in Component service since it would make Component dependent on
// the Calendar service and create a circular dependency
view.calendar = new Calendar({ id: view.id, name: view.calendarName });
}
});
});
});
}
// Change calendar's view
function changeView($event) {
@@ -6,8 +6,8 @@
/**
* @ngInject
*/
CalendarListController.$inject = ['$scope', '$timeout', '$state', '$mdDialog', 'Dialog', 'Preferences', 'Calendar', 'Component'];
function CalendarListController($scope, $timeout, $state, $mdDialog, Dialog, Preferences, Calendar, Component) {
CalendarListController.$inject = ['$rootScope', '$timeout', '$state', '$mdDialog', 'Dialog', 'Preferences', 'Calendar', 'Component'];
function CalendarListController($rootScope, $timeout, $state, $mdDialog, Dialog, Preferences, Calendar, Component) {
var vm = this;
vm.component = Component;
@@ -25,6 +25,7 @@
vm.filteredBy = filteredBy;
vm.sort = sort;
vm.sortedBy = sortedBy;
vm.reload = reload;
vm.cancelSearch = cancelSearch;
vm.mode = { search: false };
@@ -39,10 +40,12 @@
});
// Refresh current list when the list of calendars is modified
$scope.$on('calendars:list', function() {
$rootScope.$on('calendars:list', function() {
Component.$filter(vm.componentType, { reload: true });
});
$rootScope.$on('calendar:dragend', updateComponentFromGhost);
// Switch between components tabs
function selectComponentType(type, options) {
if (options && options.reload || vm.componentType != type) {
@@ -111,17 +114,21 @@
});
}
function newComponent($event) {
function newComponent($event, baseComponent) {
var type = 'appointment', component;
if (vm.componentType == 'tasks')
type = 'task';
component = new Component({ pid: 'personal', type: type });
if (baseComponent)
component = baseComponent;
else
// TODO respect SOGoDefaultCalendar
component = new Component({ pid: 'personal', type: type });
// UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox or
// UI/Templates/SchedulerUI/UIxTaskEditorTemplate.wox
var templateUrl = 'UIx' + type.capitalize() + 'EditorTemplate';
$mdDialog.show({
return $mdDialog.show({
parent: angular.element(document.body),
targetEvent: $event,
clickOutsideToClose: true,
@@ -135,6 +142,84 @@
});
}
// Adjust component or create new component through drag'n'drop
function updateComponentFromGhost($event) {
var component, pointerHandler, coordinates, delta, params;
component = Component.$ghost.component;
pointerHandler = Component.$ghost.pointerHandler;
if (component.isNew) {
coordinates = pointerHandler.currentEventCoordinates;
component.setDelta(coordinates.duration * 15);
newComponent(null, component).finally(function() {
$timeout(function() {
Component.$ghost.pointerHandler = null;
Component.$ghost.component = null;
});
});
}
else {
delta = pointerHandler.currentEventCoordinates.getDelta(pointerHandler.originalEventCoordinates);
params = {
days: delta.dayNumber,
start: delta.start * 15,
duration: delta.duration * 15
};
if (component.isException || !component.occurrenceId)
// Component is an exception to a recurrence or is not recurrent;
// Immediately perform the adjustments
component.$adjust(params).then(function() {
$rootScope.$emit('calendars:list');
$timeout(function() {
Component.$ghost = {};
});
});
else if (component.occurrenceId) {
$mdDialog.show({
clickOutsideToClose: true,
escapeToClose: true,
locals: {
component: component,
params: params
},
template: [
'<md-dialog>',
' <md-dialog-content class="md-dialog-content">',
' <p>' + l('editRepeatingItem') + '</p>',
' </md-dialog-content>',
' <div class="md-actions">',
' <md-button ng-click="updateThisOccurrence()">' + l('button_thisOccurrenceOnly') + '</md-button>',
' <md-button ng-click="updateAllOccurrences()">' + l('button_allOccurrences') + '</md-button>',
' </div>',
'</md-dialog>'
].join(''),
controller: RecurrentComponentDialogController
}).then(function() {
$rootScope.$emit('calendars:list');
}).finally(function() {
$timeout(function() {
Component.$ghost = {};
});
});
}
}
/**
* @ngInject
*/
RecurrentComponentDialogController.$inject = ['$scope', '$mdDialog', 'component', 'params'];
function RecurrentComponentDialogController($scope, $mdDialog, component, params) {
$scope.updateThisOccurrence = function() {
component.$adjust(params).then($mdDialog.hide, $mdDialog.cancel);
};
$scope.updateAllOccurrences = function() {
delete component.occurrenceId;
component.$adjust(params).then($mdDialog.hide, $mdDialog.cancel);
};
}
}
function filter(filterpopup) {
Component.$filter(vm.componentType, { filterpopup: filterpopup });
}
@@ -151,6 +236,10 @@
return Component['$query' + vm.componentType.capitalize()].sort == field;
}
function reload() {
$rootScope.$emit('calendars:list');
}
function cancelSearch() {
vm.mode.search = false;
Component.$filter(vm.componentType, { value: '' });
@@ -6,8 +6,8 @@
/**
* @ngInject
*/
CalendarsController.$inject = ['$scope', '$window', '$mdDialog', '$log', 'sgFocus', 'Dialog', 'sgSettings', 'Calendar', 'User', 'stateCalendars'];
function CalendarsController($scope, $window, $mdDialog, $log, focus, Dialog, Settings, Calendar, User, stateCalendars) {
CalendarsController.$inject = ['$rootScope', '$scope', '$window', '$mdDialog', '$log', 'sgFocus', 'Dialog', 'sgSettings', 'Calendar', 'User', 'stateCalendars'];
function CalendarsController($rootScope, $scope, $window, $mdDialog, $log, focus, Dialog, Settings, Calendar, User, stateCalendars) {
var vm = this;
vm.activeUser = Settings.activeUser;
@@ -42,7 +42,7 @@
_.each(ids, function(id) {
var calendar = Calendar.$get(id);
calendar.$setActivation().then(function() {
$scope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
});
});
}
@@ -77,7 +77,7 @@
// Unsubscribe without confirmation
folder.$delete()
.then(function() {
$scope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
}, function(data, status) {
Dialog.alert(l('An error occured while deleting the calendar "%{0}".', folder.name),
l(data.error));
@@ -88,7 +88,7 @@
.then(function() {
folder.$delete()
.then(function() {
$scope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
}, function(data, status) {
Dialog.alert(l('An error occured while deleting the calendar "%{0}".', folder.name),
l(data.error));
@@ -46,7 +46,8 @@
$queryEvents: { sort: 'start', asc: 1, filterpopup: 'view_next7' },
// Filter parameters specific to tasks
$queryTasks: { sort: 'status', asc: 1, filterpopup: 'view_incomplete' },
$refreshTimeout: null
$refreshTimeout: null,
$ghost: {}
});
Preferences.ready().then(function() {
// Initialize filter parameters from user's settings
@@ -274,7 +275,7 @@
* @returns a promise of a collection of objects describing the events blocks
*/
Component.$eventsBlocks = function(view, startDate, endDate) {
var params, futureComponentData, i, dates = [],
var params, futureComponentData, i, j, dates = [],
deferred = Component.$q.defer();
params = { view: view.toLowerCase(), sd: startDate.getDayString(), ed: endDate.getDayString() };
@@ -287,12 +288,14 @@
var componentData = _.object(this.eventsFields, eventData),
start = new Date(componentData.c_startdate * 1000);
componentData.hour = start.getHourString();
componentData.blocks = [];
objects.push(new Component(componentData));
return objects;
};
associateComponent = function(block) {
block.component = this[block.nbr];
this[block.nbr].blocks.push(block); // Associate block to component
block.component = this[block.nbr]; // Associate component to block
};
Component.$views = [];
@@ -300,6 +303,12 @@
_.forEach(views, function(data) {
var components = [], blocks = {}, allDayBlocks = {}, viewData;
// Change some attributes names
data.eventsFields.splice(_.indexOf(data.eventsFields, 'c_folder'), 1, 'pid');
data.eventsFields.splice(_.indexOf(data.eventsFields, 'c_name'), 1, 'id');
data.eventsFields.splice(_.indexOf(data.eventsFields, 'c_recurrence_id'), 1, 'occurrenceId');
data.eventsFields.splice(_.indexOf(data.eventsFields, 'c_title'), 1, 'summary');
// Instantiate Component objects
_.reduce(data.events, reduceComponent, components, data);
@@ -318,14 +327,32 @@
// Convert array of blocks to object with days as keys
for (i = 0; i < data.blocks.length; i++) {
for (j = 0; j < data.blocks[i].length; j++)
data.blocks[i][j].dayNumber = i;
blocks[dates[i]] = data.blocks[i];
}
// Convert array of all-day blocks to object with days as keys
for (i = 0; i < data.allDayBlocks.length; i++) {
for (j = 0; j < data.allDayBlocks[i].length; j++)
data.allDayBlocks[i][j].dayNumber = i;
allDayBlocks[dates[i]] = data.allDayBlocks[i];
}
// "blocks" is now an object literal with the following structure:
// { day: [
// { start: number,
// length: number,
// siblings: number,
// realSiblings: number,
// position: number,
// nbr: number,
// component: Component },
// .. ],
// .. }
//
// Where day is a string with format YYYYMMDD
Component.$log.debug('blocks ready (' + _.flatten(data.blocks).length + ')');
Component.$log.debug('all day blocks ready (' + _.flatten(data.allDayBlocks).length + ')');
@@ -360,6 +387,9 @@
return futureComponentData.then(function(data) {
return Component.$timeout(function() {
var fields = _.invoke(data.fields, 'toLowerCase');
fields.splice(_.indexOf(fields, 'c_folder'), 1, 'pid');
fields.splice(_.indexOf(fields, 'c_name'), 1, 'id');
fields.splice(_.indexOf(fields, 'c_recurrence_id'), 1, 'occurrenceId');
// Instanciate Component objects
_.reduce(data[type], function(components, componentData, i) {
@@ -582,7 +612,7 @@
* @returns true if the percent completion should be displayed
*/
Component.prototype.enablePercentComplete = function() {
return (this.component = 'vtodo' &&
return (this.type == 'task' &&
this.status != 'not-specified' &&
this.status != 'cancelled');
};
@@ -718,7 +748,7 @@
Component.prototype.getClassName = function(base) {
if (angular.isUndefined(base))
base = 'fg';
return base + '-folder' + (this.destinationCalendar || this.c_folder);
return base + '-folder' + (this.destinationCalendar || this.c_folder || this.pid);
};
/**
@@ -884,7 +914,7 @@
};
/**
* @function reply
* @function $reply
* @memberof Component.prototype
* @desc Reply to an invitation.
* @returns a promise of the HTTP operation
@@ -909,6 +939,27 @@
});
};
/**
* @function $adjust
* @memberof Component.prototype
* @desc Adjust the start, day, and/or duration of the component
* @returns a promise of the HTTP operation
*/
Component.prototype.$adjust = function(params) {
var path = [this.pid, this.id];
if (_.every(_.values(params), function(v) { return v === 0; }))
// No changes
return Component.$q.when();
if (this.occurrenceId)
path.push(this.occurrenceId);
Component.$log.debug('adjust ' + path.join('/') + ' ' + JSON.stringify(params));
return Component.$$resource.save(path.join('/'), params, { action: 'adjust' });
};
/**
* @function $save
* @memberof Component.prototype
@@ -979,7 +1030,9 @@
Component.prototype.$omit = function() {
var component = {}, date;
angular.forEach(this, function(value, key) {
if (key != 'constructor' && key[0] != '$') {
if (key != 'constructor' &&
key[0] != '$' &&
key != 'blocks') {
component[key] = angular.copy(value);
}
});
@@ -23,7 +23,7 @@
// Load all attributes of component
if (angular.isUndefined(vm.component.$futureComponentData)) {
component = Calendar.$get(vm.component.c_folder).$getComponent(vm.component.c_name, vm.component.c_recurrence_id);
component = Calendar.$get(vm.component.pid).$getComponent(vm.component.id, vm.component.occurrenceId);
component.$futureComponentData.then(function() {
vm.component = component;
vm.organizer = [vm.component.organizer];
@@ -72,7 +72,7 @@
var c = component || vm.component;
c.$reply().then(function() {
$rootScope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
$mdDialog.hide();
Alarm.getAlarms();
});
@@ -94,14 +94,14 @@
function deleteOccurrence() {
vm.component.remove(true).then(function() {
$rootScope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
$mdDialog.hide();
});
}
function deleteAllOccurrences() {
vm.component.remove().then(function() {
$rootScope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
$mdDialog.hide();
});
}
@@ -222,7 +222,7 @@
if (form.$valid) {
vm.component.$save()
.then(function(data) {
$rootScope.$broadcast('calendars:list');
$rootScope.$emit('calendars:list');
$mdDialog.hide();
Alarm.getAlarms();
}, function(data, status) {
@@ -237,7 +237,7 @@
// Cancelling the creation of a component
vm.component = null;
}
$mdDialog.hide();
$mdDialog.cancel();
}
function getDays() {
@@ -29,8 +29,8 @@
})
.state('calendars.view', {
url: '/{view:(?:day|week|month|multicolumnday)}/:day',
sticky: true,
deepStateRedirect: true,
//sticky: true,
//deepStateRedirect: true,
views: {
calendarView: {
templateUrl: function($stateParams) {
@@ -93,6 +93,8 @@
.then(function(views) {
_.forEach(views, function(view) {
if (view.id) {
// Note: this can't be done in Component service since it would make Component dependent on
// the Calendar service and create a circular dependency
view.calendar = new Calendar({ id: view.id, name: view.calendarName });
}
});
@@ -0,0 +1,51 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
(function() {
/* jshint validthis: true */
'use strict';
/*
* sgCalendarDay - An element that represents a day in the calendar's view
* @memberof SOGo.SchedulerUI
* @restrict element
* @param {string} sgDay - the day of the events to display (YYYYMMDD)
* @param {string} sgDayString - the day in ISO8601 format (YYYY-MM-DDTHH:MM+-HH:MM)
* @param {number} sgDayNumber - the day index within the calendar's view
*
* @example:
<sg-calendar-day
sg-day-string="2015-11-01T00:00-05:00"
sg-day-number="0"
sg-day="20151101">
..
</sg-calendar-day-table>
*/
function sgCalendarDay() {
return {
restrict: 'E',
scope: {
day: '@sgDay',
dayNumber: '@sgDayNumber',
dayString: '@sgDayString'
},
controller: sgCalendarDayController
};
}
/**
* @ngInject
*/
sgCalendarDayController.$inject = ['$scope'];
function sgCalendarDayController($scope) {
// Expose some scope variables to the controller
// See the sgCalendarDayTable directive
this.day = $scope.day;
this.dayNumber = $scope.dayNumber;
this.dayString = $scope.dayString;
}
angular
.module('SOGo.SchedulerUI')
.directive('sgCalendarDay', sgCalendarDay);
})();
@@ -5,7 +5,7 @@
/*
* sgCalendarDayBlock - An event block to be displayed in a week
* @memberof SOGo.Common
* @memberof SOGo.SchedulerUI
* @restrict element
* @param {object} sgBlock - the event block definition
* @param {function} sgClick - the function to call when clicking on a block.
@@ -19,7 +19,8 @@
sg-block="block"
sg-click="open(clickEvent, clickComponent)" />
*/
function sgCalendarDayBlock() {
sgCalendarDayBlock.$inject = ['CalendarSettings'];
function sgCalendarDayBlock(CalendarSettings) {
return {
restrict: 'E',
scope: {
@@ -28,33 +29,33 @@
},
replace: true,
template: [
'<div class="sg-event sg-draggable">',
'<div class="sg-event sg-draggable-calendar-block"',
// Add a class while dragging
' ng-class="{\'sg-event--dragging\': block.dragging}">',
' <div class="eventInside" ng-click="clickBlock({clickEvent: $event, clickComponent: block.component})">',
' <div class="gradient">',
' </div>',
' <div class="text">{{ block.component.c_title }}',
' <span class="icons">',
// Component has an alarm
' <md-icon ng-if="block.component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
// Component is confidential
' <md-icon ng-if="block.component.c_classification == 1" class="material-icons icon-visibility-off"></md-icon>',
// Component is private
' <md-icon ng-if="block.component.c_classification == 2" class="material-icons icon-vpn-key"></md-icon>',
' </span></div>',
' <div class="text">{{ block.component.summary }}',
' <span class="icons">',
// Component has an alarm
' <md-icon ng-if="block.component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
// Component is confidential
' <md-icon ng-if="block.component.c_classification == 1" class="material-icons icon-visibility-off"></md-icon>',
// Component is private
' <md-icon ng-if="block.component.c_classification == 2" class="material-icons icon-vpn-key"></md-icon>',
' </span>',
' </div>',
' <div class="topDragGrip"></div>',
' <div class="bottomDragGrip"></div>',
' </div>',
'</div>'
].join(''),
link: link
};
function link(scope, iElement, attrs) {
// Compute overlapping (2%)
var pc = 100 / scope.block.siblings,
left = scope.block.position * pc,
right = 100 - (scope.block.position + 1) * pc;
var pc, left, right;
// Compute overlapping (2%)
pc = 100 / scope.block.siblings;
left = scope.block.position * pc;
right = 100 - (scope.block.position + 1) * pc;
if (pc < 100) {
if (left > 0)
left -= 2;
@@ -73,7 +74,9 @@
iElement.css('right', right + '%');
iElement.addClass('starts' + scope.block.start);
iElement.addClass('lasts' + scope.block.length);
iElement.addClass('bg-folder' + scope.block.component.c_folder);
// Set background color
iElement.addClass('bg-folder' + scope.block.component.pid);
}
}
@@ -5,7 +5,7 @@
/*
* sgCalendarDayTable - Build list of blocks for a specific day
* @memberof SOGo.Common
* @memberof SOGo.SchedulerUI
* @restrict element
* @param {object} sgBlocks - the events blocks definitions for the current view
* @param {string} sgDay - the day of the events to display
@@ -29,7 +29,7 @@
clickBlock: '&sgClick'
},
template: [
'<sg-calendar-day-block class="sg-event draggable"',
'<sg-calendar-day-block',
' ng-repeat="block in blocks[day]"',
' sg-block="block"',
' sg-click="clickBlock({event: clickEvent, component: clickComponent})"/>'
@@ -26,7 +26,7 @@
template: [
'<div class="sg-event sg-draggable" ng-click="clickBlock({clickEvent: $event, clickComponent: block.component})">',
' <span ng-if="!block.component.c_isallday">{{ block.starthour }} - </span>',
' {{ block.component.c_title }}',
' {{ block.component.summary }}',
' <span class="icons">',
' <md-icon ng-if="block.component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
' <md-icon ng-if="block.component.c_classification == 1" class="material-icons icon-visibility-off"></md-icon>',
@@ -40,7 +40,7 @@
};
function link(scope, iElement, attrs) {
iElement.addClass('bg-folder' + scope.block.component.c_folder);
iElement.addClass('bg-folder' + scope.block.component.pid);
}
}
@@ -0,0 +1,181 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
(function() {
/* jshint validthis: true */
'use strict';
/*
* sgCalendarScrollView - scrollable view that contains draggable elements
* @memberof SOGo.SchedulerUI
* @restrict attribute
* @param {string} sgCalendarScrollView - the view type (multiday, multiday-allday, or monthly)
*
* @example:
<md-content sg-calendar-scroll-view="multiday">
..
</md-content>
*/
sgCalendarScrollView.$inject = ['$rootScope', '$window', '$document', '$q', '$timeout', '$mdGesture', 'Calendar', 'Component'];
function sgCalendarScrollView($rootScope, $window, $document, $q, $timeout, $mdGesture, Calendar, Component) {
return {
restrict: 'A',
scope: {
type: '@sgCalendarScrollView'
},
controller: sgCalendarScrollViewController,
link: function(scope, element, attrs, controller) {
var view, scrollView, type, lastScroll, deregisterDragStart, deregisterDragStop;
scrollView = element[0];
type = scope.type; // multiday, multiday-allday, monthly, unknown?
lastScroll = 0;
// Listen to dragstart and dragend events
deregisterDragStart = $rootScope.$on('calendar:dragstart', onDragStart);
deregisterDragStop = $rootScope.$on('calendar:dragend', onDragEnd);
// Update the "view" object literal once the Angular template has been transformed
$timeout(initView);
// Deregister listeners when destroying the view
scope.$on('$destroy', function() {
deregisterDragStart();
deregisterDragStop();
element.off('mouseover', updateFromPointerHandler);
angular.element($window).off('resize', updateCoordinates);
});
function initView() {
var quarterHeight;
// Quarter height doesn't change if window is resize; compute it only once
quarterHeight = getQuarterHeight();
view = {
type: type,
quarterHeight: quarterHeight,
scrollStep: 6 * quarterHeight,
maxX: getMaxColumns(),
// Expose a reference of the view element
element: scrollView
};
// Compute coordinates of view element; recompute it on window resize
angular.element($window).on('resize', updateCoordinates);
updateCoordinates();
}
function getQuarterHeight() {
var hour0, hour23, height;
hour0 = document.getElementById('hour0');
hour23 = document.getElementById('hour23');
height = ((hour23.offsetTop - hour0.offsetTop) / (23 * 4));
return height;
}
function getDayWidth(viewLeft) {
var width, offset, nodes, domRect;
width = 0;
offset = 0;
nodes = scrollView.getElementsByClassName('day0');
if (nodes.length > 0) {
domRect = nodes[0].getBoundingClientRect();
width = domRect.width;
offset = domRect.left - viewLeft;
}
return [width, offset];
}
function getMaxColumns() {
var max = 0;
//if (type == 'multiday') {
max = scrollView.getElementsByClassName('day').length - 1;
//}
return max;
}
// View has been resized;
// Compute the view's origins (x, y), a day's width (dayWidth) and the left margin (daysOffset).
function updateCoordinates() {
var domRect, dayWidth;
domRect = scrollView.getBoundingClientRect();
dayWidth = getDayWidth(domRect.left);
angular.extend(view, {
coordinates: {
x: domRect.left,
y: domRect.top
},
dayWidth: dayWidth[0],
daysOffset: dayWidth[1]
});
}
function onDragStart() {
element.on('mouseover', updateFromPointerHandler);
updateFromPointerHandler();
}
function onDragEnd() {
element.off('mouseover', updateFromPointerHandler);
Calendar.$view = null;
}
// From SOGoScrollController.updateFromPointerHandler
function updateFromPointerHandler() {
var scrollStep, pointerHandler, pointerCoordinates, now, scrollY, minY, delta;
scrollStep = view.scrollStep;
pointerHandler = Component.$ghost.pointerHandler;
if (pointerHandler) {
pointerCoordinates = pointerHandler.getContainerBasedCoordinates(view);
if (pointerCoordinates) {
// Pointer is inside view; Adjust scrollbar if necessary
Calendar.$view = view;
now = new Date().getTime();
if (!lastScroll || now > lastScroll + 100) {
lastScroll = now;
scrollY = pointerCoordinates.y - scrollStep;
if (scrollY < 0) {
minY = -scrollView.scrollTop;
if (scrollY < minY)
scrollY = minY;
scrollView.scrollTop += scrollY;
}
else {
scrollY = pointerCoordinates.y + scrollStep;
delta = scrollY - scrollView.clientHeight;
if (delta > 0) {
scrollView.scrollTop += delta;
}
}
}
}
}
}
}
};
}
sgCalendarScrollViewController.$inject = ['$scope'];
function sgCalendarScrollViewController($scope) {
// Expose the view type to the controller
// See sgCalendarDayBlockGhost
this.type = $scope.type;
}
angular
.module('SOGo.SchedulerUI')
.directive('sgCalendarScrollView', sgCalendarScrollView);
})();
@@ -0,0 +1,516 @@
/* -*- 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);
})();
+187 -25
View File
@@ -1,6 +1,6 @@
/// SchedulerUI.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
$hours_margin: 50px;
$scrollbar_width: 16px;
$block_margin: 2%; // See sgCalendarDayBlock.directive.js
/**
* Affected templates:
@@ -64,10 +64,13 @@ $scrollbar_width: 16px;
z-index: $z-index-toolbar - 1;
.days {
margin-left: $hours_margin;
margin-right: $scrollbar_width; // scrollbar
overflow-y: scroll;
&.dayLabels {
.day {
padding-left: 1%;
div {
overflow: hidden;
}
}
}
}
@@ -80,11 +83,11 @@ $scrollbar_width: 16px;
}
}
}
// &.monthView {
// > div {
// margin-right: $scrollbar_width;
// }
// }
&.monthView {
> div {
overflow-y: scroll;
}
}
}
// The all-day events appear in the shrinkable toolbar, bellow the days labels
@@ -92,17 +95,50 @@ $scrollbar_width: 16px;
border-bottom: 1px solid sg-color($sogoPaper, 300);
max-height: $sg-font-size-4 * 6;
.sg-event {
margin: 2%; // See sgCalendarDayBlock.directive.js
line-height: initial;
.day {
position: relative;
}
.gradient, .text {
.sg-event {
margin: $block_margin;
line-height: initial;
position: relative;
&--ghost {
position: absolute;
top: 0;
left: 0;
right: 0;
margin-left: 0;
margin-right: 0;
border-radius: 0;
padding-left: $block_margin;
padding-right: $block_margin;
&--first {
margin-left: $block_margin;
padding-left: 0;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
&--last {
margin-right: $block_margin;
padding-right: 0;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
.text {
position: relative;
}
}
md-content {
overflow-y: scroll;
overflow-x: hidden;
}
// Days row
.days {
display: flex;
@@ -115,21 +151,24 @@ $scrollbar_width: 16px;
.clickableHourCell {
height: 40px;
border-bottom: 1px solid sg-color($sogoPaper, 300);
user-select: none;
&.outOfDay {
background-color: $colorGrey50;
}
}
}
}
// Header of month tiles
.sg-calendar-tile-header {
font-size: $sg-font-size-2;
min-height: $sg-font-size-2 + 8px;
overflow: hidden;
padding: 4px;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
font-size: $sg-font-size-2;
padding: 4px;
min-height: $sg-font-size-2 + 8px;
text-align: right;
//font-weight: $sg-font-light;
overflow: hidden;
}
// The left column of hours
@@ -173,7 +212,8 @@ $scrollbar_width: 16px;
}
// Events from editable calendars are draggable
.sg-draggable {
.sg-draggable-calendar-block,
.sg-event--ghost {
cursor: move;
}
@@ -183,16 +223,135 @@ $scrollbar_width: 16px;
left: 0;
right: 0;
opacity: 0.9; // When events from a same calendar overlap, it creates a border to help distinguish the events
overflow: hidden;
user-select: none;
transition: $swift-linear;
$i: 0;
@while $i < 96 { // number of 15-minutes blocks in a day
&.starts#{$i} { top: 10px * $i; }
&.lasts#{$i} { height: 10px * $i; }
$i: $i + 1;
}
&--notransition {
transition: none;
}
&--ghost {
opacity: 1;
left: $block_margin;
right: $block_margin;
}
&--dragging {
background-image: repeating-linear-gradient(-45deg,
rgba(255,255,255,0.2),
rgba(255,255,255,0.2) 2px,
transparent 2px,
transparent 4px );
opacity: 0.5;
}
.eventInside {
overflow: hidden;
}
// Event DnD drag grips
&:hover {
.dragGrip {
&-top, &-bottom {
display: block;
cursor: ns-resize;
}
&-left, &-right {
display: block;
cursor: ew-resize;
}
}
}
.dragGrip {
&-top, &-bottom, &-left, &-right {
display: none;
position: absolute;
}
&-top, &-bottom {
left: 1px;
right: 1px;
height: 10px;
line-height: 8px;
}
&-top {
top: 0;
.dragGrip {
right: 0;
top: -3px;
}
}
&-bottom {
bottom: 0;
.dragGrip {
bottom: -3px;
}
}
&-left, &-right {
top: 1px;
bottom: 1px;
width: 10px;
line-height: 8px;
}
&-left {
left: -$block_margin;
.dragGrip {
bottom: 0;
left: -1px;
}
}
&-right {
right: -$block_margin;
.dragGrip {
right: -1px;
}
}
}
.dragGrip {
background-color: white;
border-radius: 50%;
border-style: solid;
border-width: 1px;
display: inline-block;
height: 8px;
position: absolute;
width: 8px;
}
// Event DnD ghost start/end hours
.ghostStartHour,
.ghostEndHour {
position: absolute;
width: 100%;
vertical-align: baseline;
height: 14px;
left: 0px;
color: #222;
text-align: center;
}
.ghostStartHour {
top: -14px;
}
.ghostEndHour {
bottom: -14px;
}
}
// Multicolumn day cell that contains the calendar name
@@ -250,8 +409,8 @@ $scrollbar_width: 16px;
position: relative;
border-radius: 1px;
overflow: hidden;
padding: 2%;
margin: 2%;
padding: $block_margin;
margin: $block_margin;
text-overflow: ellipsis;
white-space: nowrap;
span { // hours
@@ -269,13 +428,15 @@ $scrollbar_width: 16px;
border-bottom: 1px dotted sg-color($sogoPaper, 300);
}
.gradient, .text {
.text {
position: absolute;
top: 1px;
left: 4px;
right: 2px;
bottom: 1px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0 2px;
overflow: hidden;
line-height: $sg-font-size-2;
}
.gradient > IMG {
@@ -311,6 +472,7 @@ $scrollbar_width: 16px;
}
md-list-item {
padding-left: 0;
padding-right: 0;
&:hover {
background-color: initial;
}