[IMP] mail: Introduce view models

This commit moves code from components to models,
by introduce some models and fields.

It also cleans a few things in models, like:
- use Timer model instead of setTimeout
- replace local variables by model fields

Task-2881075

All changes:
- [IMP] mail: remove ChannelMemberListCategoryComponent/channelMemberListView
Task-2831082
- [IMP] mail: introduce ChannelMemberListCategoryView/members
Task-2864112
- [IMP] mail: introduce ChannelMemberListCategoryView/title
Task-2864115
- [IMP] mail: introduce ChannelMemberView
Task-2864119
- [IMP] mail: introduce 'Chatter/attachmentsLoaderTimer'
Task-2868781
- [IMP] mail: introduce 'MessageView/highlightTimer'
Task-2868794
- [IMP] mail: introduce 'CallView/showOverlayTimer'
Task-2868867
- [IMP] mail: introduce RtcSession/broadcastTimer
Task-2868929
- [IMP] mail: introduce CallView/tileContainerRef
Task-2871859
- [IMP] mail: introduce CallView/tileWidth
Task-2871869
- [IMP] mail: introduce CallView/tileHeight
Task-2871878
- [IMP] mail: introduce ChatWindowManager/endGapWidth
Task-2871960
- [IMP] mail: remove unused CallView/columnCount
Task-2871885
- [IMP] mail: introduce ChatWindowManager/hiddenMenuWidth
Task-2871963
- [IMP] mail: introduce CallView/calculateTessellation
Task-2871867
- [IMP] mail: replace model methods of MediaPreview by record methods
Task-2871934
- [IMP] mail: introduce ChatWindowManager/betweenGapWidth
Task-2871956
- [IMP] mail: introduce ChatWindowManager/chatWindowWidth
Task-2871957
- [IMP] mail: introduce ChatWindowManager/startGapWidth
Task-2871965
- [IMP] mail: introduce ChatWindowManager/availableVisibleSlots
Task-2871971
- [IMP] mail: introduce ChatWindowManager/visual/hiddenChatWindowLocalIds
Task-2871980
- [IMP] mail: introduce ChatWindowManager/visual/isHiddenMenuVisible
Task-2871981
- [IMP] mail: introduce ChatWindowManager/visual/hiddenMenuOffset
Task-2871984

