Browse Source

Merge pull request #735 from Horanet/10.0

[10.0][IMP] web_timeline improvements
pull/673/merge
Pedro M. Baeza 7 years ago
committed by GitHub
parent
commit
d78ee651f5
  1. 24
      web_timeline/README.rst
  2. 5
      web_timeline/__manifest__.py
  3. 78
      web_timeline/i18n/fr.po
  4. BIN
      web_timeline/static/lib/vis/img/network/acceptDeleteIcon.png
  5. BIN
      web_timeline/static/lib/vis/img/network/addNodeIcon.png
  6. BIN
      web_timeline/static/lib/vis/img/network/backIcon.png
  7. BIN
      web_timeline/static/lib/vis/img/network/connectIcon.png
  8. BIN
      web_timeline/static/lib/vis/img/network/cross.png
  9. BIN
      web_timeline/static/lib/vis/img/network/cross2.png
  10. BIN
      web_timeline/static/lib/vis/img/network/deleteIcon.png
  11. BIN
      web_timeline/static/lib/vis/img/network/downArrow.png
  12. BIN
      web_timeline/static/lib/vis/img/network/editIcon.png
  13. BIN
      web_timeline/static/lib/vis/img/network/leftArrow.png
  14. BIN
      web_timeline/static/lib/vis/img/network/minus.png
  15. BIN
      web_timeline/static/lib/vis/img/network/plus.png
  16. BIN
      web_timeline/static/lib/vis/img/network/rightArrow.png
  17. BIN
      web_timeline/static/lib/vis/img/network/upArrow.png
  18. BIN
      web_timeline/static/lib/vis/img/network/zoomExtends.png
  19. BIN
      web_timeline/static/lib/vis/img/timeline/delete.png
  20. 1
      web_timeline/static/lib/vis/vis-timeline-graph2d.min.css
  21. 40
      web_timeline/static/lib/vis/vis-timeline-graph2d.min.js
  22. 810
      web_timeline/static/lib/vis/vis.css
  23. 35665
      web_timeline/static/lib/vis/vis.js
  24. 1
      web_timeline/static/lib/vis/vis.map
  25. 1
      web_timeline/static/lib/vis/vis.min.css
  26. 44
      web_timeline/static/lib/vis/vis.min.js
  27. 217
      web_timeline/static/src/js/web_timeline.js
  28. 4
      web_timeline/views/web_timeline.xml

24
web_timeline/README.rst

@ -20,12 +20,20 @@ the possible attributes for the tag:
* date_start (required): it defines the name of the field of type date that * date_start (required): it defines the name of the field of type date that
contains the start of the event. contains the start of the event.
* date_end (optional): it defines the name of the field of type date that * date_end (optional): it defines the name of the field of type date that
contains the end of the event.
* date_delay (optional): it defines the name of the field of type date that
contains the end of the event.
contains the end of the event. The date_end can be equal to the attribute
date_start to display events has 'point' on the Timeline (instantaneous event)
* date_delay (optional): it defines the name of the field of type float/integer
that contain the duration in hours of the event, default = 1
* default_group_by (required): it defines the name of the field that will be * default_group_by (required): it defines the name of the field that will be
taken as default group by when accessing the view or when no other group by taken as default group by when accessing the view or when no other group by
is selected. is selected.
* zoomKey (optional): Specifies whether the Timeline is only zoomed when an
additional key is down. Available values are '' (does not apply), 'altKey',
'ctrlKey', or 'metaKey'. Set this option if you want to be able to use the
scroll to navigate vertically on views with a lot of events.
* default_window (optional): Specifies the initial visible window. Aviable values are:
'day' to display the next 24 hours, 'week', 'month' and 'fit'.
Default value is 'fit' to adjust the visible window such that it fits all items
* event_open_popup (optional): when set to true, it allows to edit the events * event_open_popup (optional): when set to true, it allows to edit the events
in a popup. If not (default value), the record is edited changing to form in a popup. If not (default value), the record is edited changing to form
view. view.
@ -49,6 +57,7 @@ Example:
string="Tasks" string="Tasks"
default_group_by="user_id" default_group_by="user_id"
event_open_popup="true" event_open_popup="true"
zoomKey="ctrlKey"
colors="#ec7063:user_id == false;#2ecb71:kanban_state=='done';"> colors="#ec7063:user_id == false;#2ecb71:kanban_state=='done';">
</timeline> </timeline>
</field> </field>
@ -93,7 +102,7 @@ new record with the group and start date linked to the area you clicked in.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/162/8.0
:target: https://runbot.odoo-community.org/runbot/162/10.0
Known issues / Roadmap Known issues / Roadmap
====================== ======================
@ -125,6 +134,9 @@ Contributors
* Adrien Peiffer <adrien.peiffer@acsone.eu> * Adrien Peiffer <adrien.peiffer@acsone.eu>
* Pedro M. Baeza <pedro.baeza@tecnativa.com> * Pedro M. Baeza <pedro.baeza@tecnativa.com>
* Leonardo Donelli <donelli@webmonks.it> * Leonardo Donelli <donelli@webmonks.it>
* Adrien Didenot <adrien.didenot@horanet.com>
Do not contact contributors directly about support or help with technical issues.
Maintainer Maintainer
---------- ----------
@ -135,6 +147,8 @@ Maintainer
This module is maintained by the OCA. This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org. To contribute to this module, please visit https://odoo-community.org.

