From c6d9bf698784236bb506bc45c11f6afae3015059 Mon Sep 17 00:00:00 2001 From: "anjeel.haria" Date: Wed, 1 Mar 2023 15:52:42 +0100 Subject: [PATCH] [MIG] web_timeline: Migration to 16.0 Syntax changes Syntax changes --- web_timeline/README.rst | 61 ++- web_timeline/__manifest__.py | 6 +- web_timeline/i18n/web_timeline.pot | 28 +- web_timeline/readme/CONFIGURE.rst | 47 ++- web_timeline/readme/CONTRIBUTORS.rst | 4 +- web_timeline/static/description/index.html | 63 ++- .../lib/vis-timeline/vis-timeline-graph2d.js | 1 - .../static/src/js/timeline_controller.esm.js | 360 +++++++++++++++++ .../static/src/js/timeline_controller.js | 364 ------------------ web_timeline/static/src/js/timeline_view.js | 3 +- 10 files changed, 509 insertions(+), 428 deletions(-) create mode 100644 web_timeline/static/src/js/timeline_controller.esm.js delete mode 100644 web_timeline/static/src/js/timeline_controller.js diff --git a/web_timeline/README.rst b/web_timeline/README.rst index fb91d6451..e5aed5fa2 100644 --- a/web_timeline/README.rst +++ b/web_timeline/README.rst @@ -14,13 +14,13 @@ Web timeline :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github - :target: https://github.com/OCA/web/tree/15.0/web_timeline + :target: https://github.com/OCA/web/tree/16.0/web_timeline :alt: OCA/web .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_timeline + :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_timeline :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/162/15.0 + :target: https://runbot.odoo-community.org/runbot/162/16.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -91,25 +91,50 @@ Example: - + colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'" + dependency_arrow="depend_on_ids" + > + + -
-
- Assigned to: - -
+ +
+ + User + + + + + + + +
+
- kanban,tree,form,calendar,gantt,timeline,graph + kanban,tree,form,calendar,timeline,pivot,graph,activity @@ -165,7 +190,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -188,7 +213,6 @@ Contributors * Adrien Peiffer * Leonardo Donelli * Adrien Didenot -* Dennis Sluijk * Thong Nguyen Van * Murtaza Mithaiwala * Ammar Officewala @@ -197,6 +221,9 @@ Contributors * Pedro M. Baeza * Alexandre Díaz * César A. Sánchez +* `Onestein `_: + * Dennis Sluijk + * Anjeel Haria Maintainers ~~~~~~~~~~~ @@ -219,6 +246,6 @@ Current `maintainer `__: |maintainer-tarteo| -This module is part of the `OCA/web `_ project on GitHub. +This module is part of the `OCA/web `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_timeline/__manifest__.py b/web_timeline/__manifest__.py index b87b79fa1..84c79ddae 100644 --- a/web_timeline/__manifest__.py +++ b/web_timeline/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Web timeline", "summary": "Interactive visualization chart to show events in time", - "version": "15.0.1.0.1", + "version": "16.0.1.0.0", "development_status": "Production/Stable", "author": "ACSONE SA/NV, " "Tecnativa, " @@ -25,11 +25,9 @@ "web_timeline/static/src/scss/web_timeline.scss", "web_timeline/static/src/js/timeline_view.js", "web_timeline/static/src/js/timeline_renderer.js", - "web_timeline/static/src/js/timeline_controller.js", + "web_timeline/static/src/js/timeline_controller.esm.js", "web_timeline/static/src/js/timeline_model.js", "web_timeline/static/src/js/timeline_canvas.js", - ], - "web.assets_qweb": [ "web_timeline/static/src/xml/web_timeline.xml", ], }, diff --git a/web_timeline/i18n/web_timeline.pot b/web_timeline/i18n/web_timeline.pot index c796429f4..a31a5b83d 100644 --- a/web_timeline/i18n/web_timeline.pot +++ b/web_timeline/i18n/web_timeline.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 15.0\n" +"Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -14,42 +14,42 @@ msgstr "" "Plural-Forms: \n" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/js/timeline_renderer.js:0 #, python-format msgid "UNASSIGNED" msgstr "" #. module: web_timeline -#. openerp-web -#: code:addons/web_timeline/static/src/js/timeline_controller.js:0 +#. odoo-javascript +#: code:addons/web_timeline/static/src/js/timeline_controller.esm.js:0 #, python-format msgid "Are you sure you want to delete this record?" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/xml/web_timeline.xml:0 #, python-format msgid "Day" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/xml/web_timeline.xml:0 #, python-format msgid "Month" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/js/timeline_renderer.js:0 #, python-format msgid "Template \"timeline-item\" not present in timeline view definition." msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/js/timeline_view.js:0 #: model:ir.model.fields.selection,name:web_timeline.selection__ir_ui_view__type__timeline #, python-format @@ -57,14 +57,14 @@ msgid "Timeline" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/js/timeline_renderer.js:0 #, python-format msgid "Timeline view has not defined 'date_start' attribute." msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/xml/web_timeline.xml:0 #, python-format msgid "Today" @@ -81,21 +81,21 @@ msgid "View Type" msgstr "" #. module: web_timeline -#. openerp-web -#: code:addons/web_timeline/static/src/js/timeline_controller.js:0 +#. odoo-javascript +#: code:addons/web_timeline/static/src/js/timeline_controller.esm.js:0 #, python-format msgid "Warning" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/xml/web_timeline.xml:0 #, python-format msgid "Week" msgstr "" #. module: web_timeline -#. openerp-web +#. odoo-javascript #: code:addons/web_timeline/static/src/xml/web_timeline.xml:0 #, python-format msgid "Year" diff --git a/web_timeline/readme/CONFIGURE.rst b/web_timeline/readme/CONFIGURE.rst index 4ae188970..db4147293 100644 --- a/web_timeline/readme/CONFIGURE.rst +++ b/web_timeline/readme/CONFIGURE.rst @@ -51,24 +51,49 @@ Example: - + colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'" + dependency_arrow="depend_on_ids" + > + + -
-
- Assigned to: - -
+ +
+ + User + + + + + + + +
+
- kanban,tree,form,calendar,gantt,timeline,graph + kanban,tree,form,calendar,timeline,pivot,graph,activity diff --git a/web_timeline/readme/CONTRIBUTORS.rst b/web_timeline/readme/CONTRIBUTORS.rst index 8c7dfbdbb..d13a44e9c 100644 --- a/web_timeline/readme/CONTRIBUTORS.rst +++ b/web_timeline/readme/CONTRIBUTORS.rst @@ -2,7 +2,6 @@ * Adrien Peiffer * Leonardo Donelli * Adrien Didenot -* Dennis Sluijk * Thong Nguyen Van * Murtaza Mithaiwala * Ammar Officewala @@ -11,3 +10,6 @@ * Pedro M. Baeza * Alexandre Díaz * César A. Sánchez +* `Onestein `_: + * Dennis Sluijk + * Anjeel Haria diff --git a/web_timeline/static/description/index.html b/web_timeline/static/description/index.html index 0e5e3fd03..597a08b73 100644 --- a/web_timeline/static/description/index.html +++ b/web_timeline/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Production/Stable License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

