Initial ng/md version of the attendees editor

This commit is contained in:
Francis Lachapelle
2015-06-12 12:05:06 -04:00
parent a2383db454
commit a95ed0f8e6
7 changed files with 463 additions and 144 deletions
+60
View File
@@ -1,3 +1,5 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
@@ -101,6 +103,52 @@ String.prototype.asDate = function () {
return newDate;
};
String.prototype.formatTime = function(hours, minutes) {
var newString = this;
// See http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSCalendarDate.html#method$NSCalendarDate-descriptionWithCalendarFormat$
var p = 'am', i = hours, m = minutes;
if (hours > 12) {
p = 'pm';
i = hours % 12;
}
if (minutes < 10) {
m = '0' + minutes;
}
// %H : hour as a decimal number using 24-hour clock
newString = newString.replace("%H", hours < 10 ? '0' + hours : hours);
// %I : hour as a decimal number using 12-hour clock
newString = newString.replace("%I", i < 10 ? '0' + i : i);
// %M : minute as decimal number
newString = newString.replace("%M", m);
// %p : 'am' or 'pm'
newString = newString.replace("%p", p);
return newString;
};
Date.prototype.daysUpTo = function(otherDate) {
var days = new Array();
var day1 = this.getTime();
var day2 = otherDate.getTime();
if (day1 > day2) {
var tmp = day1;
day1 = day2;
day2 = tmp;
}
var nbrDays = Math.round((day2 - day1) / 86400000) + 1;
for (var i = 0; i < nbrDays; i++) {
var newDate = new Date();
newDate.setTime(day1 + (i * 86400000));
days.push(newDate);
}
return days;
};
String.prototype.asCSSIdentifier = function() {
var characters = [ '_' , '\\.', '#' , '@' , '\\*', ':' , ',' , ' '
, "'", '&', '\\+' ];
@@ -145,6 +193,18 @@ Date.prototype.addDays = function(nbrDays) {
this.setTime(milliSeconds);
};
Date.prototype.addHours = function(nbrHours) {
var milliSeconds = this.getTime();
milliSeconds += 3600000 * nbrHours;
this.setTime(milliSeconds);
};
Date.prototype.addMinutes = function(nbrMinutes) {
var milliSeconds = this.getTime();
milliSeconds += 60000 * nbrMinutes;
this.setTime(milliSeconds);
};
Date.prototype.beginOfDay = function() {
var beginOfDay = new Date(this.getTime());
beginOfDay.setHours(0);
@@ -25,7 +25,7 @@
this.$unwrap(futureComponentData);
}
}
/**
* @memberof Component
* @desc The factory we'll use to register with Angular
@@ -40,6 +40,11 @@
$categories: window.UserDefaults.SOGoCalendarCategoriesColors
});
if (window.UserDefaults && window.UserDefaults.SOGoTimeFormat)
Component.timeFormat = window.UserDefaults.SOGoTimeFormat;
else
Component.timeFormat = "%H:%M";
return Component; // return constructor
}];
@@ -236,10 +241,17 @@
* @param {object} data - attributes of component
*/
Component.prototype.init = function(data) {
var _this = this;
this.categories = [];
this.repeat = {};
angular.extend(this, data);
if (this.startDate)
this.start = new Date(this.startDate.substring(0,10) + ' ' + this.startDate.substring(11,16));
if (this.endDate)
this.end = new Date(this.endDate.substring(0,10) + ' ' + this.endDate.substring(11,16));
// Parse recurrence rule definition and initialize default values
if (this.repeat.days) {
var byDayMask = _.find(this.repeat.days, function(o) {
@@ -281,8 +293,23 @@
// Allow the event to be moved to a different calendar
this.destinationCalendar = this.pid;
// Load freebusy of attendees
this.freebusy = this.updateFreeBusyCoverage();
if (this.attendees) {
_.each(this.attendees, function(attendee) {
_this.updateFreeBusy(attendee);
});
}
};
/**
* @function hasCustomRepeat
* @memberof Component.prototype
* @desc Check if the component has a custom recurrence rule.
* @returns true if the the recurrence rule requires the full recurrence editor
*/
Component.prototype.hasCustomRepeat = function() {
var b = angular.isDefined(this.repeat) &&
(this.repeat.interval > 1 ||
@@ -292,6 +319,114 @@
return b;
};
/**
* @function coversFreeBusy
* @memberof Component.prototype
* @desc Check if a specific quarter matches the component's period
* @returns true if the quarter covers the component's period
*/
Component.prototype.coversFreeBusy = function(day, hour, quarter) {
var b = (angular.isDefined(this.freebusy[day]) &&
angular.isDefined(this.freebusy[day][hour]) &&
this.freebusy[day][hour][quarter] == 1);
return b;
};
/**
* @function updateFreeBusyCoverage
* @memberof Component.prototype
* @desc Build a 15-minute-based representation of the component's period.
* @returns an object literal hashed by days and hours and arrays of four 1's and 0's
*/
Component.prototype.updateFreeBusyCoverage = function() {
var _this = this, freebusy = {};
if (this.start && this.end) {
var roundedStart = new Date(this.start.getTime()),
roundedEnd = new Date(this.end.getTime()),
startQuarter = parseInt(roundedStart.getMinutes()/15 + 0.5),
endQuarter = parseInt(roundedEnd.getMinutes()/15 + 0.5);
roundedStart.setMinutes(15*startQuarter);
roundedEnd.setMinutes(15*endQuarter);
_.each(roundedStart.daysUpTo(roundedEnd), function(date, index) {
var currentDay = date.getDate(),
dayKey = date.getDayString(),
hourKey;
if (dayKey == _this.start.getDayString()) {
hourKey = date.getHours().toString();
freebusy[dayKey] = {};
freebusy[dayKey][hourKey] = [];
while (startQuarter > 0) {
freebusy[dayKey][hourKey].push(0);
startQuarter--;
}
}
else {
date = date.beginOfDay();
freebusy[dayKey] = {};
}
while (date.getTime() < _this.end.getTime() &&
date.getDate() == currentDay) {
hourKey = date.getHours().toString();
if (angular.isUndefined(freebusy[dayKey][hourKey]))
freebusy[dayKey][hourKey] = [];
freebusy[dayKey][hourKey].push(1);
date.addMinutes(15);
}
});
return freebusy;
}
};
/**
* @function updateFreeBusy
* @memberof Component.prototype
* @desc Update the freebusy information for the component's period for a specific attendee.
* @param {Object} card - an Card object instance of the attendee
*/
Component.prototype.updateFreeBusy = function(attendee) {
var params, url, days;
if (attendee.uid) {
params =
{
sday: this.start.getDayString(),
eday: this.end.getDayString()
};
url = ['..', '..', attendee.uid, 'freebusy.ifb'];
days = _.map(this.start.daysUpTo(this.end), function(day) { return day.getDayString(); });
if (angular.isUndefined(attendee.freebusy))
attendee.freebusy = {};
// Fetch FreeBusy information
Component.$$resource.fetch(url.join('/'), 'ajaxRead', params).then(function(data) {
_.each(days, function(day) {
var hour;
if (angular.isUndefined(attendee.freebusy[day]))
attendee.freebusy[day] = {};
if (angular.isUndefined(data[day]))
data[day] = {};
for (var i = 0; i <= 23; i++) {
hour = i.toString();
if (data[day][hour])
attendee.freebusy[day][hour] = [
data[day][hour]["0"],
data[day][hour]["15"],
data[day][hour]["30"],
data[day][hour]["45"]
];
else
attendee.freebusy[day][hour] = [0, 0, 0, 0];
}
});
});
}
};
/**
* @function getClassName
* @memberof Component.prototype
@@ -305,6 +440,34 @@
return base + '-folder' + (this.destinationCalendar || this.c_folder);
};
/**
* @function addAttendee
* @memberof Component.prototype
* @desc Add an attendee and fetch his freebusy info.
* @param {Object} card - an Card object instance to be added to the attendees list
*/
Component.prototype.addAttendee = function(card) {
var attendee, url, params;
if (card) {
attendee = {
name: card.c_cn,
email: card.$preferredEmail(),
role: 'req-participant',
status: 'needs-action',
uid: card.c_uid
};
if (!_.find(this.attendees, function(o) {
return o.email == attendee.email;
})) {
if (this.attendees)
this.attendees.push(attendee);
else
this.attendees = [attendee];
this.updateFreeBusy(attendee);
}
}
};
/**
* @function $reset
* @memberof Component.prototype
@@ -343,7 +506,7 @@
/**
* @function $unwrap
* @memberof Component.prototype
* @desc Unwrap a promise.
* @desc Unwrap a promise.
* @param {promise} futureComponentData - a promise of some of the Component's data
*/
Component.prototype.$unwrap = function(futureComponentData) {
@@ -6,8 +6,8 @@
/**
* @ngInject
*/
ComponentController.$inject = ['$scope', '$log', '$timeout', '$state', '$previousState', '$mdSidenav', '$mdDialog', 'Calendar', 'Component', 'stateCalendars', 'stateComponent'];
function ComponentController($scope, $log, $timeout, $state, $previousState, $mdSidenav, $mdDialog, Calendar, Component, stateCalendars, stateComponent) {
ComponentController.$inject = ['$scope', '$log', '$q', '$timeout', '$state', '$previousState', '$mdSidenav', '$mdDialog', 'User', 'Calendar', 'Component', 'AddressBook', 'Card', 'stateCalendars', 'stateComponent'];
function ComponentController($scope, $log, $q, $timeout, $state, $previousState, $mdSidenav, $mdDialog, User, Calendar, Component, AddressBook, Card, stateCalendars, stateComponent) {
var vm = this;
vm.calendars = stateCalendars;
@@ -15,8 +15,19 @@
vm.categories = {};
vm.showRecurrenceEditor = vm.event.$hasCustomRepeat;
vm.toggleRecurrenceEditor = toggleRecurrenceEditor;
vm.showAttendeesEditor = angular.isDefined(vm.event.attendees);
vm.toggleAttendeesEditor = toggleAttendeesEditor;
vm.cardFilter = cardFilter;
vm.cardResults = [];
vm.addAttendee = addAttendee;
vm.cancel = cancel;
vm.save = save;
vm.attendeesEditor = {
startDate: vm.event.startDate,
endDate: vm.event.endDate,
days: getDays(),
hours: getHours()
};
// Open sidenav when loading the view;
// Return to previous state when closing the sidenav.
@@ -36,11 +47,59 @@
}, 100); // don't ask why
});
$scope.$watch('editor.event.startDate', function(newStartDate, oldStartDate) {
if (newStartDate) {
$timeout(function() {
vm.event.start = new Date(newStartDate.substring(0,10) + ' ' + newStartDate.substring(11,16));
vm.event.freebusy = vm.event.updateFreeBusyCoverage();
vm.attendeesEditor.days = getDays();
});
}
});
$scope.$watch('editor.event.endDate', function(newEndDate, oldEndDate) {
if (newEndDate) {
$timeout(function() {
vm.event.end = new Date(newEndDate.substring(0,10) + ' ' + newEndDate.substring(11,16));
vm.event.freebusy = vm.event.updateFreeBusyCoverage();
vm.attendeesEditor.days = getDays();
});
}
});
function toggleRecurrenceEditor() {
vm.showRecurrenceEditor = !vm.showRecurrenceEditor;
vm.event.$hasCustomRepeat = vm.showRecurrenceEditor;
}
function toggleAttendeesEditor() {
vm.showAttendeesEditor = !vm.showAttendeesEditor;
}
// Autocomplete cards for attendees
function cardFilter($query) {
if ($query) {
AddressBook.$filterAll($query).then(function(results) {
vm.cardResults.splice(0, vm.cardResults.length);
_.each(results, function(card) {
// TODO don't show cards matching an attendee's email address
vm.cardResults.push(card);
});
});
}
return vm.cardResults;
}
function addAttendee(card) {
if (angular.isString(card)) {
// User pressed "Enter" in search field, adding a non-matching card
// TODO: only create card if the string is an email address
card = new Card({ emails: [{ value: card }] });
vm.searchText = '';
}
vm.event.addAttendee(card);
}
function save(form) {
if (form.$valid) {
vm.event.$save()
@@ -61,6 +120,27 @@
}
$mdSidenav('right').close();
}
function getDays() {
var days = [];
if (vm.event.start && vm.event.end)
days = vm.event.start.daysUpTo(vm.event.end);
return _.map(days, function(date) {
return { stringWithSeparator: date.stringWithSeparator(),
getDayString: date.getDayString() };
});
}
function getHours() {
var hours = [];
for (var i = 0; i <= 23; i++) {
//hours.push(Component.timeFormat.formatTime(i, 0));
hours.push(i.toString());
}
return hours;
}
}
angular