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; } }