[FIX] web_timeline: Redraw issues with initial mode

This commit fixes redraw issues when setting a `mode` attribute in the
`timeline` view tag.

This mode specifies a default scale one would want to set; same as when
clicking on Day/Week/Month buttons at the top of the view.

Initial rendering had issues here because data was loaded too soon,
before the timeline component was rendered/ready. The fix is to load
data into the component only after initial redraw event, called
`changed` (see <https://visjs.github.io/vis-timeline/docs/timeline/#Events>).

There was old code attempting to call `on_scale_xxx_clicked` methods at
load time to simulate clicks on these Day/Week/Month buttons, but these
methods have been renamed so this code is no longer working.

This commit also removes the `current_window` instance variable, not
needed and actually confusing as the timeline component already
maintains its own start/end information (which we can query with
`timeline.getWindow()`).
pull/2969/head
Houzéfa Abbasbhay 2024-03-21 17:38:33 +01:00 committed by Carlos Lopez
parent c8490eb2f3
commit 0d816893a7
5 changed files with 38 additions and 48 deletions

View File

@ -7,7 +7,7 @@ Web timeline
!! This file is generated by oca-gen-addon-readme !! !! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !! !! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c8c5da5c7631fad4d64b447787e487826952622707762815d1166990b565fcd0 !! source digest: sha256:bd504d36989179a1e11223c03dbf38ac45f0a792c6ad15a2786c9283bc64650b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png

View File

@ -4,7 +4,7 @@
{ {
"name": "Web timeline", "name": "Web timeline",
"summary": "Interactive visualization chart to show events in time", "summary": "Interactive visualization chart to show events in time",
"version": "16.0.1.1.2", "version": "16.0.1.1.4",
"development_status": "Production/Stable", "development_status": "Production/Stable",
"author": "ACSONE SA/NV, " "author": "ACSONE SA/NV, "
"Tecnativa, " "Tecnativa, "

View File

@ -8,10 +8,11 @@
/* /*
:Author: David Goodger (goodger@python.org) :Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain. :Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils. Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet. customize this style sheet.
@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ; margin-left: 2em ;
margin-right: 2em } margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */ pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee } pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 } pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@ -300,7 +301,7 @@ span.option {
span.pre { span.pre {
white-space: pre } white-space: pre }
span.problematic { span.problematic, pre.problematic {
color: red } color: red }
span.section-subtitle { span.section-subtitle {
@ -366,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !! !! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !! !! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c8c5da5c7631fad4d64b447787e487826952622707762815d1166990b565fcd0 !! source digest: sha256:bd504d36989179a1e11223c03dbf38ac45f0a792c6ad15a2786c9283bc64650b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/16.0/web_timeline"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_timeline"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p> <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/16.0/web_timeline"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_timeline"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Define a new view displaying events in an interactive visualization chart.</p> <p>Define a new view displaying events in an interactive visualization chart.</p>
@ -614,7 +615,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<div class="section" id="maintainers"> <div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2> <h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p> <p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a> <a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use.</p> promote its widespread use.</p>

View File

@ -41,6 +41,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
this.fields = params.fields; this.fields = params.fields;
this.timeline = false; this.timeline = false;
this.initial_data_loaded = false;
}, },
/** /**
@ -48,11 +49,6 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
*/ */
start: function () { start: function () {
const attrs = this.arch.attrs; const attrs = this.arch.attrs;
this.current_window = {
start: new moment(),
end: new moment().add(24, "hours"),
};
this.$el.addClass(attrs.class); this.$el.addClass(attrs.class);
this.$timeline = this.$(".oe_timeline_widget"); this.$timeline = this.$(".oe_timeline_widget");
@ -85,7 +81,6 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
// Prevent Double Rendering on Updates // Prevent Double Rendering on Updates
if (!this.timeline) { if (!this.timeline) {
this.init_timeline(); this.init_timeline();
$(window).trigger("resize");
} }
}); });
}, },
@ -96,13 +91,11 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
* @private * @private
*/ */
_onTodayClicked: function () { _onTodayClicked: function () {
this.current_window = {
start: new moment(),
end: new moment().add(24, "hours"),
};
if (this.timeline) { if (this.timeline) {
this.timeline.setWindow(this.current_window); this.timeline.setWindow({
start: new moment(),
end: new moment().add(24, "hours"),
});
} }
}, },
@ -112,7 +105,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
* @private * @private
*/ */
_onScaleDayClicked: function () { _onScaleDayClicked: function () {
this._scaleCurrentWindow(24); this._scaleCurrentWindow(() => 24);
}, },
/** /**
@ -121,7 +114,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
* @private * @private
*/ */
_onScaleWeekClicked: function () { _onScaleWeekClicked: function () {
this._scaleCurrentWindow(24 * 7); this._scaleCurrentWindow(() => 24 * 7);
}, },
/** /**
@ -130,9 +123,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
* @private * @private
*/ */
_onScaleMonthClicked: function () { _onScaleMonthClicked: function () {
this._scaleCurrentWindow( this._scaleCurrentWindow((start) => 24 * moment(start).daysInMonth());
24 * moment(this.current_window.start).daysInMonth()
);
}, },
/** /**
@ -142,24 +133,22 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
*/ */
_onScaleYearClicked: function () { _onScaleYearClicked: function () {
this._scaleCurrentWindow( this._scaleCurrentWindow(
24 * (moment(this.current_window.start).isLeapYear() ? 366 : 365) (start) => 24 * (moment(start).isLeapYear() ? 366 : 365)
); );
}, },
/** /**
* Scales the timeline window based on the current window. * Scales the timeline window based on the current window.
* *
* @param {Integer} factor The timespan (in hours) the window must be scaled to. * @param {function} getHoursFromStart Function which returns the timespan
* (in hours) the window must be scaled to, starting from the "start" moment.
* @private * @private
*/ */
_scaleCurrentWindow: function (factor) { _scaleCurrentWindow: function (getHoursFromStart) {
if (this.timeline) { if (this.timeline) {
this.current_window = this.timeline.getWindow(); const start = this.timeline.getWindow().start;
this.current_window.end = moment(this.current_window.start).add( const end = moment(start).add(getHoursFromStart(start), "hours");
factor, this.timeline.setWindow(start, end);
"hours"
);
this.timeline.setWindow(this.current_window);
} }
}, },
@ -219,6 +208,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
onUpdate: this.on_update, onUpdate: this.on_update,
onRemove: this.on_remove, onRemove: this.on_remove,
}); });
this.options.xss = {disabled: true};
this.qweb = new QWeb(session.debug, {_s: session.origin}, false); this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
if (this.arch.children.length) { if (this.arch.children.length) {
const tmpl = utils.json_node_to_xml( const tmpl = utils.json_node_to_xml(
@ -227,25 +217,17 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
this.qweb.add_template(tmpl); this.qweb.add_template(tmpl);
} }
this.timeline = new vis.Timeline( this.timeline = new vis.Timeline(this.$timeline.get(0), {}, this.options);
this.$timeline.get(0),
{},
{xss: {disabled: true}}
);
this.timeline.setOptions(this.options);
if (this.mode && this["on_scale_" + this.mode + "_clicked"]) {
this["on_scale_" + this.mode + "_clicked"]();
}
this.timeline.on("click", this.on_group_click); this.timeline.on("click", this.on_group_click);
const group_bys = this.arch.attrs.default_group_by.split(","); const group_bys = this.arch.attrs.default_group_by.split(",");
this.last_group_bys = group_bys; this.last_group_bys = group_bys;
this.last_domains = this.modelClass.data.domain; this.last_domains = this.modelClass.data.domain;
this.on_data_loaded(this.modelClass.data.data, group_bys);
this.$centerContainer = $(this.timeline.dom.centerContainer); this.$centerContainer = $(this.timeline.dom.centerContainer);
this.canvas = new TimelineCanvas(this); this.canvas = new TimelineCanvas(this);
this.canvas.appendTo(this.$centerContainer); this.canvas.appendTo(this.$centerContainer);
this.timeline.on("changed", () => { this.timeline.on("changed", () => {
this.draw_canvas(); this.draw_canvas();
this.load_initial_data();
}); });
}, },
@ -313,6 +295,16 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
); );
}, },
/* Load initial data. This is called once after each redraw; we only handle the first one.
* Deferring this initial load here avoids rendering issues. */
load_initial_data: function () {
if (!this.initial_data_loaded) {
this.on_data_loaded(this.modelClass.data.data, this.last_group_bys);
this.initial_data_loaded = true;
this.timeline.redraw();
}
},
/** /**
* Load display_name of records. * Load display_name of records.
* *

View File

@ -86,10 +86,6 @@ odoo.define("web_timeline.TimelineView", function (require) {
const mode = attrs.mode || attrs.default_window || "fit"; const mode = attrs.mode || attrs.default_window || "fit";
const min_height = attrs.min_height || 300; const min_height = attrs.min_height || 300;
const current_window = {
start: new moment(),
end: new moment().add(24, "hours"),
};
if (!isNullOrUndef(attrs.quick_create_instance)) { if (!isNullOrUndef(attrs.quick_create_instance)) {
this.quick_create_instance = "instance." + attrs.quick_create_instance; this.quick_create_instance = "instance." + attrs.quick_create_instance;
} }
@ -104,7 +100,6 @@ odoo.define("web_timeline.TimelineView", function (require) {
this.rendererParams.model = this.modelName; this.rendererParams.model = this.modelName;
this.rendererParams.view = this; this.rendererParams.view = this;
this.rendererParams.options = this._preapre_vis_timeline_options(attrs); this.rendererParams.options = this._preapre_vis_timeline_options(attrs);
this.rendererParams.current_window = current_window;
this.rendererParams.date_start = date_start; this.rendererParams.date_start = date_start;
this.rendererParams.date_stop = date_stop; this.rendererParams.date_stop = date_stop;
this.rendererParams.date_delay = date_delay; this.rendererParams.date_delay = date_delay;