Style events depending on user participation state
Also use one-time binding for non-ghost events.pull/207/head
parent
0bc7e5f5bd
commit
1a30df03c8
1
NEWS
1
NEWS
|
@ -14,6 +14,7 @@ Enhancements
|
|||
- [web] now able to copy/move events and also duplicate them (#3196)
|
||||
- [web] improve preferences validation and check for unsaved changes
|
||||
- [web] display events and tasks priorities in list and day/week views
|
||||
- [web] style events depending on the user participation state
|
||||
|
||||
Bug fixes
|
||||
- [web] fixed missing columns in SELECT statements (PostgreSQL)
|
||||
|
|
|
@ -868,7 +868,7 @@ static inline void _feedBlockWithMonthBasedData (NSMutableDictionary *block, uns
|
|||
number: (NSNumber *) number
|
||||
onDay: (unsigned int) dayStart
|
||||
recurrenceTime: (unsigned int) recurrenceTime
|
||||
userState: (iCalPersonPartStat) userState
|
||||
userState: (NSString *) userState
|
||||
{
|
||||
NSMutableDictionary *block;
|
||||
|
||||
|
@ -882,22 +882,22 @@ static inline void _feedBlockWithMonthBasedData (NSMutableDictionary *block, uns
|
|||
if (recurrenceTime)
|
||||
[block setObject: [NSNumber numberWithInt: recurrenceTime]
|
||||
forKey: @"recurrenceTime"];
|
||||
if (userState != iCalPersonPartStatOther)
|
||||
[block setObject: [NSNumber numberWithInt: userState]
|
||||
if (userState != nil)
|
||||
[block setObject: userState
|
||||
forKey: @"userState"];
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
static inline iCalPersonPartStat _userStateInEvent (NSArray *event)
|
||||
static inline NSString* _userStateInEvent (NSArray *event)
|
||||
{
|
||||
unsigned int count, max;
|
||||
iCalPersonPartStat state;
|
||||
NSString *state;
|
||||
NSArray *participants, *states;
|
||||
SOGoUser *user;
|
||||
|
||||
participants = nil;
|
||||
state = iCalPersonPartStatOther;
|
||||
state = nil;
|
||||
|
||||
participants = [event objectAtIndex: eventPartMailsIndex];
|
||||
|
||||
|
@ -909,10 +909,10 @@ static inline iCalPersonPartStat _userStateInEvent (NSArray *event)
|
|||
max = [participants count];
|
||||
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
|
||||
roles: nil];
|
||||
while (state == iCalPersonPartStatOther && count < max)
|
||||
while (state == nil && count < max)
|
||||
{
|
||||
if ([user hasEmail: [participants objectAtIndex: count]])
|
||||
state = [[states objectAtIndex: count] intValue];
|
||||
state = [states objectAtIndex: count];
|
||||
else
|
||||
count++;
|
||||
}
|
||||
|
@ -929,7 +929,7 @@ static inline iCalPersonPartStat _userStateInEvent (NSArray *event)
|
|||
eventEnd, computedEventEnd, offset, recurrenceTime, swap;
|
||||
NSMutableArray *currentDay;
|
||||
NSMutableDictionary *eventBlock;
|
||||
iCalPersonPartStat userState;
|
||||
NSString *userState;
|
||||
|
||||
eventStart = [[event objectAtIndex: eventStartDateIndex] intValue];
|
||||
if (eventStart < 0)
|
||||
|
|
|
@ -55,6 +55,9 @@
|
|||
' .bdr-folder{{ cssCtrl.ngModel.id }} {',
|
||||
' border-color: {{ cssCtrl.ngModel.color }} !important;',
|
||||
' }',
|
||||
' .contrast-bdr-folder{{ cssCtrl.ngModel.id }} {',
|
||||
' border-color: {{ cssCtrl.contrast(cssCtrl.ngModel.color) }} !important;',
|
||||
' }',
|
||||
/* Checkbox color */
|
||||
' .checkbox-folder{{ cssCtrl.ngModel.id }} ._md-icon {',
|
||||
' background-color: {{ cssCtrl.ngModel.color }} !important;',
|
||||
|
@ -69,7 +72,7 @@
|
|||
function sgFolderStylesheetController() {
|
||||
var vm = this;
|
||||
|
||||
vm.contrast = contrast;
|
||||
vm.contrast = contrast; // defined in Common/utils.js
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,73 +28,91 @@
|
|||
clickBlock: '&sgClick'
|
||||
},
|
||||
replace: true,
|
||||
template: [
|
||||
template: template,
|
||||
link: link
|
||||
};
|
||||
|
||||
function template(tElem, tAttrs) {
|
||||
var p = _.has(tAttrs, 'sgCalendarGhost')? '' : '::';
|
||||
|
||||
return [
|
||||
'<div class="sg-event"',
|
||||
// Add a class while dragging
|
||||
' ng-class="{\'sg-event--dragging\': block.dragging}">',
|
||||
' <div class="eventInside"',
|
||||
' ng-click="clickBlock({clickEvent: $event, clickComponent: block.component})">',
|
||||
// Categories color stripes
|
||||
' <div class="sg-category" ng-repeat="category in block.component.categories"',
|
||||
' <div class="sg-category" ng-repeat="category in '+p+'block.component.categories"',
|
||||
' ng-class="\'bg-category\' + category"',
|
||||
' ng-style="{ right: ($index * 3) + \'px\' }"></div>',
|
||||
' <div class="text">',
|
||||
' <span ng-show="block.component.c_priority" class="sg-priority">{{block.component.c_priority}}</span>',
|
||||
' {{ block.component.summary }}',
|
||||
// Priority
|
||||
' <span ng-show="'+p+'block.component.c_priority" class="sg-priority">{{'+p+'block.component.c_priority}}</span>',
|
||||
// Summary
|
||||
' {{ '+p+'block.component.summary }}',
|
||||
' <span class="icons">',
|
||||
// Component is reccurent
|
||||
' <md-icon ng-if="block.component.occurrenceId" class="material-icons icon-repeat"></md-icon>',
|
||||
' <md-icon ng-if="'+p+'block.component.occurrenceId" class="material-icons icon-repeat"></md-icon>',
|
||||
// Component has an alarm
|
||||
' <md-icon ng-if="block.component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
|
||||
' <md-icon ng-if="'+p+'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>',
|
||||
' <md-icon ng-if="'+p+'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>',
|
||||
' <md-icon ng-if="'+p+'block.component.c_classification == 2" class="material-icons icon-vpn-key"></md-icon>',
|
||||
' </span>',
|
||||
// Location
|
||||
' <div class="secondary" ng-if="block.component.c_location">',
|
||||
' <md-icon>place</md-icon> {{block.component.c_location}}',
|
||||
' <div class="secondary" ng-if="'+p+'block.component.c_location">',
|
||||
' <md-icon>place</md-icon> {{'+p+'block.component.c_location}}',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="ghostStartHour" ng-if="block.startHour">{{ block.startHour }}</div>',
|
||||
' <div class="ghostEndHour" ng-if="block.endHour">{{ block.endHour }}</div>',
|
||||
'</div>'
|
||||
].join(''),
|
||||
link: link
|
||||
};
|
||||
].join('');
|
||||
}
|
||||
|
||||
function link(scope, iElement, attrs) {
|
||||
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;
|
||||
if (right > 0)
|
||||
right -= 2;
|
||||
if (!_.has(attrs, 'sgCalendarGhost')) {
|
||||
|
||||
// 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;
|
||||
if (right > 0)
|
||||
right -= 2;
|
||||
}
|
||||
|
||||
// Add some padding (2%)
|
||||
if (left === 0)
|
||||
left = 2;
|
||||
if (right === 0)
|
||||
right = 2;
|
||||
|
||||
// Set position
|
||||
iElement.css('left', left + '%');
|
||||
iElement.css('right', right + '%');
|
||||
if (!scope.block.component || !scope.block.component.c_isallday) {
|
||||
iElement.addClass('starts' + scope.block.start);
|
||||
iElement.addClass('lasts' + scope.block.length);
|
||||
}
|
||||
|
||||
// Add class for user's participation state
|
||||
if (scope.block.userState)
|
||||
iElement.addClass('sg-event--' + scope.block.userState);
|
||||
|
||||
// Set background color
|
||||
if (scope.block.component) {
|
||||
iElement.addClass('bg-folder' + scope.block.component.pid);
|
||||
iElement.addClass('contrast-bdr-folder' + scope.block.component.pid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add some padding (2%)
|
||||
if (left === 0)
|
||||
left = 2;
|
||||
if (right === 0)
|
||||
right = 2;
|
||||
|
||||
// Set position
|
||||
iElement.css('left', left + '%');
|
||||
iElement.css('right', right + '%');
|
||||
if (!scope.block.component || !scope.block.component.c_isallday) {
|
||||
iElement.addClass('starts' + scope.block.start);
|
||||
iElement.addClass('lasts' + scope.block.length);
|
||||
}
|
||||
|
||||
// Set background color
|
||||
if (scope.block.component)
|
||||
iElement.addClass('bg-folder' + scope.block.component.pid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
});
|
||||
|
||||
function initGhost() {
|
||||
var pid, calendarData;
|
||||
var pid, calendarData, userState;
|
||||
|
||||
// Expose ghost block to the scope
|
||||
scope.block = Component.$ghost;
|
||||
|
@ -62,6 +62,11 @@
|
|||
if (!pid)
|
||||
pid = scope.block.component.pid;
|
||||
|
||||
// Add class for user's participation state
|
||||
userState = scope.block.component.blocks[0].userState;
|
||||
if (userState)
|
||||
iElement.addClass('sg-event--' + userState);
|
||||
|
||||
// Set background color
|
||||
iElement.addClass('bg-folder' + pid);
|
||||
}
|
||||
|
|
|
@ -23,31 +23,49 @@
|
|||
clickBlock: '&sgClick'
|
||||
},
|
||||
replace: true,
|
||||
template: [
|
||||
template: template,
|
||||
link: link
|
||||
};
|
||||
|
||||
function template(tElem, tAttrs) {
|
||||
var p = _.has(tAttrs, 'sgCalendarGhost')? '' : '::';
|
||||
|
||||
return [
|
||||
'<div class="sg-event"',
|
||||
// Add a class while dragging
|
||||
' ng-class="{\'sg-event--dragging\': block.dragging}"',
|
||||
' ng-click="clickBlock({clickEvent: $event, clickComponent: block.component})">',
|
||||
' <span class="secondary" ng-if="!block.component.c_isallday">{{ block.starthour }}</span>',
|
||||
' {{ block.component.summary }}',
|
||||
' <span class="secondary" ng-if="'+p+'!block.component.c_isallday">{{ '+p+'block.starthour }}</span>',
|
||||
// Priority
|
||||
' <span ng-show="'+p+'block.component.c_priority" class="sg-priority">{{'+p+'block.component.c_priority}}</span>',
|
||||
// Summary
|
||||
' {{ '+p+'block.component.summary }}',
|
||||
' <span class="icons">',
|
||||
// Component is reccurent
|
||||
' <md-icon ng-if="block.component.occurrenceId" class="material-icons icon-repeat"></md-icon>',
|
||||
' <md-icon ng-if="'+p+'block.component.occurrenceId" class="material-icons icon-repeat"></md-icon>',
|
||||
// Component has an alarm
|
||||
' <md-icon ng-if="block.component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
|
||||
' <md-icon ng-if="'+p+'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>',
|
||||
' <md-icon ng-if="'+p+'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>',
|
||||
' <md-icon ng-if="'+p+'block.component.c_classification == 2" class="material-icons icon-vpn-key"></md-icon>',
|
||||
' </span>',
|
||||
'</div>'
|
||||
].join(''),
|
||||
link: link
|
||||
};
|
||||
].join('');
|
||||
}
|
||||
|
||||
function link(scope, iElement, attrs) {
|
||||
if (scope.block.component)
|
||||
iElement.addClass('bg-folder' + scope.block.component.pid);
|
||||
if (!_.has(attrs, 'sgCalendarGhost')) {
|
||||
|
||||
// Add class for user's participation state
|
||||
if (scope.block.userState)
|
||||
iElement.addClass('sg-event--' + scope.block.userState);
|
||||
|
||||
// Set background color
|
||||
if (scope.block.component)
|
||||
iElement.addClass('bg-folder' + scope.block.component.pid);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -290,6 +290,23 @@ $quarter_height: 10px;
|
|||
}
|
||||
}
|
||||
|
||||
// User participation status is "needs action"
|
||||
&--needs-action {
|
||||
border-width: 1px;
|
||||
border-style: dashed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// User participation status is "tentative"
|
||||
&--tentative {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// User has declined the invitation
|
||||
&--declined {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.eventInside {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue