diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index be2f8421a..f48c9c023 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -562,10 +562,10 @@ * @apiSuccess (Success 200) {String} summary Summary * @apiSuccess (Success 200) {String} [location] Location * @apiSuccess (Success 200) {String} [comment] Comment - * @apiSuccess (Success 200) {String} [status] Status + * @apiSuccess (Success 200) {String} [status] Status (tentative, confirmed, or cancelled) * @apiSuccess (Success 200) {String} [attachUrl] Attached URL * @apiSuccess (Success 200) {String} [createdBy] Value of custom header X-SOGo-Component-Created-By or organizer's "SENT-BY" - * @apiSuccess (Success 200) {Number} priority Priority + * @apiSuccess (Success 200) {Number} priority Priority (0-9) * @apiSuccess (Success 200) {NSString} [classification] Either public, confidential or private * @apiSuccess (Success 200) {String[]} [categories] Categories * @apiSuccess (Success 200) {Object} [organizer] Appointment organizer diff --git a/UI/Scheduler/UIxCalMainView.m b/UI/Scheduler/UIxCalMainView.m index e13a05738..5c70e6263 100644 --- a/UI/Scheduler/UIxCalMainView.m +++ b/UI/Scheduler/UIxCalMainView.m @@ -328,3 +328,53 @@ } @end + +@interface UIxAppointmentEditorTemplate : UIxComponent +{ + id item; +} +@end + +@implementation UIxAppointmentEditorTemplate + +- (void) dealloc +{ + [item release]; + [super dealloc]; +} + +- (void) setItem: (id) _item +{ + ASSIGN (item, _item); +} + +- (id) item +{ + return item; +} + +- (NSArray *) repeatList +{ + static NSArray *repeatItems = nil; + + if (!repeatItems) + { + repeatItems = [NSArray arrayWithObjects: @"daily", + @"weekly", + @"bi-weekly", + @"every_weekday", + @"monthly", + @"yearly", + nil]; + [repeatItems retain]; + } + + return repeatItems; +} + +- (NSString *) itemRepeatText +{ + return [self labelForKey: [NSString stringWithFormat: @"repeat_%@", [item uppercaseString]]]; +} + +@end diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index a224fbc4d..a1724c67b 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -130,6 +130,10 @@ pageName = "UIxCalUserRightsEditor"; actionName = "saveUserRights"; }; + UIxAppointmentEditorTemplate = { + protectedBy = "View"; + pageName = "UIxAppointmentEditorTemplate"; + }; editAttendees = { protectedBy = "View"; pageName = "UIxAttendeesEditor"; @@ -250,7 +254,7 @@ }; raw = { protectedBy = "ViewAllComponent"; - actionClass = "UIxComponentEditor"; + actionClass = "UIxAppointmentEditor"; actionName = "raw"; }; adjust = { @@ -299,7 +303,7 @@ }; raw = { protectedBy = "ViewAllComponent"; - pageName = "UIxComponentEditor"; + pageName = "UIxTaskEditor"; actionName = "raw"; }; }; diff --git a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox new file mode 100644 index 000000000..95171900a --- /dev/null +++ b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox @@ -0,0 +1,129 @@ + + +
+ + + + + + + + + + + + + + +
+ + + {{category}} + + +
+ +
+ + + {{calendar.name}} + +
+ +
+
+ +
+ + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+ +
+ + + + + + + + + +
+
+
+
diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index ba659dc00..2fc0033cc 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -264,7 +264,6 @@
+ diff --git a/UI/Templates/UIxPageFrame.wox b/UI/Templates/UIxPageFrame.wox index a780fad43..a43307a88 100644 --- a/UI/Templates/UIxPageFrame.wox +++ b/UI/Templates/UIxPageFrame.wox @@ -220,6 +220,7 @@ + diff --git a/UI/WebServerResources/Gruntfile.js b/UI/WebServerResources/Gruntfile.js index 7952eb17e..4c4d22c87 100644 --- a/UI/WebServerResources/Gruntfile.js +++ b/UI/WebServerResources/Gruntfile.js @@ -105,6 +105,7 @@ module.exports = function(grunt) { '<%= src %>/angular-aria/angular-aria{,.min}.js{,.map}', '<%= src %>/angular-material/angular-material{,.min}.js{,.map}', '<%= src %>/angular-ui-router/release/angular-ui-router{,.min}.js', + '<%= src %>/ui-router-extras/release/ct-ui-router-extras{,.min}.js', '<%= src %>/angular-recursion/angular-recursion{,.min}.js', '<%= src %>/angular-vs-repeat/src/angular-vs-repeat{,.min}.js', '<%= src %>/angular-file-upload/angular-file-upload{,.min}.js{,map}', diff --git a/UI/WebServerResources/bower.json b/UI/WebServerResources/bower.json index 0e2cbc367..f93476ae5 100644 --- a/UI/WebServerResources/bower.json +++ b/UI/WebServerResources/bower.json @@ -6,6 +6,7 @@ "angular-animate": "1.3.x", "angular-sanitize": "1.3.x", "angular-ui-router": "latest", + "ui-router-extras": "latest", "angular-recursion": "latest", "angular-vs-repeat": "latest", "angular-file-upload": "latest", diff --git a/UI/WebServerResources/js/Appointments/component-model.js b/UI/WebServerResources/js/Appointments/component-model.js index 59905fcef..dab6b1f62 100644 --- a/UI/WebServerResources/js/Appointments/component-model.js +++ b/UI/WebServerResources/js/Appointments/component-model.js @@ -11,7 +11,7 @@ function Component(futureComponentData) { // Data is immediately available if (typeof futureComponentData.then !== 'function') { - angular.extend(this, futureComponentData); + this.init(futureComponentData); } else { // The promise will be unwrapped first @@ -29,7 +29,8 @@ $q: $q, $timeout: $timeout, $log: $log, - $$resource: new Resource(Settings.baseURL, Settings.activeUser) + $$resource: new Resource(Settings.baseURL, Settings.activeUser), + $categories: window.UserDefaults.SOGoCalendarCategoriesColors }); return Component; // return constructor @@ -73,6 +74,33 @@ return this.$unwrapCollection(type, futureComponentData); }; + /** + * @memberof Card + * @desc Fetch a card from a specific addressbook. + * @param {string} addressbook_id - the addressbook ID + * @param {string} card_id - the card ID + * @see {@link AddressBook.$getCard} + */ + Component.$find = function(calendarId, componentId) { + var futureComponentData = this.$$resource.fetch([calendarId, componentId].join('/'), 'view'); + + return new Component(futureComponentData); + }; + + /** + * @function filterCategories + * @memberof Component.prototype + * @desc Search for categories matching some criterias + * @param {string} search - the search string to match + * @returns a collection of strings + */ + Component.filterCategories = function(query) { + var re = new RegExp(query, 'i'); + return _.filter(_.keys(Component.$categories), function(category) { + return category.search(re) != -1; + }); + }; + /** * @function $eventsBlocksForView * @memberof Component.prototype @@ -198,6 +226,56 @@ return deferred.promise; }; + Component.prototype.init = function(data) { + this.categories = []; + angular.extend(this, data); + }; + + /** + * @function getClassName + * @memberof Component.prototype + * @desc Return the component CSS class name based on its container (calendar) ID. + * @param {string} [base] - the prefix to add to the class name (defaults to "fg") + * @returns a string representing the foreground CSS class name + */ + Component.prototype.getClassName = function(base) { + if (angular.isUndefined(base)) + base = 'fg'; + return base + '-folder' + (this.pid || this.c_folder); + }; + + /** + * @function $reset + * @memberof Card.prototype + * @desc Reset the original state the card's data. + */ + Component.prototype.$reset = function() { + var _this = this; + angular.forEach(this, function(value, key) { + if (key != 'constructor' && key[0] != '$') { + delete _this[key]; + } + }); + angular.extend(this, this.$shadowData); + this.$shadowData = this.$omit(true); + }; + + /** + * @function $save + * @memberof Component.prototype + * @desc Save the component to the server. + */ + Component.prototype.$save = function() { + var _this = this; + + return Component.$$resource.save([this.pid, this.id].join('/'), this.$omit()) + .then(function(data) { + // Make a copy of the data for an eventual reset + _this.$shadowData = _this.$omit(true); + return data; + }); + }; + /** * @function $unwrap * @memberof Component.prototype @@ -215,7 +293,9 @@ this.$futureComponentData.then(function(data) { // Calling $timeout will force Angular to refresh the view Component.$timeout(function() { - angular.extend(_this, data); + _this.init(data); + // Make a copy of the data for an eventual reset + _this.$shadowData = _this.$omit(); deferred.resolve(_this); }); }, function(data) { diff --git a/UI/WebServerResources/js/Common/ui-desktop.js b/UI/WebServerResources/js/Common/ui-desktop.js index 2d8eb488f..c56ad6274 100644 --- a/UI/WebServerResources/js/Common/ui-desktop.js +++ b/UI/WebServerResources/js/Common/ui-desktop.js @@ -22,7 +22,7 @@ template: '

' + '

' + - '' + l('OK') + '' + + '' + l('OK') + '' + '', windowClass: 'small', controller: function($scope, $modalInstance) { @@ -455,7 +455,7 @@ * @param {Function} sgSubscribeOnSelect - the function to call when subscribing to a folder * @example: -
+ Subscribe .. */ .directive('sgSubscribe', [function() { console.debug('registering sgSubscribe'); @@ -466,11 +466,8 @@ onFolderSelect: '&sgSubscribeOnSelect' }, replace: false, - link: function(scope, element, attrs, controller) { - element.on('click', controller.showDialog); - }, - controllerAs: 'vm', bindToController: true, + controllerAs: 'vm', controller: ['$scope', '$mdDialog', function($scope, $mdDialog) { var vm = this; vm.showDialog = function() { @@ -495,6 +492,9 @@ }); }; }], + link: function(scope, element, attrs, controller) { + element.on('click', controller.showDialog); + } }; }]) @@ -704,9 +704,12 @@ }, template: '' } }]) @@ -755,16 +758,22 @@ block: '=sgBlock' }, replace: true, - template: - '
' + - '
' + - '
' + - '
' + - '
{{ block.component.c_title }}
' + - '
' + - '
' + - '
' + - '
', + template: [ + '
', + '
', + '
', + '
', + '
{{ block.component.c_title }}', + ' ', + ' ', + ' ', + ' ', + '
', + '
', + '
', + '
', + '
' + ].join(''), link: link }; @@ -786,7 +795,7 @@ iElement.css('right', right + '%'); iElement.addClass('starts' + scope.block.start); iElement.addClass('lasts' + scope.block.length); - iElement.addClass('folder' + scope.block.component.c_folder); + iElement.addClass('bg-folder' + scope.block.component.c_folder); } }]) @@ -852,7 +861,7 @@ }; function link(scope, iElement, attrs) { - iElement.addClass('folder' + scope.block.component.c_folder); + iElement.addClass('bg-folder' + scope.block.component.c_folder); } }]); diff --git a/UI/WebServerResources/js/SchedulerUI.js b/UI/WebServerResources/js/SchedulerUI.js index d05a42c9a..530797136 100644 --- a/UI/WebServerResources/js/SchedulerUI.js +++ b/UI/WebServerResources/js/SchedulerUI.js @@ -7,7 +7,7 @@ angular.module('SOGo.Common', []); angular.module('SOGo.ContactsUI', []); - angular.module('SOGo.SchedulerUI', ['ngSanitize', 'ui.router', 'vs-repeat', 'SOGo.Common', 'SOGo.UI', 'SOGo.UIDesktop', 'SOGo.ContactsUI']) + angular.module('SOGo.SchedulerUI', ['ngSanitize', 'ui.router', 'ct.ui.router.extras.sticky', 'ct.ui.router.extras.previous', 'vs-repeat', 'SOGo.Common', 'SOGo.UI', 'SOGo.UIDesktop', 'SOGo.ContactsUI']) .constant('sgSettings', { baseURL: ApplicationBaseURL, @@ -39,6 +39,8 @@ }) .state('calendars.view', { url: '/{view:(?:day|week|month)}/:day', + sticky: true, + deepStateRedirect: true, views: { calendarView: { templateUrl: function($stateParams) { @@ -56,6 +58,21 @@ return Component.$eventsBlocksForView($stateParams.view, $stateParams.day.asDate()); }] } + }) + .state('calendars.component', { + url: '/:calendarId/event/:componentId', + views: { + componentEditor: { + templateUrl: 'UIxAppointmentEditorTemplate', + controller: 'ComponentController', + controllerAs: 'editor' + } + }, + resolve: { + stateComponent: ['$stateParams', 'sgCalendar', function($stateParams, Calendar) { + return Calendar.$get($stateParams.calendarId).$getComponent($stateParams.componentId); + }] + } }); $urlRouterProvider.when('/calendar/day', function() { @@ -198,7 +215,7 @@ }; }]) - .controller('CalendarListController', ['$scope', '$rootScope', '$timeout', 'sgFocus', 'encodeUriFilter', 'sgDialog', 'sgSettings', 'sgCalendar', 'sgComponent', function($scope, $rootScope, $timeout, focus, encodeUriFilter, Dialog, Settings, Calendar, Component) { + .controller('CalendarListController', ['$scope', '$rootScope', '$timeout', 'sgFocus', 'encodeUriFilter', 'sgDialog', 'sgSettings', 'sgCalendar', 'sgComponent', '$mdSidenav', function($scope, $rootScope, $timeout, focus, encodeUriFilter, Dialog, Settings, Calendar, Component, $mdSidenav) { // Scope variables this.component = Component; this.componentType = null; @@ -241,6 +258,64 @@ ctrl.blocks = data; }); })); + }]) + + .controller('ComponentController', ['$scope', '$log', '$timeout', '$state', '$previousState', '$mdSidenav', '$mdDialog', 'sgCalendar', 'sgComponent', 'stateCalendars', 'stateComponent', function($scope, $log, $timeout, $state, $previousState, $mdSidenav, $mdDialog, Calendar, Component, stateCalendars, stateComponent) { + var vm = this; + + vm.calendars = stateCalendars; + vm.event = stateComponent; + vm.categories = {}; + vm.editRecurrence = editRecurrence; + vm.cancel = cancel; + vm.save = save; + + $scope.$on('$viewContentLoaded', function(event) { + $timeout(function() { + $mdSidenav('right').open() + .then(function() { + $scope.$watch($mdSidenav('right').isOpen, function(isOpen, wasOpen) { + if (!isOpen) { + if ($previousState.get()) + $previousState.go() + else + $state.go('calendars'); + } + }); + }); + }, 100); // don't ask why + }); + + function editRecurrence($event) { + $mdDialog.show({ + templateUrl: 'editRecurrence', // UI/Templates/SchedulerUI/UIxRecurrenceEditor.wox + controller: RecurrenceController + }); + function RecurrenceController() { + + } + } + + function save(form) { + if (form.$valid) { + vm.event.$save() + .then(function(data) { + $scope.$emit('calendars:list'); + $mdSidenav('right').close(); + }, function(data, status) { + console.debug('failed'); + }); + } + } + + function cancel() { + vm.event.$reset(); + if (vm.event.isNew) { + // Cancelling the creation of a card + vm.event = null; + } + $mdSidenav('right').close(); + } }]); })(); diff --git a/UI/WebServerResources/scss/components/sidenav/sidenav.scss b/UI/WebServerResources/scss/components/sidenav/sidenav.scss index 5707aec70..cb55e463e 100644 --- a/UI/WebServerResources/scss/components/sidenav/sidenav.scss +++ b/UI/WebServerResources/scss/components/sidenav/sidenav.scss @@ -21,19 +21,14 @@ md-sidenav { } } -.md-sidenav-right { - width: $sidenav-right-width; - max-width: $sidenav-right-width; -} - // MAIN SIDENAV, actually to the left // ---------------------------------------------------------------------------- .md-sidenav-left { & md-content, & md-toolbar { background-color: inherit; - //background-image: url("../img/cardboard-transp.png"); - //background-blend-mode: multiply; + background-image: url("../img/cardboard-transp.png"); + background-blend-mode: multiply; } } @@ -41,6 +36,16 @@ md-sidenav { box-shadow: none; } +.md-sidenav-right { + width: $sidenav-right-width; + max-width: $sidenav-right-width; + & md-content, + & md-toolbar { + background-color: inherit; + background-image: none; + } +} + // MAILER Folder tree (in the left sidenav // --------------------------------------- $i: 1;