5
web_timeline/__manifest__.py

@ -5,12 +5,15 @@
{ {
'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": "10.0.1.0.0",
"version": "10.0.1.1.0",
'author': 'ACSONE SA/NV, ' 'author': 'ACSONE SA/NV, '
'Tecnativa, ' 'Tecnativa, '
'Monk Software, ' 'Monk Software, '
'Odoo Community Association (OCA)', 'Odoo Community Association (OCA)',
"category": "web", "category": "web",
"license": "AGPL-3",
"application": False,
"installable": True,
"website": "http://acsone.eu", "website": "http://acsone.eu",
'depends': [ 'depends': [
'web', 'web',

78
web_timeline/i18n/fr.po

@ -0,0 +1,78 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_timeline
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-27 07:55+0000\n"
"PO-Revision-Date: 2017-09-27 07:55+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/js/web_timeline.js:442
#, python-format
msgid "Are you sure you want to delete this record ?"
msgstr "Êtes vous sûr de vouloir supprimer cet enregistrement ?"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:8
#, python-format
msgid "Day"
msgstr "Jour"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:10
#, python-format
msgid "Month"
msgstr "Mois"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/js/web_timeline.js:33
#, python-format
msgid "Timeline"
msgstr "Chronologie"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/js/web_timeline.js:107
#, python-format
msgid "Timeline view has not defined 'date_start' attribute."
msgstr "La vue chronologique n'a pas défini l'attribut 'date_start'."
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:5
#, python-format
msgid "Today"
msgstr "Aujourd'hui"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:9
#, python-format
msgid "Week"
msgstr "Semaine"
#. module: web_timeline
#. openerp-web
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:11
#, python-format
msgid "Year"
msgstr "Année"
#. module: web_timeline
#: model:ir.model,name:web_timeline.model_ir_ui_view
msgid "ir.ui.view"
msgstr "ir.ui.view"

BIN
web_timeline/static/lib/vis/img/network/acceptDeleteIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/addNodeIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/backIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/connectIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/cross.png

Before

Width: 7  |  Height: 7  |  Size: 18 KiB

BIN
web_timeline/static/lib/vis/img/network/cross2.png

Before

Width: 5  |  Height: 5  |  Size: 17 KiB

BIN
web_timeline/static/lib/vis/img/network/deleteIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/downArrow.png

Before

Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
web_timeline/static/lib/vis/img/network/editIcon.png

Before

Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
web_timeline/static/lib/vis/img/network/leftArrow.png

Before

Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
web_timeline/static/lib/vis/img/network/minus.png

Before

Width: 30  |  Height: 30  |  Size: 4.0 KiB

BIN
web_timeline/static/lib/vis/img/network/plus.png

Before

Width: 30  |  Height: 30  |  Size: 4.2 KiB

BIN
web_timeline/static/lib/vis/img/network/rightArrow.png

Before

Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
web_timeline/static/lib/vis/img/network/upArrow.png

Before

Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
web_timeline/static/lib/vis/img/network/zoomExtends.png

Before

Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
web_timeline/static/lib/vis/img/timeline/delete.png

Before

Width: 16  |  Height: 16  |  Size: 665 B

1
web_timeline/static/lib/vis/vis-timeline-graph2d.min.css
File diff suppressed because it is too large
View File

40
web_timeline/static/lib/vis/vis-timeline-graph2d.min.js
File diff suppressed because it is too large
View File

810
web_timeline/static/lib/vis/vis.css

@ -1,810 +0,0 @@
.vis .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* Must be displayed above for example selected Timeline items */
z-index: 10;
}
.vis-active {
box-shadow: 0 0 10px #86d5f8;
}
/* override some bootstrap styles screwing up the timelines css */
.vis [class*="span"] {
min-height: 0;
width: auto;
}
.vis.timeline {
}
.vis.timeline.root {
position: relative;
border: 1px solid #bfbfbf;
overflow: hidden;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .vispanel {
position: absolute;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.left,
.vis.timeline .vispanel.right,
.vis.timeline .vispanel.top,
.vis.timeline .vispanel.bottom {
border: 1px #bfbfbf;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.left,
.vis.timeline .vispanel.right {
border-top-style: solid;
border-bottom-style: solid;
overflow: hidden;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.top,
.vis.timeline .vispanel.bottom {
border-left-style: solid;
border-right-style: solid;
}
.vis.timeline .background {
overflow: hidden;
}
.vis.timeline .vispanel > .content {
position: relative;
}
.vis.timeline .vispanel .shadow {
position: absolute;
width: 100%;
height: 1px;
box-shadow: 0 0 10px rgba(0,0,0,0.8);
/* TODO: find a nice way to ensure shadows are drawn on top of items
z-index: 1;
*/
}
.vis.timeline .vispanel .shadow.top {
top: -1px;
left: 0;
}
.vis.timeline .vispanel .shadow.bottom {
bottom: -1px;
left: 0;
}
.vis.timeline .labelset {
position: relative;
overflow: hidden;
box-sizing: border-box;
}
.vis.timeline .labelset .vlabel {
position: relative;
left: 0;
top: 0;
width: 100%;
color: #4d4d4d;
box-sizing: border-box;
}
.vis.timeline .labelset .vlabel {
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .labelset .vlabel:last-child {
border-bottom: none;
}
.vis.timeline .labelset .vlabel .inner {
display: inline-block;
padding: 5px;
}
.vis.timeline .labelset .vlabel .inner.hidden {
padding: 0;
}
.vis.timeline .itemset {
position: relative;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .itemset .background,
.vis.timeline .itemset .foreground {
position: absolute;
width: 100%;
height: 100%;
overflow: visible;
}
.vis.timeline .axis {
position: absolute;
width: 100%;
height: 0;
left: 0;
z-index: 1;
}
.vis.timeline .foreground .group {
position: relative;
box-sizing: border-box;
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .foreground .group:last-child {
border-bottom: none;
}
.vis.timeline .item {
position: absolute;
color: #1A1A1A;
border-color: #97B0F8;
border-width: 1px;
background-color: #D5DDF6;
display: inline-block;
padding: 5px;
}
.vis.timeline .item.selected {
border-color: #FFC200;
background-color: #FFF785;
/* z-index must be higher than the z-index of custom time bar and current time bar */
z-index: 2;
}
.vis.timeline .editable .item.selected {
cursor: move;
}
.vis.timeline .item.point.selected {
background-color: #FFF785;
}
.vis.timeline .item.box {
text-align: center;
border-style: solid;
border-radius: 2px;
}
.vis.timeline .item.point {
background: none;
}
.vis.timeline .item.dot {
position: absolute;
padding: 0;
border-width: 4px;
border-style: solid;
border-radius: 4px;
}
.vis.timeline .item.range {
border-style: solid;
border-radius: 2px;
box-sizing: border-box;
}
.vis.timeline .item.background {
overflow: hidden;
border: none;
background-color: rgba(213, 221, 246, 0.4);
box-sizing: border-box;
padding: 0;
margin: 0;
}
.vis.timeline .item.range .content {
position: relative;
display: inline-block;
max-width: 100%;
overflow: hidden;
}
.vis.timeline .item.background .content {
position: absolute;
display: inline-block;
overflow: hidden;
max-width: 100%;
margin: 5px;
}
.vis.timeline .item.line {
padding: 0;
position: absolute;
width: 0;
border-left-width: 1px;
border-left-style: solid;
}
.vis.timeline .item .content {
white-space: nowrap;
overflow: hidden;
}
.vis.timeline .item .delete {
background: url('img/timeline/delete.png') no-repeat top center;
position: absolute;
width: 24px;
height: 24px;
top: 0;
right: -24px;
cursor: pointer;
}
.vis.timeline .item.range .drag-left {
position: absolute;
width: 24px;
max-width: 20%;
height: 100%;
top: 0;
left: -4px;
cursor: w-resize;
}
.vis.timeline .item.range .drag-right {
position: absolute;
width: 24px;
max-width: 20%;
height: 100%;
top: 0;
right: -4px;
cursor: e-resize;
}
.vis.timeline .timeaxis {
position: relative;
overflow: hidden;
}
.vis.timeline .timeaxis.foreground {
top: 0;
left: 0;
width: 100%;
}
.vis.timeline .timeaxis.background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.vis.timeline .timeaxis .text {
position: absolute;
color: #4d4d4d;
padding: 3px;
white-space: nowrap;
}
.vis.timeline .timeaxis .text.measure {
position: absolute;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
visibility: hidden;
}
.vis.timeline .timeaxis .grid.vertical {
position: absolute;
border-left: 1px solid;
}
.vis.timeline .timeaxis .grid.minor {
border-color: #e5e5e5;
}
.vis.timeline .timeaxis .grid.major {
border-color: #bfbfbf;
}
.vis.timeline .currenttime {
background-color: #FF7F6E;
width: 2px;
z-index: 1;
}
.vis.timeline .customtime {
background-color: #6E94FF;
width: 2px;
cursor: move;
z-index: 1;
}
.vis.timeline.root {
/*
-webkit-transition: height .4s ease-in-out;
transition: height .4s ease-in-out;
*/
}
.vis.timeline .vispanel {
/*
-webkit-transition: height .4s ease-in-out, top .4s ease-in-out;
transition: height .4s ease-in-out, top .4s ease-in-out;
*/
}
.vis.timeline .axis {
/*
-webkit-transition: top .4s ease-in-out;
transition: top .4s ease-in-out;
*/
}
/* TODO: get animation working nicely
.vis.timeline .item {
-webkit-transition: top .4s ease-in-out;
transition: top .4s ease-in-out;
}
.vis.timeline .item.line {
-webkit-transition: height .4s ease-in-out, top .4s ease-in-out;
transition: height .4s ease-in-out, top .4s ease-in-out;
}
/**/
.vis.timeline .vispanel.background.horizontal .grid.horizontal {
position: absolute;
width: 100%;
height: 0;
border-bottom: 1px solid;
}
.vis.timeline .vispanel.background.horizontal .grid.minor {
border-color: #e5e5e5;
}
.vis.timeline .vispanel.background.horizontal .grid.major {
border-color: #bfbfbf;
}
.vis.timeline .dataaxis .yAxis.major {
width: 100%;
position: absolute;
color: #4d4d4d;
white-space: nowrap;
}
.vis.timeline .dataaxis .yAxis.major.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
border: 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.minor{
position: absolute;
width: 100%;
color: #bebebe;
white-space: nowrap;
}
.vis.timeline .dataaxis .yAxis.minor.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
border: 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.title{
position: absolute;
color: #4d4d4d;
white-space: nowrap;
bottom: 20px;
text-align: center;
}
.vis.timeline .dataaxis .yAxis.title.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.title.left {
bottom: 0px;
-webkit-transform-origin: left top;
-moz-transform-origin: left top;
-ms-transform-origin: left top;
-o-transform-origin: left top;
transform-origin: left bottom;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.vis.timeline .dataaxis .yAxis.title.right {
bottom: 0px;
-webkit-transform-origin: right bottom;
-moz-transform-origin: right bottom;
-ms-transform-origin: right bottom;
-o-transform-origin: right bottom;
transform-origin: right bottom;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}
.vis.timeline .legend {
background-color: rgba(247, 252, 255, 0.65);
padding: 5px;
border-color: #b3b3b3;
border-style:solid;
border-width: 1px;
box-shadow: 2px 2px 10px rgba(154, 154, 154, 0.55);
}
.vis.timeline .legendText {
/*font-size: 10px;*/
white-space: nowrap;
display: inline-block
}
.vis.timeline .graphGroup0 {
fill:#4f81bd;
fill-opacity:0;
stroke-width:2px;
stroke: #4f81bd;
}
.vis.timeline .graphGroup1 {
fill:#f79646;
fill-opacity:0;
stroke-width:2px;
stroke: #f79646;
}
.vis.timeline .graphGroup2 {
fill: #8c51cf;
fill-opacity:0;
stroke-width:2px;
stroke: #8c51cf;
}
.vis.timeline .graphGroup3 {
fill: #75c841;
fill-opacity:0;
stroke-width:2px;
stroke: #75c841;
}
.vis.timeline .graphGroup4 {
fill: #ff0100;
fill-opacity:0;
stroke-width:2px;
stroke: #ff0100;
}
.vis.timeline .graphGroup5 {
fill: #37d8e6;
fill-opacity:0;
stroke-width:2px;
stroke: #37d8e6;
}
.vis.timeline .graphGroup6 {
fill: #042662;
fill-opacity:0;
stroke-width:2px;
stroke: #042662;
}
.vis.timeline .graphGroup7 {
fill:#00ff26;
fill-opacity:0;
stroke-width:2px;
stroke: #00ff26;
}
.vis.timeline .graphGroup8 {
fill:#ff00ff;
fill-opacity:0;
stroke-width:2px;
stroke: #ff00ff;
}
.vis.timeline .graphGroup9 {
fill: #8f3938;
fill-opacity:0;
stroke-width:2px;
stroke: #8f3938;
}
.vis.timeline .fill {
fill-opacity:0.1;
stroke: none;
}
.vis.timeline .bar {
fill-opacity:0.5;
stroke-width:1px;
}
.vis.timeline .point {
stroke-width:2px;
fill-opacity:1.0;
}
.vis.timeline .legendBackground {
stroke-width:1px;
fill-opacity:0.9;
fill: #ffffff;
stroke: #c2c2c2;
}
.vis.timeline .outline {
stroke-width:1px;
fill-opacity:1;
fill: #ffffff;
stroke: #e5e5e5;
}
.vis.timeline .iconFill {
fill-opacity:0.3;
stroke: none;
}
div.network-manipulationDiv {
border-width: 0;
border-bottom: 1px;
border-style:solid;
border-color: #d6d9d8;
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */
background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 30px;
}
div.network-manipulation-editMode {
position:absolute;
left: 0;
top: 15px;
height: 30px;
}
div.network-manipulation-closeDiv {
position:absolute;
right: 0;
top: 0;
width: 30px;
height: 30px;
background-position: 20px 3px;
background-repeat: no-repeat;
background-image: url("img/network/cross.png");
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-manipulation-closeDiv:hover {
opacity: 0.6;
}
div.network-manipulationUI {
position:relative;
top:-7px;
font-family: verdana;
font-size: 12px;
-moz-border-radius: 15px;
border-radius: 15px;
display:inline-block;
background-position: 0px 0px;
background-repeat:no-repeat;
height:24px;
margin: 0px 0px 0px 10px;
vertical-align:middle;
cursor: pointer;
padding: 0px 8px 0px 8px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-manipulationUI:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20);
}
div.network-manipulationUI:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50);
}
div.network-manipulationUI.back {
background-image: url("img/network/backIcon.png");
}
div.network-manipulationUI.none:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
cursor: default;
}
div.network-manipulationUI.none:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
}
div.network-manipulationUI.none {
padding: 0;
}
div.network-manipulationUI.notification{
margin: 2px;
font-weight: bold;
}
div.network-manipulationUI.add {
background-image: url("img/network/addNodeIcon.png");
}
div.network-manipulationUI.edit {
background-image: url("img/network/editIcon.png");
}
div.network-manipulationUI.edit.editmode {
background-color: #fcfcfc;
border-style:solid;
border-width:1px;
border-color: #cccccc;
}
div.network-manipulationUI.connect {
background-image: url("img/network/connectIcon.png");
}
div.network-manipulationUI.delete {
background-image: url("img/network/deleteIcon.png");
}
/* top right bottom left */
div.network-manipulationLabel {
margin: 0px 0px 0px 23px;
line-height: 25px;
}
div.network-seperatorLine {
display:inline-block;
width:1px;
height:20px;
background-color: #bdbdbd;
margin: 5px 7px 0px 15px;
}
div.network-navigation_wrapper {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
div.network-navigation {
width:34px;
height:34px;
-moz-border-radius: 17px;
border-radius: 17px;
position:absolute;
display:inline-block;
background-position: 2px 2px;
background-repeat:no-repeat;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-navigation:hover {
box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30);
}
div.network-navigation:active {
box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95);
}
div.network-navigation.up {
background-image: url("img/network/upArrow.png");
bottom:50px;
left:55px;
}
div.network-navigation.down {
background-image: url("img/network/downArrow.png");
bottom:10px;
left:55px;
}
div.network-navigation.left {
background-image: url("img/network/leftArrow.png");
bottom:10px;
left:15px;
}
div.network-navigation.right {
background-image: url("img/network/rightArrow.png");
bottom:10px;
left:95px;
}
div.network-navigation.zoomIn {
background-image: url("img/network/plus.png");
bottom:10px;
right:15px;
}
div.network-navigation.zoomOut {
background-image: url("img/network/minus.png");
bottom:10px;
right:55px;
}
div.network-navigation.zoomExtends {
background-image: url("img/network/zoomExtends.png");
bottom:50px;
right:15px;
}
div.network-tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
white-space: nowrap;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
border: 1px solid;
box-shadow: 3px 3px 10px rgba(128, 128, 128, 0.5);
}

35665
web_timeline/static/lib/vis/vis.js
File diff suppressed because it is too large
View File

1
web_timeline/static/lib/vis/vis.map
File diff suppressed because it is too large
View File

1
web_timeline/static/lib/vis/vis.min.css
File diff suppressed because it is too large
View File

44
web_timeline/static/lib/vis/vis.min.js
File diff suppressed because it is too large
View File

217
web_timeline/static/src/js/web_timeline.js

@ -40,7 +40,7 @@ odoo.define('web_timeline.TimelineView', function (require) {
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
}, },
get_perm: function(name){
get_perm: function (name) {
var self = this; var self = this;
var promise = self.permissions[name]; var promise = self.permissions[name];
if (self.permissions[name]) { if (self.permissions[name]) {
@ -62,12 +62,17 @@ odoo.define('web_timeline.TimelineView', function (require) {
// }); // });
// }, // },
parse_colors: function(){
if(this.fields_view.arch.attrs.colors) {
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain().compact().map(function(color_pair) {
parse_colors: function () {
if (this.fields_view.arch.attrs.colors) {
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain().compact().map(function (color_pair) {
var pair = color_pair.split(':'), color = pair[0], expr = pair[1]; var pair = color_pair.split(':'), color = pair[0], expr = pair[1];
var temp = py.parse(py.tokenize(expr)); var temp = py.parse(py.tokenize(expr));
return {'color': color, 'field': temp.expressions[0].value, 'opt': temp.operators[0], 'value': temp.expressions[1].value};
return {
'color': color,
'field': temp.expressions[0].value,
'opt': temp.operators[0],
'value': temp.expressions[1].value
};
}).value(); }).value();
} }
}, },
@ -90,7 +95,7 @@ odoo.define('web_timeline.TimelineView', function (require) {
this.proxy(this.on_scale_year_clicked)); this.proxy(this.on_scale_year_clicked));
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']);
@ -103,6 +108,10 @@ odoo.define('web_timeline.TimelineView', function (require) {
} }
this.date_start = attrs.date_start; this.date_start = attrs.date_start;
this.date_stop = attrs.date_stop; this.date_stop = attrs.date_stop;
this.date_delay = attrs.date_delay;
this.no_period = this.date_start == this.date_stop;
this.zoomKey = attrs.zoomKey || '';
this.default_window = attrs.default_window || 'fit';
if (!isNullOrUndef(attrs.quick_create_instance)) { if (!isNullOrUndef(attrs.quick_create_instance)) {
self.quick_create_instance = 'instance.' + attrs.quick_create_instance; self.quick_create_instance = 'instance.' + attrs.quick_create_instance;
@ -123,24 +132,24 @@ odoo.define('web_timeline.TimelineView', function (require) {
} }
var fields_get = new Model(this.dataset.model) var fields_get = new Model(this.dataset.model)
.call('fields_get')
.then(function (fields) {
self.fields = fields;
});
.call('fields_get')
.then(function (fields) {
self.fields = fields;
});
this._super.apply(this, self); this._super.apply(this, self);
return $.when( return $.when(
self.fields_get, self.fields_get,
self.get_perm('unlink'), self.get_perm('unlink'),
self.get_perm('write'), self.get_perm('write'),
self.get_perm('create') self.get_perm('create')
).then(function() {
).then(function () {
self.init_timeline(); self.init_timeline();
$(window).trigger('resize'); $(window).trigger('resize');
self.trigger('timeline_view_loaded', fv); self.trigger('timeline_view_loaded', fv);
}); });
}, },
init_timeline: function() {
init_timeline: function () {
var self = this; var self = this;
var options = { var options = {
groupOrder: self.group_order, groupOrder: self.group_order,
@ -161,7 +170,29 @@ odoo.define('web_timeline.TimelineView', function (require) {
onMove: self.on_move, onMove: self.on_move,
onUpdate: self.on_update, onUpdate: self.on_update,
onRemove: self.on_remove, onRemove: self.on_remove,
zoomKey: this.zoomKey
}; };
if (this.default_window) {
var start = new moment();
var end;
switch (this.default_window) {
case 'day':
end = new moment().add(1, 'days');
break;
case 'week':
end = new moment().add(1, 'weeks');
break;
case 'month':
end = new moment().add(1, 'months');
break;
}
if (end) {
options['start'] = start;
options['end'] = end;
}else{
this.default_window = 'fit';
}
}
self.timeline = new vis.Timeline(self.$timeline.empty().get(0)); self.timeline = new vis.Timeline(self.$timeline.empty().get(0));
self.timeline.setOptions(options); self.timeline.setOptions(options);
if (self.mode && self['on_scale_' + self.mode + '_clicked']) { if (self.mode && self['on_scale_' + self.mode + '_clicked']) {
@ -170,12 +201,12 @@ odoo.define('web_timeline.TimelineView', function (require) {
self.timeline.on('click', self.on_click); self.timeline.on('click', self.on_click);
}, },
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;
} }
if (grp2.id === -1){
if (grp2.id === -1) {
return +1; return +1;
} }
return grp1.content - grp2.content; return grp1.content - grp2.content;
@ -183,12 +214,12 @@ odoo.define('web_timeline.TimelineView', function (require) {
}, },
/* Transform Odoo event object to timeline event object */ /* Transform Odoo event object to timeline event object */
event_data_transform: function(evt) {
event_data_transform: function (evt) {
var self = this; var self = this;
var date_start = new moment(); var date_start = new moment();
var date_stop = new moment();
var date_stop;
var date_delay = evt[this.date_delay] || 1.0,
var 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,
res_computed_text = '', res_computed_text = '',
the_title = '', the_title = '',
@ -199,35 +230,42 @@ odoo.define('web_timeline.TimelineView', function (require) {
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;
} }
else { else {
date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0],'start');
date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0],'stop') : null;
date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0], 'start');
if (this.no_period) {
date_stop = date_start
} else {
date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0], 'stop') : null;
}
} }
if (!date_start){
if (!date_start) {
date_start = new moment(); date_start = new moment();
} }
if(!date_stop) {
if (!date_stop && date_delay) {
date_stop = moment(date_start).add(date_delay, 'hours').toDate(); date_stop = moment(date_start).add(date_delay, 'hours').toDate();
} }
var group = evt[self.last_group_bys[0]]; var group = evt[self.last_group_bys[0]];
if (group){
group = _.first(group);
if (group) {
group = _.first(group);
} else { } else {
group = -1;
group = -1;
} }
_.each(self.colors, function(color){
if(eval("'" + evt[color.field] + "' " + color.opt + " '" + color.value + "'"))
_.each(self.colors, function (color) {
if (eval("'" + evt[color.field] + "' " + color.opt + " '" + color.value + "'"))
self.color = color.color; self.color = color.color;
}); });
var r = { var r = {
'start': date_start, 'start': date_start,
'end': date_stop,
'content': evt.__name != undefined ? evt.__name : evt.display_name, 'content': evt.__name != undefined ? evt.__name : evt.display_name,
'id': evt.id, 'id': evt.id,
'group': group, 'group': group,
'evt': evt, 'evt': evt,
'style': 'background-color: ' + self.color + ';' 'style': 'background-color: ' + self.color + ';'
}; };
// Check if the event is instantaneous, if so, display it with a point on the timeline (no 'end')
if (date_stop && !moment(date_start).isSame(date_stop)) {
r.end = date_stop;
}
self.color = undefined; self.color = undefined;
return r; return r;
}, },
@ -246,60 +284,66 @@ odoo.define('web_timeline.TimelineView', function (require) {
} }
self.last_group_bys = n_group_bys; self.last_group_bys = n_group_bys;
// gather the fields to get // gather the fields to get
var fields = _.compact(_.map(["date_start", "date_delay", "date_stop", "progress"], function(key) {
var fields = _.compact(_.map(["date_start", "date_delay", "date_stop", "progress"], function (key) {
return self.fields_view.arch.attrs[key] || ''; return self.fields_view.arch.attrs[key] || '';
})); }));
fields = _.uniq(fields.concat(_.pluck(this.colors, "field").concat(n_group_bys))); fields = _.uniq(fields.concat(_.pluck(this.colors, "field").concat(n_group_bys)));
return $.when(this.has_been_loaded).then(function() {
return $.when(this.has_been_loaded).then(function () {
return self.dataset.read_slice(fields, { return self.dataset.read_slice(fields, {
domain: domains, domain: domains,
context: contexts context: contexts
}).then(function(data) {
}).then(function (data) {
return self.on_data_loaded(data, n_group_bys); return self.on_data_loaded(data, n_group_bys);
}); });
}); });
}, },
reload: function() {
reload: function () {
var self = this; var self = this;
if (this.last_domains !== undefined){
if (this.last_domains !== undefined) {
self.current_window = self.timeline.getWindow(); self.current_window = self.timeline.getWindow();
return this.do_search(this.last_domains, this.last_contexts, this.last_group_bys); return this.do_search(this.last_domains, this.last_contexts, this.last_group_bys);
} }
}, },
on_data_loaded: function(events, group_bys) {
on_data_loaded: function (events, group_bys) {
var self = this; var self = this;
var ids = _.pluck(events, "id"); var ids = _.pluck(events, "id");
return this.dataset.name_get(ids).then(function(names) {
var nevents = _.map(events, function(event) {
return _.extend({__name: _.detect(names, function(name) { return name[0] == event.id; })[1]}, event);
return this.dataset.name_get(ids).then(function (names) {
var nevents = _.map(events, function (event) {
return _.extend({
__name: _.detect(names, function (name) {
return name[0] == event.id;
})[1]
}, event);
}); });
return self.on_data_loaded_2(nevents, group_bys); return self.on_data_loaded_2(nevents, group_bys);
}); });
}, },
on_data_loaded_2: function(events, group_bys) {
on_data_loaded_2: function (events, group_bys) {
var self = this; var self = this;
var data = []; var data = [];
var groups = []; var groups = [];
this.grouped_by = group_bys; this.grouped_by = group_bys;
_.each(events, function(event) {
if (event[self.date_start]){
_.each(events, function (event) {
if (event[self.date_start]) {
data.push(self.event_data_transform(event)); data.push(self.event_data_transform(event));
} }
}); });
// get the groups // get the groups
var split_groups = function(events, group_bys) {
var split_groups = function (events, group_bys) {
if (group_bys.length === 0) if (group_bys.length === 0)
return events; return events;
var groups = []; var groups = [];
groups.push({id:-1, content: _t('-')})
_.each(events, function(event) {
groups.push({id: -1, content: _t('-')})
_.each(events, function (event) {
var group_name = event[_.first(group_bys)]; var group_name = event[_.first(group_bys)];
if (group_name) { if (group_name) {
var group = _.find(groups, function(group) { return _.isEqual(group.id, group_name[0]); });
var group = _.find(groups, function (group) {
return _.isEqual(group.id, group_name[0]);
});
if (group === undefined) { if (group === undefined) {
group = {id: group_name[0], content: group_name[1]}; group = {id: group_name[0], content: group_name[1]};
groups.push(group); groups.push(group);
@ -311,26 +355,28 @@ odoo.define('web_timeline.TimelineView', function (require) {
var groups = split_groups(events, group_bys); var groups = split_groups(events, group_bys);
this.timeline.setGroups(groups); this.timeline.setGroups(groups);
this.timeline.setItems(data); this.timeline.setItems(data);
this.timeline.fit();
if (!this.default_window || this.default_window == 'fit'){
this.timeline.fit();
}
}, },
do_show: function() {
do_show: function () {
this.do_push_state({}); this.do_push_state({});
return this._super(); return this._super();
}, },
is_action_enabled: function(action) {
is_action_enabled: function (action) {
if (action === 'create' && !this.options.creatable) { if (action === 'create' && !this.options.creatable) {
return false; return false;
} }
return this._super(action); return this._super(action);
}, },
create_completed: function(id) {
create_completed: function (id) {
var self = this; var self = this;
this.dataset.ids = this.dataset.ids.concat([id]); this.dataset.ids = this.dataset.ids.concat([id]);
this.dataset.trigger("dataset_changed", id); this.dataset.trigger("dataset_changed", id);
this.dataset.read_ids([id], this.fields).done(function(records) {
this.dataset.read_ids([id], this.fields).done(function (records) {
var new_event = self.event_data_transform(records[0]); var new_event = self.event_data_transform(records[0]);
var items = self.timeline.itemsData; var items = self.timeline.itemsData;
items.add(new_event); items.add(new_event);
@ -338,13 +384,18 @@ odoo.define('web_timeline.TimelineView', function (require) {
}); });
}, },
on_add: function(item, callback) {
on_add: function (item, callback) {
var self = this; var self = this;
var context = this.dataset.get_context(); var context = this.dataset.get_context();
// Initialize default values for creation // Initialize default values for creation
var default_context = {}; var default_context = {};
default_context['default_'.concat(this.date_start)] = item.start; default_context['default_'.concat(this.date_start)] = item.start;
default_context['default_'.concat(this.date_stop)] = moment(item.start).add(1, 'hours').toDate();
if (this.date_delay) {
default_context['default_'.concat(this.date_delay)] = 1;
}
if (this.date_stop) {
default_context['default_'.concat(this.date_stop)] = moment(item.start).add(1, 'hours').toDate();
}
if (item.group > 0) { if (item.group > 0) {
default_context['default_'.concat(this.last_group_bys[0])] = item.group; default_context['default_'.concat(this.last_group_bys[0])] = item.group;
} }
@ -360,24 +411,24 @@ odoo.define('web_timeline.TimelineView', function (require) {
return false; return false;
}, },
write_completed: function(id) {
write_completed: function (id) {
this.dataset.trigger("dataset_changed", id); this.dataset.trigger("dataset_changed", id);
this.current_window = this.timeline.getWindow(); this.current_window = this.timeline.getWindow();
this.reload(); this.reload();
this.timeline.setWindow(this.current_window); this.timeline.setWindow(this.current_window);
}, },
on_update: function(item, callback) {
on_update: function (item, callback) {
var self = this; var self = this;
var id = item.evt.id; var id = item.evt.id;
var title = item.evt.__name; var title = item.evt.__name;
if (! this.open_popup_action) {
if (!this.open_popup_action) {
var index = this.dataset.get_id_index(id); var index = this.dataset.get_id_index(id);
this.dataset.index = index; this.dataset.index = index;
if (this.write_right) { if (this.write_right) {
this.do_switch_view('form', null, { mode: "edit" });
this.do_switch_view('form', null, {mode: "edit"});
} else { } else {
this.do_switch_view('form', null, { mode: "view" });
this.do_switch_view('form', null, {mode: "view"});
} }
} }
else { else {
@ -393,33 +444,45 @@ odoo.define('web_timeline.TimelineView', function (require) {
return false; return false;
}, },
on_move: function(item, callback) {
on_move: function (item, callback) {
var self = this; var self = this;
var start = item.start;
var end = item.end;
var event_start = item.start;
var event_end = item.end;
var group = false; var group = false;
if (item.group != -1) { if (item.group != -1) {
group = item.group; group = item.group;
} }
var data = {}; var data = {};
data[self.fields_view.arch.attrs.date_start] =
time.auto_date_to_str(start, self.fields[self.fields_view.arch.attrs.date_start].type);
data[self.fields_view.arch.attrs.date_stop] =
time.auto_date_to_str(end, self.fields[self.fields_view.arch.attrs.date_stop].type);
if (self.grouped_by){
// In case of a move event, the date_delay stay the same, only date_start and stop must be updated
data[this.date_start] = time.auto_date_to_str(event_start, self.fields[this.date_start].type);
if (this.date_stop) {
// In case of instantaneous event, item.end is not defined
if (event_end) {
data[this.date_stop] = time.auto_date_to_str(event_end, self.fields[this.date_stop].type);
} else {
data[this.date_stop] = data[this.date_start]
}
}
if (this.date_delay && event_end) {
var diff_seconds = Math.round((event_end.getTime() - event_start.getTime()) / 1000);
data[this.date_delay] = diff_seconds / 3600;
}
if (self.grouped_by) {
data[self.grouped_by[0]] = group; data[self.grouped_by[0]] = group;
} }
var id = item.evt.id; var id = item.evt.id;
this.dataset.write(id, data); this.dataset.write(id, data);
}, },
on_remove: function(item, callback) {
on_remove: function (item, callback) {
var self = this; var self = this;
function do_it() { function do_it() {
return $.when(self.dataset.unlink([item.evt.id])).then(function() {
return $.when(self.dataset.unlink([item.evt.id])).then(function () {
callback(item); callback(item);
}); });
} }
if (this.options.confirm_on_delete) { if (this.options.confirm_on_delete) {
if (confirm(_t("Are you sure you want to delete this record ?"))) { if (confirm(_t("Are you sure you want to delete this record ?"))) {
return do_it(); return do_it();
@ -428,14 +491,14 @@ odoo.define('web_timeline.TimelineView', function (require) {
return do_it(); return do_it();
}, },
on_click: function(e) {
on_click: function (e) {
// handle a click on a group header // handle a click on a group header
if (e.what == 'group-label') { if (e.what == 'group-label') {
return this.on_group_click(e); return this.on_group_click(e);
} }
}, },
on_group_click: function(e) {
on_group_click: function (e) {
if (e.group == -1) { if (e.group == -1) {
return; return;
} }
@ -448,18 +511,18 @@ odoo.define('web_timeline.TimelineView', function (require) {
}); });
}, },
scale_current_window: function(factor){
if (this.timeline){
scale_current_window: function (factor) {
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);
} }
}, },
on_today_clicked: function(){
on_today_clicked: 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) {
@ -467,19 +530,19 @@ odoo.define('web_timeline.TimelineView', function (require) {
} }
}, },
on_scale_day_clicked: function() {
on_scale_day_clicked: function () {
this.scale_current_window(24); this.scale_current_window(24);
}, },
on_scale_week_clicked: function() {
on_scale_week_clicked: function () {
this.scale_current_window(24 * 7); this.scale_current_window(24 * 7);
}, },
on_scale_month_clicked: function() {
on_scale_month_clicked: function () {
this.scale_current_window(24 * 30); this.scale_current_window(24 * 30);
}, },
on_scale_year_clicked: function() {
on_scale_year_clicked: function () {
this.scale_current_window(24 * 365); this.scale_current_window(24 * 365);
} }
}); });

4
web_timeline/views/web_timeline.xml

@ -3,10 +3,10 @@
<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" href="/web_timeline/static/lib/vis/vis.css"/>
<link rel="stylesheet" href="/web_timeline/static/lib/vis/vis-timeline-graph2d.min.css"/>
<link rel="stylesheet" href="/web_timeline/static/src/css/web_timeline.css"/> <link rel="stylesheet" href="/web_timeline/static/src/css/web_timeline.css"/>
<script type="text/javascript" src="/web_timeline/static/lib/vis/vis.js"/>
<script type="text/javascript" src="/web_timeline/static/lib/vis/vis-timeline-graph2d.min.js"/>
<script type="text/javascript" src="/web_timeline/static/src/js/web_timeline.js"/> <script type="text/javascript" src="/web_timeline/static/src/js/web_timeline.js"/>
</xpath> </xpath>
</template> </template>

Loading…
Cancel
Save