diff --git a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/data.js b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/data.js index ca66557ff1e..8240e9a0e4c 100644 --- a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/data.js +++ b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/data.js @@ -3,6 +3,8 @@ const apiVersion = '2024-02-01'; const endpoint = 'https://public-api.devexpress.com/demo-openai'; const apiKey = 'DEMO'; const REGENERATION_TEXT = 'Regeneration...'; +const CHAT_DISABLED_CLASS = 'dx-chat-disabled'; +const ALERT_TIMEOUT = 1000 * 60; const user = { id: 'user', }; diff --git a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/index.js b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/index.js index 23c3279605d..861136fb745 100644 --- a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/index.js +++ b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/index.js @@ -25,8 +25,8 @@ $(() => { temperature: 0.7, }; - const responseAzure = await chatService.chat.completions.create(params); - const data = { choices: responseAzure.choices }; + const response = await chatService.chat.completions.create(params); + const data = { choices: response.choices }; return data.choices[0].message?.content; } @@ -40,10 +40,23 @@ $(() => { setTimeout(() => { instance.option({ alerts: [] }); - }, 10000); + }, ALERT_TIMEOUT); } - async function processMessageSending() { + function toggleDisabledState(disabled, event) { + instance.element().toggleClass(CHAT_DISABLED_CLASS, disabled); + + if (disabled) { + event?.target.blur(); + } else { + event?.target.focus(); + } + }; + + async function processMessageSending(message, event) { + toggleDisabledState(true, event); + + messages.push({ role: 'user', content: message.text }); instance.option({ typingUsers: [assistant] }); try { @@ -54,15 +67,20 @@ $(() => { messages.push({ role: 'assistant', content: aiResponse }); - renderMessage(aiResponse); + renderAssistantMessage(aiResponse); }, 200); } catch { instance.option({ typingUsers: [] }); + messages.pop(); alertLimitReached(); + } finally { + toggleDisabledState(false, event); } } async function regenerate() { + toggleDisabledState(true); + try { const aiResponse = await getAIResponse(messages.slice(0, -1)); @@ -71,10 +89,12 @@ $(() => { } catch { updateLastMessage(messages.at(-1).content); alertLimitReached(); + } finally { + toggleDisabledState(false); } } - function renderMessage(text) { + function renderAssistantMessage(text) { const message = { id: Date.now(), timestamp: new Date(), @@ -82,17 +102,17 @@ $(() => { text, }; - customStore.push([{ type: 'insert', data: message }]); + dataSource.store().push([{ type: 'insert', data: message }]); } function updateLastMessage(text) { - const { items } = instance.option(); + const items = dataSource.items(); const lastMessage = items.at(-1); const data = { text: text ?? REGENERATION_TEXT, }; - customStore.push([{ + dataSource.store().push([{ type: 'update', key: lastMessage.id, data, @@ -110,6 +130,57 @@ $(() => { return result; } + function onCopyButtonClick(component, text) { + navigator.clipboard?.writeText(text); + + component.option({ icon: 'check' }); + + setTimeout(() => { + component.option({ icon: 'copy' }); + }, 2500); + } + + function onRegenerateButtonClick() { + if (instance.option('alerts').length) { + return; + } + + updateLastMessage(); + regenerate(); + } + + function renderMessageContent(message, element) { + $('
') + .addClass('dx-chat-messagebubble-text') + .html(convertToHtml(message.text)) + .appendTo(element); + + const $buttonContainer = $('
') + .addClass('dx-bubble-button-container'); + + $('
') + .dxButton({ + icon: 'copy', + stylingMode: 'text', + hint: 'Copy', + onClick: ({ component }) => { + onCopyButtonClick(component, message.text); + }, + }) + .appendTo($buttonContainer); + + $('
') + .dxButton({ + icon: 'refresh', + stylingMode: 'text', + hint: 'Regenerate', + onClick: onRegenerateButtonClick, + }) + .appendTo($buttonContainer); + + $buttonContainer.appendTo(element); + } + const customStore = new DevExpress.data.CustomStore({ key: 'id', load: () => { @@ -132,21 +203,27 @@ $(() => { return d.promise(); }, }); + + const dataSource = new DevExpress.data.DataSource({ + store: customStore, + paginate: false, + }); const instance = $('#dx-ai-chat').dxChat({ - dataSource: customStore, + user, + height: 710, + dataSource, reloadOnChange: false, showAvatar: false, showDayHeaders: false, - user, - height: 710, onMessageEntered: (e) => { - const { message } = e; + const { message, event } = e; - customStore.push([{ type: 'insert', data: { id: Date.now(), ...message } }]); - messages.push({ role: 'user', content: message.text }); - - processMessageSending(); + dataSource.store().push([{ type: 'insert', data: { id: Date.now(), ...message } }]); + + if (!instance.option('alerts').length) { + processMessageSending(message, event); + } }, messageTemplate: (data, element) => { const { message } = data; @@ -156,42 +233,7 @@ $(() => { return; } - const $textElement = $('
') - .addClass('dx-chat-messagebubble-text') - .html(convertToHtml(message.text)) - .appendTo(element); - - const $buttonContainer = $('
') - .addClass('dx-bubble-button-container'); - - $('
') - .dxButton({ - icon: 'copy', - stylingMode: 'text', - hint: 'Copy', - onClick: ({ component }) => { - navigator.clipboard.writeText($textElement.text()); - component.option({ icon: 'check' }); - setTimeout(() => { - component.option({ icon: 'copy' }); - }, 5000); - }, - }) - .appendTo($buttonContainer); - - $('
') - .dxButton({ - icon: 'refresh', - stylingMode: 'text', - hint: 'Regenerate', - onClick: () => { - updateLastMessage(); - regenerate(); - }, - }) - .appendTo($buttonContainer); - - $buttonContainer.appendTo(element); + renderMessageContent(message, element); }, }).dxChat('instance'); }); diff --git a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/styles.css b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/styles.css index 4d6ace3854f..52d39eaa6e2 100644 --- a/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/styles.css +++ b/apps/demos/Demos/Chat/AIAndChatbotIntegration/jQuery/styles.css @@ -58,3 +58,8 @@ font-size: revert; font-weight: revert; } + +.dx-chat-disabled .dx-chat-messagebox { + opacity: 0.5; + pointer-events: none; +} diff --git a/apps/demos/Demos/Chat/Customization/Angular/app/app.component.css b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.css new file mode 100644 index 00000000000..9c2d96ac5b0 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.css @@ -0,0 +1,38 @@ +.demo-container { + min-width: 720px; + display: flex; + gap: 20px; +} + +::ng-deep .chat-container { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; +} + +::ng-deep .options { + padding: 20px; + display: flex; + flex-direction: column; + min-width: 280px; + background-color: rgba(191, 191, 191, 0.15); + gap: 16px; +} + +::ng-deep .dx-chat { + max-width: 480px; +} + +::ng-deep .caption { + font-size: var(--dx-font-size-sm); + font-weight: 500; +} + +::ng-deep .option-separator { + border-bottom: 1px solid var(--dx-color-border); +} + +::ng-deep .dx-avatar { + border: 1px solid var(--dx-color-border); +} diff --git a/apps/demos/Demos/Chat/Customization/Angular/app/app.component.html b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.html new file mode 100644 index 00000000000..f56f172747d --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.html @@ -0,0 +1,79 @@ +
+
+ + +
+ +
+
Options
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ Day Header Format: + +
+ +
+ +
+ +
+ +
+ Message Timestamp Format: + +
+ +
+ +
+ +
+
+
diff --git a/apps/demos/Demos/Chat/Customization/Angular/app/app.component.ts b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.ts new file mode 100644 index 00000000000..4761f32429a --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Angular/app/app.component.ts @@ -0,0 +1,63 @@ +import { NgModule, Component, enableProdMode } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { DxChatModule, DxCheckBoxModule, DxSelectBoxModule } from 'devextreme-angular'; +import { User, Message, MessageEnteredEvent } from 'devextreme/ui/chat'; +import { Observable } from 'rxjs'; +import { AppService } from './app.service'; + +if (!/localhost/.test(document.location.host)) { + enableProdMode(); +} + +let modulePrefix = ''; +// @ts-ignore +if (window && window.config.packageConfigPaths) { + modulePrefix = '/app'; +} + +@Component({ + selector: 'demo-app', + templateUrl: `.${modulePrefix}/app.component.html`, + styleUrls: [`.${modulePrefix}/app.component.css`], +}) +export class AppComponent { + currentUser: User; + + supportAgent: User; + + messages$: Observable; + + dayHeaderFormats = this.appService.dayHeaderFormats; + + messageTimestampFormats = this.appService.messageTimestampFormats; + + dayHeaderLabel = this.appService.dayHeaderLabel; + + messageTimestampLabel = this.appService.messageTimestampLabel; + + constructor(private readonly appService: AppService) { + [this.currentUser, this.supportAgent] = this.appService.getUsers(); + this.messages$ = this.appService.messages$; + } + + onMessageEntered(event: MessageEnteredEvent) { + this.appService.onMessageEntered(event); + } +} + +@NgModule({ + imports: [ + BrowserModule, + DxChatModule, + DxCheckBoxModule, + DxSelectBoxModule, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [AppService], +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/apps/demos/Demos/Chat/Customization/Angular/app/app.service.ts b/apps/demos/Demos/Chat/Customization/Angular/app/app.service.ts new file mode 100644 index 00000000000..76090483287 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Angular/app/app.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { User, Message, MessageEnteredEvent } from 'devextreme/ui/chat'; + +@Injectable({ + providedIn: 'root', +}) +export class AppService { + date: Date; + + currentUser: User = { + id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', + name: 'John Doe', + }; + + supportAgent: User = { + id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3', + name: 'Support Agent', + avatarUrl: '../../../../images/petersmith.png', + }; + + dayHeaderFormats = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; + + messageTimestampFormats = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; + + messageTimestampLabel = { 'aria-label': 'Message Timestamp Format' }; + + dayHeaderLabel = { 'aria-label': 'Day Header Format' }; + + messages: Message[] = []; + + messagesSubject: BehaviorSubject = new BehaviorSubject([]); + + constructor() { + this.date = new Date(); + this.date.setHours(0, 0, 0, 0); + + this.messages = [ + { + timestamp: this.getTimestamp(this.date, -9), + author: this.supportAgent, + text: 'Hello, John!\nHow can I assist you today?', + }, + { + timestamp: this.getTimestamp(this.date, -7), + author: this.currentUser, + text: 'Hi, I\'m having trouble accessing my account.', + }, + { + timestamp: this.getTimestamp(this.date, -7), + author: this.currentUser, + text: 'It says my password is incorrect.', + }, + { + timestamp: this.getTimestamp(this.date, -7), + author: this.supportAgent, + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', + }, + { + timestamp: this.getTimestamp(this.date, 1), + author: this.currentUser, + text: 'john.doe1357', + }, + { + timestamp: this.getTimestamp(this.date, 1), + author: this.supportAgent, + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', + }, + ]; + + this.messagesSubject.next(this.messages); + } + + get messages$(): Observable { + return this.messagesSubject.asObservable(); + } + + getUsers(): User[] { + return [this.currentUser, this.supportAgent]; + } + + getTimestamp(date: Date, offsetMinutes = 0): number { + return date.getTime() + offsetMinutes * 60000; + } + + onMessageEntered(event: MessageEnteredEvent) { + this.messages = [...this.messages, event.message]; + this.messagesSubject.next(this.messages); + } +} diff --git a/apps/demos/Demos/Chat/Customization/Angular/index.html b/apps/demos/Demos/Chat/Customization/Angular/index.html new file mode 100644 index 00000000000..1ab1fb54a1d --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Angular/index.html @@ -0,0 +1,26 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + + + +
+ Loading... +
+ + diff --git a/apps/demos/Demos/Chat/Customization/React/App.tsx b/apps/demos/Demos/Chat/Customization/React/App.tsx new file mode 100644 index 00000000000..0702563c234 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/React/App.tsx @@ -0,0 +1,119 @@ +import React, { useState, useCallback } from 'react'; +import Chat, { ChatTypes } from 'devextreme-react/chat'; +import { MessageEnteredEvent } from 'devextreme/ui/chat'; +import SelectBox from 'devextreme-react/select-box'; +import CheckBox from 'devextreme-react/check-box'; + +import { + currentUser, + messages as initialMessages, + dayHeaderFormats as headerFormats, + messageTimestampFormats as messageTimestamps, + messageTimestampLabel, + dayHeaderLabel, +} from './data.ts'; + +export default function App() { + const [messages, setMessages] = useState(initialMessages); + const [showAvatar, setShowAvatar] = useState(true); + const [showUsername, setShowUsername] = useState(true); + const [showDayHeaders, setDayHeaders] = useState(true); + const [dayHeaderFormat, setDayHeaderFormat] = useState(headerFormats[0]); + const [showMessageTimestamp, setMessageTimestamp] = useState(true); + const [messageTimestampFormat, setMessageTimestampFormat] = useState(messageTimestamps[0]); + const [isDisabled, setDisabled] = useState(false); + + const onMessageEntered = useCallback(({ message }: MessageEnteredEvent) => { + setMessages((prevMessages) => [...prevMessages, message]); + }, []); + + return ( + +
+ +
+ +
+
Options
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ Day Header Format: + +
+ +
+ +
+ +
+ +
+ Message Timestamp Format: + +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/apps/demos/Demos/Chat/Customization/React/data.ts b/apps/demos/Demos/Chat/Customization/React/data.ts new file mode 100644 index 00000000000..de004f28c98 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/React/data.ts @@ -0,0 +1,57 @@ +import { ChatTypes } from 'devextreme-react/chat'; + +function getTimestamp(date, offsetMinutes = 0) { + return date.getTime() + offsetMinutes * 60000; +} + +const date = new Date(); +date.setHours(0, 0, 0, 0); + +export const currentUser: ChatTypes.User = { + id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', + name: 'John Doe', +}; + +export const supportAgent: ChatTypes.User = { + id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3', + name: 'Support Agent', + avatarUrl: '../../../../images/petersmith.png', +}; + +export const messages = [ + { + timestamp: getTimestamp(date, -9), + author: supportAgent, + text: 'Hello, John!\nHow can I assist you today?', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'Hi, I\'m having trouble accessing my account.', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'It says my password is incorrect.', + }, + { + timestamp: getTimestamp(date, -7), + author: supportAgent, + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', + }, + { + timestamp: getTimestamp(date, 1), + author: currentUser, + text: 'john.doe1357', + }, + { + timestamp: getTimestamp(date, 1), + author: supportAgent, + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', + }, +]; + +export const dayHeaderFormats = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; +export const messageTimestampFormats = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; +export const messageTimestampLabel = { 'aria-label': 'Message Timestamp Format' }; +export const dayHeaderLabel = { 'aria-label': 'Day Header Format' }; diff --git a/apps/demos/Demos/Chat/Customization/React/index.html b/apps/demos/Demos/Chat/Customization/React/index.html new file mode 100644 index 00000000000..e90d25ad879 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/React/index.html @@ -0,0 +1,24 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/Chat/Customization/React/index.tsx b/apps/demos/Demos/Chat/Customization/React/index.tsx new file mode 100644 index 00000000000..8acbec4b617 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/React/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App.tsx'; + +ReactDOM.render( + , + document.getElementById('app'), +); diff --git a/apps/demos/Demos/Chat/Customization/React/styles.css b/apps/demos/Demos/Chat/Customization/React/styles.css new file mode 100644 index 00000000000..7a5b25461db --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/React/styles.css @@ -0,0 +1,38 @@ +#app { + min-width: 720px; + display: flex; + gap: 20px; +} + +.chat-container { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; +} + +.options { + padding: 20px; + display: flex; + flex-direction: column; + min-width: 280px; + background-color: rgba(191, 191, 191, 0.15); + gap: 16px; +} + +.dx-chat { + max-width: 480px; +} + +.caption { + font-size: var(--dx-font-size-sm); + font-weight: 500; +} + +.option-separator { + border-bottom: 1px solid var(--dx-color-border); +} + +.dx-avatar { + border: 1px solid var(--dx-color-border); +} diff --git a/apps/demos/Demos/Chat/Customization/ReactJs/App.js b/apps/demos/Demos/Chat/Customization/ReactJs/App.js new file mode 100644 index 00000000000..92f10777d68 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/ReactJs/App.js @@ -0,0 +1,115 @@ +import React, { useState, useCallback } from 'react'; +import Chat from 'devextreme-react/chat'; +import SelectBox from 'devextreme-react/select-box'; +import CheckBox from 'devextreme-react/check-box'; +import { + currentUser, + messages as initialMessages, + dayHeaderFormats as headerFormats, + messageTimestampFormats as messageTimestamps, + messageTimestampLabel, + dayHeaderLabel, +} from './data.js'; + +export default function App() { + const [messages, setMessages] = useState(initialMessages); + const [showAvatar, setShowAvatar] = useState(true); + const [showUsername, setShowUsername] = useState(true); + const [showDayHeaders, setDayHeaders] = useState(true); + const [dayHeaderFormat, setDayHeaderFormat] = useState(headerFormats[0]); + const [showMessageTimestamp, setMessageTimestamp] = useState(true); + const [messageTimestampFormat, setMessageTimestampFormat] = useState(messageTimestamps[0]); + const [isDisabled, setDisabled] = useState(false); + const onMessageEntered = useCallback(({ message }) => { + setMessages((prevMessages) => [...prevMessages, message]); + }, []); + return ( + +
+ +
+ +
+
Options
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ Day Header Format: + +
+ +
+ +
+ +
+ +
+ Message Timestamp Format: + +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/apps/demos/Demos/Chat/Customization/ReactJs/data.js b/apps/demos/Demos/Chat/Customization/ReactJs/data.js new file mode 100644 index 00000000000..40b37bf56ff --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/ReactJs/data.js @@ -0,0 +1,50 @@ +function getTimestamp(date, offsetMinutes = 0) { + return date.getTime() + offsetMinutes * 60000; +} +const date = new Date(); +date.setHours(0, 0, 0, 0); +export const currentUser = { + id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', + name: 'John Doe', +}; +export const supportAgent = { + id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3', + name: 'Support Agent', + avatarUrl: '../../../../images/petersmith.png', +}; +export const messages = [ + { + timestamp: getTimestamp(date, -9), + author: supportAgent, + text: 'Hello, John!\nHow can I assist you today?', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: "Hi, I'm having trouble accessing my account.", + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'It says my password is incorrect.', + }, + { + timestamp: getTimestamp(date, -7), + author: supportAgent, + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', + }, + { + timestamp: getTimestamp(date, 1), + author: currentUser, + text: 'john.doe1357', + }, + { + timestamp: getTimestamp(date, 1), + author: supportAgent, + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', + }, +]; +export const dayHeaderFormats = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; +export const messageTimestampFormats = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; +export const messageTimestampLabel = { 'aria-label': 'Message Timestamp Format' }; +export const dayHeaderLabel = { 'aria-label': 'Day Header Format' }; diff --git a/apps/demos/Demos/Chat/Customization/ReactJs/index.html b/apps/demos/Demos/Chat/Customization/ReactJs/index.html new file mode 100644 index 00000000000..c9ee055d50a --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/ReactJs/index.html @@ -0,0 +1,44 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/Chat/Customization/ReactJs/index.js b/apps/demos/Demos/Chat/Customization/ReactJs/index.js new file mode 100644 index 00000000000..b853e0be824 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/ReactJs/index.js @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App.js'; + +ReactDOM.render(, document.getElementById('app')); diff --git a/apps/demos/Demos/Chat/Customization/ReactJs/styles.css b/apps/demos/Demos/Chat/Customization/ReactJs/styles.css new file mode 100644 index 00000000000..7a5b25461db --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/ReactJs/styles.css @@ -0,0 +1,38 @@ +#app { + min-width: 720px; + display: flex; + gap: 20px; +} + +.chat-container { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; +} + +.options { + padding: 20px; + display: flex; + flex-direction: column; + min-width: 280px; + background-color: rgba(191, 191, 191, 0.15); + gap: 16px; +} + +.dx-chat { + max-width: 480px; +} + +.caption { + font-size: var(--dx-font-size-sm); + font-weight: 500; +} + +.option-separator { + border-bottom: 1px solid var(--dx-color-border); +} + +.dx-avatar { + border: 1px solid var(--dx-color-border); +} diff --git a/apps/demos/Demos/Chat/Customization/Vue/App.vue b/apps/demos/Demos/Chat/Customization/Vue/App.vue new file mode 100644 index 00000000000..2299a9350d1 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Vue/App.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/apps/demos/Demos/Chat/Customization/Vue/data.ts b/apps/demos/Demos/Chat/Customization/Vue/data.ts new file mode 100644 index 00000000000..de0ab54cf1e --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Vue/data.ts @@ -0,0 +1,55 @@ +const date = new Date(); +date.setHours(0, 0, 0, 0); + +function getTimestamp(date, offsetMinutes = 0) { + return date.getTime() + offsetMinutes * 60000; +} + +export const currentUser = { + id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', + name: 'John Doe', +}; + +export const supportAgent = { + id: 'd16d1a4c-5c67-4e20-b7v0e-2991c22747c3', + name: 'Support Agent', + avatarUrl: '../../../../images/petersmith.png', +}; + +export const messages = [ + { + timestamp: getTimestamp(date, -9), + author: supportAgent, + text: 'Hello, John!\nHow can I assist you today?', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'Hi, I\'m having trouble accessing my account.', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'It says my password is incorrect.', + }, + { + timestamp: getTimestamp(date, -7), + author: supportAgent, + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', + }, + { + timestamp: getTimestamp(date, 1), + author: currentUser, + text: 'john.doe1357', + }, + { + timestamp: getTimestamp(date, 1), + author: supportAgent, + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', + }, +]; + +export const dayHeaderFormats = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; +export const messageTimestampFormats = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; +export const messageTimestampLabel = { 'aria-label': 'Message Timestamp Format' }; +export const dayHeaderLabel = { 'aria-label': 'Day Header Format' }; diff --git a/apps/demos/Demos/Chat/Customization/Vue/index.html b/apps/demos/Demos/Chat/Customization/Vue/index.html new file mode 100644 index 00000000000..2413f2254bf --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Vue/index.html @@ -0,0 +1,29 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/Chat/Customization/Vue/index.ts b/apps/demos/Demos/Chat/Customization/Vue/index.ts new file mode 100644 index 00000000000..684d04215d7 --- /dev/null +++ b/apps/demos/Demos/Chat/Customization/Vue/index.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue'; +import App from './App.vue'; + +createApp(App).mount('#app'); diff --git a/apps/demos/Demos/Chat/Customization/jQuery/data.js b/apps/demos/Demos/Chat/Customization/jQuery/data.js index d08b0148f92..a1fac597078 100644 --- a/apps/demos/Demos/Chat/Customization/jQuery/data.js +++ b/apps/demos/Demos/Chat/Customization/jQuery/data.js @@ -49,5 +49,5 @@ const messages = [ }, ]; -const dayHeaderFormat = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; -const messageTimestampFormat = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; +const dayHeaderFormats = ['dd/MM/yyyy', 'dd.MM.yyyy', 'MMMM dd, yyyy', 'EEEE, MMMM dd']; +const messageTimestampFormats = ['hh:mm a', 'hh:mm:ss a', 'HH:mm', 'HH:mm:ss']; diff --git a/apps/demos/Demos/Chat/Customization/jQuery/index.html b/apps/demos/Demos/Chat/Customization/jQuery/index.html index 982e6920c30..3e7795ca02b 100644 --- a/apps/demos/Demos/Chat/Customization/jQuery/index.html +++ b/apps/demos/Demos/Chat/Customization/jQuery/index.html @@ -36,8 +36,8 @@
- Day Headers Format: -
+ Day Header Format: +
diff --git a/apps/demos/Demos/Chat/Customization/jQuery/index.js b/apps/demos/Demos/Chat/Customization/jQuery/index.js index b6381e0deed..60d457efe70 100644 --- a/apps/demos/Demos/Chat/Customization/jQuery/index.js +++ b/apps/demos/Demos/Chat/Customization/jQuery/index.js @@ -3,16 +3,16 @@ $(() => { height: 710, items: messages, user: currentUser, - dayHeaderFormat: dayHeaderFormat[0], + dayHeaderFormat: dayHeaderFormats[0], + messageTimestampFormat: messageTimestampFormats[0], onMessageEntered({ component, message }) { component.renderMessage(message); }, - messageTimestampFormat: messageTimestampFormat[0], }).dxChat('instance'); $('#show-avatar').dxCheckBox({ value: true, - text: 'Show Avatar', + text: 'Avatar', onValueChanged(data) { chat.option('showAvatar', data.value); }, @@ -20,7 +20,7 @@ $(() => { $('#show-user-name').dxCheckBox({ value: true, - text: 'Show User Name', + text: 'User Name', onValueChanged(data) { chat.option('showUserName', data.value); }, @@ -28,16 +28,16 @@ $(() => { $('#show-day-headers').dxCheckBox({ value: true, - text: 'Show Day Headers', + text: 'Day Headers', onValueChanged(data) { chat.option('showDayHeaders', data.value); }, }); - $('#day-headers-format').dxSelectBox({ - items: dayHeaderFormat, - value: dayHeaderFormat[0], - inputAttr: { 'aria-label': 'Day Headers Format' }, + $('#day-header-format').dxSelectBox({ + items: dayHeaderFormats, + value: dayHeaderFormats[0], + inputAttr: { 'aria-label': 'Day Header Format' }, onValueChanged(data) { chat.option('dayHeaderFormat', data.value); }, @@ -45,15 +45,15 @@ $(() => { $('#show-message-timestamp').dxCheckBox({ value: true, - text: 'Show Message Timestamp', + text: 'Message Timestamp', onValueChanged(data) { chat.option('showMessageTimestamp', data.value); }, }); $('#message-timestamp-format').dxSelectBox({ - items: messageTimestampFormat, - value: messageTimestampFormat[0], + items: messageTimestampFormats, + value: messageTimestampFormats[0], inputAttr: { 'aria-label': 'Message Timestamp Format' }, onValueChanged(data) { chat.option('messageTimestampFormat', data.value); diff --git a/apps/demos/Demos/Chat/Overview/Angular/app/app.component.ts b/apps/demos/Demos/Chat/Overview/Angular/app/app.component.ts index d7524f752de..441a9da8424 100644 --- a/apps/demos/Demos/Chat/Overview/Angular/app/app.component.ts +++ b/apps/demos/Demos/Chat/Overview/Angular/app/app.component.ts @@ -4,8 +4,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { DxChatModule } from 'devextreme-angular'; import { User, Message, MessageEnteredEvent } from 'devextreme/ui/chat'; -import { AppService } from './app.service'; import { Observable } from 'rxjs'; +import { AppService } from './app.service'; if (!/localhost/.test(document.location.host)) { enableProdMode(); @@ -24,12 +24,16 @@ if (window && window.config.packageConfigPaths) { }) export class AppComponent { currentUser: User; + supportAgent: User; + messages$: Observable; + userChatTypingUsers$: Observable; + supportChatTypingUsers$: Observable; - constructor(private appService: AppService) { + constructor(private readonly appService: AppService) { [this.currentUser, this.supportAgent] = this.appService.getUsers(); this.messages$ = this.appService.messages$; this.userChatTypingUsers$ = this.appService.userChatTypingUsers$; diff --git a/apps/demos/Demos/Chat/Overview/Angular/app/app.service.ts b/apps/demos/Demos/Chat/Overview/Angular/app/app.service.ts index 890f1fd173f..2c886093144 100644 --- a/apps/demos/Demos/Chat/Overview/Angular/app/app.service.ts +++ b/apps/demos/Demos/Chat/Overview/Angular/app/app.service.ts @@ -23,6 +23,7 @@ export class AppService { messages: Message[] = []; userChatTypingUsersSubject: BehaviorSubject = new BehaviorSubject([]); + supportChatTypingUsersSubject: BehaviorSubject = new BehaviorSubject([]); messagesSubject: BehaviorSubject = new BehaviorSubject([]); @@ -35,32 +36,32 @@ export class AppService { { timestamp: this.getTimestamp(this.date, -9), author: this.supportAgent, - text: 'Hello, John!\nHow can I assist you today?' + text: 'Hello, John!\nHow can I assist you today?', }, { timestamp: this.getTimestamp(this.date, -7), author: this.currentUser, - text: 'Hi, I\'m having trouble accessing my account.' + text: 'Hi, I\'m having trouble accessing my account.', }, { timestamp: this.getTimestamp(this.date, -7), author: this.currentUser, - text: 'It says my password is incorrect.' + text: 'It says my password is incorrect.', }, { timestamp: this.getTimestamp(this.date, -7), author: this.supportAgent, - text: 'I can help you with that. Can you please confirm your UserID for security purposes?' + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', }, { timestamp: this.getTimestamp(this.date, 1), author: this.currentUser, - text: 'john.doe1357' + text: 'john.doe1357', }, { timestamp: this.getTimestamp(this.date, 1), author: this.supportAgent, - text: '✅ Instructions to restore access have been sent to the email address associated with your account.' + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', }, ]; @@ -85,7 +86,7 @@ export class AppService { return [this.currentUser, this.supportAgent]; } - getTimestamp(date: Date, offsetMinutes: number = 0): number { + getTimestamp(date: Date, offsetMinutes = 0): number { return date.getTime() + offsetMinutes * 60000; } diff --git a/apps/demos/Demos/Chat/Overview/React/App.tsx b/apps/demos/Demos/Chat/Overview/React/App.tsx index 57cba74af15..4fc1a0f2e8a 100644 --- a/apps/demos/Demos/Chat/Overview/React/App.tsx +++ b/apps/demos/Demos/Chat/Overview/React/App.tsx @@ -11,7 +11,7 @@ export default function App() { const [messages, setMessages] = useState(initialMessages); function onMessageEntered({ message }: MessageEnteredEvent) { - setMessages(prevMessages => [...prevMessages, message]); + setMessages((prevMessages) => [...prevMessages, message]); } function typingStart({ user }: TypingStartEvent) { diff --git a/apps/demos/Demos/Chat/Overview/React/data.ts b/apps/demos/Demos/Chat/Overview/React/data.ts index 45bc06d30ea..7d233683b19 100644 --- a/apps/demos/Demos/Chat/Overview/React/data.ts +++ b/apps/demos/Demos/Chat/Overview/React/data.ts @@ -22,31 +22,31 @@ export const initialMessages = [ { timestamp: getTimestamp(date, -9), author: supportAgent, - text: 'Hello, John!\nHow can I assist you today?' + text: 'Hello, John!\nHow can I assist you today?', }, { timestamp: getTimestamp(date, -7), author: currentUser, - text: 'Hi, I\'m having trouble accessing my account.' + text: 'Hi, I\'m having trouble accessing my account.', }, { timestamp: getTimestamp(date, -7), author: currentUser, - text: 'It says my password is incorrect.' + text: 'It says my password is incorrect.', }, { timestamp: getTimestamp(date, -7), author: supportAgent, - text: 'I can help you with that. Can you please confirm your UserID for security purposes?' + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', }, { timestamp: getTimestamp(date, 1), author: currentUser, - text: 'john.doe1357' + text: 'john.doe1357', }, { timestamp: getTimestamp(date, 1), author: supportAgent, - text: '✅ Instructions to restore access have been sent to the email address associated with your account.' + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', }, ]; diff --git a/apps/demos/Demos/Chat/Overview/Vue/App.vue b/apps/demos/Demos/Chat/Overview/Vue/App.vue index 0141f6fff78..3fcf8fe9a28 100644 --- a/apps/demos/Demos/Chat/Overview/Vue/App.vue +++ b/apps/demos/Demos/Chat/Overview/Vue/App.vue @@ -20,11 +20,7 @@