From 305fbc0e1042736c330974d14b11a3c1fed2d201 Mon Sep 17 00:00:00 2001 From: "Michael (mcm)" Date: Tue, 13 Sep 2022 13:49:27 +0000 Subject: [PATCH] [IMP] web, mail: change layout of form view This commit changes the layout of the form view to move the control panel next to the chatter instead of being on top. The chatter now takes all the place on the right side of the view. task id: 2822553 X-original-commit: 7f36dd05b3238c3d27aab6e5af12233df27067e4 Part-of: odoo/odoo#101118 --- addons/mail/__manifest__.py | 1 + .../static/src/views/form/form_compiler.js | 114 +++++++++++++----- .../static/src/views/form/form_controller.js | 79 ++++++++++++ .../static/src/views/form/form_controller.xml | 15 +++ .../static/src/views/form/form_renderer.js | 7 ++ .../widgets/form_renderer/form_renderer.scss | 1 - .../src/views/form/form_controller.scss | 8 ++ .../static/src/views/form/form_controller.xml | 81 ++++++++----- .../static/src/views/form/form_renderer.js | 4 + addons/web/static/src/views/view_compiler.js | 3 +- .../settings_form_renderer.js | 10 +- .../src/webclient/webclient_layout.scss | 4 +- 12 files changed, 256 insertions(+), 71 deletions(-) create mode 100644 addons/mail/static/src/views/form/form_controller.js create mode 100644 addons/mail/static/src/views/form/form_controller.xml diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index 2bbb0b657a5..ce912d8d49d 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -193,6 +193,7 @@ For more specific needs, you may also assign custom-defined actions 'mail/static/src/components/*/*.js', 'mail/static/src/components/*/*.scss', 'mail/static/src/components/*/*.xml', + 'mail/static/src/views/*/*.xml', ('include', 'mail.assets_messaging'), 'mail/static/src/services/*.js', 'mail/static/src/views/**/*.js', diff --git a/addons/mail/static/src/views/form/form_compiler.js b/addons/mail/static/src/views/form/form_compiler.js index 74feeed4915..d6503c8741d 100644 --- a/addons/mail/static/src/views/form/form_compiler.js +++ b/addons/mail/static/src/views/form/form_compiler.js @@ -5,9 +5,9 @@ import { registry } from "@web/core/registry"; import { SIZES } from "@web/core/ui/ui_service"; import { patch } from "@web/core/utils/patch"; import { append, createElement, setAttributes } from "@web/core/utils/xml"; +import { ViewCompiler, getModifier } from "@web/views/view_compiler"; import { FormCompiler } from "@web/views/form/form_compiler"; - function compileChatter(node, params) { let hasActivities = false; let hasFollowers = false; @@ -44,9 +44,9 @@ function compileChatter(node, params) { "hasParentReloadOnFollowersUpdate": hasParentReloadOnFollowersUpdate, "hasParentReloadOnMessagePosted": hasParentReloadOnMessagePosted, "isAttachmentBoxVisibleInitially": isAttachmentBoxVisibleInitially, - "threadId": "props.record.resId or undefined", - "threadModel": "props.record.resModel", - "webRecord": "props.record", + "threadId": params.threadId, + "threadModel": params.threadModel, + "webRecord": params.webRecord, }); const chatterContainerHookXml = createElement("div"); chatterContainerHookXml.classList.add("o_FormRenderer_chatterContainer"); @@ -59,27 +59,94 @@ function compileAttachmentPreview(node, params) { webClientViewAttachmentViewContainerHookXml.classList.add('o_attachment_preview'); const webClientViewAttachmentViewContainerXml = createElement("WebClientViewAttachmentViewContainer"); setAttributes(webClientViewAttachmentViewContainerXml, { - "threadId": "props.record.resId or undefined", - "threadModel": "props.record.resModel", + "threadId": params.threadId, + "threadModel": params.threadModel, }); append(webClientViewAttachmentViewContainerHookXml, webClientViewAttachmentViewContainerXml); return webClientViewAttachmentViewContainerHookXml; } +export class MailFormCompiler extends ViewCompiler { + setup() { + this.compilers.push({ selector: "t", fn: this.compileT }); + this.compilers.push({ selector: "div.oe_chatter", fn: this.compileChatter }); + this.compilers.push({ + selector: "div.o_attachment_preview", + fn: this.compileAttachmentPreview, + }); + } + + compile(node, params) { + const res = super.compile(node, params).children[0]; + const chatterContainerHookXml = res.querySelector(".o_FormRenderer_chatterContainer"); + if (chatterContainerHookXml) { + setAttributes(chatterContainerHookXml, { + "t-if": `!hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`, + "t-attf-class": `{{ uiService.size >= ${SIZES.XXL} ? "o-aside" : "" }}`, + }); + } + const attachmentViewHookXml = res.querySelector(".o_attachment_preview"); + if (attachmentViewHookXml) { + setAttributes(attachmentViewHookXml, { + "t-if": `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`, + }); + } + return res; + } + + compileT(node, params) { + const compiledRoot = createElement("t"); + for (const child of node.childNodes) { + const invisible = getModifier(child, "invisible"); + let compiledChild = this.compileNode(child, params, false); + compiledChild = this.applyInvisible(invisible, compiledChild, { + ...params, + recordExpr: "model.root", + }); + append(compiledRoot, compiledChild); + } + return compiledRoot; + } + + compileChatter(node) { + return compileChatter(node, { + threadId: "model.root.resId or undefined", + threadModel: "model.root.resModel", + webRecord: "model.root", + }); + } + + compileAttachmentPreview(node) { + return compileAttachmentPreview(node, { + threadId: "model.root.resId or undefined", + threadModel: "model.root.resModel", + }); + } +} + registry.category("form_compilers").add("chatter_compiler", { selector: "div.oe_chatter", - fn: compileChatter, + fn: (node) => + compileChatter(node, { + threadId: "props.record.resId or undefined", + threadModel: "props.record.resModel", + webRecord: "props.record", + }), }); registry.category("form_compilers").add("attachment_preview_compiler", { selector: "div.o_attachment_preview", - fn: compileAttachmentPreview, + fn: (node) => + compileAttachmentPreview(node, { + threadId: "props.record.resId or undefined", + threadModel: "props.record.resModel", + }), }); patch(FormCompiler.prototype, 'mail', { - compile() { + compile(node, params) { // TODO no chatter if in dialog? - const res = this._super(...arguments); + const res = this._super(node, params); const chatterContainerHookXml = res.querySelector('.o_FormRenderer_chatterContainer'); if (!chatterContainerHookXml) { return res; // no chatter, keep the result as it is @@ -97,17 +164,12 @@ patch(FormCompiler.prototype, 'mail', { if (!parentXml) { return res; // miss-config: a sheet-bg is required for the rest } - const webClientViewAttachmentViewHookXml = res.querySelector('.o_attachment_preview'); - // TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form) - if (webClientViewAttachmentViewHookXml) { + if (params.hasAttachmentViewer) { // in sheet bg (attachment viewer present) - setAttributes(webClientViewAttachmentViewHookXml, { - 't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`, - }); - const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true); + const sheetBgChatterContainerHookXml = chatterContainerHookXml; sheetBgChatterContainerHookXml.classList.add('o-isInFormSheetBg'); setAttributes(sheetBgChatterContainerHookXml, { - 't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`, + 't-if': `uiService.size < ${SIZES.XXL} or hasAttachmentViewer()`, }); append(formSheetBgXml, sheetBgChatterContainerHookXml); const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector('ChatterContainer'); @@ -116,29 +178,19 @@ patch(FormCompiler.prototype, 'mail', { "hasExternalBorder": "true", "hasMessageListScrollAdjust": "false", }); - } - // after sheet bg (standard position, either aside or below) - if (webClientViewAttachmentViewHookXml) { - setAttributes(chatterContainerHookXml, { - 't-if': `!(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`, - 't-attf-class': `{{ uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`, - }); - setAttributes(chatterContainerXml, { - "isInFormSheetBg": "hasAttachmentViewer()", - "hasExternalBorder": `!(uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}))`, - "hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`, - }); } else { + // after sheet bg (standard position, either aside or below) setAttributes(chatterContainerXml, { "isInFormSheetBg": "false", "hasExternalBorder": `uiService.size < ${SIZES.XXL}`, "hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL}`, }); setAttributes(chatterContainerHookXml, { + 't-if': `uiService.size < ${SIZES.XXL}`, 't-attf-class': `{{ uiService.size >= ${SIZES.XXL} ? "o-aside" : "" }}`, }); + append(parentXml, chatterContainerHookXml); } - append(parentXml, chatterContainerHookXml); return res; }, }); diff --git a/addons/mail/static/src/views/form/form_controller.js b/addons/mail/static/src/views/form/form_controller.js new file mode 100644 index 00000000000..0920be0f0af --- /dev/null +++ b/addons/mail/static/src/views/form/form_controller.js @@ -0,0 +1,79 @@ +/** @odoo-module */ + +import { useModels } from "@mail/component_hooks/use_models"; +import { ChatterContainer } from "@mail/components/chatter_container/chatter_container"; +import { WebClientViewAttachmentViewContainer } from "@mail/components/web_client_view_attachment_view_container/web_client_view_attachment_view_container"; + +import { browser } from "@web/core/browser/browser"; +import { useService } from "@web/core/utils/hooks"; +import { patch } from "@web/core/utils/patch"; +import { useDebounced } from "@web/core/utils/timing"; +import { FormController } from "@web/views/form/form_controller"; +import { useViewCompiler } from "@web/views/view_compiler"; +import { evalDomain } from "@web/views/utils"; + +import { MailFormCompiler } from "./form_compiler"; + +const { onMounted, onWillUnmount } = owl; + +patch(FormController.prototype, "mail", { + setup() { + this._super(); + this.uiService = useService("ui"); + this.hasAttachmentViewerInArch = false; + + if (this.env.services.messaging) { + useModels(); + } + + const { archInfo } = this.props; + const { arch, xmlDoc } = archInfo; + + const template = document.createElement("t"); + const xmlDocAttachmentPreview = xmlDoc.querySelector("div.o_attachment_preview"); + if (xmlDocAttachmentPreview && xmlDocAttachmentPreview.parentNode.nodeName === "form") { + // TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form) + template.appendChild(xmlDocAttachmentPreview); + this.hasAttachmentViewerInArch = true; + } + + const xmlDocChatter = xmlDoc.querySelector("div.oe_chatter"); + if (xmlDocChatter && xmlDocChatter.parentNode.nodeName === "form") { + template.appendChild(xmlDocChatter.cloneNode(true)); + } + + const mailTemplates = useViewCompiler(MailFormCompiler, arch, { Mail: template }, {}); + this.mailTemplate = mailTemplates.Mail; + + this.onResize = useDebounced(this.render, 200); + onMounted(() => browser.addEventListener("resize", this.onResize)); + onWillUnmount(() => browser.removeEventListener("resize", this.onResize)); + }, + /** + * @returns {Messaging|undefined} + */ + getMessaging() { + return this.env.services.messaging && this.env.services.messaging.modelManager.messaging; + }, + /** + * @returns {boolean} + */ + hasAttachmentViewer() { + if (!this.getMessaging() || !this.model.root.resId) { + return false; + } + const thread = this.getMessaging().models['Thread'].insert({ + id: this.model.root.resId, + model: this.model.root.resModel, + }); + return thread.attachmentsInWebClientView.length > 0; + }, + evalDomainFromRecord(record, expr) { + return evalDomain(expr, record.evalContext); + }, +}); + +Object.assign(FormController.components, { + ChatterContainer, + WebClientViewAttachmentViewContainer, +}); diff --git a/addons/mail/static/src/views/form/form_controller.xml b/addons/mail/static/src/views/form/form_controller.xml new file mode 100644 index 00000000000..b15f4d7e4fd --- /dev/null +++ b/addons/mail/static/src/views/form/form_controller.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + hasAttachmentViewerInArch + + + + diff --git a/addons/mail/static/src/views/form/form_renderer.js b/addons/mail/static/src/views/form/form_renderer.js index 40237e82aa6..82d84e74721 100644 --- a/addons/mail/static/src/views/form/form_renderer.js +++ b/addons/mail/static/src/views/form/form_renderer.js @@ -15,6 +15,13 @@ patch(FormRenderer.prototype, 'mail', { } }, + get compileParams() { + return { + ...this._super(), + hasAttachmentViewer: this.props.hasAttachmentViewer, + }; + }, + //-------------------------------------------------------------------------- // Mail Methods //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer.scss b/addons/mail/static/src/widgets/form_renderer/form_renderer.scss index 7ff19adfab6..aef5bcd2c61 100644 --- a/addons/mail/static/src/widgets/form_renderer/form_renderer.scss +++ b/addons/mail/static/src/widgets/form_renderer/form_renderer.scss @@ -34,7 +34,6 @@ .o_FormRenderer_chatterContainer { display: flex; - flex: 1 1 auto; margin: 0 auto; max-width: $o-form-view-sheet-max-width; padding: map-get($spacers, 3) map-get($spacers, 3) map-get($spacers, 5); diff --git a/addons/web/static/src/views/form/form_controller.scss b/addons/web/static/src/views/form/form_controller.scss index d075bb3e586..6a44a81253e 100644 --- a/addons/web/static/src/views/form/form_controller.scss +++ b/addons/web/static/src/views/form/form_controller.scss @@ -224,6 +224,13 @@ } } + .o_form_view_container { + display: flex; + flex-flow: column nowrap; + height: 100%; + flex: 1 1 auto; + } + // No sheet &.o_form_nosheet, .o_form_nosheet { @include o-webclient-padding($top: $o-sheet-vpadding, $bottom: $o-sheet-vpadding); @@ -1150,6 +1157,7 @@ } .o_form_view.o_xxl_form_view { + flex-flow: row nowrap !important; .o_form_sheet_bg { flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety diff --git a/addons/web/static/src/views/form/form_controller.xml b/addons/web/static/src/views/form/form_controller.xml index 20a3e726a42..89541836651 100644 --- a/addons/web/static/src/views/form/form_controller.xml +++ b/addons/web/static/src/views/form/form_controller.xml @@ -3,43 +3,58 @@
- - - - +
+ + + + + + + + - - + + + + - - - - + + + + - - - - + + + + - - - + + +
diff --git a/addons/web/static/src/views/form/form_renderer.js b/addons/web/static/src/views/form/form_renderer.js index 9034eac12dc..8725b330733 100644 --- a/addons/web/static/src/views/form/form_renderer.js +++ b/addons/web/static/src/views/form/form_renderer.js @@ -65,6 +65,10 @@ export class FormRenderer extends Component { evalDomainFromRecord(record, expr) { return evalDomain(expr, record.evalContext); } + + get compileParams() { + return {}; + } } FormRenderer.template = xml``; diff --git a/addons/web/static/src/views/view_compiler.js b/addons/web/static/src/views/view_compiler.js index c34815ac0d8..0cc5e426e39 100644 --- a/addons/web/static/src/views/view_compiler.js +++ b/addons/web/static/src/views/view_compiler.js @@ -254,7 +254,8 @@ export class ViewCompiler { return; } if (!params.enableInvisible) { - let isVisileExpr = `!evalDomainFromRecord(props.record,${JSON.stringify(invisible)})`; + const recordExpr = params.recordExpr || "props.record"; + let isVisileExpr = `!evalDomainFromRecord(${recordExpr},${JSON.stringify(invisible)})`; if (compiled.hasAttribute("t-if")) { const formerTif = compiled.getAttribute("t-if"); isVisileExpr = `( ${formerTif} ) and ${isVisileExpr}`; diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js b/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js index 30552a9d0e6..9a7c4f19ed5 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js @@ -20,13 +20,17 @@ export class SettingsFormRenderer extends FormRenderer { if (!labels[this.props.archInfo.arch]) { labels[this.props.archInfo.arch] = []; } - this.compileParams = { + super.setup(); + this.searchState = useState(this.env.searchState); + } + + get compileParams() { + return { + ...super.compileParams, labels: labels[this.props.archInfo.arch], getFieldExpr: this.getFieldExpr, record: this.props.record, }; - super.setup(); - this.searchState = useState(this.env.searchState); } getFieldExpr(fieldName, fieldWidget) { diff --git a/addons/web/static/src/webclient/webclient_layout.scss b/addons/web/static/src/webclient/webclient_layout.scss index 4d35d4f298e..c225285ff2f 100644 --- a/addons/web/static/src/webclient/webclient_layout.scss +++ b/addons/web/static/src/webclient/webclient_layout.scss @@ -34,7 +34,7 @@ html { > .o_control_panel { flex: 0 0 auto; } - > .o_content { + .o_content { flex: 1 1 auto; position: relative; // Allow to redistribute the 100% height to its child overflow: auto; @@ -47,7 +47,7 @@ html { &:not(.o_action_delegate_scroll) { overflow: auto; - > .o_content { + .o_content { overflow: initial; } }