[FIX] mail: avoid losing chatter record when changing container

Form view has multiple containers that are exclusive, before this commit
switching from one container to the other would lose all current chatter state.

closes odoo/odoo#102934

X-original-commit: a3d151d0c9c84c2dcffc28a342c40f38d0b28370
Related: odoo/enterprise#32644
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Sébastien Theys
2022-10-10 18:08:55 +02:00
parent 35f2de06c5
commit 7dd6124807
8 changed files with 66 additions and 23 deletions
@@ -12,6 +12,7 @@
</t>
<div class="o_Chatter_fixedPanel">
<ChatterTopbar
t-if="chatter.topbar"
className="'o_Chatter_topbar'"
record="chatter.topbar"
/>
@@ -8,7 +8,7 @@ import { getMessagingComponent } from "@mail/utils/messaging_component";
const { Component, onWillDestroy, onWillUpdateProps } = owl;
const getChatterNextTemporaryId = (function () {
export const getChatterNextTemporaryId = (function () {
let tmpId = 0;
return () => {
tmpId += 1;
@@ -32,23 +32,21 @@ export class ChatterContainer extends Component {
setup() {
useModels();
super.setup();
this.chatter = undefined;
this.chatterId = getChatterNextTemporaryId();
this.localChatter = undefined;
this._insertFromProps(this.props);
onWillUpdateProps(nextProps => this._willUpdateProps(nextProps));
onWillDestroy(() => this._onWillDestroy());
onWillUpdateProps(nextProps => {
this._insertFromProps(nextProps);
});
onWillDestroy(() => this.deleteLocalChatter());
}
_willUpdateProps(nextProps) {
this._insertFromProps(nextProps);
get chatter() {
return this.props.chatter || this.localChatter;
}
/**
* @override
*/
_onWillDestroy() {
if (this.chatter && this.chatter.exists()) {
this.chatter.delete();
deleteLocalChatter() {
if (this.localChatter && this.localChatter.exists()) {
this.localChatter.delete();
}
}
@@ -64,15 +62,23 @@ export class ChatterContainer extends Component {
if (owl.status(this) === "destroyed") {
return;
}
const values = { id: this.chatterId, ...props };
const values = { ...props };
delete values.chatter;
delete values.className;
if (values.threadId === undefined) {
values.threadId = clear();
}
this.chatter = messaging.models['Chatter'].insert(values);
const hasToCreateChatter = !props.chatter && !this.localChatter;
if (hasToCreateChatter) {
this.localChatter = messaging.models['Chatter'].insert({ id: getChatterNextTemporaryId(), ...values });
}
const chatter = props.chatter || this.localChatter;
if (!hasToCreateChatter) {
chatter.update(values);
}
if (owl.status(this) === "destroyed") {
// insert might trigger a re-render which might destroy the current component
this.chatter.delete();
this.deleteLocalChatter();
return;
}
/**
@@ -90,7 +96,9 @@ export class ChatterContainer extends Component {
* calling the props change method but it is in general not a good
* assumption to make.
*/
this.chatter.refresh();
if (chatter.thread) {
chatter.refresh();
}
this.render();
}
@@ -99,6 +107,10 @@ export class ChatterContainer extends Component {
Object.assign(ChatterContainer, {
components: { Chatter: getMessagingComponent('Chatter') },
props: {
chatter: {
type: Object,
optional: true,
},
className: {
type: String,
optional: true,
@@ -3,7 +3,7 @@
<t t-name="mail.ChatterContainer" owl="1">
<div class="o_ChatterContainer flex-grow-1 d-flex" t-att-class="{ 'o-isInFormSheetBg mx-auto': props.isInFormSheetBg }" t-attf-class="{{ props.className }}" data-command-category="mail" t-ref="root">
<t t-if="chatter">
<t t-if="chatter and chatter.thread">
<Chatter record="chatter"/>
</t>
<t t-else="">
+12 -2
View File
@@ -172,6 +172,9 @@ registerModel({
* @private
*/
_onThreadIdOrThreadModelChanged() {
if (!this.threadModel) {
return;
}
if (this.threadId) {
if (this.thread && this.thread.isTemporary) {
this.thread.delete();
@@ -261,6 +264,9 @@ registerModel({
}),
dropZoneView: one('DropZoneView', {
compute() {
if (!this.thread) {
return clear();
}
if (this.useDragVisibleDropZone.isVisible) {
return {};
}
@@ -403,6 +409,9 @@ registerModel({
*/
threadViewer: one('ThreadViewer', {
compute() {
if (!this.thread) {
return clear();
}
return {
hasThreadView: this.hasThreadView,
order: 'desc',
@@ -410,10 +419,11 @@ registerModel({
};
},
inverse: 'chatter',
required: true,
}),
topbar: one('ChatterTopbar', {
default: {},
compute() {
return this.thread ? {} : clear();
},
inverse: 'chatter',
}),
useDragVisibleDropZone: one('UseDragVisibleDropZone', {
@@ -54,7 +54,7 @@ registerModel({
return;
}
// since dropdown is conditionally shown based on state, dropdownRef can be null
if (this.dropdownRef.el && !this.dropdownRef.el.contains(ev.target)) {
if (this.dropdownRef && this.dropdownRef.el && !this.dropdownRef.el.contains(ev.target)) {
this.hide();
}
},
@@ -37,6 +37,7 @@ function compileChatter(node, params) {
}
const chatterContainerXml = createElement("ChatterContainer");
setAttributes(chatterContainerXml, {
"chatter": params.chatter,
"hasActivities": hasActivities,
"hasFollowers": hasFollowers,
"hasMessageList": hasMessageList,
@@ -116,6 +117,7 @@ export class MailFormCompiler extends ViewCompiler {
compileChatter(node) {
return compileChatter(node, {
chatter: "chatter",
threadId: "model.root.resId or undefined",
threadModel: "model.root.resModel",
webRecord: "model.root",
@@ -134,6 +136,7 @@ registry.category("form_compilers").add("chatter_compiler", {
selector: "div.oe_chatter",
fn: (node) =>
compileChatter(node, {
chatter: "props.chatter",
threadId: "props.record.resId or undefined",
threadModel: "props.record.resModel",
webRecord: "props.record",
@@ -1,7 +1,7 @@
/** @odoo-module */
import { useModels } from "@mail/component_hooks/use_models";
import { ChatterContainer } from "@mail/components/chatter_container/chatter_container";
import { ChatterContainer, getChatterNextTemporaryId } 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";
@@ -15,16 +15,27 @@ import { evalDomain } from "@web/views/utils";
import { MailFormCompiler } from "./form_compiler";
const { onMounted, onWillUnmount } = owl;
const { onMounted, onWillDestroy, onWillUnmount } = owl;
patch(FormController.prototype, "mail", {
setup() {
this._super();
this.uiService = useService("ui");
this.hasAttachmentViewerInArch = false;
this.chatter = undefined;
if (this.env.services.messaging) {
useModels();
this.env.services.messaging.modelManager.messagingCreatedPromise.then(() => {
if (owl.status(this) === "destroyed") {
return;
}
const messaging = this.env.services.messaging.modelManager.messaging;
this.chatter = messaging.models['Chatter'].insert({ id: getChatterNextTemporaryId() });
if (owl.status(this) === "destroyed") {
this.chatter.delete();
}
});
}
const { archInfo } = this.props;
@@ -49,6 +60,11 @@ patch(FormController.prototype, "mail", {
this.onResize = useDebounced(this.render, 200);
onMounted(() => browser.addEventListener("resize", this.onResize));
onWillUnmount(() => browser.removeEventListener("resize", this.onResize));
onWillDestroy(() => {
if (this.chatter && this.chatter.exists()) {
this.chatter.delete();
}
});
},
/**
* @returns {Messaging|undefined}
@@ -8,6 +8,7 @@
</t>
</xpath>
<xpath expr="//Layout/t[@t-component='props.Renderer']" position="attributes">
<attribute name="chatter">chatter</attribute>
<attribute name="hasAttachmentViewerInArch">hasAttachmentViewerInArch</attribute>
<attribute name="hasAttachmentViewer">hasAttachmentViewer()</attribute>
</xpath>