3
0
Fork 0

[MIG] web_timeline: Finish migration to 13.0

13.0
Alexandre Díaz 2020-03-23 02:39:25 +01:00
parent 245d3087b0
commit 07666a25cc
19 changed files with 1947 additions and 595 deletions

View File

@ -0,0 +1 @@
../../../../web_timeline

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -2,8 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json import json
from unittest import mock
import mock
from odoo import exceptions from odoo import exceptions
from odoo.tests import common from odoo.tests import common

View File

@ -14,16 +14,16 @@ Web timeline
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3 :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/12.0/web_timeline :target: https://github.com/OCA/web/tree/13.0/web_timeline
:alt: OCA/web :alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_timeline :target: https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_timeline
:alt: Translate me on Weblate :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/12.0 :target: https://runbot.odoo-community.org/runbot/162/13.0
:alt: Try me on Runbot :alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
Define a new view displaying events in an interactive visualization chart. Define a new view displaying events in an interactive visualization chart.
@ -88,7 +88,7 @@ Example:
<field name="model">project.task</field> <field name="model">project.task</field>
<field name="type">timeline</field> <field name="type">timeline</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<timeline date_start="date_start" <timeline date_start="date_assign"
date_stop="date_end" date_stop="date_end"
string="Tasks" string="Tasks"
default_group_by="user_id" default_group_by="user_id"
@ -159,7 +159,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_timeline%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. `feedback <https://github.com/OCA/web/issues/new?body=module:%20web_timeline%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues. Do not contact contributors directly about support or help with technical issues.
@ -185,14 +185,7 @@ Contributors
* Adrien Didenot <adrien.didenot@horanet.com> * Adrien Didenot <adrien.didenot@horanet.com>
* Dennis Sluijk <d.sluijk@onestein.nl> * Dennis Sluijk <d.sluijk@onestein.nl>
* Thong Nguyen Van <thongnv@trobz.com> * Thong Nguyen Van <thongnv@trobz.com>
* Alexandre Díaz <alexandre.diaz@tecnativa.com>
Other credits
~~~~~~~~~~~~~
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Maintainers Maintainers
~~~~~~~~~~~ ~~~~~~~~~~~
@ -213,8 +206,8 @@ promote its widespread use.
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__: Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-tarteo| |maintainer-tarteo|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/12.0/web_timeline>`_ project on GitHub. This module is part of the `OCA/web <https://github.com/OCA/web/tree/13.0/web_timeline>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -48,7 +48,7 @@ Example:
<field name="model">project.task</field> <field name="model">project.task</field>
<field name="type">timeline</field> <field name="type">timeline</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<timeline date_start="date_start" <timeline date_start="date_assign"
date_stop="date_end" date_stop="date_end"
string="Tasks" string="Tasks"
default_group_by="user_id" default_group_by="user_id"

View File

@ -5,3 +5,4 @@
* Adrien Didenot <adrien.didenot@horanet.com> * Adrien Didenot <adrien.didenot@horanet.com>
* Dennis Sluijk <d.sluijk@onestein.nl> * Dennis Sluijk <d.sluijk@onestein.nl>
* Thong Nguyen Van <thongnv@trobz.com> * Thong Nguyen Van <thongnv@trobz.com>
* Alexandre Díaz <alexandre.diaz@tecnativa.com>

View File

@ -1,4 +0,0 @@
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

View File

@ -367,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. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" 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" 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" href="https://github.com/OCA/web/tree/12.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" href="https://translation.odoo-community.org/projects/web-12-0/web-12-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" href="https://runbot.odoo-community.org/runbot/162/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p> <p><a class="reference external" 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" 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" href="https://github.com/OCA/web/tree/13.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" href="https://translation.odoo-community.org/projects/web-13-0/web-13-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" href="https://runbot.odoo-community.org/runbot/162/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-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>
<p>The widget is based on the external library <p>The widget is based on the external library
<a class="reference external" href="https://visjs.github.io/vis-timeline/examples/timeline">https://visjs.github.io/vis-timeline/examples/timeline</a></p> <a class="reference external" href="https://visjs.github.io/vis-timeline/examples/timeline">https://visjs.github.io/vis-timeline/examples/timeline</a></p>
@ -381,11 +381,7 @@ ul.auto-toc {
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul> <li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li> <li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li> <li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id8">Other credits</a><ul> <li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
<li><a class="reference internal" href="#images" id="id9">Images</a></li>
</ul>
</li>
<li><a class="reference internal" href="#maintainers" id="id10">Maintainers</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -471,7 +467,7 @@ These are the variables available in template rendering:</p>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;model&quot;</span><span class="nt">&gt;</span>project.task<span class="nt">&lt;/field&gt;</span> <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;model&quot;</span><span class="nt">&gt;</span>project.task<span class="nt">&lt;/field&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;type&quot;</span><span class="nt">&gt;</span>timeline<span class="nt">&lt;/field&gt;</span> <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;type&quot;</span><span class="nt">&gt;</span>timeline<span class="nt">&lt;/field&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;arch&quot;</span> <span class="na">type=</span><span class="s">&quot;xml&quot;</span><span class="nt">&gt;</span> <span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;arch&quot;</span> <span class="na">type=</span><span class="s">&quot;xml&quot;</span><span class="nt">&gt;</span>
<span class="nt">&lt;timeline</span> <span class="na">date_start=</span><span class="s">&quot;date_start&quot;</span> <span class="nt">&lt;timeline</span> <span class="na">date_start=</span><span class="s">&quot;date_assign&quot;</span>
<span class="na">date_stop=</span><span class="s">&quot;date_end&quot;</span> <span class="na">date_stop=</span><span class="s">&quot;date_end&quot;</span>
<span class="na">string=</span><span class="s">&quot;Tasks&quot;</span> <span class="na">string=</span><span class="s">&quot;Tasks&quot;</span>
<span class="na">default_group_by=</span><span class="s">&quot;user_id&quot;</span> <span class="na">default_group_by=</span><span class="s">&quot;user_id&quot;</span>
@ -537,7 +533,7 @@ with the dragged start and end date.</p>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>. <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_timeline%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> <a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_timeline%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
@ -562,19 +558,11 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<li>Adrien Didenot &lt;<a class="reference external" href="mailto:adrien.didenot&#64;horanet.com">adrien.didenot&#64;horanet.com</a>&gt;</li> <li>Adrien Didenot &lt;<a class="reference external" href="mailto:adrien.didenot&#64;horanet.com">adrien.didenot&#64;horanet.com</a>&gt;</li>
<li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</li> <li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</li>
<li>Thong Nguyen Van &lt;<a class="reference external" href="mailto:thongnv&#64;trobz.com">thongnv&#64;trobz.com</a>&gt;</li> <li>Thong Nguyen Van &lt;<a class="reference external" href="mailto:thongnv&#64;trobz.com">thongnv&#64;trobz.com</a>&gt;</li>
<li>Alexandre Díaz &lt;<a class="reference external" href="mailto:alexandre.diaz&#64;tecnativa.com">alexandre.diaz&#64;tecnativa.com</a>&gt;</li>
</ul> </ul>
</div> </div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id8">Other credits</a></h2>
<div class="section" id="images">
<h3><a class="toc-backref" href="#id9">Images</a></h3>
<ul class="simple">
<li>Odoo Community Association: <a class="reference external" href="https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg">Icon</a>.</li>
</ul>
</div>
</div>
<div class="section" id="maintainers"> <div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id10">Maintainers</a></h2> <h2><a class="toc-backref" href="#id8">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
@ -582,7 +570,7 @@ mission is to support the collaborative development of Odoo features and
promote its widespread use.</p> promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p> <p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/tarteo"><img alt="tarteo" src="https://github.com/tarteo.png?size=40px" /></a></p> <p><a class="reference external" href="https://github.com/tarteo"><img alt="tarteo" src="https://github.com/tarteo.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/12.0/web_timeline">OCA/web</a> project on GitHub.</p> <p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_timeline">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,69 +1,83 @@
/* Copyright 2018 Onestein /* Copyright 2018 Onestein
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('web_timeline.TimelineCanvas', function (require) { odoo.define("web_timeline.TimelineCanvas", function(require) {
"use strict"; "use strict";
var Widget = require('web.Widget'); const Widget = require("web.Widget");
/** /**
* Used to draw stuff on upon the timeline view. * Used to draw stuff on upon the timeline view.
*/ */
var TimelineCanvas = Widget.extend({ const TimelineCanvas = Widget.extend({
template: 'TimelineView.Canvas', template: "TimelineView.Canvas",
/** /**
* Clears all drawings (svg elements) from the canvas. * Clears all drawings (svg elements) from the canvas.
*/ */
clear: function () { clear: function() {
this.$(' > :not(defs)').remove(); this.$(" > :not(defs)").remove();
}, },
/** /**
* Gets the path from one point to another. * Gets the path from one point to another.
* *
* @param {Number} coordx1 * @param {Object} rectFrom
* @param {Number} coordy1 * @param {Object} rectTo
* @param {Number} coordx2
* @param {Number} coordy2
* @param {Number} width1
* @param {Number} height1
* @param {Number} width2
* @param {Number} height2
* @param {Number} widthMarker The marker's width of the polyline * @param {Number} widthMarker The marker's width of the polyline
* @param {Number} breakAt The space between the line turns * @param {Number} breakAt The space between the line turns
* @returns {Array} Each item represents a coordinate * @returns {Array} Each item represents a coordinate
*/ */
get_polyline_points: function (coordx1, coordy1, coordx2, coordy2, width1, height1, width2, height2, widthMarker, breakAt) { get_polyline_points: function(rectFrom, rectTo, widthMarker, breakAt) {
var halfHeight1 = height1 / 2; let fromX = 0,
var halfHeight2 = height2 / 2; toX = 0;
var x1 = coordx1 - widthMarker; if (rectFrom.x < rectTo.x + rectTo.w) {
var y1 = coordy1 + halfHeight1; fromX = rectFrom.x + rectFrom.w + widthMarker;
var x2 = coordx2 + width2; toX = rectTo.x;
var y2 = coordy2 + halfHeight2; } else {
var xDiff = x1 - x2; fromX = rectFrom.x - widthMarker;
var yDiff = y1 - y2; toX = rectTo.x + rectTo.w;
var threshold = breakAt + widthMarker;
var spaceY = halfHeight2 + 6;
var points = [[x1, y1]];
if (y1 !== y2) {
if (xDiff > threshold) {
points.push([x1 - breakAt, y1]);
points.push([x1 - breakAt, y1 - yDiff]);
} else {
var yDiffSpace = yDiff > 0 ? spaceY : -spaceY;
points.push([x1 - breakAt, y1]);
points.push([x1 - breakAt, y2 + yDiffSpace]);
points.push([x2 + breakAt, y2 + yDiffSpace]);
points.push([x2 + breakAt, y2]);
}
} else if (x1 < x2) {
points.push([x1 - breakAt, y1]);
points.push([x1 - breakAt, y1 + spaceY]);
points.push([x2 + breakAt, y2 + spaceY]);
points.push([x2 + breakAt, y2]);
} }
points.push([x2, y2]); let deltaBreak = 0;
if (fromX < toX) {
deltaBreak = fromX + breakAt - (toX - breakAt);
} else {
deltaBreak = fromX - breakAt - (toX + breakAt);
}
const fromHalfHeight = rectFrom.h / 2;
const fromY = rectFrom.y + fromHalfHeight;
const toHalfHeight = rectTo.h / 2;
const toY = rectTo.y + toHalfHeight;
const xDiff = fromX - toX;
const yDiff = fromY - toY;
const threshold = breakAt + widthMarker;
const spaceY = toHalfHeight + 2;
const points = [[fromX, fromY]];
const _addPoints = (space, ePoint, mode) => {
if (mode) {
points.push([fromX + breakAt, fromY]);
points.push([fromX + breakAt, ePoint + space]);
points.push([toX - breakAt, toY + space]);
points.push([toX - breakAt, toY]);
} else {
points.push([fromX - breakAt, fromY]);
points.push([fromX - breakAt, ePoint + space]);
points.push([toX + breakAt, toY + space]);
points.push([toX + breakAt, toY]);
}
};
if (fromY !== toY) {
if (Math.abs(xDiff) < threshold) {
points.push([fromX + breakAt, toY + yDiff]);
points.push([fromX + breakAt, toY]);
} else {
const yDiffSpace = yDiff > 0 ? spaceY : -spaceY;
_addPoints(yDiffSpace, toY, rectFrom.x < rectTo.x + rectTo.w);
}
} else if (Math.abs(deltaBreak) >= threshold) {
_addPoints(spaceY, fromY, fromX < toX);
}
points.push([toX, toY]);
return points; return points;
}, },
@ -77,8 +91,8 @@ odoo.define('web_timeline.TimelineCanvas', function (require) {
* @param {Number} width Width of the line * @param {Number} width Width of the line
* @returns {HTMLElement} The created SVG polyline * @returns {HTMLElement} The created SVG polyline
*/ */
draw_arrow: function (from, to, color, width) { draw_arrow: function(from, to, color, width) {
return this.draw_line(from, to, color, width, '#arrowhead', 10, 12); return this.draw_line(from, to, color, width, "#arrowhead", 10, 12);
}, },
/** /**
@ -93,31 +107,49 @@ odoo.define('web_timeline.TimelineCanvas', function (require) {
* @param {Number} breakLineAt The space between the line turns * @param {Number} breakLineAt The space between the line turns
* @returns {HTMLElement} The created SVG polyline * @returns {HTMLElement} The created SVG polyline
*/ */
draw_line: function (from, to, color, width, markerStart, widthMarker, breakLineAt) { draw_line: function(
var x1 = from.offsetLeft, from,
y1 = from.offsetTop + from.parentElement.offsetTop, to,
x2 = to.offsetLeft, color,
y2 = to.offsetTop + to.parentElement.offsetTop, width,
width1 = from.clientWidth, markerStart,
height1 = from.clientHeight, widthMarker,
width2 = to.clientWidth, breakLineAt
height2 = to.clientHeight; ) {
const $from = $(from);
var points = this.get_polyline_points(x1, y1, x2, y2, width1, height1, width2, height2, widthMarker, breakLineAt); const childPosFrom = $from.offset();
const parentPosFrom = $from.closest(".vis-center").offset();
var polyline_points = _.map(points, function (point) { const rectFrom = {
return point.join(','); x: childPosFrom.left - parentPosFrom.left,
}).join(); y: childPosFrom.top - parentPosFrom.top,
w: $from.width(),
var line = document.createElementNS( h: $from.height(),
'http://www.w3.org/2000/svg', 'polyline' };
const $to = $(to);
const childPosTo = $to.offset();
const parentPosTo = $to.closest(".vis-center").offset();
const rectTo = {
x: childPosTo.left - parentPosTo.left,
y: childPosTo.top - parentPosTo.top,
w: $to.width(),
h: $to.height(),
};
const points = this.get_polyline_points(
rectFrom,
rectTo,
widthMarker,
breakLineAt
); );
line.setAttribute('points', polyline_points); const line = document.createElementNS(
line.setAttribute('stroke', color || '#000'); "http://www.w3.org/2000/svg",
line.setAttribute('stroke-width', width || 1); "polyline"
line.setAttribute('fill', 'none'); );
line.setAttribute("points", _.flatten(points).join(","));
line.setAttribute("stroke", color || "#000");
line.setAttribute("stroke-width", width || 1);
line.setAttribute("fill", "none");
if (markerStart) { if (markerStart) {
line.setAttribute('marker-start', 'url(' + markerStart + ')'); line.setAttribute("marker-start", "url(" + markerStart + ")");
} }
this.$el.append(line); this.$el.append(line);
return line; return line;

View File

@ -1,27 +1,27 @@
odoo.define('web_timeline.TimelineController', function (require) { odoo.define("web_timeline.TimelineController", function(require) {
"use strict"; "use strict";
var AbstractController = require('web.AbstractController'); const AbstractController = require("web.AbstractController");
var dialogs = require('web.view_dialogs'); const dialogs = require("web.view_dialogs");
var core = require('web.core'); const core = require("web.core");
var time = require('web.time'); const time = require("web.time");
var Dialog = require('web.Dialog'); const Dialog = require("web.Dialog");
var _t = core._t; const _t = core._t;
var TimelineController = AbstractController.extend({ const TimelineController = AbstractController.extend({
custom_events: _.extend({}, AbstractController.prototype.custom_events, { custom_events: _.extend({}, AbstractController.prototype.custom_events, {
onGroupClick: '_onGroupClick', onGroupClick: "_onGroupClick",
onUpdate: '_onUpdate', onUpdate: "_onUpdate",
onRemove: '_onRemove', onRemove: "_onRemove",
onMove: '_onMove', onMove: "_onMove",
onAdd: '_onAdd', onAdd: "_onAdd",
}), }),
/** /**
* @override * @override
*/ */
init: function (parent, model, renderer, params) { init: function(parent, model, renderer, params) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.open_popup_action = params.open_popup_action; this.open_popup_action = params.open_popup_action;
this.date_start = params.date_start; this.date_start = params.date_start;
@ -35,77 +35,81 @@ odoo.define('web_timeline.TimelineController', function (require) {
/** /**
* @override * @override
*/ */
update: function (params, options) { update: function(params, options) {
var res = this._super.apply(this, arguments); const res = this._super.apply(this, arguments);
if (_.isEmpty(params)) { if (_.isEmpty(params)) {
return res; return res;
} }
var defaults = _.defaults({}, options, { const defaults = _.defaults({}, options, {
adjust_window: true, adjust_window: true,
}); });
var self = this; const domains = params.domain;
var domains = params.domain; const contexts = params.context;
var contexts = params.context; const group_bys = params.groupBy;
var group_bys = params.groupBy;
this.last_domains = domains; this.last_domains = domains;
this.last_contexts = contexts; this.last_contexts = contexts;
// Select the group by // Select the group by
var n_group_bys = group_bys; let n_group_bys = group_bys;
if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) { if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) {
n_group_bys = this.renderer.arch.attrs.default_group_by.split(','); n_group_bys = this.renderer.arch.attrs.default_group_by.split(",");
} }
this.renderer.last_group_bys = n_group_bys; this.renderer.last_group_bys = n_group_bys;
this.renderer.last_domains = domains; this.renderer.last_domains = domains;
var fields = this.renderer.fieldNames; let fields = this.renderer.fieldNames;
fields = _.uniq(fields.concat(n_group_bys)); fields = _.uniq(fields.concat(n_group_bys));
return $.when( return $.when(
res, res,
self._rpc({ this._rpc({
model: self.model.modelName, model: this.model.modelName,
method: 'search_read', method: "search_read",
kwargs: { kwargs: {
fields: fields, fields: fields,
domain: domains, domain: domains,
}, },
context: self.getSession().user_context, context: this.getSession().user_context,
}).then(function (data) { }).then(data =>
return self.renderer.on_data_loaded(data, n_group_bys, defaults.adjust_window); this.renderer.on_data_loaded(
}) data,
n_group_bys,
defaults.adjust_window
)
)
); );
}, },
/** /**
* Gets triggered when a group in the timeline is clicked (by the TimelineRenderer). * Gets triggered when a group in the timeline is
* clicked (by the TimelineRenderer).
* *
* @private * @private
* @param {EventObject} event * @param {EventObject} event
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
_onGroupClick: function (event) { _onGroupClick: function(event) {
var groupField = this.renderer.last_group_bys[0]; const groupField = this.renderer.last_group_bys[0];
return this.do_action({ return this.do_action({
type: 'ir.actions.act_window', type: "ir.actions.act_window",
res_model: this.renderer.view.fields[groupField].relation, res_model: this.renderer.fields[groupField].relation,
res_id: event.data.item.group, res_id: event.data.item.group,
target: 'new', target: "new",
views: [[false, 'form']], views: [[false, "form"]],
}); });
}, },
/** /**
* Opens a form view of a clicked timeline item (triggered by the TimelineRenderer). * Opens a form view of a clicked timeline
* item (triggered by the TimelineRenderer).
* *
* @private * @private
* @param {EventObject} event * @param {EventObject} event
*/ */
_onUpdate: function (event) { _onUpdate: function(event) {
var self = this;
this.renderer = event.data.renderer; this.renderer = event.data.renderer;
var rights = event.data.rights; const rights = event.data.rights;
var item = event.data.item; const item = event.data.item;
var id = Number(item.evt.id) || item.evt.id; const id = Number(item.evt.id) || item.evt.id;
var title = item.evt.__name; const title = item.evt.__name;
if (this.open_popup_action) { if (this.open_popup_action) {
new dialogs.FormViewDialog(this, { new dialogs.FormViewDialog(this, {
res_model: this.model.modelName, res_model: this.model.modelName,
@ -113,17 +117,17 @@ odoo.define('web_timeline.TimelineController', function (require) {
context: this.getSession().user_context, context: this.getSession().user_context,
title: title, title: title,
view_id: Number(this.open_popup_action), view_id: Number(this.open_popup_action),
on_saved: function () { on_saved: () => {
self.write_completed(); this.write_completed();
}, },
}).open(); }).open();
} else { } else {
var mode = 'readonly'; let mode = "readonly";
if (rights.write) { if (rights.write) {
mode = 'edit'; mode = "edit";
} }
this.trigger_up('switch_view', { this.trigger_up("switch_view", {
view_type: 'form', view_type: "form",
res_id: id, res_id: id,
mode: mode, mode: mode,
model: this.model.modelName, model: this.model.modelName,
@ -132,38 +136,49 @@ odoo.define('web_timeline.TimelineController', function (require) {
}, },
/** /**
* Gets triggered when a timeline item is moved (triggered by the TimelineRenderer). * Gets triggered when a timeline item is
* moved (triggered by the TimelineRenderer).
* *
* @private * @private
* @param {EventObject} event * @param {EventObject} event
*/ */
_onMove: function (event) { _onMove: function(event) {
var item = event.data.item; const item = event.data.item;
var view = this.renderer.view; const fields = this.renderer.fields;
var fields = view.fields; const event_start = item.start;
var event_start = item.start; const event_end = item.end;
var event_end = item.end; let group = false;
var group = false;
if (item.group !== -1) { if (item.group !== -1) {
group = item.group; group = item.group;
} }
var data = {}; const data = {};
// In case of a move event, the date_delay stay the same, // In case of a move event, the date_delay stay the same,
// only date_start and stop must be updated // only date_start and stop must be updated
data[this.date_start] = time.auto_date_to_str(event_start, fields[this.date_start].type); data[this.date_start] = time.auto_date_to_str(
event_start,
fields[this.date_start].type
);
if (this.date_stop) { if (this.date_stop) {
// In case of instantaneous event, item.end is not defined // In case of instantaneous event, item.end is not defined
if (event_end) { if (event_end) {
data[this.date_stop] = time.auto_date_to_str(event_end, fields[this.date_stop].type); data[this.date_stop] = time.auto_date_to_str(
event_end,
fields[this.date_stop].type
);
} else { } else {
data[this.date_stop] = data[this.date_start]; data[this.date_stop] = data[this.date_start];
} }
} }
if (this.date_delay && event_end) { if (this.date_delay && event_end) {
var diff_seconds = Math.round((event_end.getTime() - event_start.getTime()) / 1000); const diff_seconds = Math.round(
(event_end.getTime() - event_start.getTime()) / 1000
);
data[this.date_delay] = diff_seconds / 3600; data[this.date_delay] = diff_seconds / 3600;
} }
if (this.renderer.last_group_bys && this.renderer.last_group_bys instanceof Array) { if (
this.renderer.last_group_bys &&
this.renderer.last_group_bys instanceof Array
) {
data[this.renderer.last_group_bys[0]] = group; data[this.renderer.last_group_bys[0]] = group;
} }
@ -177,31 +192,30 @@ odoo.define('web_timeline.TimelineController', function (require) {
}, },
/** /**
* Write enqueued moves to Odoo. After all writes are finished it updates the view once * Write enqueued moves to Odoo. After all writes are finished it updates
* (prevents flickering of the view when multiple timeline items are moved at once). * the view once (prevents flickering of the view when multiple timeline items
* are moved at once).
* *
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
internalMove: function () { internalMove: function() {
var self = this; const queues = this.moveQueue.slice();
var queues = this.moveQueue.slice();
this.moveQueue = []; this.moveQueue = [];
var defers = []; const defers = [];
for (const item of queues) { for (const item of queues) {
defers.push(self._rpc({ defers.push(
model: self.model.modelName, this._rpc({
method: 'write', model: this.model.modelName,
args: [ method: "write",
[item.event.data.item.id], args: [[item.event.data.item.id], item.data],
item.data, context: this.getSession().user_context,
], }).then(() => {
context: self.getSession().user_context, item.event.data.callback(item.event.data.item);
}).then(function () { })
item.event.data.callback(item.event.data.item); );
}));
} }
return $.when.apply($, defers).done(function () { return $.when.apply($, defers).done(() => {
self.write_completed({ this.write_completed({
adjust_window: false, adjust_window: false,
}); });
}); });
@ -215,14 +229,13 @@ odoo.define('web_timeline.TimelineController', function (require) {
* @param {EventObject} event * @param {EventObject} event
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
_onRemove: function (event) { _onRemove: function(event) {
var self = this;
var def = $.Deferred(); var def = $.Deferred();
Dialog.confirm(this, _t("Are you sure you want to delete this record?"), { Dialog.confirm(this, _t("Are you sure you want to delete this record?"), {
title: _t("Warning"), title: _t("Warning"),
confirm_callback: function () { confirm_callback: () => {
self.remove_completed(event).then(def.resolve.bind(def)); this.remove_completed(event).then(def.resolve.bind(def));
}, },
cancel_callback: def.resolve.bind(def), cancel_callback: def.resolve.bind(def),
}); });
@ -237,27 +250,27 @@ odoo.define('web_timeline.TimelineController', function (require) {
* @param {EventObject} event * @param {EventObject} event
* @returns {dialogs.FormViewDialog} * @returns {dialogs.FormViewDialog}
*/ */
_onAdd: function (event) { _onAdd: function(event) {
var self = this; const item = event.data.item;
var item = event.data.item;
// Initialize default values for creation // Initialize default values for creation
var default_context = {}; const default_context = {};
default_context['default_'.concat(this.date_start)] = item.start; default_context["default_".concat(this.date_start)] = item.start;
if (this.date_delay) { if (this.date_delay) {
default_context['default_'.concat(this.date_delay)] = 1; default_context["default_".concat(this.date_delay)] = 1;
} }
if (this.date_start) { if (this.date_start) {
default_context['default_'.concat(this.date_start)] = moment(item.start).add(1, 'hours').format( default_context["default_".concat(this.date_start)] = moment(item.start)
'YYYY-MM-DD HH:mm:ss' .add(1, "hours")
); .format("YYYY-MM-DD HH:mm:ss");
} }
if (this.date_stop && item.end) { if (this.date_stop && item.end) {
default_context['default_'.concat(this.date_stop)] = moment(item.end).add(1, 'hours').format( default_context["default_".concat(this.date_stop)] = moment(item.end)
'YYYY-MM-DD HH:mm:ss' .add(1, "hours")
); .format("YYYY-MM-DD HH:mm:ss");
} }
if (item.group > 0) { if (item.group > 0) {
default_context['default_'.concat(this.renderer.last_group_bys[0])] = item.group; default_context["default_".concat(this.renderer.last_group_bys[0])] =
item.group;
} }
// Show popup // Show popup
new dialogs.FormViewDialog(this, { new dialogs.FormViewDialog(this, {
@ -265,12 +278,14 @@ odoo.define('web_timeline.TimelineController', function (require) {
res_id: null, res_id: null,
context: _.extend(default_context, this.context), context: _.extend(default_context, this.context),
view_id: Number(this.open_popup_action), view_id: Number(this.open_popup_action),
on_saved: function (record) { on_saved: record => {
self.create_completed([record.res_id]); this.create_completed([record.res_id]);
}, },
}).open().on('closed', this, function () { })
event.data.callback(); .open()
}); .on("closed", this, () => {
event.data.callback();
});
return false; return false;
}, },
@ -282,22 +297,18 @@ odoo.define('web_timeline.TimelineController', function (require) {
* @param {RecordId} id * @param {RecordId} id
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
create_completed: function (id) { create_completed: function(id) {
var self = this;
return this._rpc({ return this._rpc({
model: this.model.modelName, model: this.model.modelName,
method: 'read', method: "read",
args: [ args: [id, this.model.fieldNames],
id,
this.model.fieldNames,
],
context: this.context, context: this.context,
}).then(function (records) { }).then(records => {
var new_event = self.renderer.event_data_transform(records[0]); var new_event = this.renderer.event_data_transform(records[0]);
var items = self.renderer.timeline.itemsData; var items = this.renderer.timeline.itemsData;
items.add(new_event); items.add(new_event);
self.renderer.timeline.setItems(items); this.renderer.timeline.setItems(items);
self.reload(); this.reload();
}); });
}, },
@ -305,8 +316,8 @@ odoo.define('web_timeline.TimelineController', function (require) {
* Triggered upon completion of writing a record. * Triggered upon completion of writing a record.
* @param {ControllerOptions} options * @param {ControllerOptions} options
*/ */
write_completed: function (options) { write_completed: function(options) {
var params = { const params = {
domain: this.renderer.last_domains, domain: this.renderer.last_domains,
context: this.context, context: this.context,
groupBy: this.renderer.last_group_bys, groupBy: this.renderer.last_group_bys,
@ -319,22 +330,21 @@ odoo.define('web_timeline.TimelineController', function (require) {
* @param {EventObject} event * @param {EventObject} event
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
remove_completed: function (event) { remove_completed: function(event) {
var self = this; return this._rpc({
return self._rpc({ model: this.modelName,
model: self.modelName, method: "unlink",
method: 'unlink',
args: [[event.data.item.id]], args: [[event.data.item.id]],
context: self.getSession().user_context, context: this.getSession().user_context,
}).then(function () { }).then(() => {
var unlink_index = false; let unlink_index = false;
for (var i = 0; i < self.model.data.data.length; i++) { for (var i = 0; i < this.model.data.data.length; i++) {
if (self.model.data.data[i].id === event.data.item.id) { if (this.model.data.data[i].id === event.data.item.id) {
unlink_index = i; unlink_index = i;
} }
} }
if (!isNaN(unlink_index)) { if (!isNaN(unlink_index)) {
self.model.data.data.splice(unlink_index, 1); this.model.data.data.splice(unlink_index, 1);
} }
event.data.callback(event.data.item); event.data.callback(event.data.item);
}); });

View File

@ -1,16 +1,14 @@
odoo.define('web_timeline.TimelineModel', function (require) { odoo.define("web_timeline.TimelineModel", function(require) {
"use strict"; "use strict";
var AbstractModel = require('web.AbstractModel'); const AbstractModel = require("web.AbstractModel");
var TimelineModel = AbstractModel.extend({ const TimelineModel = AbstractModel.extend({
init: function() {
init: function () {
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
load: function (params) { load: function(params) {
var self = this;
this.modelName = params.modelName; this.modelName = params.modelName;
this.fieldNames = params.fieldNames; this.fieldNames = params.fieldNames;
if (!this.preload_def) { if (!this.preload_def) {
@ -18,24 +16,24 @@ odoo.define('web_timeline.TimelineModel', function (require) {
$.when( $.when(
this._rpc({ this._rpc({
model: this.modelName, model: this.modelName,
method: 'check_access_rights', method: "check_access_rights",
args: ["write", false], args: ["write", false],
}), }),
this._rpc({ this._rpc({
model: this.modelName, model: this.modelName,
method: 'check_access_rights', method: "check_access_rights",
args: ["unlink", false], args: ["unlink", false],
}), }),
this._rpc({ this._rpc({
model: this.modelName, model: this.modelName,
method: 'check_access_rights', method: "check_access_rights",
args: ["create", false], args: ["create", false],
}) })
).then(function (write, unlink, create) { ).then((write, unlink, create) => {
self.write_right = write; this.write_right = write;
self.unlink_right = unlink; this.unlink_right = unlink;
self.create_right = create; this.create_right = create;
self.preload_def.resolve(); this.preload_def.resolve();
}); });
} }
@ -53,20 +51,19 @@ odoo.define('web_timeline.TimelineModel', function (require) {
* @private * @private
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
_loadTimeline: function () { _loadTimeline: function() {
var self = this; return this._rpc({
return self._rpc({ model: this.modelName,
model: self.modelName, method: "search_read",
method: 'search_read', context: this.data.context,
context: self.data.context, fields: this.fieldNames,
fields: self.fieldNames, domain: this.data.domain,
domain: self.data.domain, }).then(events => {
}).then(function (events) { this.data.data = events;
self.data.data = events; this.data.rights = {
self.data.rights = { unlink: this.unlink_right,
'unlink': self.unlink_right, create: this.create_right,
'create': self.create_right, write: this.write_right,
'write': self.write_right,
}; };
}); });
}, },

View File

@ -1,36 +1,34 @@
odoo.define('web_timeline.TimelineRenderer', function (require) { /* global vis, py */
odoo.define("web_timeline.TimelineRenderer", function(require) {
"use strict"; "use strict";
var AbstractRenderer = require('web.AbstractRenderer'); const AbstractRenderer = require("web.AbstractRenderer");
var core = require('web.core'); const core = require("web.core");
var time = require('web.time'); const time = require("web.time");
var utils = require('web.utils'); const utils = require("web.utils");
var session = require('web.session'); const session = require("web.session");
var QWeb = require('web.QWeb'); const QWeb = require("web.QWeb");
var field_utils = require('web.field_utils'); const field_utils = require("web.field_utils");
var TimelineCanvas = require('web_timeline.TimelineCanvas'); const TimelineCanvas = require("web_timeline.TimelineCanvas");
const _t = core._t;
var _t = core._t; const TimelineRenderer = AbstractRenderer.extend({
var TimelineRenderer = AbstractRenderer.extend({
template: "TimelineView", template: "TimelineView",
events: _.extend({}, AbstractRenderer.prototype.events, { events: _.extend({}, AbstractRenderer.prototype.events, {
'click .oe_timeline_button_today': '_onTodayClicked', "click .oe_timeline_button_today": "_onTodayClicked",
'click .oe_timeline_button_scale_day': '_onScaleDayClicked', "click .oe_timeline_button_scale_day": "_onScaleDayClicked",
'click .oe_timeline_button_scale_week': '_onScaleWeekClicked', "click .oe_timeline_button_scale_week": "_onScaleWeekClicked",
'click .oe_timeline_button_scale_month': '_onScaleMonthClicked', "click .oe_timeline_button_scale_month": "_onScaleMonthClicked",
'click .oe_timeline_button_scale_year': '_onScaleYearClicked', "click .oe_timeline_button_scale_year": "_onScaleYearClicked",
}), }),
init: function (parent, state, params) { init: function(parent, state, params) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.modelName = params.model; this.modelName = params.model;
this.mode = params.mode; this.mode = params.mode;
this.options = params.options; this.options = params.options;
this.permissions = params.permissions;
this.timeline = params.timeline;
this.min_height = params.min_height; this.min_height = params.min_height;
this.date_start = params.date_start; this.date_start = params.date_start;
this.date_stop = params.date_stop; this.date_stop = params.date_stop;
@ -38,35 +36,39 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
this.colors = params.colors; this.colors = params.colors;
this.fieldNames = params.fieldNames; this.fieldNames = params.fieldNames;
this.dependency_arrow = params.dependency_arrow; this.dependency_arrow = params.dependency_arrow;
this.view = params.view; this.modelClass = params.view.model;
this.modelClass = this.view.model; this.fields = params.fields;
this.timeline = false;
}, },
/** /**
* @override * @override
*/ */
start: function () { start: function() {
var self = this; const attrs = this.arch.attrs;
var attrs = this.arch.attrs;
this.current_window = { this.current_window = {
start: new moment(), start: new moment(),
end: new moment().add(24, 'hours'), 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");
if (!this.date_start) { if (!this.date_start) {
throw new Error(_t("Timeline view has not defined 'date_start' attribute.")); throw new Error(
_t("Timeline view has not defined 'date_start' attribute.")
);
} }
this._super.apply(this, self); this._super.apply(this, arguments);
}, },
/** /**
* Triggered when the timeline is attached to the DOM. * Triggered when the timeline is attached to the DOM.
*/ */
on_attach_callback: function () { on_attach_callback: function() {
var height = this.$el.parent().height() - this.$('.oe_timeline_buttons').height(); const height =
this.$el.parent().height() - this.$(".oe_timeline_buttons").height();
if (height > this.min_height && this.timeline) { if (height > this.min_height && this.timeline) {
this.timeline.setOptions({ this.timeline.setOptions({
height: height, height: height,
@ -77,13 +79,12 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
/** /**
* @override * @override
*/ */
_render: function () { _render: function() {
var self = this; return Promise.resolve().then(() => {
return $.when().then(function () {
// Prevent Double Rendering on Updates // Prevent Double Rendering on Updates
if (!self.timeline) { if (!this.timeline) {
self.init_timeline(); this.init_timeline();
$(window).trigger('resize'); $(window).trigger("resize");
} }
}); });
}, },
@ -93,10 +94,10 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_onTodayClicked: function () { _onTodayClicked: function() {
this.current_window = { this.current_window = {
start: new moment(), start: new moment(),
end: new moment().add(24, 'hours'), end: new moment().add(24, "hours"),
}; };
if (this.timeline) { if (this.timeline) {
@ -109,7 +110,7 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_onScaleDayClicked: function () { _onScaleDayClicked: function() {
this._scaleCurrentWindow(24); this._scaleCurrentWindow(24);
}, },
@ -118,7 +119,7 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_onScaleWeekClicked: function () { _onScaleWeekClicked: function() {
this._scaleCurrentWindow(24 * 7); this._scaleCurrentWindow(24 * 7);
}, },
@ -127,8 +128,10 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_onScaleMonthClicked: function () { _onScaleMonthClicked: function() {
this._scaleCurrentWindow(24 * moment(this.current_window.start).daysInMonth()); this._scaleCurrentWindow(
24 * moment(this.current_window.start).daysInMonth()
);
}, },
/** /**
@ -136,8 +139,10 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_onScaleYearClicked: function () { _onScaleYearClicked: function() {
this._scaleCurrentWindow(24 * (moment(this.current_window.start).isLeapYear() ? 366 : 365)); this._scaleCurrentWindow(
24 * (moment(this.current_window.start).isLeapYear() ? 366 : 365)
);
}, },
/** /**
@ -146,10 +151,13 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* @param {Integer} factor The timespan (in hours) the window must be scaled to. * @param {Integer} factor The timespan (in hours) the window must be scaled to.
* @private * @private
*/ */
_scaleCurrentWindow: function (factor) { _scaleCurrentWindow: function(factor) {
if (this.timeline) { if (this.timeline) {
this.current_window = this.timeline.getWindow(); this.current_window = this.timeline.getWindow();
this.current_window.end = moment(this.current_window.start).add(factor, 'hours'); this.current_window.end = moment(this.current_window.start).add(
factor,
"hours"
);
this.timeline.setWindow(this.current_window); this.timeline.setWindow(this.current_window);
} }
}, },
@ -159,39 +167,40 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
_computeMode: function () { _computeMode: function() {
if (this.mode) { if (this.mode) {
var start = false, end = false; let start = false,
end = false;
switch (this.mode) { switch (this.mode) {
case 'day': case "day":
start = new moment().startOf('day'); start = new moment().startOf("day");
end = new moment().endOf('day'); end = new moment().endOf("day");
break; break;
case 'week': case "week":
start = new moment().startOf('week'); start = new moment().startOf("week");
end = new moment().endOf('week'); end = new moment().endOf("week");
break; break;
case 'month': case "month":
start = new moment().startOf('month'); start = new moment().startOf("month");
end = new moment().endOf('month'); end = new moment().endOf("month");
break; break;
} }
if (end && start) { if (end && start) {
this.options.start = start; this.options.start = start;
this.options.end = end; this.options.end = end;
} else { } else {
this.mode = 'fit'; this.mode = "fit";
} }
} }
}, },
/** /**
* Initializes the timeline (https://visjs.github.io/vis-timeline/docs/timeline). * Initializes the timeline
* (https://visjs.github.io/vis-timeline/docs/timeline).
* *
* @private * @private
*/ */
init_timeline: function () { init_timeline: function() {
var self = this;
this._computeMode(); this._computeMode();
this.options.editable = { this.options.editable = {
// Add new items by double tapping // Add new items by double tapping
@ -204,41 +213,34 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
remove: this.modelClass.data.rights.unlink, remove: this.modelClass.data.rights.unlink,
}; };
$.extend(this.options, { $.extend(this.options, {
onAdd: self.on_add, onAdd: this.on_add,
onMove: self.on_move, onMove: this.on_move,
onUpdate: self.on_update, onUpdate: this.on_update,
onRemove: self.on_remove, onRemove: this.on_remove,
}); });
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) {
var tmpl = utils.json_node_to_xml( const tmpl = utils.json_node_to_xml(
_.filter(this.arch.children, function (item) { _.filter(this.arch.children, item => item.tag === "templates")[0]
return item.tag === 'templates';
})[0]
); );
this.qweb.add_template(tmpl); this.qweb.add_template(tmpl);
} }
this.timeline = new vis.Timeline(self.$timeline.get(0)); this.timeline = new vis.Timeline(this.$timeline.get(0));
this.timeline.setOptions(this.options); this.timeline.setOptions(this.options);
if (self.mode && self['on_scale_' + self.mode + '_clicked']) { if (this.mode && this["on_scale_" + this.mode + "_clicked"]) {
self['on_scale_' + self.mode + '_clicked'](); this["on_scale_" + this.mode + "_clicked"]();
} }
this.timeline.on('click', self.on_group_click); this.timeline.on("click", this.on_group_click);
var 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.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', function () { this.timeline.on("changed", () => {
self.draw_canvas(); this.draw_canvas();
self.canvas.$el.attr(
'style',
self.$('.vis-content').attr('style') +
self.$('.vis-itemset').attr('style')
);
}); });
}, },
@ -247,7 +249,7 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
draw_canvas: function () { draw_canvas: function() {
this.canvas.clear(); this.canvas.clear();
if (this.dependency_arrow) { if (this.dependency_arrow) {
this.draw_dependencies(); this.draw_dependencies();
@ -259,16 +261,22 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* *
* @private * @private
*/ */
draw_dependencies: function () { draw_dependencies: function() {
var self = this; const items = this.timeline.itemSet.items;
var items = this.timeline.itemSet.items; const datas = this.timeline.itemsData;
for (const item of items) { if (!items || !datas) {
if (!item.data.evt) { return;
}
const keys = Object.keys(items);
for (const key of keys) {
const item = items[key];
const data = datas._data[key];
if (!data || !data.evt) {
return; return;
} }
for (const id of item.data.evt[self.dependency_arrow]) { for (const id of data.evt[this.dependency_arrow]) {
if (id in items) { if (keys.indexOf(id.toString()) !== -1) {
self.draw_dependency(item, items[id]); this.draw_dependency(item, items[id]);
} }
} }
} }
@ -284,67 +292,72 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* @param {Object} options.line_width The width of the line * @param {Object} options.line_width The width of the line
* @private * @private
*/ */
draw_dependency: function (from, to, options) { draw_dependency: function(from, to, options) {
if (!from.displayed || !to.displayed) { if (!from.displayed || !to.displayed) {
return; return;
} }
const defaults = _.defaults({}, options, {
var defaults = _.defaults({}, options, { line_color: "black",
line_color: 'black',
line_width: 1, line_width: 1,
}); });
this.canvas.draw_arrow(
this.canvas.draw_arrow(from.dom.box, to.dom.box, defaults.line_color, defaults.line_width); from.dom.box,
to.dom.box,
defaults.line_color,
defaults.line_width
);
}, },
/** /**
* Load display_name of records. * Load display_name of records.
* *
* @param {Object[]} events
* @param {String[]} group_bys
* @param {Boolean} adjust_window
* @private * @private
* @returns {jQuery.Deferred} * @returns {jQuery.Deferred}
*/ */
on_data_loaded: function (events, group_bys, adjust_window) { on_data_loaded: function(events, group_bys, adjust_window) {
var self = this; const ids = _.pluck(events, "id");
var ids = _.pluck(events, "id");
return this._rpc({ return this._rpc({
model: this.modelName, model: this.modelName,
method: 'name_get', method: "name_get",
args: [ args: [ids],
ids,
],
context: this.getSession().user_context, context: this.getSession().user_context,
}).then(function (names) { }).then(names => {
var nevents = _.map(events, function (event) { const nevents = _.map(events, event =>
return _.extend({ _.extend(
__name: _.detect(names, function (name) { {
return name[0] === event.id; __name: _.detect(names, name => name[0] === event.id)[1],
})[1], },
}, event); event
}); )
return self.on_data_loaded_2(nevents, group_bys, adjust_window); );
return this.on_data_loaded_2(nevents, group_bys, adjust_window);
}); });
}, },
/** /**
* Set groups and events. * Set groups and events.
* *
* @param {Object[]} events
* @param {String[]} group_bys
* @param {Boolean} adjust_window
* @private * @private
*/ */
on_data_loaded_2: function (events, group_bys, adjust_window) { on_data_loaded_2: function(events, group_bys, adjust_window) {
var self = this; const data = [];
var data = [];
var groups = [];
this.grouped_by = group_bys; this.grouped_by = group_bys;
for (const evt of events) { for (const evt of events) {
if (evt[self.date_start]) { if (evt[this.date_start]) {
data.push(self.event_data_transform(evt)); data.push(this.event_data_transform(evt));
} }
} }
groups = this.split_groups(events, group_bys); const groups = this.split_groups(events, group_bys);
this.timeline.setGroups(groups); this.timeline.setGroups(groups);
this.timeline.setItems(data); this.timeline.setItems(data);
var mode = !this.mode || this.mode === 'fit'; const mode = !this.mode || this.mode === "fit";
var adjust = _.isUndefined(adjust_window) || adjust_window; const adjust = _.isUndefined(adjust_window) || adjust_window;
if (mode && adjust) { if (mode && adjust) {
this.timeline.fit(); this.timeline.fit();
} }
@ -353,22 +366,25 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
/** /**
* Get the groups. * Get the groups.
* *
* @param {Object[]} events
* @param {String[]} group_bys
* @private * @private
* @returns {Array} * @returns {Array}
*/ */
split_groups: function (events, group_bys) { split_groups: function(events, group_bys) {
if (group_bys.length === 0) { if (group_bys.length === 0) {
return events; return events;
} }
var groups = []; const groups = [];
groups.push({id: -1, content: _t('-')}); groups.push({id: -1, content: _t("<b>UNASSIGNED</b>")});
for (const evt of events) { for (const evt of events) {
var group_name = evt[_.first(group_bys)]; const group_name = evt[_.first(group_bys)];
if (group_name) { if (group_name) {
if (group_name instanceof Array) { if (group_name instanceof Array) {
var group = _.find(groups, function (existing_group) { const group = _.find(
return existing_group.id === group_name[0]; groups,
}); existing_group => existing_group.id === group_name[0]
);
if (_.isUndefined(group)) { if (_.isUndefined(group)) {
groups.push({ groups.push({
id: group_name[0], id: group_name[0],
@ -382,60 +398,84 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
}, },
/** /**
* Transform Odoo event object to timeline event object. * Get dates from given event
* *
* @private * @param {TransformEvent} evt
* @returns {Object} * @returns {Object}
*/ */
event_data_transform: function (evt) { _get_event_dates: function(evt) {
var self = this; let date_start = new moment();
var date_start = new moment(); let date_stop = null;
var date_stop = null;
var date_delay = evt[this.date_delay] || false, const date_delay = evt[this.date_delay] || false,
all_day = this.all_day ? evt[this.all_day] : false; all_day = this.all_day ? evt[this.all_day] : false;
if (all_day) { if (all_day) {
date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0], 'start'); date_start = time.auto_str_to_date(
evt[this.date_start].split(" ")[0],
"start"
);
if (this.no_period) { if (this.no_period) {
date_stop = date_start; date_stop = date_start;
} else { } else {
date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0], 'stop') : null; date_stop = this.date_stop
? time.auto_str_to_date(
evt[this.date_stop].split(" ")[0],
"stop"
)
: null;
} }
} else { } else {
date_start = time.auto_str_to_date(evt[this.date_start]); date_start = time.auto_str_to_date(evt[this.date_start]);
date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop]) : null; date_stop = this.date_stop
? time.auto_str_to_date(evt[this.date_stop])
: null;
} }
if (!date_stop && date_delay) { if (!date_stop && date_delay) {
date_stop = date_start.clone().add(date_delay, 'hours').toDate(); date_stop = date_start
.clone()
.add(date_delay, "hours")
.toDate();
} }
var group = evt[self.last_group_bys[0]]; return [date_start, date_stop];
},
/**
* Transform Odoo event object to timeline event object.
*
* @param {TransformEvent} evt
* @private
* @returns {Object}
*/
event_data_transform: function(evt) {
const [date_start, date_stop] = this._get_event_dates(evt);
let group = evt[this.last_group_bys[0]];
if (group && group instanceof Array) { if (group && group instanceof Array) {
group = _.first(group); group = _.first(group);
} else { } else {
group = -1; group = -1;
} }
for (const color of self.colors) { for (const color of this.colors) {
if (py.eval("'" + evt[color.field] + "' " + color.opt + " '" + color.value + "'")) { if (py.eval(`'${evt[color.field]}' ${color.opt} '${color.value}'`)) {
self.color = color.color; this.color = color.color;
} }
} }
var content = evt.__name || evt.display_name; let content = evt.__name || evt.display_name;
if (this.arch.children.length) { if (this.arch.children.length) {
content = this.render_timeline_item(evt); content = this.render_timeline_item(evt);
} }
var r = { const r = {
'start': date_start, start: date_start,
'content': content, content: content,
'id': evt.id, id: evt.id,
'group': group, group: group,
'evt': evt, evt: evt,
'style': `background-color: ${this.color};`, style: `background-color: ${this.color};`,
}; };
// Check if the event is instantaneous, // Check if the event is instantaneous,
// if so, display it with a point on the timeline (no 'end') // if so, display it with a point on the timeline (no 'end')
@ -453,11 +493,11 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
* @private * @private
* @returns {String} Rendered template * @returns {String} Rendered template
*/ */
render_timeline_item: function (evt) { render_timeline_item: function(evt) {
if (this.qweb.has_template('timeline-item')) { if (this.qweb.has_template("timeline-item")) {
return this.qweb.render('timeline-item', { return this.qweb.render("timeline-item", {
'record': evt, record: evt,
'field_utils': field_utils, field_utils: field_utils,
}); });
} }
@ -469,66 +509,81 @@ odoo.define('web_timeline.TimelineRenderer', function (require) {
/** /**
* Handle a click on a group header. * Handle a click on a group header.
* *
* @param {ClickEvent} e
* @private * @private
*/ */
on_group_click: function (e) { on_group_click: function(e) {
if (e.what === 'group-label' && e.group !== -1) { if (e.what === "group-label" && e.group !== -1) {
this._trigger(e, function () { this._trigger(
// Do nothing e,
}, 'onGroupClick'); () => {
// Do nothing
},
"onGroupClick"
);
} }
}, },
/** /**
* Trigger onUpdate. * Trigger onUpdate.
* *
* @param {Object} item
* @param {Function} callback
* @private * @private
*/ */
on_update: function (item, callback) { on_update: function(item, callback) {
this._trigger(item, callback, 'onUpdate'); this._trigger(item, callback, "onUpdate");
}, },
/** /**
* Trigger onMove. * Trigger onMove.
* *
* @param {Object} item
* @param {Function} callback
* @private * @private
*/ */
on_move: function (item, callback) { on_move: function(item, callback) {
this._trigger(item, callback, 'onMove'); this._trigger(item, callback, "onMove");
}, },
/** /**
* Trigger onRemove. * Trigger onRemove.
* *
* @param {Object} item
* @param {Function} callback
* @private * @private
*/ */
on_remove: function (item, callback) { on_remove: function(item, callback) {
this._trigger(item, callback, 'onRemove'); this._trigger(item, callback, "onRemove");
}, },
/** /**
* Trigger onAdd. * Trigger onAdd.
* *
* @param {Object} item
* @param {Function} callback
* @private * @private
*/ */
on_add: function (item, callback) { on_add: function(item, callback) {
this._trigger(item, callback, 'onAdd'); this._trigger(item, callback, "onAdd");
}, },
/** /**
* Trigger_up encapsulation adds by default the rights, and the renderer. * Trigger_up encapsulation adds by default the rights, and the renderer.
* *
* @param {HTMLElement} item
* @param {Function} callback
* @param {String} trigger
* @private * @private
*/ */
_trigger: function (item, callback, trigger) { _trigger: function(item, callback, trigger) {
this.trigger_up(trigger, { this.trigger_up(trigger, {
'item': item, item: item,
'callback': callback, callback: callback,
'rights': this.modelClass.data.rights, rights: this.modelClass.data.rights,
'renderer': this, renderer: this,
}); });
}, },
}); });
return TimelineRenderer; return TimelineRenderer;

View File

@ -1,31 +1,31 @@
/* global py */
/* Odoo web_timeline /* Odoo web_timeline
* Copyright 2015 ACSONE SA/NV * Copyright 2015 ACSONE SA/NV
* Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> * Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define('web_timeline.TimelineView', function (require) { odoo.define("web_timeline.TimelineView", function(require) {
"use strict"; "use strict";
var core = require('web.core'); const core = require("web.core");
var utils = require('web.utils'); const utils = require("web.utils");
var view_registry = require('web.view_registry'); const view_registry = require("web.view_registry");
var AbstractView = require('web.AbstractView'); const AbstractView = require("web.AbstractView");
var TimelineRenderer = require('web_timeline.TimelineRenderer'); const TimelineRenderer = require("web_timeline.TimelineRenderer");
var TimelineController = require('web_timeline.TimelineController'); const TimelineController = require("web_timeline.TimelineController");
var TimelineModel = require('web_timeline.TimelineModel'); const TimelineModel = require("web_timeline.TimelineModel");
const _lt = core._lt;
var _lt = core._lt;
function isNullOrUndef(value) { function isNullOrUndef(value) {
return _.isUndefined(value) || _.isNull(value); return _.isUndefined(value) || _.isNull(value);
} }
var TimelineView = AbstractView.extend({ var TimelineView = AbstractView.extend({
display_name: _lt('Timeline'), display_name: _lt("Timeline"),
icon: 'fa-clock-o', icon: "fa-tasks",
jsLibs: ['/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.js'], jsLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.js"],
cssLibs: ['/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.css'], cssLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.css"],
config: { config: {
Model: TimelineModel, Model: TimelineModel,
Controller: TimelineController, Controller: TimelineController,
@ -35,123 +35,112 @@ odoo.define('web_timeline.TimelineView', function (require) {
/** /**
* @override * @override
*/ */
init: function (viewInfo, params) { init: function(viewInfo, params) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
var self = this;
this.timeline = false;
this.arch = this.rendererParams.arch;
var attrs = this.arch.attrs;
this.fields = viewInfo.fields;
this.modelName = this.controllerParams.modelName; this.modelName = this.controllerParams.modelName;
this.action = params.action;
var fieldNames = this.fields.display_name ? ['display_name'] : []; const action = params.action;
var mapping = {}; this.arch = this.rendererParams.arch;
var fieldsToGather = [ const attrs = this.arch.attrs;
const date_start = attrs.date_start;
const date_stop = attrs.date_stop;
const date_delay = attrs.date_delay;
const dependency_arrow = attrs.dependency_arrow;
const fields = viewInfo.fields;
let fieldNames = fields.display_name ? ["display_name"] : [];
const fieldsToGather = [
"date_start", "date_start",
"date_stop", "date_stop",
"default_group_by", "default_group_by",
"progress", "progress",
"date_delay", "date_delay",
attrs.default_group_by,
]; ];
fieldsToGather.push(attrs.default_group_by);
for (const field of fieldsToGather) { for (const field of fieldsToGather) {
if (attrs[field]) { if (attrs[field]) {
var fieldName = attrs[field]; fieldNames.push(attrs[field]);
mapping[field] = fieldName;
fieldNames.push(fieldName);
} }
} }
var archFieldNames = _.map(_.filter(this.arch.children, function (item) { const archFieldNames = _.map(
return item.tag === 'field'; _.filter(this.arch.children, item => item.tag === "field"),
}), function (item) { item => item.attrs.name
return item.attrs.name;
});
fieldNames = _.union(
fieldNames,
archFieldNames
); );
fieldNames = _.union(fieldNames, archFieldNames);
this.parse_colors(); const colors = this.parse_colors();
for (const color of this.colors) { for (const color of colors) {
fieldNames.push(color.field); fieldNames.push(color.field);
} }
if (attrs.dependency_arrow) { if (dependency_arrow) {
fieldNames.push(attrs.dependency_arrow); fieldNames.push(dependency_arrow);
} }
this.permissions = {}; const mode = attrs.mode || attrs.default_window || "fit";
this.grouped_by = false; const min_height = attrs.min_height || 300;
this.date_start = attrs.date_start;
this.date_stop = attrs.date_stop;
this.date_delay = attrs.date_delay;
this.dependency_arrow = attrs.dependency_arrow;
this.no_period = this.date_start === this.date_stop; const current_window = {
this.zoomKey = attrs.zoomKey || '';
this.margin = attrs.margin || '{}';
this.mode = attrs.mode || attrs.default_window || 'fit';
this.min_height = attrs.min_height || 300;
this.current_window = {
start: new moment(), start: new moment(),
end: new moment().add(24, 'hours'), end: new moment().add(24, "hours"),
}; };
if (!isNullOrUndef(attrs.quick_create_instance)) { if (!isNullOrUndef(attrs.quick_create_instance)) {
self.quick_create_instance = 'instance.' + attrs.quick_create_instance; this.quick_create_instance = "instance." + attrs.quick_create_instance;
} }
this.stack = true; let open_popup_action = false;
if (!isNullOrUndef(attrs.stack) && !utils.toBoolElse(attrs.stack, true)) { if (
this.stack = false; !isNullOrUndef(attrs.event_open_popup) &&
utils.toBoolElse(attrs.event_open_popup, true)
) {
open_popup_action = attrs.event_open_popup;
} }
this.options = { this.rendererParams.mode = mode;
this.rendererParams.model = this.modelName;
this.rendererParams.view = this;
this.rendererParams.options = this._preapre_vis_timeline_options(attrs);
this.rendererParams.current_window = current_window;
this.rendererParams.date_start = date_start;
this.rendererParams.date_stop = date_stop;
this.rendererParams.date_delay = date_delay;
this.rendererParams.colors = colors;
this.rendererParams.fieldNames = fieldNames;
this.rendererParams.min_height = min_height;
this.rendererParams.dependency_arrow = dependency_arrow;
this.rendererParams.fields = fields;
this.loadParams.modelName = this.modelName;
this.loadParams.fieldNames = fieldNames;
this.controllerParams.open_popup_action = open_popup_action;
this.controllerParams.date_start = date_start;
this.controllerParams.date_stop = date_stop;
this.controllerParams.date_delay = date_delay;
this.controllerParams.actionContext = action.context;
this.withSearchPanel = false;
},
_preapre_vis_timeline_options: function(attrs) {
return {
groupOrder: this.group_order, groupOrder: this.group_order,
orientation: 'both', orientation: "both",
selectable: true, selectable: true,
multiselect: true, multiselect: true,
showCurrentTime: true, showCurrentTime: true,
stack: this.stack, stack: isNullOrUndef(attrs.stack)
margin: JSON.parse(this.margin), ? true
zoomKey: this.zoomKey, : utils.toBoolElse(attrs.stack, true),
margin: attrs.margin ? JSON.parse(attrs.margin) : {item: 2},
zoomKey: attrs.zoomKey || "ctrlKey",
}; };
if (isNullOrUndef(attrs.event_open_popup) || !utils.toBoolElse(attrs.event_open_popup, true)) {
this.open_popup_action = false;
} else {
this.open_popup_action = attrs.event_open_popup;
}
this.rendererParams.mode = this.mode;
this.rendererParams.model = this.modelName;
this.rendererParams.options = this.options;
this.rendererParams.permissions = this.permissions;
this.rendererParams.current_window = this.current_window;
this.rendererParams.timeline = this.timeline;
this.rendererParams.date_start = this.date_start;
this.rendererParams.date_stop = this.date_stop;
this.rendererParams.date_delay = this.date_delay;
this.rendererParams.colors = this.colors;
this.rendererParams.fieldNames = fieldNames;
this.rendererParams.view = this;
this.rendererParams.min_height = this.min_height;
this.rendererParams.dependency_arrow = this.dependency_arrow;
this.loadParams.modelName = this.modelName;
this.loadParams.fieldNames = fieldNames;
this.controllerParams.open_popup_action = this.open_popup_action;
this.controllerParams.date_start = this.date_start;
this.controllerParams.date_stop = this.date_stop;
this.controllerParams.date_delay = this.date_delay;
this.controllerParams.actionContext = this.action.context;
this.withSearchPanel = false;
}, },
/** /**
* Order function for groups. * Order function for groups.
* @param {Object} grp1
* @param {Object} grp2
* @returns {Integer} * @returns {Integer}
*/ */
group_order: function (grp1, grp2) { group_order: function(grp1, grp2) {
// Display non grouped elements first // Display non grouped elements first
if (grp1.id === -1) { if (grp1.id === -1) {
return -1; return -1;
@ -167,26 +156,31 @@ odoo.define('web_timeline.TimelineView', function (require) {
* Parse the colors attribute. * Parse the colors attribute.
* *
* @private * @private
* @returns {Array}
*/ */
parse_colors: function () { parse_colors: function() {
if (this.arch.attrs.colors) { if (this.arch.attrs.colors) {
this.colors = _(this.arch.attrs.colors.split(';')).chain().compact().map(function (color_pair) { return _(this.arch.attrs.colors.split(";"))
var pair = color_pair.split(':'), color = pair[0], expr = pair[1]; .chain()
var temp = py.parse(py.tokenize(expr)); .compact()
return { .map(color_pair => {
'color': color, const pair = color_pair.split(":");
'field': temp.expressions[0].value, const color = pair[0];
'opt': temp.operators[0], const expr = pair[1];
'value': temp.expressions[1].value, const temp = py.parse(py.tokenize(expr));
}; return {
}).value(); color: color,
} else { field: temp.expressions[0].value,
this.colors = []; opt: temp.operators[0],
value: temp.expressions[1].value,
};
})
.value();
} }
return [];
}, },
}); });
view_registry.add('timeline', TimelineView); view_registry.add("timeline", TimelineView);
return TimelineView; return TimelineView;
}); });

View File

@ -1,8 +1,11 @@
$vis-weekend-background-color: #dcdcdc;
$vis-item-content-padding: 0 3px !important;
.oe_timeline_view .vis-timeline { .oe_timeline_view .vis-timeline {
.vis-grid { .vis-grid {
.vis-saturday, .vis-sunday { &.vis-saturday,
// very light gray background in weekends &.vis-sunday {
background: #DCDCDC; background: $vis-weekend-background-color;
} }
} }
@ -13,5 +16,17 @@
&.vis-item-overflow { &.vis-item-overflow {
overflow: visible; overflow: visible;
} }
.vis-item-content {
padding: $vis-item-content-padding;
}
} }
} }
.oe_timeline_view_canvas {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

View File

@ -1,25 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<template> <template>
<t t-name="TimelineView"> <t t-name="TimelineView">
<div class="oe_timeline_view"> <div class="oe_timeline_view">
<div class="oe_timeline_buttons"> <div class="oe_timeline_buttons">
<button class="btn btn-default btn-sm oe_timeline_button_today">Today</button> <button
class="btn btn-default btn-sm oe_timeline_button_today"
>Today</button>
<div class="btn-group btn-sm"> <div class="btn-group btn-sm">
<button class="btn btn-default oe_timeline_button_scale_day">Day</button> <button
<button class="btn btn-default oe_timeline_button_scale_week">Week</button> class="btn btn-default oe_timeline_button_scale_day"
<button class="btn btn-default oe_timeline_button_scale_month">Month</button> >Day</button>
<button class="btn btn-default oe_timeline_button_scale_year">Year</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> </div>
<div class="oe_timeline_widget"/> <div class="oe_timeline_widget" />
</div> </div>
</t> </t>
<svg t-name="TimelineView.Canvas" class="oe_timeline_view_canvas"> <svg t-name="TimelineView.Canvas" class="oe_timeline_view_canvas">
<defs> <defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"> <marker
<polygon points="10 0, 10 7, 0 3.5"/> id="arrowhead"
markerWidth="10"
markerHeight="7"
refX="10"
refY="3.5"
orient="auto"
>
<polygon points="10 0, 10 7, 0 3.5" />
</marker> </marker>
</defs> </defs>
</svg> </svg>

View File

@ -1,16 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<template id="assets_backend" name="web_timeline assets" inherit_id="web.assets_backend"> <template
id="assets_backend"
name="web_timeline assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" type="text/css" href="/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.css"/> <link
<link rel="stylesheet" type="text/scss" href="/web_timeline/static/src/scss/web_timeline.scss"/> rel="stylesheet"
type="text/css"
<script type="text/javascript" src="/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.min.js"/> href="/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.css"
<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"/> <link
<script type="text/javascript" src="/web_timeline/static/src/js/timeline_controller.js"/> rel="stylesheet"
<script type="text/javascript" src="/web_timeline/static/src/js/timeline_model.js"/> type="text/scss"
<script type="text/javascript" src="/web_timeline/static/src/js/timeline_canvas.js"/> href="/web_timeline/static/src/scss/web_timeline.scss"
/>
<script
type="text/javascript"
src="/web_timeline/static/lib/vis-timeline/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"
/>
<script
type="text/javascript"
src="/web_timeline/static/src/js/timeline_controller.js"
/>
<script
type="text/javascript"
src="/web_timeline/static/src/js/timeline_model.js"
/>
<script
type="text/javascript"
src="/web_timeline/static/src/js/timeline_canvas.js"
/>
</xpath> </xpath>
</template> </template>
</odoo> </odoo>