Skip to content

Commit

Permalink
Chat: partial support for dataSource operations 'update' and 'remove'
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeniyKiyashko committed Nov 7, 2024
1 parent 8b6d1ba commit 54370da
Show file tree
Hide file tree
Showing 5 changed files with 518 additions and 48 deletions.
14 changes: 4 additions & 10 deletions packages/devextreme/js/__internal/ui/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type {
} from '@js/ui/chat';
import type { OptionChanged } from '@ts/core/widget/types';
import Widget from '@ts/core/widget/widget';
import { applyBatch } from '@ts/data/m_array_utils';

import AlertList from './alertlist';
import ChatHeader from './header';
Expand Down Expand Up @@ -100,15 +99,10 @@ class Chat extends Widget<Properties> {
if (e?.changes) {
this._messageList._modifyByChanges(e.changes);

// @ts-expect-error
const dataSource = this.getDataSource();
// @ts-expect-error
applyBatch({
// // @ts-expect-error
keyInfo: dataSource,
data: this.option('items'),
changes: e.changes,
});
this._setOptionWithoutOptionChange('items', newItems.slice());
this._messageList._setOptionWithoutOptionChange('items', newItems.slice());

this._messageList._renderEmptyView();
} else {
this.option('items', newItems.slice());
}
Expand Down
5 changes: 4 additions & 1 deletion packages/devextreme/js/__internal/ui/chat/messagebubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Widget from '@ts/core/widget/widget';
import type Chat from './chat';
import type { MessageTemplate } from './messagelist';

const CHAT_MESSAGEBUBBLE_CLASS = 'dx-chat-messagebubble';
export const CHAT_MESSAGEBUBBLE_CLASS = 'dx-chat-messagebubble';

export interface Properties extends WidgetOptions<MessageBubble> {
text?: string;
Expand Down Expand Up @@ -44,6 +44,9 @@ class MessageBubble extends Widget<Properties> {

const messageTemplate = this._getTemplateByOption('template');

// @ts-expect-error
templateData.message.text = text;

messageTemplate.render({
container: this.element(),
model: templateData,
Expand Down
8 changes: 6 additions & 2 deletions packages/devextreme/js/__internal/ui/chat/messagegroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import type Chat from './chat';
import MessageBubble from './messagebubble';
import type { MessageTemplate } from './messagelist';

const CHAT_MESSAGEGROUP_CLASS = 'dx-chat-messagegroup';
export const MESSAGE_DATA_KEY = 'dxMessageData';

export const CHAT_MESSAGEGROUP_CLASS = 'dx-chat-messagegroup';
export const CHAT_MESSAGEGROUP_ALIGNMENT_START_CLASS = 'dx-chat-messagegroup-alignment-start';
export const CHAT_MESSAGEGROUP_ALIGNMENT_END_CLASS = 'dx-chat-messagegroup-alignment-end';
const CHAT_MESSAGEGROUP_INFORMATION_CLASS = 'dx-chat-messagegroup-information';
Expand Down Expand Up @@ -111,7 +113,9 @@ class MessageGroup extends Widget<Properties> {
}

_renderMessageBubble(message: Message): void {
const $bubble = $('<div>');
const $bubble = $('<div>')
.data(MESSAGE_DATA_KEY, message);

const { messageTemplate, messageTemplateData } = this.option();

this._createComponent($bubble, MessageBubble, {
Expand Down
125 changes: 104 additions & 21 deletions packages/devextreme/js/__internal/ui/chat/messagelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ import { getScrollTopMax } from '@ts/ui/scroll_view/utils/get_scroll_top_max';

import { isElementVisible } from '../splitter/utils/layout';
import type Chat from './chat';
import MessageBubble, { CHAT_MESSAGEBUBBLE_CLASS } from './messagebubble';
import type { MessageGroupAlignment } from './messagegroup';
import MessageGroup, {
CHAT_MESSAGEGROUP_ALIGNMENT_END_CLASS,
CHAT_MESSAGEGROUP_ALIGNMENT_START_CLASS,
CHAT_MESSAGEGROUP_CLASS,
MESSAGE_DATA_KEY,
} from './messagegroup';
import TypingIndicator from './typingindicator';

Expand All @@ -52,7 +55,7 @@ export const MESSAGEGROUP_TIMEOUT = 5 * 1000 * 60;
export interface Change {
type: 'insert' | 'update' | 'remove';
data?: DeepPartial<Message>;
key?: unknown;
key?: string | number;
index?: number;
}

Expand All @@ -77,8 +80,6 @@ export interface Properties extends WidgetOptions<MessageList> {
}

class MessageList extends Widget<Properties> {
private _messageGroups?: MessageGroup[];

private _lastMessageDate?: null | string | number | Date;

private _containerClientHeight!: number;
Expand Down Expand Up @@ -112,7 +113,6 @@ class MessageList extends Widget<Properties> {
_init(): void {
super._init();

this._messageGroups = [];
this._lastMessageDate = null;
}

Expand Down Expand Up @@ -206,13 +206,6 @@ class MessageList extends Widget<Properties> {
});
}

_removeEmptyView(): void {
this.$element()
.removeClass(CHAT_MESSAGELIST_EMPTY_CLASS)
.removeClass(CHAT_MESSAGELIST_EMPTY_LOADING_CLASS);
this._$content.empty();
}

_isEmpty(): boolean {
const { items } = this.option();

Expand Down Expand Up @@ -241,7 +234,7 @@ class MessageList extends Widget<Properties> {

const $messageGroup = $('<div>').appendTo(this._$content);

const messageGroup = this._createComponent($messageGroup, MessageGroup, {
this._createComponent($messageGroup, MessageGroup, {
items,
alignment: this._messageGroupAlignment(userId),
showAvatar,
Expand All @@ -251,8 +244,6 @@ class MessageList extends Widget<Properties> {
messageTemplateData,
messageTimestampFormat,
});

this._messageGroups?.push(messageGroup);
}

_renderScrollView(): void {
Expand Down Expand Up @@ -325,6 +316,8 @@ class MessageList extends Widget<Properties> {
}

_renderEmptyView(): void {
this._getEmptyView().remove();

const { isLoading } = this.option();

this.$element()
Expand Down Expand Up @@ -400,10 +393,24 @@ class MessageList extends Widget<Properties> {
$lastAlignmentEndGroup.addClass(CHAT_LAST_MESSAGEGROUP_ALIGNMENT_END_CLASS);
}

_getLastMessageGroup(): MessageGroup | undefined {
const $lastMessageGroup = this._$content.find(`.${CHAT_MESSAGEGROUP_CLASS}`);

if ($lastMessageGroup.length) {
return this._getMessageGroupInstanceByElement($lastMessageGroup);
}

return undefined;
}

_getMessageGroupInstanceByElement($element: dxElementWrapper): MessageGroup {
return MessageGroup.getInstance($element) as MessageGroup;
}

_renderMessage(message: Message): void {
const { author, timestamp } = message;

const lastMessageGroup = this._messageGroups?.at(-1);
const lastMessageGroup = this._getLastMessageGroup();
const shouldCreateDayHeader = this._shouldAddDayHeader(timestamp);

if (lastMessageGroup) {
Expand Down Expand Up @@ -431,6 +438,80 @@ class MessageList extends Widget<Properties> {
this._scrollDownContent();
}

_getMessageData(message: Element): Message {
// @ts-expect-error
return $(message).data(MESSAGE_DATA_KEY);
}

_findMessageElementByKey(key: string | number): dxElementWrapper {
const $bubbles = this.$element().find(`.${CHAT_MESSAGEBUBBLE_CLASS}`);

let result = $();

$bubbles.each((_, item) => {
const messageData = this._getMessageData(item);

if (messageData.id === key) {
result = $(item);
return false;
}

return true;
});

return result;
}

_updateMessageByKey(key: string | number | undefined, data: Message): void {
if (key) {
const $targetMessage = this._findMessageElementByKey(key);

const bubble = MessageBubble.getInstance($targetMessage);
bubble.option('text', data.text);
}
}

_removeMessageByKey(key: string | number | undefined): void {
if (!key) {
return;
}

const $targetMessage = this._findMessageElementByKey(key);

if (!$targetMessage.length) {
return;
}

const $currentMessageGroup = $targetMessage.closest(`.${CHAT_MESSAGEGROUP_CLASS}`);

const group = this._getMessageGroupInstanceByElement($currentMessageGroup);

const { items } = group.option();
const newItems = items.filter((item) => item.id !== key);

if (newItems.length === 0) {
const { showDayHeaders } = this.option();

if (showDayHeaders) {
const $prev = group.$element().prev();
const $next = group.$element().next();

const shouldRemoveDayHeader = $prev.length
&& $prev.hasClass(CHAT_MESSAGELIST_DAY_HEADER_CLASS)
&& (($next.length && $next.hasClass(CHAT_MESSAGELIST_DAY_HEADER_CLASS)) || !$next.length);

if (shouldRemoveDayHeader) {
$prev.remove();
}
}
group.$element().remove();
} else {
group.option('items', newItems);
}

this._setLastMessageGroupClasses();
}

_scrollDownContent(): void {
this._scrollView.scrollTo({
top: getScrollTopMax(this._scrollableContainer()),
Expand Down Expand Up @@ -468,9 +549,7 @@ class MessageList extends Widget<Properties> {
if (shouldItemsBeUpdatedCompletely) {
this._invalidate();
} else {
if (!previousValue.length) {
this._removeEmptyView();
}
this._renderEmptyView();

const newMessage = value[value.length - 1];

Expand Down Expand Up @@ -530,8 +609,11 @@ class MessageList extends Widget<Properties> {
return $(this._scrollView.content());
}

_getEmptyView(): dxElementWrapper {
return this._$content.find(`.${CHAT_MESSAGELIST_EMPTY_VIEW_CLASS}`);
}

_clean(): void {
this._messageGroups = [];
this._lastMessageDate = null;

super._clean();
Expand All @@ -541,14 +623,15 @@ class MessageList extends Widget<Properties> {
changes.forEach((change) => {
switch (change.type) {
case 'update':
this._updateMessageByKey(change.key, change.data ?? {});
break;
case 'insert': {
const { items } = this.option();

this.option('items', [...items, change.data ?? {}]);
break;
}
case 'remove':
this._removeMessageByKey(change.key);
break;
default:
break;
Expand Down Expand Up @@ -588,7 +671,7 @@ class MessageList extends Widget<Properties> {

getEmptyViewId(): string | null {
if (this._isEmpty()) {
const $emptyView = this._$content.find(`.${CHAT_MESSAGELIST_EMPTY_VIEW_CLASS}`);
const $emptyView = this._getEmptyView();
const emptyViewId = $emptyView.attr('id') ?? null;

return emptyViewId;
Expand Down
Loading

0 comments on commit 54370da

Please sign in to comment.