+

Production/Stable License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

Define a new view displaying events in an interactive visualization chart.

The widget is based on the external library https://visjs.github.io/vis-timeline/examples/timeline

@@ -470,25 +470,50 @@ These are the variables available in template rendering:

<timeline date_start="date_assign" date_stop="date_end" string="Tasks" - default_group_by="user_id" + default_group_by="project_id" event_open_popup="true" - zoomKey="ctrlKey" - colors="#ec7063:user_id == false;#2ecb71:kanban_state=='done';" - dependency_arrow="task_dependency_ids"> - <field name="user_id"/> + colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'" + dependency_arrow="depend_on_ids" + > + <field name="user_ids" /> + <field name="planned_hours" /> <templates> - <div t-name="timeline-item"> - <div t-esc="record.display_name"/> - Assigned to: - <span t-esc="record.user_id[1]"/> - </div> + <t t-name="timeline-item"> + <div class="o_project_timeline_item"> + <t t-foreach="record.user_ids" t-as="user"> + <img + t-if="record.user_ids" + t-attf-src="/web/image/res.users/#{user}/image_128/16x16" + t-att-title="record.user" + width="16" + height="16" + class="mr8" + alt="User" + /> + </t> + <span name="display_name"> + <t t-esc="record.display_name" /> + </span> + <small + name="planned_hours" + class="text-info ml4" + t-if="record.planned_hours" + > + <t + t-esc="field_utils.format.float_time(record.planned_hours)" + /> + </small> + </div> + </t> </templates> </timeline> </field> </record> <record id="project.action_view_task" model="ir.actions.act_window"> - <field name="view_mode">kanban,tree,form,calendar,gantt,timeline,graph</field> + <field + name="view_mode" + >kanban,tree,form,calendar,timeline,pivot,graph,activity</field> </record> </odoo> @@ -539,7 +564,7 @@ the value according the group of the dragged item.

