[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:
@@ -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>
|
||||
|
||||
+1
-49
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
|
||||
+1
-1
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user