Skip to content

Commit 63e12fc

Browse files
marker-daomarker dao ®
andauthored
Chat File Attachments Demo: Create framework demos (#31682)
Co-authored-by: marker dao ® <[email protected]>
1 parent 576aaaa commit 63e12fc

File tree

21 files changed

+887
-1
lines changed

21 files changed

+887
-1
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.demo-container {
2+
min-width: 720px;
3+
}
4+
5+
.chat-container {
6+
display: flex;
7+
flex-grow: 1;
8+
align-items: center;
9+
justify-content: center;
10+
}
11+
12+
::ng-deep .dx-chat {
13+
max-width: 480px;
14+
}
15+
16+
::ng-deep .caption {
17+
font-size: var(--dx-font-size-sm);
18+
font-weight: 500;
19+
}
20+
21+
::ng-deep .dx-avatar {
22+
border: 1px solid var(--dx-color-border);
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<div class="demo-container">
2+
<div class="chat-container">
3+
<dx-chat
4+
[dataSource]="dataSource"
5+
[reloadOnChange]="false"
6+
[user]="user"
7+
height="710"
8+
(onMessageEntered)="onMessageEntered($event)"
9+
(onAttachmentDownloadClick)="onAttachmentDownloadClick($event)"
10+
>
11+
<dxo-chat-file-uploader-options
12+
[uploadFile]="uploadFile"
13+
[onUploaded]="onUploaded"
14+
>
15+
</dxo-chat-file-uploader-options>
16+
</dx-chat>
17+
</div>
18+
</div>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { NgModule, Component, enableProdMode } from '@angular/core';
2+
import { BrowserModule } from '@angular/platform-browser';
3+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4+
5+
import { DxChatModule } from 'devextreme-angular';
6+
import { DxChatTypes } from 'devextreme-angular/ui/chat';
7+
import { type DxFileUploaderTypes } from 'devextreme-angular/ui/file-uploader';
8+
import { DataSource } from 'devextreme-angular/common/data';
9+
import { AppService } from './app.service';
10+
11+
if (!document.location.host.includes('localhost')) {
12+
enableProdMode();
13+
}
14+
15+
let modulePrefix = '';
16+
// @ts-expect-error
17+
if (window && window.config?.packageConfigPaths) {
18+
modulePrefix = '/app';
19+
}
20+
21+
@Component({
22+
selector: 'demo-app',
23+
templateUrl: `.${modulePrefix}/app.component.html`,
24+
styleUrls: [`.${modulePrefix}/app.component.css`],
25+
})
26+
export class AppComponent {
27+
dataSource: DataSource;
28+
29+
user: DxChatTypes.User;
30+
31+
constructor(private readonly appService: AppService) {
32+
this.dataSource = this.appService.dataSource;
33+
this.user = this.appService.currentUser;
34+
}
35+
36+
onMessageEntered(e: DxChatTypes.MessageEnteredEvent): void {
37+
this.appService.onMessageEntered(e);
38+
}
39+
40+
onAttachmentDownloadClick(e: DxChatTypes.AttachmentDownloadClickEvent): void {
41+
if (e.attachment) {
42+
this.appService.onAttachmentDownloadClick(e.attachment);
43+
}
44+
}
45+
46+
onUploaded = (e: DxFileUploaderTypes.UploadedEvent): void => {
47+
this.appService.onUploaded(e.file);
48+
};
49+
50+
uploadFile = () => {};
51+
}
52+
53+
@NgModule({
54+
imports: [
55+
BrowserModule,
56+
DxChatModule,
57+
],
58+
declarations: [AppComponent],
59+
bootstrap: [AppComponent],
60+
providers: [AppService],
61+
})
62+
export class AppModule { }
63+
64+
platformBrowserDynamic().bootstrapModule(AppModule);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Injectable } from '@angular/core';
2+
import { type DxChatTypes } from 'devextreme-angular/ui/chat';
3+
import { Guid } from 'devextreme-angular/common';
4+
import { CustomStore, DataSource } from 'devextreme-angular/common/data';
5+
6+
@Injectable()
7+
export class AppService {
8+
date: Date;
9+
10+
customStore: CustomStore;
11+
12+
dataSource: DataSource;
13+
14+
currentUser: DxChatTypes.User = {
15+
id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3',
16+
name: 'John Doe',
17+
};
18+
19+
supportAgent: DxChatTypes.User = {
20+
id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3',
21+
name: 'Support Agent',
22+
avatarUrl: '../../../../images/petersmith.png',
23+
};
24+
25+
messages: DxChatTypes.Message[] = [];
26+
27+
uploadedFilesMap: Map<string, string>;
28+
29+
constructor() {
30+
this.date = new Date();
31+
this.date.setHours(0, 0, 0, 0);
32+
33+
this.messages = [
34+
{
35+
id: new Guid().toString(),
36+
timestamp: new Date(this.getTimestamp(this.date, -7)),
37+
author: this.currentUser,
38+
text: 'Hi! I\'m having trouble accessing my account.\nThe website says my password is incorrect. I\'m sending a few screenshots so you can see where I get the error.',
39+
attachments: [
40+
{
41+
name: 'Pic1.png',
42+
url: '../../../../images/Chat/FileAttachments/Pic1.png',
43+
size: 1024 * 10,
44+
},
45+
{
46+
name: 'Pic2.png',
47+
url: '../../../../images/Chat/FileAttachments/Pic2.png',
48+
size: 1024 * 10,
49+
},
50+
{
51+
name: 'Pic3.png',
52+
url: '../../../../images/Chat/FileAttachments/Pic3.png',
53+
size: 1024 * 10,
54+
},
55+
],
56+
},
57+
{
58+
id: new Guid().toString(),
59+
timestamp: new Date(this.getTimestamp(this.date, -7)),
60+
author: this.supportAgent,
61+
text: 'Hello! Thanks for including screenshots. To restore access, please follow instructions in the attached file.\nLet me know if you need anything else.',
62+
attachments: [
63+
{
64+
name: 'Instructions.pdf',
65+
url: '../../../../images/Chat/FileAttachments/Instructions.pdf',
66+
size: 1024 * 10,
67+
},
68+
],
69+
},
70+
];
71+
72+
this.uploadedFilesMap = new Map();
73+
this.initDataSource();
74+
}
75+
76+
getTimestamp(date: Date, offsetMinutes = 0): number {
77+
return date.getTime() + offsetMinutes * 60000;
78+
}
79+
80+
initDataSource() {
81+
this.customStore = new CustomStore({
82+
key: 'id',
83+
load: async () => this.messages,
84+
insert: async (message) => {
85+
this.messages.push(message);
86+
return message;
87+
},
88+
});
89+
90+
this.dataSource = new DataSource({
91+
store: this.customStore,
92+
paginate: false,
93+
});
94+
}
95+
96+
getFileUrl(filename: string): string | undefined {
97+
return this.uploadedFilesMap.get(filename);
98+
}
99+
100+
onUploaded(file: File) {
101+
const url = URL.createObjectURL(file);
102+
this.uploadedFilesMap.set(file.name, url);
103+
}
104+
105+
onMessageEntered({ message }: DxChatTypes.MessageEnteredEvent): void {
106+
const attachmentsWithUrls = message.attachments?.map((attachment: DxChatTypes.Attachment) => ({
107+
...attachment,
108+
url: this.getFileUrl(attachment.name),
109+
}));
110+
111+
this.dataSource.store().push([{
112+
type: 'insert',
113+
data: {
114+
...message,
115+
id: new Guid().toString(),
116+
attachments: attachmentsWithUrls,
117+
},
118+
}]);
119+
}
120+
121+
onAttachmentDownloadClick(attachment: DxChatTypes.Attachment) {
122+
if (!attachment?.url) {
123+
return;
124+
}
125+
126+
const link = document.createElement('a');
127+
link.setAttribute('href', attachment.url);
128+
link.setAttribute('download', attachment.name);
129+
130+
document.body.appendChild(link);
131+
link.click();
132+
document.body.removeChild(link);
133+
}
134+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3+
<head>
4+
<title>DevExtreme Demo</title>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
8+
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme-dist/css/dx.light.css" />
9+
10+
<script src="../../../../node_modules/core-js/client/shim.min.js"></script>
11+
<script src="../../../../node_modules/zone.js/bundles/zone.umd.js"></script>
12+
<script src="../../../../node_modules/reflect-metadata/Reflect.js"></script>
13+
<script src="../../../../node_modules/systemjs/dist/system.js"></script>
14+
15+
<script src="config.js"></script>
16+
<script>
17+
System.import("app").catch(console.error.bind(console));
18+
</script>
19+
</head>
20+
21+
<body class="dx-viewport">
22+
<div class="demo-container">
23+
<demo-app>Loading...</demo-app>
24+
</div>
25+
</body>
26+
</html>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { useCallback, useRef } from 'react';
2+
import Chat, { FileUploaderOptions, type ChatTypes } from 'devextreme-react/chat';
3+
import { type FileUploaderTypes } from 'devextreme-react/file-uploader';
4+
import { Guid } from 'devextreme-react/common';
5+
import { CustomStore, DataSource } from 'devextreme-react/common/data';
6+
7+
import { currentUser, messages as initialMessages } from './data.ts';
8+
9+
const store: ChatTypes.Message[] = [...initialMessages];
10+
11+
const customStore = new CustomStore({
12+
key: 'id',
13+
load: async () => store,
14+
insert: async (message) => {
15+
store.push(message);
16+
return message;
17+
},
18+
});
19+
20+
const dataSource = new DataSource({
21+
store: customStore,
22+
paginate: false,
23+
});
24+
25+
export default function App() {
26+
const uploadedFilesMapRef = useRef(new Map<string, string>());
27+
28+
function getFileUrl(filename: string): string | undefined {
29+
return uploadedFilesMapRef.current.get(filename);
30+
}
31+
32+
const onUploaded = useCallback((e: FileUploaderTypes.UploadedEvent): void => {
33+
const { file } = e;
34+
const url = URL.createObjectURL(file);
35+
uploadedFilesMapRef.current.set(file.name, url);
36+
}, []);
37+
38+
const onMessageEntered = useCallback((
39+
{ message }: ChatTypes.MessageEnteredEvent,
40+
): void => {
41+
const attachmentsWithUrls = message.attachments?.map((attachment: ChatTypes.Attachment) => ({
42+
...attachment,
43+
url: getFileUrl(attachment.name),
44+
}));
45+
46+
const newMessage = {
47+
id: new Guid().toString(),
48+
...message,
49+
attachments: attachmentsWithUrls,
50+
};
51+
52+
dataSource.store().push([{
53+
type: 'insert',
54+
key: newMessage.id,
55+
data: newMessage,
56+
}]);
57+
}, []);
58+
59+
const onAttachmentDownloadClick = useCallback((
60+
{ attachment }: ChatTypes.AttachmentDownloadClickEvent,
61+
): void => {
62+
if (!attachment?.url) {
63+
return;
64+
}
65+
66+
const link = document.createElement('a');
67+
link.setAttribute('href', attachment.url);
68+
link.setAttribute('download', attachment.name);
69+
70+
document.body.appendChild(link);
71+
link.click();
72+
document.body.removeChild(link);
73+
}, []);
74+
75+
const uploadFile = useCallback((): void => {}, []);
76+
77+
return (
78+
<React.Fragment>
79+
<div className="chat-container">
80+
<Chat
81+
height={710}
82+
dataSource={dataSource}
83+
reloadOnChange={false}
84+
user={currentUser}
85+
onMessageEntered={onMessageEntered}
86+
onAttachmentDownloadClick={onAttachmentDownloadClick}
87+
>
88+
<FileUploaderOptions
89+
uploadFile={uploadFile}
90+
onUploaded={onUploaded}
91+
/>
92+
</Chat>
93+
</div>
94+
</React.Fragment>
95+
);
96+
}

0 commit comments

Comments
 (0)