Bugs are tracked on GitHub Issues. 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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -561,7 +586,6 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
  • Adrien Peiffer <adrien.peiffer@acsone.eu>
  • Leonardo Donelli <donelli@webmonks.it>
  • Adrien Didenot <adrien.didenot@horanet.com>
  • -
  • Dennis Sluijk <d.sluijk@onestein.nl>
  • Thong Nguyen Van <thongnv@trobz.com>
  • Murtaza Mithaiwala <mmithaiwala@opensourceintegrators.com>
  • Ammar Officewala <aofficewala@opensourceintegrators.com>
  • @@ -571,6 +595,15 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
  • César A. Sánchez
  • +
  • +
    Onestein:
    +
    +
    +
    +
  • @@ -582,7 +615,7 @@ mission is to support the collaborative development of Odoo features and promote its widespread use.

    Current maintainer:

    tarteo

    -

    This module is part of the OCA/web project on GitHub.

    +

    This module is part of the OCA/web project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js b/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js index d776b5609..55e2137e4 100644 --- a/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js +++ b/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js @@ -50109,4 +50109,3 @@ Object.defineProperty(exports, '__esModule', { value: true }); })); -//# sourceMappingURL=vis-timeline-graph2d.js.map diff --git a/web_timeline/static/src/js/timeline_controller.esm.js b/web_timeline/static/src/js/timeline_controller.esm.js new file mode 100644 index 000000000..630609a1f --- /dev/null +++ b/web_timeline/static/src/js/timeline_controller.esm.js @@ -0,0 +1,360 @@ +/** @odoo-module alias=web_timeline.TimelineController **/ +/* Copyright 2023 Onestein - Anjeel Haria + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +import AbstractController from "web.AbstractController"; +import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog"; +import time from "web.time"; +import core from "web.core"; +import Dialog from "web.Dialog"; +var _t = core._t; +import {Component} from "@odoo/owl"; + +export default AbstractController.extend({ + custom_events: _.extend({}, AbstractController.prototype.custom_events, { + onGroupClick: "_onGroupClick", + onUpdate: "_onUpdate", + onRemove: "_onRemove", + onMove: "_onMove", + onAdd: "_onAdd", + }), + + /** + * @override + */ + init: function (parent, model, renderer, params) { + this._super.apply(this, arguments); + this.open_popup_action = params.open_popup_action; + this.date_start = params.date_start; + this.date_stop = params.date_stop; + this.date_delay = params.date_delay; + this.context = params.actionContext; + this.moveQueue = []; + this.debouncedInternalMove = _.debounce(this.internalMove, 0); + }, + on_detach_callback() { + if (this.Dialog) { + this.Dialog(); + this.Dialog = undefined; + } + return this._super.apply(this, arguments); + }, + /** + * @override + */ + update: function (params, options) { + const res = this._super.apply(this, arguments); + if (_.isEmpty(params)) { + return res; + } + const defaults = _.defaults({}, options, { + adjust_window: true, + }); + const domains = params.domain || this.renderer.last_domains || []; + const contexts = params.context || []; + const group_bys = params.groupBy || this.renderer.last_group_bys || []; + this.last_domains = domains; + this.last_contexts = contexts; + // Select the group by + let n_group_bys = group_bys; + if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) { + n_group_bys = this.renderer.arch.attrs.default_group_by.split(","); + } + this.renderer.last_group_bys = n_group_bys; + this.renderer.last_domains = domains; + + let fields = this.renderer.fieldNames; + fields = _.uniq(fields.concat(n_group_bys)); + $.when( + res, + this._rpc({ + model: this.model.modelName, + method: "search_read", + kwargs: { + fields: fields, + domain: domains, + order: [{name: this.renderer.arch.attrs.default_group_by}], + }, + context: this.getSession().user_context, + }).then((data) => + this.renderer.on_data_loaded(data, n_group_bys, defaults.adjust_window) + ) + ); + return res; + }, + + /** + * Gets triggered when a group in the timeline is + * clicked (by the TimelineRenderer). + * + * @private + * @param {EventObject} event + * @returns {jQuery.Deferred} + */ + _onGroupClick: function (event) { + const groupField = this.renderer.last_group_bys[0]; + return this.do_action({ + type: "ir.actions.act_window", + res_model: this.renderer.fields[groupField].relation, + res_id: event.data.item.group, + target: "new", + views: [[false, "form"]], + }); + }, + + /** + * Opens a form view of a clicked timeline + * item (triggered by the TimelineRenderer). + * + * @private + * @param {EventObject} event + */ + _onUpdate: function (event) { + this.renderer = event.data.renderer; + const rights = event.data.rights; + const item = event.data.item; + const id = Number(item.evt.id) || item.evt.id; + const title = item.evt.__name; + if (this.open_popup_action) { + this.Dialog = Component.env.services.dialog.add( + FormViewDialog, + { + resId: id, + context: this.getSession().user_context, + title: title, + onRecordSaved: () => this.write_completed(), + resModel: this.model.modelName, + }, + {} + ); + } else { + let mode = "readonly"; + if (rights.write) { + mode = "edit"; + } + this.trigger_up("switch_view", { + view_type: "form", + res_id: id, + mode: mode, + model: this.model.modelName, + }); + } + }, + + /** + * Gets triggered when a timeline item is + * moved (triggered by the TimelineRenderer). + * + * @private + * @param {EventObject} event + */ + _onMove: function (event) { + const item = event.data.item; + const fields = this.renderer.fields; + const event_start = item.start; + const event_end = item.end; + let group = false; + if (item.group !== -1) { + group = item.group; + } + const data = {}; + // 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, + 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, + fields[this.date_stop].type + ); + } else { + data[this.date_stop] = data[this.date_start]; + } + } + if (this.date_delay && event_end) { + const diff_seconds = Math.round( + (event_end.getTime() - event_start.getTime()) / 1000 + ); + data[this.date_delay] = diff_seconds / 3600; + } + const grouped_field = this.renderer.last_group_bys[0]; + this._rpc({ + model: this.modelName, + method: "fields_get", + args: [grouped_field], + context: this.getSession().user_context, + }).then(async (fields_processed) => { + if ( + this.renderer.last_group_bys && + this.renderer.last_group_bys instanceof Array && + fields_processed[grouped_field].type !== "many2many" + ) { + data[this.renderer.last_group_bys[0]] = group; + } + + this.moveQueue.push({ + id: event.data.item.id, + data: data, + event: event, + }); + + this.debouncedInternalMove(); + }); + }, + + /** + * Write enqueued moves to Odoo. After all writes are finished it updates + * the view once (prevents flickering of the view when multiple timeline items + * are moved at once). + * + * @returns {jQuery.Deferred} + */ + internalMove: function () { + const queues = this.moveQueue.slice(); + this.moveQueue = []; + const defers = []; + for (const item of queues) { + defers.push( + this._rpc({ + model: this.model.modelName, + method: "write", + args: [[item.event.data.item.id], item.data], + context: this.getSession().user_context, + }).then(() => { + item.event.data.callback(item.event.data.item); + }) + ); + } + return $.when.apply($, defers).done(() => { + this.write_completed({ + adjust_window: false, + }); + }); + }, + + /** + * Triggered when a timeline item gets removed from the view. + * Requires user confirmation before it gets actually deleted. + * + * @private + * @param {EventObject} event + * @returns {jQuery.Deferred} + */ + _onRemove: function (event) { + var def = $.Deferred(); + + Dialog.confirm(this, _t("Are you sure you want to delete this record?"), { + title: _t("Warning"), + confirm_callback: () => { + this.remove_completed(event).then(def.resolve.bind(def)); + }, + cancel_callback: def.resolve.bind(def), + }); + + return def; + }, + + /** + * Triggered when a timeline item gets added and opens a form view. + * + * @private + * @param {EventObject} event + * @returns {dialogs.FormViewDialog} + */ + _onAdd: function (event) { + const item = event.data.item; + // Initialize default values for creation + const default_context = {}; + default_context["default_".concat(this.date_start)] = item.start; + if (this.date_delay) { + default_context["default_".concat(this.date_delay)] = 1; + } + if (this.date_start) { + default_context["default_".concat(this.date_start)] = moment(item.start) + .utc() + .format("YYYY-MM-DD HH:mm:ss"); + } + if (this.date_stop && item.end) { + default_context["default_".concat(this.date_stop)] = moment(item.end) + .utc() + .format("YYYY-MM-DD HH:mm:ss"); + } + if (item.group > 0) { + default_context["default_".concat(this.renderer.last_group_bys[0])] = + item.group; + } + // Show popup + this.Dialog = Component.env.services.dialog.add( + FormViewDialog, + { + resId: null, + context: _.extend(default_context, this.context), + onRecordSaved: (record) => this.create_completed([record.res_id]), + resModel: this.model.modelName, + }, + {onClose: () => event.data.callback()} + ); + return false; + }, + + /** + * Triggered upon completion of a new record. + * Updates the timeline view with the new record. + * + * @param {RecordId} id + * @returns {jQuery.Deferred} + */ + create_completed: function (id) { + return this._rpc({ + model: this.model.modelName, + method: "read", + args: [id, this.model.fieldNames], + context: this.context, + }).then((records) => { + var new_event = this.renderer.event_data_transform(records[0]); + var items = this.renderer.timeline.itemsData; + items.add(new_event); + }); + }, + + /** + * Triggered upon completion of writing a record. + * @param {ControllerOptions} options + */ + write_completed: function (options) { + const params = { + domain: this.renderer.last_domains, + context: this.context, + groupBy: this.renderer.last_group_bys, + }; + this.update(params, options); + }, + + /** + * Triggered upon confirm of removing a record. + * @param {EventObject} event + * @returns {jQuery.Deferred} + */ + remove_completed: function (event) { + return this._rpc({ + model: this.modelName, + method: "unlink", + args: [[event.data.item.id]], + context: this.getSession().user_context, + }).then(() => { + let unlink_index = false; + for (var i = 0; i < this.model.data.data.length; i++) { + if (this.model.data.data[i].id === event.data.item.id) { + unlink_index = i; + } + } + if (!isNaN(unlink_index)) { + this.model.data.data.splice(unlink_index, 1); + } + event.data.callback(event.data.item); + }); + }, +}); diff --git a/web_timeline/static/src/js/timeline_controller.js b/web_timeline/static/src/js/timeline_controller.js deleted file mode 100644 index bd814f0aa..000000000 --- a/web_timeline/static/src/js/timeline_controller.js +++ /dev/null @@ -1,364 +0,0 @@ -odoo.define("web_timeline.TimelineController", function (require) { - "use strict"; - - const AbstractController = require("web.AbstractController"); - const dialogs = require("web.view_dialogs"); - const core = require("web.core"); - const time = require("web.time"); - const Dialog = require("web.Dialog"); - - const _t = core._t; - - const TimelineController = AbstractController.extend({ - custom_events: _.extend({}, AbstractController.prototype.custom_events, { - onGroupClick: "_onGroupClick", - onUpdate: "_onUpdate", - onRemove: "_onRemove", - onMove: "_onMove", - onAdd: "_onAdd", - }), - - /** - * @override - */ - init: function (parent, model, renderer, params) { - this._super.apply(this, arguments); - this.open_popup_action = params.open_popup_action; - this.date_start = params.date_start; - this.date_stop = params.date_stop; - this.date_delay = params.date_delay; - this.context = params.actionContext; - this.moveQueue = []; - this.debouncedInternalMove = _.debounce(this.internalMove, 0); - }, - - /** - * @override - */ - update: function (params, options) { - const res = this._super.apply(this, arguments); - if (_.isEmpty(params)) { - return res; - } - const defaults = _.defaults({}, options, { - adjust_window: true, - }); - const domains = params.domain || this.renderer.last_domains || []; - const contexts = params.context || []; - const group_bys = params.groupBy || this.renderer.last_group_bys || []; - this.last_domains = domains; - this.last_contexts = contexts; - // Select the group by - let n_group_bys = group_bys; - if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) { - n_group_bys = this.renderer.arch.attrs.default_group_by.split(","); - } - this.renderer.last_group_bys = n_group_bys; - this.renderer.last_domains = domains; - - let fields = this.renderer.fieldNames; - fields = _.uniq(fields.concat(n_group_bys)); - $.when( - res, - this._rpc({ - model: this.model.modelName, - method: "search_read", - kwargs: { - fields: fields, - domain: domains, - order: [{name: this.renderer.arch.attrs.default_group_by}], - }, - context: this.getSession().user_context, - }).then((data) => - this.renderer.on_data_loaded( - data, - n_group_bys, - defaults.adjust_window - ) - ) - ); - return res; - }, - - /** - * Gets triggered when a group in the timeline is - * clicked (by the TimelineRenderer). - * - * @private - * @param {EventObject} event - * @returns {jQuery.Deferred} - */ - _onGroupClick: function (event) { - const groupField = this.renderer.last_group_bys[0]; - return this.do_action({ - type: "ir.actions.act_window", - res_model: this.renderer.fields[groupField].relation, - res_id: event.data.item.group, - target: "new", - views: [[false, "form"]], - }); - }, - - /** - * Opens a form view of a clicked timeline - * item (triggered by the TimelineRenderer). - * - * @private - * @param {EventObject} event - */ - _onUpdate: function (event) { - this.renderer = event.data.renderer; - const rights = event.data.rights; - const item = event.data.item; - const id = Number(item.evt.id) || item.evt.id; - const title = item.evt.__name; - if (this.open_popup_action) { - new dialogs.FormViewDialog(this, { - res_model: this.model.modelName, - res_id: id, - context: this.getSession().user_context, - title: title, - view_id: Number(this.open_popup_action), - on_saved: () => { - this.write_completed(); - }, - }).open(); - } else { - let mode = "readonly"; - if (rights.write) { - mode = "edit"; - } - this.trigger_up("switch_view", { - view_type: "form", - res_id: id, - mode: mode, - model: this.model.modelName, - }); - } - }, - - /** - * Gets triggered when a timeline item is - * moved (triggered by the TimelineRenderer). - * - * @private - * @param {EventObject} event - */ - _onMove: function (event) { - const item = event.data.item; - const fields = this.renderer.fields; - const event_start = item.start; - const event_end = item.end; - let group = false; - if (item.group !== -1) { - group = item.group; - } - const data = {}; - // 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, - 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, - fields[this.date_stop].type - ); - } else { - data[this.date_stop] = data[this.date_start]; - } - } - if (this.date_delay && event_end) { - const diff_seconds = Math.round( - (event_end.getTime() - event_start.getTime()) / 1000 - ); - data[this.date_delay] = diff_seconds / 3600; - } - const grouped_field = this.renderer.last_group_bys[0]; - this._rpc({ - model: this.modelName, - method: "fields_get", - args: [grouped_field], - context: this.getSession().user_context, - }).then(async (fields_processed) => { - if ( - this.renderer.last_group_bys && - this.renderer.last_group_bys instanceof Array && - fields_processed[grouped_field].type !== "many2many" - ) { - data[this.renderer.last_group_bys[0]] = group; - } - - this.moveQueue.push({ - id: event.data.item.id, - data: data, - event: event, - }); - - this.debouncedInternalMove(); - }); - }, - - /** - * Write enqueued moves to Odoo. After all writes are finished it updates - * the view once (prevents flickering of the view when multiple timeline items - * are moved at once). - * - * @returns {jQuery.Deferred} - */ - internalMove: function () { - const queues = this.moveQueue.slice(); - this.moveQueue = []; - const defers = []; - for (const item of queues) { - defers.push( - this._rpc({ - model: this.model.modelName, - method: "write", - args: [[item.event.data.item.id], item.data], - context: this.getSession().user_context, - }).then(() => { - item.event.data.callback(item.event.data.item); - }) - ); - } - return $.when.apply($, defers).done(() => { - this.write_completed({ - adjust_window: false, - }); - }); - }, - - /** - * Triggered when a timeline item gets removed from the view. - * Requires user confirmation before it gets actually deleted. - * - * @private - * @param {EventObject} event - * @returns {jQuery.Deferred} - */ - _onRemove: function (event) { - var def = $.Deferred(); - - Dialog.confirm(this, _t("Are you sure you want to delete this record?"), { - title: _t("Warning"), - confirm_callback: () => { - this.remove_completed(event).then(def.resolve.bind(def)); - }, - cancel_callback: def.resolve.bind(def), - }); - - return def; - }, - - /** - * Triggered when a timeline item gets added and opens a form view. - * - * @private - * @param {EventObject} event - * @returns {dialogs.FormViewDialog} - */ - _onAdd: function (event) { - const item = event.data.item; - // Initialize default values for creation - const default_context = {}; - default_context["default_".concat(this.date_start)] = item.start; - if (this.date_delay) { - default_context["default_".concat(this.date_delay)] = 1; - } - if (this.date_start) { - default_context["default_".concat(this.date_start)] = moment(item.start) - .utc() - .format("YYYY-MM-DD HH:mm:ss"); - } - if (this.date_stop && item.end) { - default_context["default_".concat(this.date_stop)] = moment(item.end) - .utc() - .format("YYYY-MM-DD HH:mm:ss"); - } - if (item.group > 0) { - default_context["default_".concat(this.renderer.last_group_bys[0])] = - item.group; - } - // Show popup - new dialogs.FormViewDialog(this, { - res_model: this.model.modelName, - res_id: null, - context: _.extend(default_context, this.context), - view_id: Number(this.open_popup_action), - on_saved: (record) => { - this.create_completed([record.res_id]); - }, - }) - .open() - .on("closed", this, () => { - event.data.callback(); - }); - - return false; - }, - - /** - * Triggered upon completion of a new record. - * Updates the timeline view with the new record. - * - * @param {RecordId} id - * @returns {jQuery.Deferred} - */ - create_completed: function (id) { - return this._rpc({ - model: this.model.modelName, - method: "read", - args: [id, this.model.fieldNames], - context: this.context, - }).then((records) => { - var new_event = this.renderer.event_data_transform(records[0]); - var items = this.renderer.timeline.itemsData; - items.add(new_event); - }); - }, - - /** - * Triggered upon completion of writing a record. - * @param {ControllerOptions} options - */ - write_completed: function (options) { - const params = { - domain: this.renderer.last_domains, - context: this.context, - groupBy: this.renderer.last_group_bys, - }; - this.update(params, options); - }, - - /** - * Triggered upon confirm of removing a record. - * @param {EventObject} event - * @returns {jQuery.Deferred} - */ - remove_completed: function (event) { - return this._rpc({ - model: this.modelName, - method: "unlink", - args: [[event.data.item.id]], - context: this.getSession().user_context, - }).then(() => { - let unlink_index = false; - for (var i = 0; i < this.model.data.data.length; i++) { - if (this.model.data.data[i].id === event.data.item.id) { - unlink_index = i; - } - } - if (!isNaN(unlink_index)) { - this.model.data.data.splice(unlink_index, 1); - } - event.data.callback(event.data.item); - }); - }, - }); - - return TimelineController; -}); diff --git a/web_timeline/static/src/js/timeline_view.js b/web_timeline/static/src/js/timeline_view.js index 82dcccd70..66acca98a 100644 --- a/web_timeline/static/src/js/timeline_view.js +++ b/web_timeline/static/src/js/timeline_view.js @@ -2,6 +2,7 @@ /* Odoo web_timeline * Copyright 2015 ACSONE SA/NV * Copyright 2016 Pedro M. Baeza + * Copyright 2023 Onestein - Anjeel Haria * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ odoo.define("web_timeline.TimelineView", function (require) { @@ -23,7 +24,7 @@ odoo.define("web_timeline.TimelineView", function (require) { var TimelineView = AbstractView.extend({ display_name: _lt("Timeline"), - icon: "fa-tasks", + icon: "fa fa-tasks", jsLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js"], cssLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.css"], config: _.extend({}, AbstractView.prototype.config, {