Browse Source

Merge d1f8d735d5 into 1e26ee3788

pull/1280/merge
Arnaud Pineux 5 years ago
committed by GitHub
parent
commit
6b3c20adfe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      web_timeline/README.rst
  2. 1
      web_timeline/static/lib/multiselect/bootstrap-multiselect.css
  3. 1776
      web_timeline/static/lib/multiselect/bootstrap-multiselect.js
  4. 6
      web_timeline/static/src/css/web_timeline.css
  5. 17
      web_timeline/static/src/js/timeline_controller.js
  6. 226
      web_timeline/static/src/js/timeline_renderer.js
  7. 1
      web_timeline/static/src/js/timeline_view.js
  8. 18
      web_timeline/static/src/xml/web_timeline.xml
  9. 2
      web_timeline/views/web_timeline.xml

2
web_timeline/README.rst

@ -66,6 +66,8 @@ the possible attributes for the tag:
+--------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| colors | No | Allows to set certain specific colors if the expressed condition (JS syntax) is met. |
+--------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| color-field | No | Allows to set a field (on the current model) that contains the HTML color (e.g. #FFFFFF). This option has priority over 'colors'. |
+--------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| dependency_arrow | No | Set this attribute to a x2many field to draw arrows between the records referenced in the x2many field. |
+--------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

1
web_timeline/static/lib/multiselect/bootstrap-multiselect.css

@ -0,0 +1 @@
span.multiselect-native-select{position:relative}span.multiselect-native-select select{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px -1px -1px -3px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;left:50%;top:30px}.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container .multiselect-reset .input-group{width:93%}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.checkbox,.multiselect-container>li>a>label.radio{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0}

1776
web_timeline/static/lib/multiselect/bootstrap-multiselect.js
File diff suppressed because it is too large
View File

6
web_timeline/static/src/css/web_timeline.css

@ -9,7 +9,7 @@
background: #DCDCDC;
}
.vis-item .vis-item-overflow {
.vis-item .vis-item-overflow:hover {
overflow: visible;
}
@ -29,3 +29,7 @@
left: 0px;
top: 0px;
}
.selected-groups {
display: inline-flex;
}

17
web_timeline/static/src/js/timeline_controller.js

