[IMP] mail: clean dialog modelling of discuss

Properly define relational fields as owner/owning/content.
For instance, with viewing attachment in a dialog, the modelling
looked roughly like this before the commit:

```
attachment_viewer -----> dialog
```
- where attachment viewer owns dialog

This commit changes it to:

```
attachment (image/card) ------> dialog -------> attachment_viewer
```
- where attachment (image/card) owns the dialog, and dialog has
  attachment viewer as content

These changes should make modelling of dialog feels more natural.

Task-2738648

closes odoo/odoo#83519

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-01-28 14:10:07 +00:00
parent 75e6b645ac
commit 48700416c1
8 changed files with 119 additions and 82 deletions
@@ -114,7 +114,7 @@ export class AttachmentViewer extends Component {
* @private
*/
_close() {
this.attachmentViewer.close();
this.attachmentViewer.delete();
}
/**
@@ -163,14 +163,10 @@ export class AttachmentViewer extends Component {
* @private
*/
_next() {
const attachmentViewer = this.attachmentViewer;
const index = attachmentViewer.attachments.findIndex(attachment =>
attachment === attachmentViewer.attachment
);
const nextIndex = (index + 1) % attachmentViewer.attachments.length;
attachmentViewer.update({
attachment: link(attachmentViewer.attachments[nextIndex]),
});
if (!this.attachmentViewer.dialogOwner || !this.attachmentViewer.dialogOwner.attachmentListOwnerAsAttachmentView) {
return;
}
this.attachmentViewer.dialogOwner.attachmentListOwnerAsAttachmentView.selectNextAttachment();
}
/**
@@ -179,16 +175,10 @@ export class AttachmentViewer extends Component {
* @private
*/
_previous() {
const attachmentViewer = this.attachmentViewer;
const index = attachmentViewer.attachments.findIndex(attachment =>
attachment === attachmentViewer.attachment
);
const nextIndex = index === 0
? attachmentViewer.attachments.length - 1
: index - 1;
attachmentViewer.update({
attachment: link(attachmentViewer.attachments[nextIndex]),
});
if (!this.attachmentViewer.dialogOwner || !this.attachmentViewer.dialogOwner.attachmentListOwnerAsAttachmentView) {
return;
}
this.attachmentViewer.dialogOwner.attachmentListOwnerAsAttachmentView.selectPreviousAttachment();
}
/**
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { insert, insertAndReplace, replace } from '@mail/model/model_field_command';
import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentCard',
@@ -15,13 +15,9 @@ registerModel({
if (!this.attachment || !this.attachment.isViewable) {
return;
}
this.messaging.dialogManager.update({
dialogs: insert({
attachmentViewer: insertAndReplace({
attachment: replace(this.attachment),
attachmentList: replace(this.attachmentList),
}),
}),
this.attachmentList.update({
attachmentListViewDialog: insertAndReplace(),
selectedAttachment: replace(this.attachment),
});
},
/**
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, insert, insertAndReplace, replace } from '@mail/model/model_field_command';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentImage',
@@ -15,13 +15,9 @@ registerModel({
if (!this.attachment || !this.attachment.isViewable) {
return;
}
this.messaging.dialogManager.update({
dialogs: insert({
attachmentViewer: insertAndReplace({
attachment: replace(this.attachment),
attachmentList: replace(this.attachmentList),
}),
}),
this.attachmentList.update({
attachmentListViewDialog: insertAndReplace(),
selectedAttachment: replace(this.attachment),
});
},
/**
@@ -8,6 +8,22 @@ registerModel({
name: 'AttachmentList',
identifyingFields: [['composerView', 'messageView', 'attachmentBoxViewOwner']],
recordMethods: {
/**
* Select the next attachment.
*/
selectNextAttachment() {
const index = this.attachments.findIndex(attachment => attachment === this.selectedAttachment);
const nextIndex = index === this.attachments.length - 1 ? 0 : index + 1;
this.update({ selectedAttachment: replace(this.attachments[nextIndex]) });
},
/**
* Select the previous attachment.
*/
selectPreviousAttachment() {
const index = this.attachments.findIndex(attachment => attachment === this.selectedAttachment);
const prevIndex = index === 0 ? this.attachments.length - 1 : index - 1;
this.update({ selectedAttachment: replace(this.attachments[prevIndex]) });
},
_computeAttachmentImages() {
return insertAndReplace(this.imageAttachments.map(attachment => {
return {
@@ -80,6 +96,10 @@ registerModel({
inverse: 'attachmentList',
isCausal: true,
}),
attachmentListViewDialog: one('Dialog', {
inverse: 'attachmentListOwnerAsAttachmentView',
isCausal: true,
}),
/**
* States the attachments to be displayed by this attachment list.
*/
@@ -116,6 +136,7 @@ registerModel({
nonImageAttachments: many('Attachment', {
compute: '_computeNonImageAttachments',
}),
selectedAttachment: one('Attachment'),
/**
* States the attachments that can be viewed inside the browser.
*/
@@ -5,17 +5,8 @@ import { attr, many, one } from '@mail/model/model_field';
registerModel({
name: 'AttachmentViewer',
identifyingFields: ['attachmentList'],
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Close the attachment viewer by closing its linked dialog.
*/
close() {
const dialog = this.messaging.models['Dialog'].find(dialog => dialog.record === this);
if (dialog) {
dialog.delete();
}
},
/**
* Returns whether the given html element is inside this attachment viewer.
*
@@ -47,9 +38,11 @@ registerModel({
angle: attr({
default: 0,
}),
attachment: one('Attachment'),
attachment: one('Attachment', {
related: 'attachmentList.selectedAttachment',
}),
attachmentList: one('AttachmentList', {
readonly: true,
related: 'dialogOwner.attachmentListOwnerAsAttachmentView',
required: true,
}),
attachments: many('Attachment', {
@@ -62,7 +55,7 @@ registerModel({
/**
* Determines the dialog displaying this attachment viewer.
*/
dialog: one('Dialog', {
dialogOwner: one('Dialog', {
inverse: 'attachmentViewer',
isCausal: true,
readonly: true,
+64 -17
View File
@@ -2,11 +2,14 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, replace } from '@mail/model/model_field_command';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'Dialog',
identifyingFields: ['manager', ['attachmentViewer', 'followerSubtypeList']],
identifyingFields: [[
'attachmentListOwnerAsAttachmentView',
'followerOwnerAsSubtypeList',
]],
recordMethods: {
/**
* @param {Element} element
@@ -15,6 +18,40 @@ registerModel({
hasElementInContent(element) {
return Boolean(this.record && this.record.containsElement(element));
},
/**
* @private
* @returns {FieldCommand}
*/
_computeAttachmentViewer() {
if (this.attachmentListOwnerAsAttachmentView) {
return insertAndReplace();
}
return clear();
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeComponentName() {
if (this.attachmentViewer) {
return 'AttachmentViewer';
}
if (this.followerSubtypeList) {
return 'FollowerSubtypeList';
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeFollowerSubtypeList() {
if (this.followerOwnerAsSubtypeList) {
return insertAndReplace();
}
return clear();
},
/**
/**
* @private
* @returns {boolean}
@@ -29,6 +66,16 @@ registerModel({
}
return true;
},
/**
* @private
* @returns {FieldCommand}
*/
_computeManager() {
if (this.messaging.dialogManager) {
return replace(this.messaging.dialogManager);
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
@@ -41,36 +88,36 @@ registerModel({
return replace(this.followerSubtypeList);
}
},
_computeComponentName() {
if (this.attachmentViewer) {
return 'AttachmentViewer';
}
if (this.followerSubtypeList) {
return 'FollowerSubtypeList';
}
return clear();
},
},
fields: {
attachmentViewer: one('AttachmentViewer', {
isCausal: true,
inverse: 'dialog',
attachmentListOwnerAsAttachmentView: one('AttachmentList', {
inverse: 'attachmentListViewDialog',
readonly: true,
}),
attachmentViewer: one('AttachmentViewer', {
compute: '_computeAttachmentViewer',
inverse: 'dialogOwner',
isCausal: true,
}),
componentName: attr({
compute: '_computeComponentName',
required: true,
}),
followerSubtypeList: one('FollowerSubtypeList', {
isCausal: true,
inverse: 'dialog',
followerOwnerAsSubtypeList: one('Follower', {
inverse: 'followerSubtypeListDialog',
readonly: true,
}),
followerSubtypeList: one('FollowerSubtypeList', {
compute: '_computeFollowerSubtypeList',
inverse: 'dialogOwner',
isCausal: true,
}),
isCloseable: attr({
compute: '_computeIsCloseable',
default: true,
}),
manager: one('DialogManager', {
compute: '_computeManager',
inverse: 'dialogs',
readonly: true,
}),
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, insert, insertAndReplace, link, replace, unlink, unlinkAll } from '@mail/model/model_field_command';
import { clear, insert, insertAndReplace, link, unlink, unlinkAll } from '@mail/model/model_field_command';
registerModel({
name: 'Follower',
@@ -47,7 +47,7 @@ registerModel({
* Close subtypes dialog
*/
closeSubtypes() {
this.update({ subtypeList: clear() });
this.update({ followerSubtypeListDialog: clear() });
},
/**
* Opens the most appropriate view that is a profile for this follower.
@@ -98,13 +98,7 @@ registerModel({
this.update({ selectedSubtypes: unlink(subtype) });
}
}
this.messaging.dialogManager.update({
dialogs: insert({
followerSubtypeList: insertAndReplace({
follower: replace(this),
}),
}),
});
this.update({ followerSubtypeListDialog: insertAndReplace() });
},
/**
* @param {FollowerSubtype} subtype
@@ -145,6 +139,10 @@ registerModel({
followedThread: one('Thread', {
inverse: 'followers',
}),
followerSubtypeListDialog: one('Dialog', {
inverse: 'followerOwnerAsSubtypeList',
isCausal: true,
}),
id: attr({
readonly: true,
required: true,
@@ -159,10 +157,6 @@ registerModel({
required: true,
}),
selectedSubtypes: many('FollowerSubtype'),
subtypeList: many('FollowerSubtypeList', {
inverse: 'follower',
isCausal: true,
}),
subtypes: many('FollowerSubtype'),
},
});
@@ -2,10 +2,11 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { replace } from '@mail/model/model_field_command';
registerModel({
name: 'FollowerSubtypeList',
identifyingFields: ['follower'],
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Returns whether the given html element is inside this follower subtype list.
@@ -25,14 +26,13 @@ registerModel({
/**
* States the dialog displaying this follower subtype list.
*/
dialog: one('Dialog', {
dialogOwner: one('Dialog', {
inverse: 'followerSubtypeList',
isCausal: true,
readonly: true,
}),
follower: one('Follower', {
inverse: 'subtypeList',
readonly: true,
related: 'dialogOwner.followerOwnerAsSubtypeList',
required: true,
}),
},