@tellescope/chat 1.2.5 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/chat.d.ts +11 -14
- package/lib/cjs/chat.d.ts.map +1 -1
- package/lib/cjs/chat.js +51 -64
- package/lib/cjs/chat.js.map +1 -1
- package/lib/cjs/components.d.ts +15 -0
- package/lib/cjs/components.d.ts.map +1 -0
- package/lib/cjs/components.js +76 -0
- package/lib/cjs/components.js.map +1 -0
- package/lib/cjs/components.native.d.ts +9 -0
- package/lib/cjs/components.native.d.ts.map +1 -0
- package/lib/cjs/components.native.js +169 -0
- package/lib/cjs/components.native.js.map +1 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/index.native.d.ts +1 -0
- package/lib/cjs/index.native.d.ts.map +1 -1
- package/lib/cjs/index.native.js +1 -0
- package/lib/cjs/index.native.js.map +1 -1
- package/lib/esm/chat.d.ts +11 -14
- package/lib/esm/chat.d.ts.map +1 -1
- package/lib/esm/chat.js +50 -39
- package/lib/esm/chat.js.map +1 -1
- package/lib/esm/components.d.ts +15 -0
- package/lib/esm/components.d.ts.map +1 -0
- package/lib/esm/components.js +49 -0
- package/lib/esm/components.js.map +1 -0
- package/lib/esm/components.native.d.ts +9 -0
- package/lib/esm/components.native.d.ts.map +1 -0
- package/lib/esm/components.native.js +165 -0
- package/lib/esm/components.native.js.map +1 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/index.native.d.ts +1 -0
- package/lib/esm/index.native.d.ts.map +1 -1
- package/lib/esm/index.native.js +1 -0
- package/lib/esm/index.native.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -8
- package/src/chat.tsx +75 -93
- package/src/components.native.tsx +174 -0
- package/src/components.tsx +88 -0
- package/src/index.native.ts +2 -1
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/chat",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
7
|
+
"react-native": "./lib/esm/index.js",
|
|
7
8
|
"types": "./lib/esm/index.d.ts",
|
|
8
9
|
"scripts": {
|
|
9
10
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -33,13 +34,13 @@
|
|
|
33
34
|
"@fontsource/roboto": "^4.5.1",
|
|
34
35
|
"@mui/icons-material": "^5.0.1",
|
|
35
36
|
"@mui/material": "^5.0.2",
|
|
36
|
-
"@tellescope/constants": "^1.2
|
|
37
|
-
"@tellescope/react-components": "^1.2
|
|
38
|
-
"@tellescope/sdk": "^1.2
|
|
39
|
-
"@tellescope/types-client": "^1.2
|
|
40
|
-
"@tellescope/types-models": "^1.2
|
|
37
|
+
"@tellescope/constants": "^1.3.2",
|
|
38
|
+
"@tellescope/react-components": "^1.3.2",
|
|
39
|
+
"@tellescope/sdk": "^1.3.2",
|
|
40
|
+
"@tellescope/types-client": "^1.3.2",
|
|
41
|
+
"@tellescope/types-models": "^1.3.2",
|
|
41
42
|
"@tellescope/types-utilities": "^1.2.2",
|
|
42
|
-
"@tellescope/utilities": "^1.2
|
|
43
|
+
"@tellescope/utilities": "^1.3.2",
|
|
43
44
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
|
44
45
|
"@typescript-eslint/parser": "^4.33.0",
|
|
45
46
|
"eslint": "^7.32.0",
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
51
52
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
52
53
|
},
|
|
53
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "df1bc04efdaa81af6df1ecb189630bb9b83f5ef2",
|
|
54
55
|
"publishConfig": {
|
|
55
56
|
"access": "public"
|
|
56
57
|
}
|
package/src/chat.tsx
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import React, { useState, CSSProperties, useEffect } from "react"
|
|
1
|
+
import React, { useState, CSSProperties, useEffect, useRef } from "react"
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
AsyncIconButton,
|
|
5
4
|
DisplayPicture,
|
|
6
5
|
IN_REACT_WEB,
|
|
7
6
|
List,
|
|
8
7
|
Flex,
|
|
9
8
|
Badge,
|
|
10
|
-
SendIcon,
|
|
11
9
|
Styled,
|
|
12
10
|
Typography,
|
|
13
|
-
TextField,
|
|
14
11
|
LoadingLinear,
|
|
15
12
|
value_is_loaded,
|
|
16
13
|
useSession,
|
|
@@ -22,6 +19,7 @@ import {
|
|
|
22
19
|
ChatRoomDisplayInfo,
|
|
23
20
|
SecureImage,
|
|
24
21
|
ImageDimensions,
|
|
22
|
+
SecureVideo,
|
|
25
23
|
} from "@tellescope/react-components"
|
|
26
24
|
|
|
27
25
|
import {
|
|
@@ -52,6 +50,7 @@ import {
|
|
|
52
50
|
Session,
|
|
53
51
|
EnduserSession,
|
|
54
52
|
} from "@tellescope/sdk"
|
|
53
|
+
import { SendMessage } from "./components"
|
|
55
54
|
|
|
56
55
|
export {
|
|
57
56
|
user_display_name, // for convenience
|
|
@@ -114,6 +113,10 @@ interface MessageStyles {
|
|
|
114
113
|
receivedMessageTextStyle?: CSSProperties,
|
|
115
114
|
sentMessageStyle?: CSSProperties,
|
|
116
115
|
sentMessageTextStyle?: CSSProperties,
|
|
116
|
+
sentBgColor?: string,
|
|
117
|
+
sentTextColor?: string,
|
|
118
|
+
receivedBgColor?: string,
|
|
119
|
+
receivedTextColor?: string,
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
interface MessageProps extends MessageStyles {
|
|
@@ -124,6 +127,10 @@ interface MessageProps extends MessageStyles {
|
|
|
124
127
|
export const Message = ({
|
|
125
128
|
message,
|
|
126
129
|
iconSize=30,
|
|
130
|
+
sentBgColor,
|
|
131
|
+
sentTextColor,
|
|
132
|
+
receivedBgColor,
|
|
133
|
+
receivedTextColor,
|
|
127
134
|
sentMessageStyle=defaultSentStyle,
|
|
128
135
|
receivedMessageStyle=defaultReceivedStyle,
|
|
129
136
|
sentMessageTextStyle=defaultSentTextStyle,
|
|
@@ -143,6 +150,22 @@ export const Message = ({
|
|
|
143
150
|
|
|
144
151
|
if (!message.message) {
|
|
145
152
|
textBGStyle.backgroundColor = undefined
|
|
153
|
+
} else {
|
|
154
|
+
if (message.senderId === chatUserId) {
|
|
155
|
+
if (sentBgColor) {
|
|
156
|
+
textBGStyle.backgroundColor = sentBgColor;
|
|
157
|
+
}
|
|
158
|
+
if (sentTextColor) {
|
|
159
|
+
textBGStyle.color = sentTextColor;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
if (receivedBgColor) {
|
|
163
|
+
textBGStyle.backgroundColor = receivedBgColor;
|
|
164
|
+
}
|
|
165
|
+
if (receivedTextColor) {
|
|
166
|
+
textBGStyle.color = receivedTextColor;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
146
169
|
}
|
|
147
170
|
|
|
148
171
|
const attachments = (
|
|
@@ -184,24 +207,38 @@ export const Message = ({
|
|
|
184
207
|
export const MessageAttachments = ({ message, chatUserId, imageDimensions } : { message: ChatMessage, chatUserId: string, imageDimensions?: ImageDimensions }) => {
|
|
185
208
|
if (!message.attachments) return null
|
|
186
209
|
if (message.attachments.length === 0) return null
|
|
187
|
-
|
|
210
|
+
|
|
188
211
|
return (
|
|
189
212
|
<Flex column alignSelf={message.senderId === chatUserId ? "flex-end" : "flex-start"}>
|
|
190
213
|
{message.attachments.filter(a => a.type === 'image').map(a=> (
|
|
191
|
-
<Flex key={a.secureName} style={{
|
|
192
|
-
|
|
214
|
+
<Flex key={a.secureName} style={{
|
|
215
|
+
marginRight: message.senderId === chatUserId ? 0 : 10,
|
|
216
|
+
marginLeft: message.senderId === chatUserId ? 10 : 0,
|
|
217
|
+
justifyContent: message.senderId === chatUserId ? "flex-end" : "flex-start",
|
|
218
|
+
}}>
|
|
219
|
+
<SecureImage secureName={a.secureName} alt="image attachment" {...imageDimensions} />
|
|
220
|
+
</Flex>
|
|
221
|
+
))}
|
|
222
|
+
{message.attachments.filter(a => a.type === 'video').map(a=> (
|
|
223
|
+
<Flex key={a.secureName} style={{
|
|
224
|
+
justifyContent: message.senderId === chatUserId ? "flex-end" : "flex-start",
|
|
225
|
+
}}>
|
|
226
|
+
<SecureVideo secureName={a.secureName} {...imageDimensions} />
|
|
193
227
|
</Flex>
|
|
194
228
|
))}
|
|
195
229
|
</Flex>
|
|
196
230
|
)
|
|
197
231
|
}
|
|
198
232
|
|
|
233
|
+
export type MessageTheme = { theme?: string }
|
|
234
|
+
|
|
199
235
|
interface Messages_T extends MessageStyles {
|
|
200
236
|
resolveSenderName?: (room: ChatRoom) => React.ReactNode;
|
|
201
237
|
messages: LoadedData<ChatMessage[]>,
|
|
202
238
|
chatUserId: string,
|
|
203
239
|
headerProps?: MessagesHeaderProps,
|
|
204
240
|
imageDimensions?: ImageDimensions,
|
|
241
|
+
markRead?: boolean,
|
|
205
242
|
}
|
|
206
243
|
export const MessagesWithHeader = ({
|
|
207
244
|
resolveSenderName,
|
|
@@ -212,7 +249,7 @@ export const MessagesWithHeader = ({
|
|
|
212
249
|
style,
|
|
213
250
|
imageDimensions,
|
|
214
251
|
...messageStyles
|
|
215
|
-
}: Messages_T & Styled & { Header?: React.JSXElementConstructor<MessagesHeaderProps> }) => (
|
|
252
|
+
}: Omit<Messages_T, 'markRead'> & Styled & { Header?: React.JSXElementConstructor<MessagesHeaderProps> }) => (
|
|
216
253
|
<LoadingLinear data={messages} render={messages => (
|
|
217
254
|
<Flex column flex={1} style={{ ...style, overflowY: 'scroll' }}>
|
|
218
255
|
{Header && <Header {...headerProps}/>}
|
|
@@ -232,14 +269,37 @@ export const Messages = ({
|
|
|
232
269
|
headerProps,
|
|
233
270
|
style,
|
|
234
271
|
imageDimensions,
|
|
272
|
+
markRead,
|
|
235
273
|
...messageStyles
|
|
236
|
-
}: Messages_T & Styled) =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
)
|
|
274
|
+
}: Messages_T & Styled) => {
|
|
275
|
+
const session = useResolvedSession()
|
|
276
|
+
const [, { updateLocalElement: updateRoom }] = useChatRooms()
|
|
277
|
+
const markReadRef = useRef(false)
|
|
278
|
+
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (!markRead) return
|
|
281
|
+
if (!value_is_loaded(messages)) return
|
|
282
|
+
if (messages.value.length === 0) return
|
|
283
|
+
|
|
284
|
+
if (markReadRef.current) return
|
|
285
|
+
markReadRef.current = true
|
|
286
|
+
|
|
287
|
+
session.api.chat_rooms.mark_read({ id: messages.value[0].roomId })
|
|
288
|
+
.then(({ updated }) => {
|
|
289
|
+
updateRoom(updated.id, updated)
|
|
290
|
+
})
|
|
291
|
+
.catch(console.error)
|
|
292
|
+
}, [session, markRead, messages, markReadRef, updateRoom])
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<LoadingLinear data={messages} render={messages => (
|
|
297
|
+
<List reverse items={messages} style={style} render={(message) => (
|
|
298
|
+
<Message message={message} imageDimensions={imageDimensions} {...messageStyles} />
|
|
299
|
+
)}/>
|
|
300
|
+
)}/>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
243
303
|
|
|
244
304
|
const defaultSidebarStyle: CSSProperties = {
|
|
245
305
|
borderRadius: 5,
|
|
@@ -351,84 +411,6 @@ export const EndusersConversations = ({ enduserId, ...p } : SidebarInfo & { endu
|
|
|
351
411
|
// deprecated while Conversations relies on useResolvedSession
|
|
352
412
|
export const UsersConversations = ({ userId, ...p } : SidebarInfo & { userId: string }) => <Conversations {...p} rooms={useChatRooms()[0]}/>
|
|
353
413
|
|
|
354
|
-
interface SendMessage_T {
|
|
355
|
-
roomId: string,
|
|
356
|
-
onNewMessage?: (m: ChatMessage) => void;
|
|
357
|
-
placeholderText?: string;
|
|
358
|
-
Icon?: React.ElementType<any>;
|
|
359
|
-
style?: CSSProperties;
|
|
360
|
-
|
|
361
|
-
// web only
|
|
362
|
-
sendOnEnterPress?: boolean,
|
|
363
|
-
multiline?: boolean,
|
|
364
|
-
maxRows?: number,
|
|
365
|
-
}
|
|
366
|
-
export const SendMessage = ({
|
|
367
|
-
roomId,
|
|
368
|
-
Icon=SendIcon,
|
|
369
|
-
onNewMessage,
|
|
370
|
-
placeholderText="Enter a message",
|
|
371
|
-
style={},
|
|
372
|
-
sendOnEnterPress,
|
|
373
|
-
multiline,
|
|
374
|
-
maxRows,
|
|
375
|
-
}: SendMessage_T) => {
|
|
376
|
-
const [message, setMessage] = useState('')
|
|
377
|
-
const [sending, setSending] = useState(false)
|
|
378
|
-
|
|
379
|
-
const [disabled, setDisabled] = useState(false)
|
|
380
|
-
const [chatFocused, setChatFocused] = React.useState(false)
|
|
381
|
-
|
|
382
|
-
const [, { createElement: createMessage }] = useChats(roomId)
|
|
383
|
-
|
|
384
|
-
useEffect(() => {
|
|
385
|
-
if (!chatFocused) return
|
|
386
|
-
if (!sendOnEnterPress) return
|
|
387
|
-
if (typeof window === 'undefined') return
|
|
388
|
-
|
|
389
|
-
const handleSend = (e: any) => {
|
|
390
|
-
if (e.key !== 'Enter') return
|
|
391
|
-
setDisabled(true)
|
|
392
|
-
|
|
393
|
-
createMessage({ message, roomId })
|
|
394
|
-
.then(m => {
|
|
395
|
-
setMessage('')
|
|
396
|
-
onNewMessage?.(m)
|
|
397
|
-
})
|
|
398
|
-
.catch(console.error)
|
|
399
|
-
.finally(() => setDisabled(false))
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
window.addEventListener('keypress', handleSend)
|
|
403
|
-
return () => { window.removeEventListener('keypress', handleSend) }
|
|
404
|
-
}, [sendOnEnterPress, chatFocused, message, roomId])
|
|
405
|
-
|
|
406
|
-
return (
|
|
407
|
-
<Flex row flex={1} alignContent="center" style={style}>
|
|
408
|
-
<Flex column flex={1}>
|
|
409
|
-
<TextField variant="outlined" value={message} onChange={setMessage} disabled={sending}
|
|
410
|
-
aria-label="Enter a message"
|
|
411
|
-
multiline={multiline} maxRows={maxRows}
|
|
412
|
-
placeholder={placeholderText}
|
|
413
|
-
onFocus={() => setChatFocused(true)}
|
|
414
|
-
onBlur={() => setChatFocused(false)}
|
|
415
|
-
/>
|
|
416
|
-
</Flex>
|
|
417
|
-
<Flex column alignSelf="center">
|
|
418
|
-
<AsyncIconButton label="send" Icon={Icon}
|
|
419
|
-
disabled={message === '' || disabled}
|
|
420
|
-
action={() => createMessage({ message, roomId })}
|
|
421
|
-
onSuccess={m => {
|
|
422
|
-
setMessage('')
|
|
423
|
-
onNewMessage?.(m)
|
|
424
|
-
}}
|
|
425
|
-
onChange={setSending}
|
|
426
|
-
/>
|
|
427
|
-
</Flex>
|
|
428
|
-
</Flex>
|
|
429
|
-
)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
414
|
const defaultSplitChatStyle: CSSProperties = {}
|
|
433
415
|
interface SplitChat_T {
|
|
434
416
|
session: EnduserSession | Session,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { launchImageLibrary, launchCamera } from 'react-native-image-picker';
|
|
2
|
+
import { Modal, View, TextInput } from "react-native"
|
|
3
|
+
import { Avatar } from 'react-native-paper'
|
|
4
|
+
import {
|
|
5
|
+
useFileUpload,
|
|
6
|
+
Flex,
|
|
7
|
+
Typography,
|
|
8
|
+
useEnduserSession,
|
|
9
|
+
AsyncIconButton,
|
|
10
|
+
SendIcon,
|
|
11
|
+
LabeledIconButton,
|
|
12
|
+
Button,
|
|
13
|
+
Paper,
|
|
14
|
+
useChats,
|
|
15
|
+
} from "@tellescope/react-components"
|
|
16
|
+
import { useState } from 'react';
|
|
17
|
+
|
|
18
|
+
const CHAT_ICON_SIZE = 35
|
|
19
|
+
const SendImageOrVideo = ({
|
|
20
|
+
onUpload,
|
|
21
|
+
disabled
|
|
22
|
+
} : {
|
|
23
|
+
disabled?: boolean,
|
|
24
|
+
onUpload: (file: { secureName: string }, assetType: 'image' | 'video') => void,
|
|
25
|
+
}) => {
|
|
26
|
+
const session = useEnduserSession()
|
|
27
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
28
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
29
|
+
const { handleUpload, uploading } = useFileUpload({ enduserId: session.userInfo.id })
|
|
30
|
+
|
|
31
|
+
const handleImageSelect = async (type: 'library' | 'camera') => {
|
|
32
|
+
if (uploading) return
|
|
33
|
+
setErrorMessage('')
|
|
34
|
+
|
|
35
|
+
const result = await (type === 'library' ? launchImageLibrary : launchCamera)({
|
|
36
|
+
mediaType: 'mixed', // library' ? 'mixed' : 'photo', // mixed only works for library
|
|
37
|
+
maxWidth: 500,
|
|
38
|
+
maxHeight: 500,
|
|
39
|
+
})
|
|
40
|
+
if (result.didCancel) return
|
|
41
|
+
if (result.errorCode || result.errorMessage) {
|
|
42
|
+
setErrorMessage(
|
|
43
|
+
result.errorCode === 'camera_unavailable' ? "Your camera is unavailable"
|
|
44
|
+
: result.errorCode === 'permission' ? "We do have permission to access your camera"
|
|
45
|
+
: result.errorMessage || 'An error occurred'
|
|
46
|
+
)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
const image = result.assets?.[0]
|
|
50
|
+
if (!image) return
|
|
51
|
+
|
|
52
|
+
const blob = await (await fetch(image.uri as string)).blob()
|
|
53
|
+
const actualSize = blob.size // image.size is incorrect by an unpredictable margin
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const file = await handleUpload({
|
|
57
|
+
name: image.fileName as string,
|
|
58
|
+
size: actualSize, // image.fileSize as number,
|
|
59
|
+
type: image.type as string,
|
|
60
|
+
}, {
|
|
61
|
+
uri: image.uri as string,
|
|
62
|
+
name: image.fileName as string,
|
|
63
|
+
type: image.type as string,
|
|
64
|
+
})
|
|
65
|
+
onUpload(
|
|
66
|
+
file,
|
|
67
|
+
result.assets?.[0]?.duration ? 'video' : 'image',
|
|
68
|
+
)
|
|
69
|
+
setMenuOpen(false)
|
|
70
|
+
} catch(err: any) {
|
|
71
|
+
setErrorMessage(err?.message ?? err.toString())
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const menuDisabled = uploading || disabled
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<Modal animationType='fade' transparent={true}
|
|
79
|
+
visible={menuOpen} onRequestClose={() => setMenuOpen(false)}
|
|
80
|
+
>
|
|
81
|
+
<Flex flex={1} alignItems="center" justifyContent="center">
|
|
82
|
+
<Paper flex style={{ padding: 25, margin: 10, }} >
|
|
83
|
+
<Button disabled={menuDisabled} variant="contained" onPress={() => handleImageSelect('library')}
|
|
84
|
+
style={{ marginBottom: 20 }}
|
|
85
|
+
>
|
|
86
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', minHeight: 40 }}>
|
|
87
|
+
<Typography style={{ fontSize: 18, marginRight: 10 }}>Upload</Typography>
|
|
88
|
+
<Avatar.Icon icon="image" size={CHAT_ICON_SIZE} />
|
|
89
|
+
</View>
|
|
90
|
+
</Button>
|
|
91
|
+
<Button disabled={menuDisabled} variant="contained" onPress={() => handleImageSelect('camera')}>
|
|
92
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', minHeight: 40 }}>
|
|
93
|
+
<Typography style={{ fontSize: 18, marginRight: 10 }}>Take Photo</Typography>
|
|
94
|
+
<Avatar.Icon icon="camera" size={CHAT_ICON_SIZE} />
|
|
95
|
+
</View>
|
|
96
|
+
</Button>
|
|
97
|
+
<Button variant='outlined' onPress={() => setMenuOpen(false)} style={{ marginTop: 25 }}>
|
|
98
|
+
Cancel
|
|
99
|
+
</Button>
|
|
100
|
+
|
|
101
|
+
<Typography style={{ marginTop: 5, color: '#ff4444'}}>
|
|
102
|
+
{errorMessage}
|
|
103
|
+
</Typography>
|
|
104
|
+
</Paper>
|
|
105
|
+
</Flex>
|
|
106
|
+
</Modal>
|
|
107
|
+
|
|
108
|
+
<LabeledIconButton label="attach image" disabled={menuDisabled}
|
|
109
|
+
onClick={() => setMenuOpen(true)}
|
|
110
|
+
Icon={() => <Avatar.Icon icon={'image'} size={CHAT_ICON_SIZE} />}
|
|
111
|
+
/>
|
|
112
|
+
</>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface SendMessage_T {
|
|
117
|
+
roomId: string,
|
|
118
|
+
placeholderText?: string;
|
|
119
|
+
style?: React.CSSProperties;
|
|
120
|
+
}
|
|
121
|
+
export const SendMessage = ({
|
|
122
|
+
roomId,
|
|
123
|
+
placeholderText="Enter a message",
|
|
124
|
+
style={},
|
|
125
|
+
}: SendMessage_T) => {
|
|
126
|
+
const [message, setMessage] = useState('')
|
|
127
|
+
const [sending, setSending] = useState(false)
|
|
128
|
+
|
|
129
|
+
const [, { createElement: createMessage }] = useChats(roomId)
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Flex row flex={1} alignContent="center" style={{ marginTop: 8, ...style }}>
|
|
133
|
+
<Flex column flex={1} style={{ marginLeft: 5, marginRight: 5 }}>
|
|
134
|
+
<TextInput value={message} onChangeText={setMessage}
|
|
135
|
+
style={{
|
|
136
|
+
width: '100%',
|
|
137
|
+
borderColor: '#0f0f0f',
|
|
138
|
+
borderWidth: 1,
|
|
139
|
+
padding: 10,
|
|
140
|
+
borderRadius: 5,
|
|
141
|
+
}}
|
|
142
|
+
aria-label="Enter a message..."
|
|
143
|
+
placeholder={placeholderText}
|
|
144
|
+
/>
|
|
145
|
+
</Flex>
|
|
146
|
+
<Flex alignSelf="center">
|
|
147
|
+
<Flex style={{ marginRight: 5 }}>
|
|
148
|
+
<AsyncIconButton label="send" Icon={() => <SendIcon size={CHAT_ICON_SIZE} />}
|
|
149
|
+
disabled={message === ''}
|
|
150
|
+
action={() => createMessage({ message, roomId })}
|
|
151
|
+
onSuccess={() => setMessage('')}
|
|
152
|
+
onChange={setSending}
|
|
153
|
+
/>
|
|
154
|
+
</Flex>
|
|
155
|
+
|
|
156
|
+
<Flex style={{ marginRight: 5 }}>
|
|
157
|
+
<SendImageOrVideo disabled={sending} onUpload={async ({ secureName }, type) => {
|
|
158
|
+
setSending(true)
|
|
159
|
+
try {
|
|
160
|
+
await createMessage({
|
|
161
|
+
roomId, message: '',
|
|
162
|
+
attachments: [{ type, secureName }]
|
|
163
|
+
})
|
|
164
|
+
} catch(err: any) {
|
|
165
|
+
console.error(err)
|
|
166
|
+
} finally {
|
|
167
|
+
setSending(false)
|
|
168
|
+
}
|
|
169
|
+
}} />
|
|
170
|
+
</Flex>
|
|
171
|
+
</Flex>
|
|
172
|
+
</Flex>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, { CSSProperties, useEffect, useState } from "react"
|
|
2
|
+
import { ChatMessage } from "@tellescope/types-client";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AsyncIconButton,
|
|
6
|
+
Flex,
|
|
7
|
+
SendIcon,
|
|
8
|
+
TextField,
|
|
9
|
+
useChats,
|
|
10
|
+
} from "@tellescope/react-components"
|
|
11
|
+
|
|
12
|
+
interface SendMessage_T {
|
|
13
|
+
roomId: string,
|
|
14
|
+
onNewMessage?: (m: ChatMessage) => void;
|
|
15
|
+
placeholderText?: string;
|
|
16
|
+
Icon?: React.ElementType<any>;
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
|
|
19
|
+
// web only
|
|
20
|
+
sendOnEnterPress?: boolean,
|
|
21
|
+
multiline?: boolean,
|
|
22
|
+
maxRows?: number,
|
|
23
|
+
}
|
|
24
|
+
export const SendMessage = ({
|
|
25
|
+
roomId,
|
|
26
|
+
Icon=SendIcon,
|
|
27
|
+
onNewMessage,
|
|
28
|
+
placeholderText="Enter a message",
|
|
29
|
+
style={},
|
|
30
|
+
sendOnEnterPress,
|
|
31
|
+
multiline,
|
|
32
|
+
maxRows,
|
|
33
|
+
}: SendMessage_T) => {
|
|
34
|
+
const [message, setMessage] = useState('')
|
|
35
|
+
const [sending, setSending] = useState(false)
|
|
36
|
+
|
|
37
|
+
const [disabled, setDisabled] = useState(false)
|
|
38
|
+
const [chatFocused, setChatFocused] = React.useState(false)
|
|
39
|
+
|
|
40
|
+
const [, { createElement: createMessage }] = useChats(roomId)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!chatFocused) return
|
|
44
|
+
if (!sendOnEnterPress) return
|
|
45
|
+
if (typeof window === 'undefined') return
|
|
46
|
+
|
|
47
|
+
const handleSend = (e: any) => {
|
|
48
|
+
if (e.key !== 'Enter') return
|
|
49
|
+
setDisabled(true)
|
|
50
|
+
|
|
51
|
+
createMessage({ message, roomId })
|
|
52
|
+
.then(m => {
|
|
53
|
+
setMessage('')
|
|
54
|
+
onNewMessage?.(m)
|
|
55
|
+
})
|
|
56
|
+
.catch(console.error)
|
|
57
|
+
.finally(() => setDisabled(false))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
window.addEventListener('keypress', handleSend)
|
|
61
|
+
return () => { window.removeEventListener('keypress', handleSend) }
|
|
62
|
+
}, [sendOnEnterPress, chatFocused, message, roomId])
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Flex row flex={1} alignContent="center" style={style}>
|
|
66
|
+
<Flex column flex={1}>
|
|
67
|
+
<TextField variant="outlined" value={message} onChange={setMessage} disabled={sending}
|
|
68
|
+
aria-label="Enter a message"
|
|
69
|
+
multiline={multiline} maxRows={maxRows}
|
|
70
|
+
placeholder={placeholderText}
|
|
71
|
+
onFocus={() => setChatFocused(true)}
|
|
72
|
+
onBlur={() => setChatFocused(false)}
|
|
73
|
+
/>
|
|
74
|
+
</Flex>
|
|
75
|
+
<Flex column alignSelf="center">
|
|
76
|
+
<AsyncIconButton label="send" Icon={Icon}
|
|
77
|
+
disabled={message === '' || disabled}
|
|
78
|
+
action={() => createMessage({ message, roomId })}
|
|
79
|
+
onSuccess={m => {
|
|
80
|
+
setMessage('')
|
|
81
|
+
onNewMessage?.(m)
|
|
82
|
+
}}
|
|
83
|
+
onChange={setSending}
|
|
84
|
+
/>
|
|
85
|
+
</Flex>
|
|
86
|
+
</Flex>
|
|
87
|
+
)
|
|
88
|
+
}
|
package/src/index.native.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./chat"
|
|
1
|
+
export * from "./chat"
|
|
2
|
+
export * from "./components"
|
package/src/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./chat";
|
|
1
|
+
export * from "./chat";
|
|
2
|
+
export * from "./components"
|