tine20/Calendar/js/TimelinePanel.js
Rama main · réplica de lectura del repositorio original.
/*
* Tine 2.0
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss <c.weiss@metaways.de>
* @copyright Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
*/
Ext.ns('Tine.Calendar');
/**
* @namespace Tine.Calendar
* @class Tine.Calendar.TimelinePanel
* @extends Ext.Panel
*
* Calendar timeline view
*
* @author Cornelius Weiss <c.weiss@metaways.de>
* @constructor
* @param {Object} config
*/
Tine.Calendar.TimelinePanel = Ext.extend(Ext.Panel, {
/**
* @cfg {Object} period
* period to display
*/
period: null,
/**
* @cfg {Ext.data.Store} store
*/
store: null,
/**
* @cfg {String} viewType
*/
viewType: 'month',
/**
* @cfg {Number} labelWidth
* width of left label column
*/
labelWidth: 150,
/**
* @cfg {String} dayFormatString
* i18n._('{0}, the {1}. of {2}')
*/
dayFormatString: '{0}, the {1}. of {2}',
/**
* @property {Number} scalingFactor
* percentage per millisecond
*/
scalingFactor: null,
groupingMetadataCache: null,
layout: 'vbox',
border: false,
layoutConfig: {
align : 'stretch',
pack : 'start'
},
initComponent: function() {
this.app = Tine.Tinebase.appMgr.get('Calendar');
this.dayFormatString = this.app.i18n._hidden(this.dayFormatString);
this.groupingMetadataCache = {};
this.labels = [];
this.items = [{
ref: 'headerRow',
height: 24,
layout: 'hbox',
border: false,
xtype: 'container',
layoutConfig: {
align : 'stretch',
pack : 'start'
},
items: [{
ref: '../periodLabel',
cls: 'cal-timelinepanel-header-label',
width: this.labelWidth,
border: false,
xtype: 'container'
}, {
ref: '../timelineHeading',
flex: 1,
layout: 'fit',
border: false,
xtype: 'container'
}]
}, {
ref: 'vScroller',
cls: 'cal-timelinepanel-vscroller',
flex: 1,
layout: 'hbox',
border: false,
xtype: 'container',
layoutConfig: {
align : 'stretch',
pack : 'start'
},
items: [{
ref: '../timelineLabels',
cls: 'cal-timelinepanel-yscroller-labels',
width: this.labelWidth,
border: false,
xtype: 'container',
autoHeight: true
}, {
ref: '../timelines',
cls: 'cal-timelinepanel-yscroller-timelines',
flex: 1,
border: false,
xtype: 'container',
autoHeight: true
}]
}];
this.groupCollection = new Tine.Tinebase.data.GroupedStoreCollection({
store: this.store,
group: this.groupingFn.createDelegate(this),
groupOnLoad: false,
listeners: {
scope: this,
add: this.onGroupAdd,
remove: this.onGroupRemove
}
});
this.store.on('beforeload', this.onStoreBeforeLoad, this);
this.store.on('load', this.onStoreLoad, this);
this.initTemplates();
if (! this.selModel) {
this.selModel = this.selModel || new Tine.Calendar.EventSelectionModel();
}
Tine.Calendar.TimelinePanel.superclass.initComponent.apply(this, arguments);
},
onStoreBeforeLoad: function(store, options) {
this.resolveGroupingItemsPromise = Promise.resolve();
// resolve members for memberOf filters
const attendeeFilter = Tine.Calendar.AttendeeFilterGrid.prototype.extractFilterValue(options.params.filter);
const memberIds = _.reduce(attendeeFilter, (memberIds, attendee) => {
return memberIds.concat(memberIds, _.get(attendee, 'user_type') === 'memberOf' ?
_.get(attendee, 'user_id.members', []) : []);
}, []);
if (memberIds.length) {
this.resolveGroupingItemsPromise = Tine.Addressbook.searchContacts([{
field: 'id', operator: 'in', value: _.compact(_.uniq(memberIds))
}]);
}
},
onStoreLoad: async function(store, records, options) {
this.groupingMetadataCache = {};
var fixedGroups = [],
attendeeFilterValue = Tine.Calendar.AttendeeFilterGrid.prototype.extractFilterValue(options.params.filter),
attendeeStore = attendeeFilterValue ? Tine.Calendar.Model.Attender.getAttendeeStore(attendeeFilterValue) : null;
options.keepLoadMask = true;
const members = await this.resolveGroupingItemsPromise;
if (attendeeStore) {
attendeeStore.each(async (attendee) => {
if (attendee.get('user_type') === 'memberOf') {
// add each list member to fixedGroups
const Attender = Tine.Calendar.Model.Attender;
_.each(_.get(attendee, 'data.user_id.members', []), (memberId) => {
const memberContact = _.find(_.get(members, 'results', {}), {id: memberId});
if (memberContact) {
const memberAttendee = new Attender(Attender.getDefaultData({user_type: 'user', user_id: memberContact}));
const groupName = memberAttendee.getCompoundId();
fixedGroups.push(groupName);
this.groupingMetadataCache[groupName] = memberAttendee;
}
});
} else {
var groupName = attendee.getCompoundId();
fixedGroups.push(groupName);
this.groupingMetadataCache[groupName] = attendee;
}
});
}
this.groupCollection.setFixedGroups(_.uniq(fixedGroups));
this.mainScreen.loadMask.hide();
},
groupingFn: function(event) {
var groups = [],
attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(event.get('attendee'));
attendeeStore.each(function(attendee) {
var groupName = this.getAttendeeType(attendee) + '-' + attendee.getUserId();
if (! this.groupingMetadataCache.hasOwnProperty(groupName)) {
this.groupingMetadataCache[groupName] = attendee;
}
groups.push(groupName);
}, this);
return groups;
},
getAttendeeType: function(attendee) {
var attendeeType = attendee.get('user_type');
return attendeeType === 'groupmember' ? 'user' : attendeeType;
},
onGroupAdd: function(idx, groupStore, groupName) {
var _ = window.lodash,
attendee = this.groupingMetadataCache[groupName],
name = Tine.Calendar.AttendeeGridPanel.prototype.renderAttenderName.call(Tine.Calendar.AttendeeGridPanel.prototype, attendee.get('user_id'), false, attendee),
type = this.getAttendeeType(attendee);
var label = new Tine.Calendar.TimelineLabel({
groupName: groupName,
label: name,
iconCls: type === 'user' ? 'tine-grid-row-action-icon renderer_typeAccountIcon' : attendee.getIconCls(),
groupSortOrder: Tine.Calendar.Model.Attender.getSortOrder(type)
});
this.labels.push(label);
this.labels = _.sortBy(this.labels, ['groupSortOrder', 'label']);
var idx = this.labels.indexOf(label);
this.timelineLabels.insert(idx, label);
this.timelineLabels.doLayout();
var view = new Tine.Calendar.TimelineView({
groupName: groupName,
store: groupStore,
period: this.period,
label: label
});
this.timelines.insert(idx, view);
this.timelines.doLayout();
this.relayEvents(view, ['click', 'dblclick', 'contextmenu']);
view.getSelectionModel().on('selectionchange', this.onSelectionChange.createDelegate(this, [view]));
},
onGroupRemove: function(groupStore, groupName) {
this.timelineLabels.remove(this.timelineLabels.find('groupName', groupName)[0]);
this.timelines.remove(this.timelines.find('groupName', groupName)[0]);
},
getSelectionModel: function() {
return this.selModel;
},
onSelectionChange: function(view) {
if (this.selectionChangeLocked) return;
this.selectionChangeLocked = true;
view = Ext.isString(view) ? this.timelines.items.get(view) : view;
this.activeView = view;
// clear selections from other views
this.timelines.items.each(function(v) {
if (v !== view) {
v.getSelectionModel().clearSelections(true);
}
}, this);
var selections = view.getSelectionModel().getSelectedEvents();
this.getSelectionModel().clearSelections();
Ext.each(selections, function(viewEvent) {
var event = this.store.getById(viewEvent.id);
// @TODO: have own (grouping) ui
if (event) {
event.ui = viewEvent.ui;
}
this.getSelectionModel().select(event, true);
}, this);
this.selectionChangeLocked = false;
},
getPeriod: function() {
return this.period;
},
updatePeriod: function(period) {
this.period = period;
var tbar = this.getTopToolbar();
if (tbar) {
tbar.periodPicker.update(period.from);
this.period = tbar.periodPicker.getPeriod();
this.viewType = tbar.view;
}
// @TODO do this on period change only
this.timelines.items.each(function(view) {
view.updatePeriod(this.period);
}, this);
this.onLayout();
},
onLayout: function() {
Tine.Calendar.TimelinePanel.superclass.onLayout.apply(this, arguments);
// update header & scaling
var msTotal = this.period.until.getTime() - this.period.from.getTime();
this.scalingFactor = 100 / msTotal;
// this.periodLabel.update(this.period.from.format())
this.setHeaders();
},
setHeaders: function() {
var items = [],
item = null,
itemStart = this.period.from,
periodEndTs = this.period.until.getTime(),
unit = this.viewType == 'day' ? Date.HOUR : Date.DAY,
formatString = this.viewType == 'day' ? 'G' : 'd',
itemEnd = itemStart.add(unit, 1);
if (this.viewType != 'day') {
itemEnd.setHours(0);
}
itemEnd.setMinutes(0);
itemEnd.setSeconds(0);
itemEnd.setMilliseconds(0);
while (itemEnd <= this.period.until) {
item = {
header: itemStart.format(formatString),
width: this.scalingFactor * (Math.min(periodEndTs, itemEnd.getTime()) - itemStart.getTime())
};
item.headerLong = this.viewType == 'day' ?
item.header :
String.format(this.dayFormatString, itemStart.format('l'), itemStart.format('j'), itemStart.format('F'));
items.push(item);
itemStart = itemEnd;
itemEnd = itemStart.add(unit, 1);
}
this.timelineHeading.update(this.templates.header.apply(items));
},
onResize: function(w, h) {
Tine.Calendar.TimelinePanel.superclass.onResize.apply(this, arguments);
},
getTargetEvent: function(e) {
var viewEl = e.getTarget('.cal-timeline-view'),
id = viewEl ? viewEl.id : undefined,
view = this.timelines.items.get(id);
return view.getTargetEvent(e);
},
getTargetDateTime: Ext.emptyFn,
print: function() {
Ext.ux.Printer.print(this.grid);
},
getView: function() {
return this;
},
getStore: function() {
return this.store;
},
initTemplates: function() {
var ts = this.templates || {};
ts.header = new Ext.XTemplate(
'<div class="cal-timelinepanel-header">',
'<tpl for=".">',
'<div class="cal-timelinepanel-headeritem<tpl if="width > 10"> cal-timelinepanel-header-long</tpl>" style="width: {width}%;">',
'<span class="cal-timelinepanel-headeritem-short">{header}</span>',
'<span class="cal-timelinepanel-headeritem-long">{headerLong}</span>',
'</div>',
'</tpl>',
'</div>'
);
for(var k in ts){
var t = ts[k];
if(t && typeof t.compile == 'function' && !t.compiled){
t.disableFormats = true;
t.compile();
}
}
this.templates = ts;
}
});