[IMP] mail: improve EmojiPicker (step 1)
Emoji picker now has a bar to filter emojis per category. Task-2898475 closes odoo/odoo#94646 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -4,12 +4,17 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class Emoji extends Component {}
|
||||
export class Emoji extends Component {
|
||||
/**
|
||||
* @returns {EmojiView}
|
||||
*/
|
||||
get emojiView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Emoji, {
|
||||
props: {
|
||||
emojiView: Object,
|
||||
},
|
||||
props: { record: Object },
|
||||
template: 'mail.Emoji',
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.Emoji" owl="1">
|
||||
<span class="o_Emoji p-1 fs-5 o_cursor_pointer" t-on-click="props.emojiView.emojiPickerView.onClickEmoji" t-att-title="props.emojiView.emoji.description" t-att-data-source="props.emojiView.emoji.sources[0]" t-att-data-unicode="props.emojiView.emoji.unicode" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-esc="props.emojiView.emoji.unicode"/>
|
||||
<span class="o_Emoji p-1 fs-3 o_cursor_pointer" t-att-class="emojiView.isHovered ? 'bg-200' : ''" t-on-click="emojiView.onClick" t-on-mouseenter="emojiView.onMouseenter" t-on-mouseleave="emojiView.onMouseleave" t-att-title="emojiView.emoji.description" t-att-data-source="emojiView.emoji.sources[0]" t-att-data-unicode="emojiView.emoji.unicode" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-esc="emojiView.emoji.unicode"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class EmojiCategoryBarView extends Component {
|
||||
/**
|
||||
* @returns {EmojiCategoryBarView}
|
||||
*/
|
||||
get emojiCategoryBarView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(EmojiCategoryBarView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.EmojiCategoryBarView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(EmojiCategoryBarView);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.EmojiCategoryBarView" owl="1">
|
||||
<div class="o_EmojiCategoryBarView d-flex flex-shrink-0 overflow-auto bg-100 border-bottom" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-foreach="emojiCategoryBarView.emojiCategoryViews" t-as="emojiCategoryView" t-key="emojiCategoryView.localId">
|
||||
<EmojiCategoryView record="emojiCategoryView"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,21 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class EmojiCategoryView extends Component {
|
||||
/**
|
||||
* @returns {EmojiCategoryView}
|
||||
*/
|
||||
get emojiCategoryView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(EmojiCategoryView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.EmojiCategoryView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(EmojiCategoryView);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.EmojiCategoryView" owl="1">
|
||||
<span class="o_EmojiCategoryView px-2 py-1 fs-5 o_cursor_pointer" t-att-class="(emojiCategoryView.isHovered or emojiCategoryView.emojiCategoryBarViewOwnerAsActive) ? 'bg-300' : ''" t-on-click="emojiCategoryView.onClick" t-on-mouseenter="emojiCategoryView.onMouseenter" t-on-mouseleave="emojiCategoryView.onMouseleave" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-esc="emojiCategoryView.emojiCategory.categoryName"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,21 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class EmojiGridView extends Component {
|
||||
/**
|
||||
* @returns {EmojiGridView}
|
||||
*/
|
||||
get emojiGridView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(EmojiGridView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.EmojiGridView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(EmojiGridView);
|
||||
@@ -0,0 +1,8 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_EmojiGridView {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.EmojiGridView" owl="1">
|
||||
<div class="o_EmojiGridView overflow-auto py-2 pl-3 pr-4" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-foreach="emojiGridView.emojiViews" t-as="emojiView" t-key="emojiView.localId">
|
||||
<Emoji className="'o_EmojiGid_emoji'" record="emojiView"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,52 +1,16 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useUpdate } from '@mail/component_hooks/use_update';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { LegacyComponent } from '@web/legacy/legacy_component';
|
||||
|
||||
export class EmojiPickerView extends LegacyComponent {
|
||||
const { Component } = owl;
|
||||
|
||||
export class EmojiPickerView extends Component {
|
||||
/**
|
||||
* @override
|
||||
* @returns {EmojiPickerView}
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useUpdate({ func: () => this._update() });
|
||||
}
|
||||
|
||||
get emojiPickerView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_update() {
|
||||
this.trigger('o-popover-compute');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
close() {
|
||||
this.trigger('o-popover-close');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given node is self or a children of self.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
contains(node) {
|
||||
return Boolean(this.root.el && this.root.el.contains(node));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(EmojiPickerView, {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_EmojiPickerView {
|
||||
// Could be replaced by "d-grid" class after migration to BS5
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
height: Min(80vh, 340px);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.EmojiPickerView" owl="1">
|
||||
<div class="o_EmojiPickerView px-1" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="emojiPickerView">
|
||||
<t t-foreach="emojiPickerView.emojiViews" t-as="emojiView" t-key="emojiView.localId">
|
||||
<Emoji emojiView="emojiView"/>
|
||||
</t>
|
||||
</t>
|
||||
<div class="o_EmojiPickerView d-flex flex-column" t-attf-class="{{ className }}" t-ref="root">
|
||||
<EmojiCategoryBarView record="emojiPickerView.emojiCategoryBarView"/>
|
||||
<EmojiGridView record="emojiPickerView.emojiGridView"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { replace } from '@mail/model/model_field_command';
|
||||
import { attr, many } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiCategory',
|
||||
identifyingFields: ['categoryName'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeEmojiRegistry() {
|
||||
return replace(this.messaging.emojiRegistry);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
allEmojis: many('Emoji', {
|
||||
inverse: 'emojiCategories',
|
||||
@@ -26,13 +16,7 @@ registerModel({
|
||||
}),
|
||||
emojiCategoryViews: many('EmojiCategoryView', {
|
||||
inverse: 'emojiCategory',
|
||||
readonly: true,
|
||||
isCausal: true,
|
||||
}),
|
||||
emojiRegistry: one('EmojiRegistry', {
|
||||
compute: '_computeEmojiRegistry',
|
||||
inverse: 'allCategories',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { many, one } from '@mail/model/model_field';
|
||||
import { insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiCategoryBarView',
|
||||
identifyingFields: ['emojiPickerViewOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeActiveCategoryView() {
|
||||
if (!this.activeByUserCategoryView) {
|
||||
return replace(this.defaultActiveCategoryView);
|
||||
}
|
||||
return replace(this.activeByUserCategoryView);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeDefaultActiveCategoryView() {
|
||||
return insertAndReplace({
|
||||
emojiCategory: insertAndReplace({ categoryName: "all" }),
|
||||
emojiCategoryBarViewOwner: replace(this),
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeEmojiCategoryViews() {
|
||||
return insertAndReplace(
|
||||
this.messaging.emojiRegistry.allCategories.map(emojiCategory => {
|
||||
return { emojiCategory: replace(emojiCategory) };
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activeByUserCategoryView: one('EmojiCategoryView', {
|
||||
inverse: 'emojiCategoryBarViewOwnerAsActiveByUser',
|
||||
}),
|
||||
activeCategoryView: one('EmojiCategoryView', {
|
||||
compute: '_computeActiveCategoryView',
|
||||
inverse: 'emojiCategoryBarViewOwnerAsActive',
|
||||
required: true,
|
||||
}),
|
||||
defaultActiveCategoryView: one('EmojiCategoryView', {
|
||||
compute: '_computeDefaultActiveCategoryView',
|
||||
}),
|
||||
emojiCategoryViews: many('EmojiCategoryView', {
|
||||
compute: '_computeEmojiCategoryViews',
|
||||
inverse: 'emojiCategoryBarViewOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
emojiPickerViewOwner: one('EmojiPickerView', {
|
||||
inverse: 'emojiCategoryBarView',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,21 +1,51 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiCategoryView',
|
||||
identifyingFields: ['emojiCategory', 'emojiPickerView'],
|
||||
identifyingFields: ['emojiCategoryBarViewOwner', 'emojiCategory'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClick() {
|
||||
this.update({ emojiCategoryBarViewOwnerAsActiveByUser: replace(this.emojiCategoryBarViewOwner) });
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onMouseenter(ev) {
|
||||
this.update({ isHovered: true });
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onMouseleave(ev) {
|
||||
this.update({ isHovered: false });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
emojiCategory: one('EmojiCategory', {
|
||||
inverse: 'emojiCategoryViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
emojiPickerView: one('EmojiPickerView', {
|
||||
emojiCategoryBarViewOwner: one('EmojiCategoryBarView', {
|
||||
inverse: 'emojiCategoryViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
emojiCategoryBarViewOwnerAsActiveByUser: one('EmojiCategoryBarView', {
|
||||
inverse: 'activeByUserCategoryView',
|
||||
}),
|
||||
emojiCategoryBarViewOwnerAsActive: one('EmojiCategoryBarView', {
|
||||
inverse: 'activeCategoryView',
|
||||
}),
|
||||
isHovered: attr({
|
||||
default: false,
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { many, one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiGridView',
|
||||
identifyingFields: ['emojiPickerViewOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeEmojiViews() {
|
||||
if (!this.emojiPickerViewOwner.emojiCategoryBarView.activeCategoryView) {
|
||||
return clear();
|
||||
}
|
||||
return insertAndReplace(
|
||||
this.emojiPickerViewOwner.emojiCategoryBarView.activeCategoryView.emojiCategory.allEmojis.map(emoji => {
|
||||
return { emoji: replace(emoji) };
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
emojiPickerViewOwner: one('EmojiPickerView', {
|
||||
inverse: 'emojiGridView',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
emojiViews: many('EmojiView', {
|
||||
compute: '_computeEmojiViews',
|
||||
inverse: 'emojiGridView',
|
||||
readonly: true,
|
||||
isCausal: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,59 +1,25 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { many, one } from '@mail/model/model_field';
|
||||
import { insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
import { one } from '@mail/model/model_field';
|
||||
import { insertAndReplace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiPickerView',
|
||||
identifyingFields: ['popoverViewOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickEmoji(ev) {
|
||||
if (this.popoverViewOwner.messageActionListOwnerAsReaction) {
|
||||
this.popoverViewOwner.messageActionListOwnerAsReaction.onClickReaction(ev);
|
||||
} else if (this.popoverViewOwner.composerViewOwnerAsEmoji) {
|
||||
this.popoverViewOwner.composerViewOwnerAsEmoji.onClickEmoji(ev);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeEmojiViews() {
|
||||
return insertAndReplace(
|
||||
this.messaging.emojiRegistry.allEmojis.map(emoji => {
|
||||
return { emoji: replace(emoji) };
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeEmojiCategoryViews() {
|
||||
return insertAndReplace(
|
||||
this.messaging.emojiRegistry.allCategories.map(emojiCategory => {
|
||||
return { emojiCategory: replace(emojiCategory) };
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
emojiCategoryViews: many('EmojiCategoryView', {
|
||||
compute: '_computeEmojiCategoryViews',
|
||||
inverse: 'emojiPickerView',
|
||||
emojiCategoryBarView: one('EmojiCategoryBarView', {
|
||||
default: insertAndReplace(),
|
||||
inverse: 'emojiPickerViewOwner',
|
||||
readonly: true,
|
||||
required: true,
|
||||
isCausal: true,
|
||||
}),
|
||||
emojiViews: many('EmojiView', {
|
||||
compute: '_computeEmojiViews',
|
||||
inverse: 'emojiPickerView',
|
||||
emojiGridView: one('EmojiGridView', {
|
||||
default: insertAndReplace(),
|
||||
inverse: 'emojiPickerViewOwner',
|
||||
readonly: true,
|
||||
required: true,
|
||||
isCausal: true,
|
||||
}),
|
||||
popoverViewOwner: one('PopoverView', {
|
||||
|
||||
@@ -105,7 +105,6 @@ registerModel({
|
||||
{ categoryName: "⚽️", allEmojis: insertAndReplace(["🏃", "🚲", "⚽️", "🏈", "🎱", "🎬", "🎤"].map(unicode => ({ unicode }))) },
|
||||
|
||||
]),
|
||||
inverse: 'emojiRegistry',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,21 +1,51 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'EmojiView',
|
||||
identifyingFields: ['emoji', 'emojiPickerView'],
|
||||
identifyingFields: ['emojiGridView', 'emoji'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClick(ev) {
|
||||
if (this.emojiGridView.emojiPickerViewOwner.popoverViewOwner.messageActionListOwnerAsReaction) {
|
||||
this.emojiGridView.emojiPickerViewOwner.popoverViewOwner.messageActionListOwnerAsReaction.onClickReaction(ev);
|
||||
return;
|
||||
}
|
||||
if (this.emojiGridView.emojiPickerViewOwner.popoverViewOwner.composerViewOwnerAsEmoji) {
|
||||
this.emojiGridView.emojiPickerViewOwner.popoverViewOwner.composerViewOwnerAsEmoji.onClickEmoji(ev);
|
||||
return;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onMouseenter(ev) {
|
||||
this.update({ isHovered: true });
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onMouseleave(ev) {
|
||||
this.update({ isHovered: false });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
emoji: one('Emoji', {
|
||||
inverse: 'emojiViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
emojiPickerView: one('EmojiPickerView', {
|
||||
emojiGridView: one('EmojiGridView', {
|
||||
inverse: 'emojiViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
isHovered: attr({
|
||||
default: false,
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ registerModel({
|
||||
return 'ChannelInvitationForm';
|
||||
}
|
||||
if (this.emojiPickerView) {
|
||||
return 'EmojiPicker';
|
||||
return 'EmojiPickerView';
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user