Part-of: odoo/odoo#93330
This commit is contained in:
Alexandre Kühn
2022-06-11 02:15:15 +02:00
parent eca952307a
commit dcfc8a91e8
19 changed files with 377 additions and 262 deletions
@@ -1,11 +1,12 @@
/** @odoo-module **/
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { useUpdate } from '@mail/component_hooks/use_update';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
import Dialog from 'web.OwlDialog';
const { Component, useRef, useState } = owl;
const { Component } = owl;
const components = {
Dialog,
@@ -20,12 +21,7 @@ export class CallView extends Component {
*/
setup() {
super.setup();
this.state = useState({
tileWidth: 0,
tileHeight: 0,
columnCount: 0,
});
this.tileContainerRef = useRef('tileContainer');
useRefToModel({ fieldName: 'tileContainerRef', refName: 'tileContainer', });
useUpdate({ func: () => this._update() });
}
@@ -44,52 +40,6 @@ export class CallView extends Component {
// Private
//--------------------------------------------------------------------------
/**
* Finds a tile layout and dimensions that respects param0.aspectRatio while maximizing
* the total area covered by the tiles within the specified container dimensions.
*
* @private
* @param {Object} param0
* @param {number} [param0.aspectRatio]
* @param {number} param0.containerHeight
* @param {number} param0.containerWidth
* @param {number} param0.tileCount
*/
_computeTessellation({ aspectRatio = 1, containerHeight, containerWidth, tileCount }) {
let optimalLayout = {
area: 0,
cols: 0,
tileHeight: 0,
tileWidth: 0,
};
for (let columnCount = 1; columnCount <= tileCount; columnCount++) {
const rowCount = Math.ceil(tileCount / columnCount);
const potentialHeight = containerWidth / (columnCount * aspectRatio);
const potentialWidth = containerHeight / rowCount;
let tileHeight;
let tileWidth;
if (potentialHeight > potentialWidth) {
tileHeight = Math.floor(potentialWidth);
tileWidth = Math.floor(tileHeight * aspectRatio);
} else {
tileWidth = Math.floor(containerWidth / columnCount);
tileHeight = Math.floor(tileWidth / aspectRatio);
}
const area = tileHeight * tileWidth;
if (area <= optimalLayout.area) {
continue;
}
optimalLayout = {
area,
columnCount,
tileHeight,
tileWidth,
};
}
return optimalLayout;
}
/**
* @private
*/
@@ -97,21 +47,22 @@ export class CallView extends Component {
if (!this.root.el) {
return;
}
if (!this.tileContainerRef.el) {
if (!this.callView.tileContainerRef.el) {
return;
}
const { width, height } = this.tileContainerRef.el.getBoundingClientRect();
const { width, height } = this.callView.tileContainerRef.el.getBoundingClientRect();
const { tileWidth, tileHeight, columnCount } = this._computeTessellation({
const { tileWidth, tileHeight } = this.callView.calculateTessellation({
aspectRatio: this.callView.aspectRatio,
containerHeight: height,
containerWidth: width,
tileCount: this.tileContainerRef.el.children.length,
tileCount: this.callView.tileContainerRef.el.children.length,
});
this.state.tileWidth = tileWidth;
this.state.tileHeight = tileHeight;
this.state.columnCount = columnCount;
this.callView.update({
tileHeight,
tileWidth,
});
}
/**
@@ -27,7 +27,7 @@
'o-isSidebar': callView.hasSidebar and callView.mainParticipantCard,
}"
t-ref="tileContainer"
t-attf-style="--height:{{state.tileHeight}}px; --width:{{state.tileWidth}}px; --columns:{{state.columnCount}};"
t-attf-style="--height:{{state.tileHeight}}px; --width:{{state.tileWidth}}px;"
>
<t t-foreach="callView.tileParticipantCards" t-as="participantCard" t-key="'grid_tile_'+participantCard.localId">
<t t-if="!callView.filterVideoGrid or (participantCard.rtcSession and participantCard.rtcSession.videoStream)">
@@ -14,10 +14,10 @@ export class ChannelMember extends Component {
}
/**
* @returns {Partner}
* @returns {ChannelMemberView}
*/
get member() {
return this.props.member;
get record() {
return this.props.record;
}
}
@@ -25,7 +25,7 @@ export class ChannelMember extends Component {
Object.assign(ChannelMember, {
props: {
channel: Object,
member: Object,
record: Object,
},
template: 'mail.ChannelMember',
});
@@ -2,23 +2,23 @@
<templates xml:space="preserve">
<t t-name="mail.ChannelMember" owl="1">
<div class="o_ChannelMember d-flex align-items-center mx-3 my-1" t-attf-class="{{ className }}" t-att-data-partner-local-id="member.localId" t-ref="root">
<div class="o_ChannelMember_avatarContainer position-relative flex-shrink-0 o_cursor_pointer" t-on-click="() => channel.onClickMemberAvatar(member)">
<img class="o_ChannelMember_avatar rounded-circle w-100 h-100" t-attf-src="/mail/channel/{{ channel.id }}/partner/{{ member.id }}/avatar_128" alt="Avatar"/>
<div class="o_ChannelMember d-flex align-items-center mx-3 my-1" t-attf-class="{{ className }}" t-att-data-partner-local-id="record.partner.localId" t-ref="root">
<div class="o_ChannelMember_avatarContainer position-relative flex-shrink-0 o_cursor_pointer" t-on-click="() => channel.onClickMemberAvatar(record.partner)">
<img class="o_ChannelMember_avatar rounded-circle w-100 h-100" t-attf-src="/mail/channel/{{ channel.id }}/partner/{{ record.partner.id }}/avatar_128" alt="Avatar"/>
<t t-if="member.im_status and member.im_status !== 'im_partner'">
<t t-if="record.partner.im_status and record.partner.im_status !== 'im_partner'">
<PartnerImStatusIcon
className="'o_ChannelMember_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center text-light'"
classNameObj="{
'o-isDeviceSmall': messaging.device.isSmall,
'small': !messaging.device.isSmall,
}"
partner="member"
partner="record.partner"
/>
</t>
</div>
<span class="o_ChannelMember_name ml-2 flex-column-1 text-truncate o_cursor_pointer" t-on-click="() => channel.onClickMemberName(member)">
<t t-esc="member.nameOrDisplayName"/>
<span class="o_ChannelMember_name ml-2 flex-column-1 text-truncate o_cursor_pointer" t-on-click="() => channel.onClickMemberName(record.partner)">
<t t-esc="record.partner.nameOrDisplayName"/>
</span>
</div>
</t>
@@ -5,10 +5,10 @@
<t t-if="channelMemberListView">
<div class="o_ChannelMemberList d-flex flex-column overflow-auto bg-light" t-attf-class="{{ className }}" t-ref="root">
<t t-if="channelMemberListView.onlineCategoryView">
<ChannelMemberListCategory record="channelMemberListView.onlineCategoryView" channelMemberListView="channelMemberListView"/>
<ChannelMemberListCategory record="channelMemberListView.onlineCategoryView"/>
</t>
<t t-if="channelMemberListView.offlineCategoryView">
<ChannelMemberListCategory record="channelMemberListView.offlineCategoryView" channelMemberListView="channelMemberListView"/>
<ChannelMemberListCategory record="channelMemberListView.offlineCategoryView"/>
</t>
<t t-if="channelMemberListView.channel.unknownMemberCount === 1">
<span class="mx-2 mt-2">And 1 other member.</span>
@@ -2,19 +2,10 @@
import { registerMessagingComponent } from '@mail/utils/messaging_component';
import { sprintf } from '@web/core/utils/strings';
const { Component } = owl;
export class ChannelMemberListCategory extends Component {
/**
* @returns {ChannelMemberListView}
*/
get channelMemberListView() {
return this.props.channelMemberListView;
}
/**
* @returns {ChannelMemberListCategoryView}
*/
@@ -22,49 +13,10 @@ export class ChannelMemberListCategory extends Component {
return this.props.record;
}
/**
* @returns {Partner[]}
*/
get members() {
if (!this.channelMemberListView.exists()) {
return [];
}
if (this.record.channelMemberListViewOwnerAsOnline) {
return this.channelMemberListView.channel.orderedOnlineMembers;
}
if (this.record.channelMemberListViewOwnerAsOffline) {
return this.channelMemberListView.channel.orderedOfflineMembers;
}
return [];
}
/**
* @returns {string}
*/
get title() {
let categoryText = "";
if (this.record.channelMemberListViewOwnerAsOnline) {
categoryText = this.env._t("Online");
}
if (this.record.channelMemberListViewOwnerAsOffline) {
categoryText = this.env._t("Offline");
}
return sprintf(
this.env._t("%(categoryText)s - %(memberCount)s"),
{
categoryText,
memberCount: this.members.length,
}
);
}
}
Object.assign(ChannelMemberListCategory, {
props: {
channelMemberListView: Object,
record: Object,
},
props: { record: Object },
template: 'mail.ChannelMemberListCategory',
});
@@ -2,9 +2,9 @@
<templates xml:space="preserve">
<t t-name="mail.ChannelMemberListCategory" owl="1">
<h6 class="m-2" t-esc="title"/>
<t t-foreach="members" t-as="member" t-key="member.localId">
<ChannelMember channel="channelMemberListView.channel" member="member"/>
<h6 class="m-2" t-esc="record.title"/>
<t t-foreach="record.channelMemberViews" t-as="channelMemberView" t-key="channelMemberView.localId">
<ChannelMember channel="record.channel" record="channelMemberView"/>
</t>
</t>
@@ -72,7 +72,7 @@ export class ChatWindowHiddenMenu extends Component {
const textDirection = this.messaging.locale.textDirection;
const offsetFrom = textDirection === 'rtl' ? 'left' : 'right';
const oppositeFrom = offsetFrom === 'right' ? 'left' : 'right';
const offset = this.messaging.chatWindowManager.visual.hidden.offset;
const offset = this.messaging.chatWindowManager.visual.hiddenMenuOffset;
this.root.el.style[offsetFrom] = `${offset}px`;
this.root.el.style[oppositeFrom] = 'auto';
}
@@ -4,7 +4,7 @@
<t t-name="mail.ChatWindowManager" owl="1">
<div class="o_ChatWindowManager flex-row-reverse d-flex" t-attf-class="{{ className }}" t-ref="root">
<!-- Note: DOM elements are ordered from left to right -->
<t t-if="messaging.chatWindowManager.visual.hidden.isVisible">
<t t-if="messaging.chatWindowManager.visual.isHiddenMenuVisible">
<ChatWindowHiddenMenu className="'o_ChatWindowManager_hiddenMenu'"/>
</t>
<t t-foreach="messaging.chatWindowManager.allOrderedVisible" t-as="chatWindow" t-key="chatWindow.localId">