diff --git a/addons/account/static/src/scss/account_dashboard.scss b/addons/account/static/src/scss/account_dashboard.scss index 5050be09648..63fa1d72e35 100644 --- a/addons/account/static/src/scss/account_dashboard.scss +++ b/addons/account/static/src/scss/account_dashboard.scss @@ -1,4 +1,4 @@ -.o_kanban_view.o_kanban_dashboard.o_account_kanban { +.o_legacy_kanban_view.o_kanban_dashboard.o_account_kanban { &.o_kanban_ungrouped .o_account_dashboard_header { margin: (0 - $o-kanban-record-margin) ($o-kanban-record-margin - $o-horizontal-padding) $o-kanban-record-margin; diff --git a/addons/account/static/src/scss/account_journal_dashboard.scss b/addons/account/static/src/scss/account_journal_dashboard.scss index 6c36e61a0a3..42003420008 100644 --- a/addons/account/static/src/scss/account_journal_dashboard.scss +++ b/addons/account/static/src/scss/account_journal_dashboard.scss @@ -1,4 +1,4 @@ -.o_kanban_view.o_kanban_dashboard.o_account_kanban { +.o_kanban_renderer.o_kanban_dashboard.o_account_kanban { .o_kanban_record { @include media-breakpoint-up(sm) { diff --git a/addons/board/static/src/legacy/img/layout_1-1-1.png b/addons/board/static/img/layout_1-1-1.png similarity index 100% rename from addons/board/static/src/legacy/img/layout_1-1-1.png rename to addons/board/static/img/layout_1-1-1.png diff --git a/addons/board/static/src/legacy/img/layout_1-1.png b/addons/board/static/img/layout_1-1.png similarity index 100% rename from addons/board/static/src/legacy/img/layout_1-1.png rename to addons/board/static/img/layout_1-1.png diff --git a/addons/board/static/src/legacy/img/layout_1-2.png b/addons/board/static/img/layout_1-2.png similarity index 100% rename from addons/board/static/src/legacy/img/layout_1-2.png rename to addons/board/static/img/layout_1-2.png diff --git a/addons/board/static/src/legacy/img/layout_1.png b/addons/board/static/img/layout_1.png similarity index 100% rename from addons/board/static/src/legacy/img/layout_1.png rename to addons/board/static/img/layout_1.png diff --git a/addons/board/static/src/legacy/img/layout_2-1.png b/addons/board/static/img/layout_2-1.png similarity index 100% rename from addons/board/static/src/legacy/img/layout_2-1.png rename to addons/board/static/img/layout_2-1.png diff --git a/addons/board/static/src/legacy/img/view_todo_arrow.png b/addons/board/static/img/view_todo_arrow.png similarity index 100% rename from addons/board/static/src/legacy/img/view_todo_arrow.png rename to addons/board/static/img/view_todo_arrow.png diff --git a/addons/board/static/src/add_to_board/add_to_board.js b/addons/board/static/src/add_to_board/add_to_board.js index f7a777e5f0d..2d4cf715199 100644 --- a/addons/board/static/src/add_to_board/add_to_board.js +++ b/addons/board/static/src/add_to_board/add_to_board.js @@ -36,11 +36,18 @@ export class AddToBoard extends Component { async addToBoard() { const { domain, globalContext } = this.env.searchModel; - const { context } = this.env.searchModel.getIrFilterValues(); + const { + comparison, + context, + groupBys, + orderBy, + } = this.env.searchModel.getPreFavoriteValues(); const contextToSave = { ...globalContext, ...context, - orderedBy: this.env.searchModel.orderBy, + comparison, + orderedBy: orderBy, + group_by: groupBys, dashboard_merge_domains_contexts: false, }; diff --git a/addons/board/static/src/legacy/js/add_to_board_menu.js b/addons/board/static/src/add_to_board/legacy_add_to_board.js similarity index 75% rename from addons/board/static/src/legacy/js/add_to_board_menu.js rename to addons/board/static/src/add_to_board/legacy_add_to_board.js index 507a2df8f37..01f4dee4abd 100644 --- a/addons/board/static/src/legacy/js/add_to_board_menu.js +++ b/addons/board/static/src/add_to_board/legacy_add_to_board.js @@ -1,11 +1,11 @@ -odoo.define('board.AddToBoardMenu', function (require) { +odoo.define("board.AddToBoardMenu", function (require) { "use strict"; - const Context = require('web.Context'); - const Domain = require('web.Domain'); - const { Dropdown } = require('@web/core/dropdown/dropdown'); - const FavoriteMenu = require('web.FavoriteMenu'); - const { sprintf } = require('web.utils'); + const Context = require("web.Context"); + const Domain = require("web.Domain"); + const { Dropdown } = require("@web/core/dropdown/dropdown"); + const FavoriteMenu = require("web.FavoriteMenu"); + const { sprintf } = require("web.utils"); const { useAutofocus } = require("@web/core/utils/hooks"); const { Component, useState } = owl; @@ -44,20 +44,23 @@ odoo.define('board.AddToBoardMenu', function (require) { * @private */ async addToBoard() { - const searchQuery = this.env.searchModel.get('query'); + const searchQuery = this.env.searchModel.get("query"); const context = new Context(this.env.action.context); context.add(searchQuery.context); context.add({ group_by: searchQuery.groupBy, orderedBy: searchQuery.orderedBy, }); - if (searchQuery.timeRanges && searchQuery.timeRanges.hasOwnProperty('fieldName')) { + if ( + searchQuery.timeRanges && + Object.prototype.hasOwnProperty.call(searchQuery.timeRanges, "fieldName") + ) { context.add({ comparison: searchQuery.timeRanges, }); } let controllerQueryParams; - this.env.searchModel.trigger('get-controller-query-params', params => { + this.env.searchModel.trigger("get-controller-query-params", (params) => { controllerQueryParams = params || {}; }); controllerQueryParams.context = controllerQueryParams.context || {}; @@ -66,11 +69,16 @@ odoo.define('board.AddToBoardMenu', function (require) { context.add(Object.assign(controllerQueryParams, queryContext)); const domainArray = new Domain(this.env.action.domain || []); - const domain = Domain.prototype.normalizeArray(domainArray.toArray().concat(searchQuery.domain)); + const domain = Domain.prototype.normalizeArray( + domainArray.toArray().concat(searchQuery.domain) + ); const evalutatedContext = context.eval(); for (const key in evalutatedContext) { - if (evalutatedContext.hasOwnProperty(key) && /^search_default_/.test(key)) { + if ( + Object.prototype.hasOwnProperty.call(evalutatedContext, key) && + /^search_default_/.test(key) + ) { delete evalutatedContext[key]; } } @@ -82,7 +90,7 @@ odoo.define('board.AddToBoardMenu', function (require) { }); const result = await this.env.services.rpc({ - route: '/board/add_to_dashboard', + route: "/board/add_to_dashboard", params: { action_id: this.env.action.id || false, context_to_save: evalutatedContext, @@ -94,13 +102,15 @@ odoo.define('board.AddToBoardMenu', function (require) { if (result) { this.env.services.notification.notify({ title: sprintf(this.env._t("'%s' added to dashboard"), this.state.name), - message: this.env._t("Please refresh your browser for the changes to take effect."), - type: 'warning', + message: this.env._t( + "Please refresh your browser for the changes to take effect." + ), + type: "warning", }); } else { this.env.services.notification.notify({ message: this.env._t("Could not add filter to dashboard"), - type: 'danger', + type: "danger", }); } } @@ -115,11 +125,11 @@ odoo.define('board.AddToBoardMenu', function (require) { */ onInputKeydown(ev) { switch (ev.key) { - case 'Enter': + case "Enter": ev.preventDefault(); this.addToBoard(); break; - case 'Escape': + case "Escape": // Gives the focus back to the component. ev.preventDefault(); ev.target.blur(); @@ -136,15 +146,15 @@ odoo.define('board.AddToBoardMenu', function (require) { * @returns {boolean} */ static shouldBeDisplayed(env) { - return env.action.type === 'ir.actions.act_window'; + return env.action.type === "ir.actions.act_window"; } } AddToBoardMenu.props = {}; - AddToBoardMenu.template = 'board.AddToBoard'; + AddToBoardMenu.template = "board.AddToBoard"; AddToBoardMenu.components = { Dropdown }; - FavoriteMenu.registry.add('add-to-board-menu', AddToBoardMenu, 10); + FavoriteMenu.registry.add("add-to-board-menu", AddToBoardMenu, 10); return AddToBoardMenu; }); diff --git a/addons/board/static/src/board_action.js b/addons/board/static/src/board_action.js new file mode 100644 index 00000000000..edf4663d14b --- /dev/null +++ b/addons/board/static/src/board_action.js @@ -0,0 +1,68 @@ +/** @odoo-module **/ + +import { useService } from "@web/core/utils/hooks"; +import { View } from "@web/views/view"; +import { makeContext } from "@web/core/context"; + +const { Component, onWillStart } = owl; + +export class BoardAction extends Component { + setup() { + const rpc = useService("rpc"); + const userService = useService("user"); + this.actionService = useService("action"); + const action = this.props.action; + this.formViewId = false; + this.isValid = true; + onWillStart(async () => { + let result = BoardAction.cache[action.actionId]; + if (!result) { + result = await rpc("/web/action/load", { action_id: action.actionId }); + BoardAction.cache[action.actionId] = result; + } + if (!result) { + // action does not exist + this.isValid = false; + return; + } + const viewMode = action.viewMode || result.views[0][1]; + const formView = result.views.find((v) => v[1] === "form"); + if (formView) { + this.formViewId = formView[0]; + } + this.viewProps = { + resModel: result.res_model, + type: viewMode, + display: { controlPanel: false }, + selectRecord: (resId) => this.selectRecord(result.res_model, resId), + }; + if (action.context) { + this.viewProps.context = makeContext([ + action.context, + { lang: userService.context.lang }, + ]); + if ("group_by" in this.viewProps.context) { + const groupBy = this.viewProps.context.group_by; + this.viewProps.groupBy = typeof groupBy === "string" ? [groupBy] : groupBy; + } + } + if (action.domain) { + this.viewProps.domain = action.domain; + } + if (viewMode === "list") { + this.viewProps.hasSelectors = false; + } + }); + } + selectRecord(resModel, resId) { + this.actionService.doAction({ + type: "ir.actions.act_window", + res_model: resModel, + views: [[this.formViewId, "form"]], + res_id: resId, + }); + } +} +BoardAction.template = "board.BoardAction"; +BoardAction.components = { View }; +BoardAction.cache = {}; diff --git a/addons/board/static/src/board_action.xml b/addons/board/static/src/board_action.xml new file mode 100644 index 00000000000..9f6ba28378c --- /dev/null +++ b/addons/board/static/src/board_action.xml @@ -0,0 +1,12 @@ + + + + + + + + +
Invalid action
+
+
+
diff --git a/addons/board/static/src/board_controller.js b/addons/board/static/src/board_controller.js new file mode 100644 index 00000000000..367038267e1 --- /dev/null +++ b/addons/board/static/src/board_controller.js @@ -0,0 +1,118 @@ +/** @odoo-module **/ + +import { browser } from "@web/core/browser/browser"; +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { useService } from "@web/core/utils/hooks"; +import { renderToString } from "@web/core/utils/render"; +import { useSortable } from "@web/core/utils/sortable"; +import { standardViewProps } from "@web/views/standard_view_props"; +import { BoardAction } from "./board_action"; + +const { Component, useState, useRef } = owl; + +export class BoardController extends Component { + setup() { + this.board = useState(this.props.board); + this.rpc = useService("rpc"); + this.dialogService = useService("dialog"); + const mainRef = useRef("main"); + useSortable({ + ref: mainRef, + elements: ".o-dashboard-action", + handle: ".o-dashboard-action-header", + cursor: "move", + groups: ".o-dashboard-column", + connectGroups: true, + onDrop: ({ element, previous, parent }) => { + const fromColIdx = parseInt(element.parentElement.dataset.idx, 10); + const fromActionIdx = parseInt(element.dataset.idx, 10); + const toColIdx = parseInt(parent.dataset.idx, 10); + const toActionIdx = previous ? parseInt(previous.dataset.idx, 10) + 1 : 0; + if (fromColIdx !== toColIdx) { + // to reduce visual flickering + element.classList.add("d-none"); + } + this.moveAction(fromColIdx, fromActionIdx, toColIdx, toActionIdx); + }, + }); + } + + moveAction(fromColIdx, fromActionIdx, toColIdx, toActionIdx) { + const action = this.board.columns[fromColIdx].actions[fromActionIdx]; + if (fromColIdx !== toColIdx) { + // action moving from a column to another + this.board.columns[fromColIdx].actions.splice(fromActionIdx, 1); + this.board.columns[toColIdx].actions.splice(toActionIdx, 0, action); + } else { + // move inside a column + if (fromActionIdx === toActionIdx) { + return; + } + const actions = this.board.columns[fromColIdx].actions; + if (fromActionIdx < toActionIdx) { + actions.splice(toActionIdx + 1, 0, action); + actions.splice(fromActionIdx, 1); + } else { + actions.splice(fromActionIdx, 1); + actions.splice(toActionIdx, 0, action); + } + } + this.saveBoard(); + } + + selectLayout(layout) { + const currentColNbr = this.board.colNumber; + const nextColNbr = layout.split("-").length; + if (nextColNbr < currentColNbr) { + // need to move all actions in last cols in the last visible col + const cols = this.board.columns; + const lastVisibleCol = cols[nextColNbr - 1]; + for (let i = nextColNbr; i < currentColNbr; i++) { + lastVisibleCol.actions.push(...cols[i].actions); + cols[i].actions = []; + } + } + this.board.layout = layout; + this.board.colNumber = nextColNbr; + this.saveBoard(); + if (document.querySelector("canvas")) { + // horrible hack to force charts to be recreated so they pick up the + // proper size. also, no idea why raf is needed :( + browser.requestAnimationFrame(() => this.render(true)); + } + } + + closeAction(column, action) { + this.dialogService.add(ConfirmationDialog, { + body: this.env._t("Are you sure that you want to remove this item?"), + confirm: () => { + const index = column.actions.indexOf(action); + column.actions.splice(index, 1); + this.saveBoard(); + }, + cancel: () => {}, + }); + } + + toggleAction(action) { + action.isFolded = !action.isFolded; + this.saveBoard(); + } + + saveBoard() { + this.rpc("/web/view/edit_custom", { + custom_id: this.board.customViewId, + arch: renderToString("board.arch", this.board), + }); + this.env.bus.trigger("CLEAR-CACHES"); + } +} + +BoardController.template = "board.BoardView"; +BoardController.components = { BoardAction, Dropdown, DropdownItem }; +BoardController.props = { + ...standardViewProps, + board: Object, +}; diff --git a/addons/board/static/src/board_controller.scss b/addons/board/static/src/board_controller.scss new file mode 100644 index 00000000000..705821800e2 --- /dev/null +++ b/addons/board/static/src/board_controller.scss @@ -0,0 +1,46 @@ +.o_dashboard { + height: 100%; + overflow: auto; +} + +.o-dashboard-layout { + display: grid; +} +.o-dashboard-layout-1 { + grid-template-columns: 1fr; +} +.o-dashboard-layout-1-1 { + grid-template-columns: 1fr 1fr; +} +.o-dashboard-layout-1-1-1 { + grid-template-columns: 1fr 1fr 1fr; +} +.o-dashboard-layout-2-1 { + grid-template-columns: 2fr 1fr; +} +.o-dashboard-layout-1-2 { + grid-template-columns: 1fr 2fr; +} + +.o-dashboard-column { + overflow-x: scroll; +} + +.o-dashboard-action { + background-color: white; + align-self: start; + border: 1px solid; + border-color: #e5e5e5 #dbdbdb #d2d2d2; + + > .o_view_controller { + padding: 0 12px 12px 12px; + } + + .o_column_quick_create { + display: none; + } +} + +.o-dashboard-action > h3 > span > i { + font-size: 12px; +} diff --git a/addons/board/static/src/board_controller.xml b/addons/board/static/src/board_controller.xml new file mode 100644 index 00000000000..6bcedfd3f85 --- /dev/null +++ b/addons/board/static/src/board_controller.xml @@ -0,0 +1,88 @@ + + + + +
+ + + + + + +
+
+ + + +
+ + + + Change Layout + + + + + + + + +
+
+ +
+ +
+

+ + + + +

+ +
+
+
+
+
+
+ + +
+
+

+ Your personal dashboard is empty +

+

+ To add your first report into this dashboard, go to any + menu, switch to list or graph view, and click "Add to + Dashboard" in the extended search options. +

+

+ You can filter and group data before inserting into the + dashboard using the search options. +

+
+
+
+ + +
+ + + + + + + +
+
+ + +
diff --git a/addons/board/static/src/board_view.js b/addons/board/static/src/board_view.js new file mode 100644 index 00000000000..8cddfc62631 --- /dev/null +++ b/addons/board/static/src/board_view.js @@ -0,0 +1,77 @@ +/** @odoo-module **/ + +import { _lt } from "@web/core/l10n/translation"; +import { registry } from "@web/core/registry"; +import { BoardController } from "./board_controller"; +import { XMLParser } from "@web/core/utils/xml"; +import { Domain } from "@web/core/domain"; + +export class BoardArchParser extends XMLParser { + parse(arch, customViewId) { + let nextId = 1; + const archInfo = { + title: null, + layout: null, + colNumber: 0, + isEmpty: true, + columns: [{ actions: [] }, { actions: [] }, { actions: [] }], + customViewId, + }; + let currentIndex = -1; + + this.visitXML(arch, (node) => { + switch (node.tagName) { + case "form": + archInfo.title = node.getAttribute("string"); + break; + case "board": + archInfo.layout = node.getAttribute("style"); + archInfo.colNumber = archInfo.layout.split("-").length; + break; + case "column": + currentIndex++; + break; + case "action": { + archInfo.isEmpty = false; + const isFolded = Boolean( + node.hasAttribute("fold") ? parseInt(node.getAttribute("fold"), 10) : 0 + ); + let action = { + id: nextId++, + title: node.getAttribute("string"), + actionId: parseInt(node.getAttribute("name"), 10), + viewMode: node.getAttribute("view_mode"), + context: node.getAttribute("context"), + isFolded, + }; + if (node.hasAttribute("domain")) { + action.domain = new Domain(node.getAttribute("domain")).toList(); + // so it can be serialized when reexporting board xml + action.domain.toString = () => node.getAttribute("domain"); + } + archInfo.columns[currentIndex].actions.push(action); + break; + } + } + }); + return archInfo; + } +} + +export const boardView = { + type: "form", + display_name: _lt("Board"), + Controller: BoardController, + + props: (genericProps, view) => { + const { arch, info } = genericProps; + const board = new BoardArchParser().parse(arch, info.customViewId); + return { + ...genericProps, + className: "o_dashboard", + board, + }; + }, +}; + +registry.category("views").add("board", boardView); diff --git a/addons/board/static/src/legacy/js/board_view.js b/addons/board/static/src/legacy/js/board_view.js deleted file mode 100644 index f414fc03ad2..00000000000 --- a/addons/board/static/src/legacy/js/board_view.js +++ /dev/null @@ -1,482 +0,0 @@ -odoo.define('board.BoardView', function (require) { -"use strict"; - -var Context = require('web.Context'); -var config = require('web.config'); -var core = require('web.core'); -var dataManager = require('web.data_manager'); -var Dialog = require('web.Dialog'); -var Domain = require('web.Domain'); -var FormController = require('web.FormController'); -var FormRenderer = require('web.FormRenderer'); -var FormView = require('web.FormView'); -var pyUtils = require('web.py_utils'); -var session = require('web.session'); -var viewRegistry = require('web.view_registry'); -const { loadLegacyViews } = require("@web/legacy/legacy_views"); - -var _t = core._t; -var _lt = core._lt; -var QWeb = core.qweb; - -var BoardController = FormController.extend({ - custom_events: _.extend({}, FormController.prototype.custom_events, { - change_layout: '_onChangeLayout', - enable_dashboard: '_onEnableDashboard', - save_dashboard: '_saveDashboard', - switch_view: '_onSwitchView', - }), - - /** - * @override - */ - init: function (parent, model, renderer, params) { - this._super.apply(this, arguments); - this.customViewID = params.customViewID; - }, - - async willStart() { - const _super = this._super.bind(this, ...arguments); - await loadLegacyViews({ rpc: this._rpc.bind(this) }); - return _super(); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - getTitle: function () { - return _t("My Dashboard"); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Actually save a dashboard - * - * @returns {Promise} - */ - _saveDashboard: function () { - var board = this.renderer.getBoard(); - var arch = QWeb.render('DashBoard.xml', _.extend({}, board)); - return this._rpc({ - route: '/web/view/edit_custom', - params: { - custom_id: this.customViewID, - arch: arch, - } - }).then(dataManager.invalidate.bind(dataManager)); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {OdooEvent} event - */ - _onChangeLayout: function (event) { - var self = this; - var dialog = new Dialog(this, { - title: _t("Edit Layout"), - $content: QWeb.render('DashBoard.layouts', _.clone(event.data)) - }); - dialog.opened().then(function () { - dialog.$('li').click(function () { - var layout = $(this).attr('data-layout'); - self.renderer.changeLayout(layout); - self._saveDashboard(); - dialog.close(); - }); - }); - dialog.open(); - }, - /** - * We need to intercept switch_view event coming from sub views, because we - * don't actually want to switch view in dashboard, we want to do a - * do_action (which will open the record in a different breadcrumb). - * - * @private - * @param {OdooEvent} event - */ - _onSwitchView: function (event) { - event.stopPropagation(); - this.do_action({ - type: 'ir.actions.act_window', - res_model: event.data.model, - views: [[event.data.formViewID || false, 'form']], - res_id: event.data.res_id, - }); - }, -}); - -var BoardRenderer = FormRenderer.extend({ - custom_events: _.extend({}, FormRenderer.prototype.custom_events, { - update_filters: '_onUpdateFilters', - switch_view: '_onSwitchView', - }), - events: _.extend({}, FormRenderer.prototype.events, { - 'click .oe_dashboard_column .oe_fold': '_onFoldClick', - 'click .oe_dashboard_link_change_layout': '_onChangeLayout', - 'click .oe_dashboard_column .oe_close': '_onCloseAction', - }), - - /** - * @override - */ - init: function (parent, state, params) { - this._super.apply(this, arguments); - this.noContentHelp = params.noContentHelp; - this.actionsDescr = {}; - this._boardSubcontrollers = []; // for board: controllers of subviews - this._boardFormViewIDs = {}; // for board: mapping subview controller to form view id - }, - /** - * Call `on_attach_callback` for each subview - * - * @override - */ - on_attach_callback: function () { - _.each(this._boardSubcontrollers, function (controller) { - if ('on_attach_callback' in controller) { - controller.on_attach_callback(); - } - }); - }, - /** - * Call `on_detach_callback` for each subview - * - * @override - */ - on_detach_callback: function () { - _.each(this._boardSubcontrollers, function (controller) { - if ('on_detach_callback' in controller) { - controller.on_detach_callback(); - } - }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @param {string} layout - */ - changeLayout: function (layout) { - var $dashboard = this.$('.oe_dashboard'); - var current_layout = $dashboard.attr('data-layout'); - if (current_layout !== layout) { - var clayout = current_layout.split('-').length, - nlayout = layout.split('-').length, - column_diff = clayout - nlayout; - if (column_diff > 0) { - var $last_column = $(); - $dashboard.find('.oe_dashboard_column').each(function (k, v) { - if (k >= nlayout) { - $(v).find('.oe_action').appendTo($last_column); - } else { - $last_column = $(v); - } - }); - } - $dashboard.toggleClass('oe_dashboard_layout_' + current_layout + ' oe_dashboard_layout_' + layout); - $dashboard.attr('data-layout', layout); - } - }, - /** - * Returns a representation of the current dashboard - * - * @returns {Object} - */ - getBoard: function () { - var self = this; - var board = { - form_title : this.arch.attrs.string, - style : this.$('.oe_dashboard').attr('data-layout'), - columns : [], - }; - this.$('.oe_dashboard_column').each(function () { - var actions = []; - $(this).find('.oe_action').each(function () { - var actionID = $(this).attr('data-id'); - var newAttrs = _.clone(self.actionsDescr[actionID]); - - /* prepare attributes as they should be saved */ - if (newAttrs.modifiers) { - newAttrs.modifiers = JSON.stringify(newAttrs.modifiers); - } - actions.push(newAttrs); - }); - board.columns.push(actions); - }); - return board; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {Object} params - * @param {jQueryElement} params.$node - * @param {integer} params.actionID - * @param {Object} params.context - * @param {any[]} params.domain - * @param {string} params.viewType - * @returns {Promise} - */ - _createController: function (params) { - var self = this; - return this._rpc({ - route: '/web/action/load', - params: {action_id: params.actionID} - }) - .then(function (action) { - if (!action) { - // the action does not exist anymore - return Promise.resolve(); - } - var evalContext = new Context(params.context).eval(); - if (evalContext.group_by && evalContext.group_by.length === 0) { - delete evalContext.group_by; - } - // tz and lang are saved in the custom view - // override the language to take the current one - var rawContext = new Context(action.context, evalContext, {lang: session.user_context.lang}); - var context = pyUtils.eval('context', rawContext, evalContext); - var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context); - - action.context = context; - action.domain = domain; - - // When creating a view, `action.views` is expected to be an array of dicts, while - // '/web/action/load' returns an array of arrays. - action._views = action.views; - action.views = $.map(action.views, function (view) { return {viewID: view[0], type: view[1]}}); - - var viewType = params.viewType || action._views[0][1]; - var view = _.find(action._views, function (descr) { - return descr[1] === viewType; - }) || [false, viewType]; - return self.loadViews(action.res_model, context, [view]) - .then(function (viewsInfo) { - var viewInfo = viewsInfo[viewType]; - var xml = new DOMParser().parseFromString(viewInfo.arch, "text/xml") - var key = xml.documentElement.getAttribute("js_class"); - var View = viewRegistry.get(key || viewType); - - const searchQuery = { - context: context, - domain: domain, - groupBy: typeof context.group_by === 'string' && context.group_by ? - [context.group_by] : - context.group_by || [], - orderedBy: context.orderedBy || [], - }; - - if (View.prototype.searchMenuTypes.includes('comparison')) { - searchQuery.timeRanges = context.comparison || {}; - } - - var view = new View(viewInfo, { - action: action, - hasSelectors: false, - modelName: action.res_model, - searchQuery, - withControlPanel: false, - withSearchPanel: false, - }); - return view.getController(self).then(function (controller) { - self._boardFormViewIDs[controller.handle] = _.first( - _.find(action._views, function (descr) { - return descr[1] === 'form'; - }) - ); - self._boardSubcontrollers.push(controller); - return controller.appendTo(params.$node); - }); - }); - }); - }, - /** - * @private - * @param {Object} node - * @returns {jQueryElement} - */ - _renderTagBoard: function (node) { - var self = this; - // we add the o_dashboard class to the renderer's $el. This means that - // this function has a side effect. This is ok because we assume that - // once we have a '' tag, we are in a special dashboard mode. - this.$el.addClass('o_dashboard'); - - var hasAction = _.detect(node.children, function (column) { - return _.detect(column.children,function (element){ - return element.tag === "action"? element: false; - }); - }); - if (!hasAction) { - return $(QWeb.render('DashBoard.NoContent')); - } - - // We should start with three columns available - node = $.extend(true, {}, node); - - // no idea why master works without this, but whatever - if (!('layout' in node.attrs)) { - node.attrs.layout = node.attrs.style; - } - for (var i = node.children.length; i < 3; i++) { - node.children.push({ - tag: 'column', - attrs: {}, - children: [] - }); - } - - // register actions, alongside a generated unique ID - _.each(node.children, function (column, column_index) { - _.each(column.children, function (action, action_index) { - action.attrs.id = 'action_' + column_index + '_' + action_index; - self.actionsDescr[action.attrs.id] = action.attrs; - }); - }); - - var $html = $('
').append($(QWeb.render('DashBoard', {node: node, isMobile: config.device.isMobile}))); - this._boardSubcontrollers = []; // dashboard controllers are reset on re-render - - // render each view - _.each(this.actionsDescr, function (action) { - self.defs.push(self._createController({ - $node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'), - actionID: _.str.toNumber(action.name), - context: action.context, - domain: Domain.prototype.stringToArray(action.domain, {}), - viewType: action.view_mode, - })); - }); - $html.find('.oe_dashboard_column').sortable({ - connectWith: '.oe_dashboard_column', - handle: '.oe_header', - scroll: false - }).bind('sortstop', function () { - self.trigger_up('save_dashboard'); - }); - - return $html; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onChangeLayout: function () { - var currentLayout = this.$('.oe_dashboard').attr('data-layout'); - this.trigger_up('change_layout', {currentLayout: currentLayout}); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onCloseAction: function (event) { - var self = this; - var $container = $(event.currentTarget).parents('.oe_action:first'); - Dialog.confirm(this, (_t("Are you sure you want to remove this item?")), { - confirm_callback: function () { - $container.remove(); - self.trigger_up('save_dashboard'); - }, - }); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onFoldClick: function (event) { - var $e = $(event.currentTarget); - var $action = $e.closest('.oe_action'); - var id = $action.data('id'); - var actionAttrs = this.actionsDescr[id]; - - if ($e.is('.oe_minimize')) { - actionAttrs.fold = '1'; - } else { - delete(actionAttrs.fold); - } - $e.toggleClass('oe_minimize oe_maximize'); - $action.find('.oe_content').toggle(); - this.trigger_up('save_dashboard'); - }, - /** - * Let FormController know which form view it should display based on the - * window action of the sub controller that is switching view - * - * @private - * @param {OdooEvent} event - */ - _onSwitchView: function (event) { - event.data.formViewID = this._boardFormViewIDs[event.target.handle]; - }, - /** - * Stops the propagation of 'update_filters' events triggered by the - * controllers instantiated by the dashboard to prevent them from - * interfering with the ActionManager. - * - * @private - * @param {OdooEvent} event - */ - _onUpdateFilters: function (event) { - event.stopPropagation(); - }, -}); - -var BoardView = FormView.extend({ - config: _.extend({}, FormView.prototype.config, { - Controller: BoardController, - Renderer: BoardRenderer, - }), - display_name: _lt('Board'), - - /** - * @override - */ - init: function (viewInfo) { - this._super.apply(this, arguments); - this.controllerParams.customViewID = viewInfo.custom_view_id; - }, - /** - * @override - */ - _extractParamsFromAction(action) { - action.target = "inline"; - action.flags = action.flags || {}; - return this._super.apply(this, arguments); - }, -}); - -return BoardView; - -}); - - -odoo.define('board.viewRegistry', function (require) { -"use strict"; - -var BoardView = require('board.BoardView'); - -var viewRegistry = require('web.view_registry'); - -viewRegistry.add('board', BoardView); - -}); diff --git a/addons/board/static/src/legacy/scss/dashboard.scss b/addons/board/static/src/legacy/scss/dashboard.scss deleted file mode 100644 index 56a3dd6d03d..00000000000 --- a/addons/board/static/src/legacy/scss/dashboard.scss +++ /dev/null @@ -1,183 +0,0 @@ -.o_dashboard { - - // Dashboard layout - .oe_dashboard_layout_1 .oe_dashboard_column { - &.index_0 { - width: 100%; - } - &.index_1, &.index_2 { - display: none; - } - } - .oe_dashboard_layout_1-1 .oe_dashboard_column { - width: 50%; - &.index_2 { - display: none; - } - } - .oe_dashboard_layout_1-1-1 .oe_dashboard_column { - width: 33%; - } - .oe_dashboard_layout_2-1 .oe_dashboard_column { - &.index_0 { - width: 70%; - } - &.index_1 { - width: 30%; - } - &.index_2 { - display: none; - } - } - .oe_dashboard_layout_1-2 .oe_dashboard_column { - &.index_0 { - width: 30%; - } - &.index_1 { - width: 70%; - } - &.index_2 { - display: none; - } - } - .oe_dashboard_column { - vertical-align: top; - } - - // Layout selector - .oe_dashboard_links { - text-align: right; - margin: 0 4px 6px 0; - } - - // Dashboard content - .oe_dashboard { - width: 100%; - .oe_action { - margin: 0 8px 8px 0; - background-color: white; - border: 1px solid; - border-color: #e5e5e5 #dbdbdb #d2d2d2; - margin: 0 8px 8px 0; - .oe_header { - font-size: 16px; - vertical-align: middle; - margin: 0; - padding: 12px; - &:hover { - cursor: move; - } - .oe_icon { - float: right; - cursor: pointer; - color: #b3b3b3; - &:hover { - color: #666; - text-decoration: none; - } - } - .oe_close:after { - content: "×"; - margin-left: 4px; - } - .oe_minimize:after { - content: "-"; - margin-left: 4px; - } - .oe_maximize:after { - content: "+"; - margin-left: 4px; - } - .oe_header_text { - width: auto; - visibility: hidden; - display: inline-block; - cursor: text; - } - span { - cursor: pointer; - } - } - .oe_header_empty { - padding-top: 0; - padding-bottom: 2px; - } - .oe_button_create { - margin-left: 4px; - padding: 0 4px 0 4px; - height: 16px !important; - } - .oe_content { - padding: 0 12px 12px 12px; - &.oe_folded { - display: none; - } - .o_view_nocontent { - display: none; // we don't have a create button on the dashboard, so no need to display that - } - } - - // Override border of many2manytags defined for form_views - .o_kanban_view .o_kanban_record .oe_kanban_list_many2many .o_field_many2manytags { - border: none; - } - - // Override height of graph. min-height doesn't do the trick - .o_legacy_graph_renderer canvas { - height: 300px; - } - - // Override height for calendar view to be displayed properly - .o_calendar_view { - height: 100vh; - } - - // Override height for map view to be displayed properly - .o_map_view { - .o_map_container, .o_pin_list_container { - height: calc(100vh - #{$o-navbar-height}); - } - } - - //Add padding to the old dashboard view (To remove when board is converted to OWL) - .o_dashboard_view { - padding: $o-sheet-vpadding 0; - } - } - } -} - -// Layout selector modal -.oe_dashboard_layout_selector { - ul { - white-space: nowrap; - } - li { - margin: 0; - padding: 0; - list-style-type: none; - float: left; - } - .oe_dashboard_selected_layout { - margin-left: -30px; - vertical-align: bottom; - margin-bottom: 10px; - } -} - -// Favorites menu in control panel -.o_add_to_dashboard { - display: none; // hidden by default - max-width: 250px; - width: auto; -} - -@include media-breakpoint-down(sm) { - .o_dashboard .oe_dashboard { - table-layout: fixed; - - .oe_action .oe_header .oe_header_text { - display: none; - } - } -} diff --git a/addons/board/static/src/legacy/xml/board.xml b/addons/board/static/src/legacy/xml/board.xml deleted file mode 100644 index 2a43bf20e12..00000000000 --- a/addons/board/static/src/legacy/xml/board.xml +++ /dev/null @@ -1,92 +0,0 @@ - -