Initial component editor in Scheduler module

This commit is contained in:
Francis Lachapelle
2015-04-24 16:48:41 -04:00
parent feee156c0a
commit c957b4b45f
12 changed files with 391 additions and 36 deletions
+2 -2
View File
@@ -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
+50
View File
@@ -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
+6 -2
View File
@@ -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";
};
};
@@ -0,0 +1,129 @@
<?xml version='1.0' standalone='yes'?>
<container
xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:label="OGo:label"
>
<form name="eventForm" ng-submit="editor.save(eventForm)">
<md-toolbar class="md-padding" ng-class="editor.event.getClassName('bg')">
<!-- summary -->
<md-input-container md-no-float="true">
<input type="text"
class="md-display-1"
label:placeholder="Title"
ng-model="editor.event.summary"/>
</md-input-container>
<!--<md-chips>
<md-chip ng-repeat="category in editor.event.categories">{{category}}</md-chip>
</md-chips>-->
</md-toolbar>
<md-content md-scroll-y="md-scroll-y" class="md-padding bg-sogoPaper-50 md-whiteframe-z1">
<!-- location -->
<md-input-container>
<label><var:string label:value="Location"/></label>
<input type="text" ng-model="editor.event.location"/>
</md-input-container>
<!-- categories -->
<div class="pseudo-input-container">
<md-chips ng-model="editor.event.categories">
<md-autocomplete
md-selected-item="editor.categories.selected"
md-search-text="editor.categories.searchText"
md-items="category in editor.event.constructor.filterCategories(editor.categories.searchText)"
label:placeholder="Add a category">
<span md-highlight-text="editor.categories.searchText">{{category}}</span>
</md-autocomplete>
</md-chips>
</div>
<!-- calendar -->
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="Calendar"/></label>
<md-select ng-model="editor.event.pid" class="pseudo-input-field"
label:placeholder="Calendar">
<md-option ng-repeat="calendar in ::editor.calendars"
ng-value="calendar.id">{{calendar.name}}</md-option>
</md-select>
</div>
<!-- priority -->
<div layout="row" layout-align="center center">
<div flex="20"><var:string label:value="Priority"/></div>
<md-slider class="md-flex"
md-discrete="true"
ng-model="rating1"
step="1"
min="0"
max="9"
label:aria-label="Priority"><!-- slider --></md-slider>
</div>
<!-- is all day -->
<md-checkbox
ng-model="editor.event.isAllDay"
ng-true-value="1"
ng-false-value="0"
label:aria-label="All day Event">
<var:string label:value="All day Event"/>
</md-checkbox>
<!-- is transparent -->
<md-checkbox
ng-model="editor.event.isTransparent"
ng-true-value="1"
ng-false-value="0"
label:aria-label="Show Time as Free">
<var:string label:value="Show Time as Free"/>
</md-checkbox>
<!-- send appointment notifications -->
<md-checkbox
ng-model="editor.event.sendAppointmentNotifications"
ng-true-value="1"
ng-false-value="0"
label:aria-label="Send Appointment Notifications">
<var:string label:value="Send Appointment Notifications"/>
</md-checkbox>
<!-- start + end -->
<div layout="row">
<md-input-container>
<label><var:string label:value="From"/></label>
<input type="text" ng-model="editor.event.startDate"/>
</md-input-container>
<md-input-container>
<label><var:string label:value="To"/></label>
<input type="text" ng-model="editor.event.endDate"/>
</md-input-container>
</div>
<!-- comment -->
<md-input-container>
<label><var:string label:value="Description"/></label>
<textarea ng-model="editor.event.comment"><!-- comment --></textarea>
</md-input-container>
<!-- repeat -->
<div layout="row">
<div class="pseudo-input-container md-flex">
<label class="pseudo-input-label"><var:string label:value="Repeat"/>{{editor.event.repeat.frequency}}</label>
<md-select ng-model="editor.event.repeat.frequency" class="pseudo-input-field"
label:placeholder="Repeat">
<var:foreach list="repeatList" item="item">
<md-option var:value="item"><var:string var:value="itemRepeatText"/></md-option>
</var:foreach>
</md-select>
</div>
<button class="iconButton md-button"
ng-click="editor.editRecurrence()">
<i class="md-icon-more-horiz"><!-- customize --></i>
</button>
</div>
<div class="fieldset md-layout-margin" layout="row" layout-align="end center">
<md-button ng-click="editor.cancel()">
<var:string label:value="Cancel"/>
</md-button>
<md-button ng-click="editor.event.$reset()">
<var:string label:value="Reset"/>
</md-button>
<md-button class="md-primary md-hue-3" type="submit">
<var:string label:value="Save"/>
</md-button>
</div>
</md-content>
</form>
</container>
+1 -1
View File
@@ -264,7 +264,6 @@
<md-list>
<md-list-item
class="md-clickable md-2-line"
n0-ng-click="list.openComponent(event)"
ng-repeat="event in list.component.$events">
<i class="md-avatar" ng-class="event.getClassName('bg')" ><!-- calendar color --></i>
<div class="md-list-item-text"
@@ -296,6 +295,7 @@
</md-content>
<md-content flex="67" ui-view="calendarView"><!-- calendar view --></md-content>
</md-content>
<md-sidenav class="md-sidenav-right" md-component-id="right" layout="column" ui-view="componentEditor"><!-- appointment editor view --><!--<var:component className="UIxAppointmentEditorTemplate" />--></md-sidenav>
</section>
</script>
+1
View File
@@ -220,6 +220,7 @@
<script type="text/javascript" rsrc:src="js/vendor/angular-aria.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-material.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-ui-router.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/ct-ui-router-extras.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-recursion.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-vs-repeat.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/Common/utils.js"><!-- space --></script>
+1
View File
@@ -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}',
+1
View File
@@ -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",
@@ -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) {
+28 -19
View File
@@ -22,7 +22,7 @@
template:
'<h2 data-ng-bind-html="title"></h2>' +
'<p data-ng-bind-html="content"></p>' +
'<a class="button button-primary" ng-click="closeModal()">' + l('OK') + '</a>' +
'<a class="button md-primary" ng-click="closeModal()">' + l('OK') + '</a>' +
'<span class="close-reveal-modal" ng-click="closeModal()"><i class="icon-close"></i></span>',
windowClass: 'small',
controller: function($scope, $modalInstance) {
@@ -455,7 +455,7 @@
* @param {Function} sgSubscribeOnSelect - the function to call when subscribing to a folder
* @example:
<div sg-subscribe="contact" sg-subscribe-on-select="subscribeToFolder"></div>
<md-button sg-subscribe="contact" sg-subscribe-on-select="subscribeToFolder">Subscribe ..</md-button>
*/
.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:
'<style type="text/css">' +
' .folder{{ ngModel.id }} {' +
' .bg-folder{{ ngModel.id }} {' +
' background-color: {{ ngModel.color }} !important;' +
' }' +
' .fg-folder{{ ngModel.id }} {' +
' color: {{ ngModel.color }} !important;' +
' }' +
'</style>'
}
}])
@@ -755,16 +758,22 @@
block: '=sgBlock'
},
replace: true,
template:
'<div class="event draggable">' +
' <div class="eventInside">' +
' <div class="gradient">' +
' </div>' +
' <div class="text">{{ block.component.c_title }}<span class="icons"></span></div>' +
' </div>' +
' <div class="topDragGrip"></div>' +
' <div class="bottomDragGrip"></div>' +
'</div>',
template: [
'<div class="event draggable">',
' <div class="eventInside">',
' <div class="gradient">',
' </div>',
' <div class="text">{{ block.component.c_title }}',
' <span class="icons">',
' <i ng-if="block.component.c_nextalarm" class="md-icon-alarm"></i>',
' <i ng-if="block.component.c_classification == 1" class="md-icon-visibility-off"></i>',
' <i ng-if="block.component.c_classification == 2" class="md-icon-vpn-key"></i>',
' </span></div>',
' </div>',
' <div class="topDragGrip"></div>',
' <div class="bottomDragGrip"></div>',
'</div>'
].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);
}
}]);
+77 -2
View File
@@ -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();
}
}]);
})();
@@ -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;