@ -107,6 +107,9 @@ odoo.define('web_timeline.TimelineController', function (require) {
var rights = event.data.rights;
var item = event.data.item;
var id = item.evt.id;
if (this.renderer.x2x) {
id = id.split('_')[0];
}
var title = item.evt.__name;
if (this.open_popup_action) {
new dialogs.FormViewDialog(this, {
@ -163,7 +166,7 @@ odoo.define('web_timeline.TimelineController', function (require) {
var diff_seconds = Math.round((event_end.getTime() - event_start.getTime()) / 1000);
data[this.date_delay] = diff_seconds / 3600;
}
if (this.renderer.last_group_bys && this.renderer.last_group_bys instanceof Array) {
if (!this.renderer.x2x && this.renderer.last_group_bys && this.renderer.last_group_bys instanceof Array) {
data[this.renderer.last_group_bys[0]] = group;
}
@ -192,7 +195,7 @@ odoo.define('web_timeline.TimelineController', function (require) {
model: self.model.modelName,
method: 'write',
args: [
[item.event.data.item.id],
[self.renderer.x2x ? item.event.data.item.id.split('_')[0] : item.event.data.item.id],
item.data,
],
context: self.getSession().user_context,
@ -222,7 +225,7 @@ odoo.define('web_timeline.TimelineController', function (require) {
model: self.model.modelName,
method: 'unlink',
args: [
[event.data.item.id],
[self.renderer.x2x ? event.data.item.id.split('_')[0] : event.data.item.id],
],
context: self.getSession().user_context,
}).then(function () {
@ -277,7 +280,7 @@ odoo.define('web_timeline.TimelineController', function (require) {
'YYYY-MM-DD HH:mm:ss'
);
}
if (item.group > 0) {
if (!this.renderer.x2x && item.group > 0) {
default_context['default_'.concat(this.renderer.last_group_bys[0])] = item.group;
}
// Show popup
@ -314,9 +317,15 @@ odoo.define('web_timeline.TimelineController', function (require) {
context: this.context,
})
.then(function (records) {
if (self.renderer.x2x) {
records[0].id = records[0].id + "_" + records[0][self.renderer.grouped_by][0];
}
var new_event = self.renderer.event_data_transform(records[0]);
var items = self.renderer.timeline.itemsData;
items.add(new_event);
if (self.renderer.x2x) {
self.renderer.updateGroups(records);
}
self.renderer.timeline.setItems(items);
self.reload();
});

226
web_timeline/static/src/js/timeline_renderer.js

@ -10,7 +10,7 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
var field_utils = require('web.field_utils');
var TimelineCanvas = require('web_timeline.TimelineCanvas');
core.qweb.add_template("/web_timeline/static/src/xml/web_timeline.xml");
var _t = core._t;
var TimelineRenderer = AbstractRenderer.extend({
@ -63,6 +63,7 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
throw new Error(_t("Timeline view has not defined 'date_start' attribute."));
}
this._super.apply(this, self);
},
/**
@ -298,7 +299,6 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
this.canvas.draw_arrow(from.dom.box, to.dom.box, defaults.line_color, defaults.line_width);
},
/**
* Load display_name of records.
*
@ -308,41 +308,164 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
on_data_loaded: function (events, group_bys, adjust_window) {
var self = this;
var ids = _.pluck(events, "id");
return this._rpc({
model: this.modelName,
var group_by_field = self.view.fields[group_bys[0]];
if (group_by_field.type == 'one2many' | group_by_field.type == 'many2many') {
self.x2x = true;
return self.split_groups_x2x(events, group_bys).then(function (groups) {
self.groups = groups.sort((a, b) => (a.content > b.content) ? 1 : -1);
return self._rpc({
model: self.modelName,
method: 'name_get',
args: [
ids,
],
context: self.getSession().user_context,
}).then(function(names) {
var nevents = _.map(events, function (event) {
return _.extend({
__name: _.detect(names, function (name) {
return name[0] === event.id;
})[1]
}, event);
});
return self.on_data_loaded_2(nevents, group_bys, self.x2x, adjust_window);
});
});
} else {
self.x2x = false;
return this._rpc({
model: this.modelName,
method: 'name_get',
args: [
ids,
],
context: this.getSession().user_context,
}).then(function(names) {
var nevents = _.map(events, function (event) {
return _.extend({
__name: _.detect(names, function (name) {
return name[0] === event.id;
})[1]
}, event);
});
return self.on_data_loaded_2(nevents, group_bys, self.x2x, adjust_window);
});
};
},
updateGroups: function(events) {
var self = this;
var group_ids = [];
// Get the missing groups from new events
_.each(events, function (event) {
var group = event[_.first(self.last_group_bys)];
if (group) {
_.each(group, function (gr) {
var found = _.find(self.groups, function(existing_group) {
return existing_group.id == gr
});
if (found === undefined) {
group_ids.push(gr);
}
});
}
});
var group_by_field = this.view.fields[self.last_group_bys];
self._rpc({
model: group_by_field.relation,
method: 'name_get',
args: [
ids,
group_ids,
],
context: this.getSession().user_context,
context: self.getSession().user_context,
}).then(function(names) {
var nevents = _.map(events, function (event) {
return _.extend({
__name: _.detect(names, function (name) {
return name[0] === event.id;
})[1]
}, event);
_.each(names, function (gr_name) {
self.groups.push({
id: gr_name[0],
content: gr_name[1],
})
self.$select_groups.append($("<option value='" + gr_name[0] + "' selected='selected'>" + gr_name[1] + "</option>"));
});
return self.on_data_loaded_2(nevents, group_bys, adjust_window);
self.$select_groups.multiselect('rebuild');
self.timeline.setGroups();
self.updateSelectedGroups();
return true;
});
},
arrayRemove: function (arr, id) {
return arr.filter( function (elmt) {
return elmt.id != id;
});
},
updateSelectedGroups: function() {
var self = this;
var selected = self.$select_groups.find("option:selected");
self.selected_groups = [];
_.each(selected, function (elmt) {
self.selected_groups.push({
id: elmt.value,
content: elmt.text,
})
});
self.timeline.setGroups(self.selected_groups);
},
createSelectGroups: function () {
var self = this;
var select_groups = $(core.qweb.render('TimelineSelectGroups', {'groups': self.groups}));
self.$el.find('.selected-groups').html(select_groups);
self.$select_groups = select_groups;
select_groups.multiselect({
buttonWidth: '350px',
maxHeight: 400,
enableFiltering: true,
enableClickableOptGroups: true,
includeSelectAllOption: true,
allSelectedText: _t('All groups selected'),
onChange: function(element, checked) {
self.updateSelectedGroups();
},
onSelectAll: function() {
self.updateSelectedGroups();
},
onDeselectAll: function() {
self.updateSelectedGroups();
},
});
},
/**
* Set groups and events.
*
* @private
*/
on_data_loaded_2: function (events, group_bys, adjust_window) {
on_data_loaded_2: function (events, group_bys, x2x, adjust_window) {
var self = this;
var data = [];
var groups = [];
this.grouped_by = group_bys;
_.each(events, function (event) {
if (event[self.date_start]) {
data.push(self.event_data_transform(event));
if (x2x) {
_.each(event[group_bys], function (gr) {
var x2x_object = jQuery.extend({}, event);
x2x_object[group_bys] = [gr];
x2x_object.id = event.id + "_" + gr;
data.push(self.event_data_transform(x2x_object));
})
} else {
data.push(self.event_data_transform(event));
}
}
});
groups = this.split_groups(events, group_bys);
if (x2x) {
groups = this.groups;
this.selected_groups = this.groups;
this.createSelectGroups();
} else {
if (this.$select_groups !== undefined) {
this.$el.find('.selected-groups').html('');
this.$select_groups.remove();
}
groups = this.split_groups(events, group_bys);
}
this.timeline.setGroups(groups);
this.timeline.setItems(data);
var mode = !this.mode || this.mode === 'fit';
@ -371,7 +494,6 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
var group = _.find(groups, function (existing_group) {
return _.isEqual(existing_group.id, group_name[0]);
});
if (_.isUndefined(group)) {
group = {
id: group_name[0],
@ -379,12 +501,56 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
};
groups.push(group);
}
} else {
var group = _.find(groups, function (existing_group) {
return _.isEqual(existing_group.id, group_name);
});
if (_.isUndefined(group)) {
group = {
id: group_name,
content: group_name
};
groups.push(group);
}
}
}
});
return groups;
},
split_groups_x2x: function (events, group_bys) {
var group_ids = [];
_.each(events, function (event) {
var group_name = event[_.first(group_bys)];
if (group_name) {
_.each(group_name, function (gr) {
if (!group_ids.includes(gr)) {
group_ids.push(gr);
}
});
}
});
var group_by_field = this.view.fields[group_bys];
return this._rpc({
model: group_by_field.relation,
method: 'name_get',
args: [
group_ids,
],
context: this.getSession().user_context,
}).then(function(names) {
var groups = [];
groups.push({id: -1, content: _t('-')});
_.each(names, function (gr_name) {
groups.push({
id: gr_name[0],
content: gr_name[1],
})
});
return groups;
});
},
/**
* Transform Odoo event object to timeline event object.
*
@ -416,16 +582,23 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
}
var group = evt[self.last_group_bys[0]];
if (group && group instanceof Array) {
group = _.first(group);
if (group) {
if (group instanceof Array) {
group = _.first(group);
}
} else {
group = -1;
}
_.each(self.colors, function (color) {
if (eval("'" + evt[color.field] + "' " + color.opt + " '" + color.value + "'")) {
self.color = color.color;
}
});
var color = null;
if (self.arch.attrs.color_field !== undefined) {
color = evt[self.arch.attrs.color_field];
} else {
_.each(self.colors, function (col) {
if (eval("'" + evt[col.field] + "' " + col.opt + " '" + col.value + "'")) {
color = col.color;
}
});
}
var content = _.isUndefined(evt.__name) ? evt.display_name : evt.__name;
if (this.arch.children.length) {
@ -438,13 +611,12 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
'id': evt.id,
'group': group,
'evt': evt,
'style': 'background-color: ' + self.color + ';'
'style': 'background-color: ' + color + ';'
};
// Check if the event is instantaneous, if so, display it with a point on the timeline (no 'end')
if (date_stop && !moment(date_start).isSame(date_stop)) {
r.end = date_stop;
}
self.color = null;
return r;
},

1
web_timeline/static/src/js/timeline_view.js

@ -61,6 +61,7 @@ odoo.define('web_timeline.TimelineView', function (require) {
"default_group_by",
"progress",
"date_delay",
"color_field",
];
fieldsToGather.push(attrs.default_group_by);

18
web_timeline/static/src/xml/web_timeline.xml

@ -2,19 +2,29 @@
<t t-name="TimelineView">
<div class="oe_timeline_view">
<div class="oe_timeline_buttons">
<button class="btn btn-default btn-sm oe_timeline_button_today">Today</button>
<div class="btn-group btn-sm">
<button class="btn btn-default btn-sm oe_timeline_button_today">Today</button>
<div class="btn-group btn-sm">
<button class="btn btn-default oe_timeline_button_scale_day">Day</button>
<button class="btn btn-default oe_timeline_button_scale_week">Week</button>
<button class="btn btn-default oe_timeline_button_scale_month">Month</button>
<button class="btn btn-default oe_timeline_button_scale_year">Year</button>
</div>
</div>
|
<div class="selected-groups"/>
</div>
<div class="oe_timeline_widget" />
</div>
</t>
<t t-name="TimelineSelectGroups">
<select class="select-groups" multiple="multiple">
<t t-foreach="groups" t-as="group">
<option t-att-value='group.id' selected="selected"><t t-esc="group.content"/></option>
</t>
</select>
</t>
<svg t-name="TimelineView.Canvas"
class="oe_timeline_view_canvas">
<defs>

2
web_timeline/views/web_timeline.xml

@ -5,7 +5,9 @@
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_timeline/static/lib/vis/vis-timeline-graph2d.min.css"/>
<link rel="stylesheet" href="/web_timeline/static/src/css/web_timeline.css"/>
<link rel="stylesheet" href="/web_timeline/static/lib/multiselect/bootstrap-multiselect.css"/>
<script type="text/javascript" src="/web_timeline/static/lib/multiselect/bootstrap-multiselect.js"/>
<script type="text/javascript" src="/web_timeline/static/lib/vis/vis-timeline-graph2d.min.js"/>
<script type="text/javascript" src="/web_timeline/static/src/js/timeline_view.js"/>
<script type="text/javascript" src="/web_timeline/static/src/js/timeline_renderer.js"/>

Loading…
Cancel
Save