mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-07-05 08:34:30 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user