Skip to content

Commit 0eb7dd2

Browse files
added events in chat component
1 parent 86dec70 commit 0eb7dd2

File tree

4 files changed

+229
-6
lines changed

4 files changed

+229
-6
lines changed

client/packages/lowcoder/src/comps/comps/chatBoxComponent/chatBoxComp.tsx

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ScrollBar, Section, sectionNames } from "lowcoder-design";
22
import styled, { css } from "styled-components";
33
import { UICompBuilder } from "../../generators";
44
import { NameConfig, NameConfigHidden, withExposingConfigs } from "../../generators/withExposing";
5+
import { withMethodExposing } from "../../generators/withMethodExposing";
56
import { TextStyle, TextStyleType, AnimationStyle, AnimationStyleType } from "comps/controls/styleControlConstants";
67
import { hiddenPropertyView } from "comps/utils/propertyUtils";
78
import React, { useContext, useEffect, useRef, useMemo, useState } from "react";
@@ -330,6 +331,28 @@ const ChatPropertyView = React.memo((props: {
330331
);
331332
});
332333

334+
// Handler for joinUser method
335+
const handleJoinUser = async (
336+
comp: any,
337+
userId: string,
338+
userName: string,
339+
) => {
340+
try {
341+
// Update the component's internal state with user credentials
342+
comp.children.userId.getView().onChange(userId);
343+
comp.children.userName.getView().onChange(userName);
344+
345+
console.log('[ChatBox] 👤 User joined as:', { userId, userName });
346+
347+
// The chat manager will automatically reconnect with new credentials
348+
// due to the useEffect that watches for userId/userName changes
349+
return true;
350+
} catch (error) {
351+
console.error('[ChatBox] 💥 Error joining as user:', error);
352+
return false;
353+
}
354+
};
355+
333356
// Main view component
334357
const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
335358
const [currentMessage, setCurrentMessage] = useState<string>("");
@@ -345,18 +368,52 @@ const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
345368
const chatAreaRef = useRef<HTMLDivElement>(null);
346369
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
347370

371+
// Helper function to trigger custom events
372+
const triggerEvent = (eventName: string) => {
373+
if (props.onEvent) {
374+
props.onEvent(eventName);
375+
}
376+
};
377+
348378
// Initialize chat manager
349379
const modeValue = props.mode as 'local' | 'collaborative' | 'hybrid';
350380

381+
// Only auto-connect if userId and userName are provided in configuration
382+
const shouldAutoConnect = !!(props.userId.value && props.userName.value);
383+
351384
const chatManager = useChatManager({
352-
userId: props.userId.value || "user_1",
353-
userName: props.userName.value || "User",
385+
userId: props.userId.value,
386+
userName: props.userName.value,
354387
applicationId: props.applicationId.value || "lowcoder_app",
355388
roomId: props.roomId.value || "general",
356389
mode: modeValue, // Use mode from props
357-
autoConnect: true,
390+
autoConnect: shouldAutoConnect, // Only auto-connect if credentials are provided
358391
});
359392

393+
// Handle reconnection when userId or userName changes (for public users)
394+
useEffect(() => {
395+
if (props.userId.value && props.userName.value) {
396+
if (chatManager.isConnected) {
397+
// Disconnect and let the chat manager reconnect with new credentials
398+
chatManager.disconnect().then(() => {
399+
console.log('[ChatBox] 🔄 Reconnecting with new user credentials');
400+
});
401+
} else {
402+
// If not connected and we have credentials, trigger connection
403+
console.log('[ChatBox] 🔌 Connecting with user credentials');
404+
}
405+
}
406+
}, [props.userId.value, props.userName.value]);
407+
408+
// Chat event handlers
409+
useEffect(() => {
410+
if (chatManager.isConnected) {
411+
triggerEvent("connected");
412+
} else if (chatManager.error) {
413+
triggerEvent("error");
414+
}
415+
}, [chatManager.isConnected, chatManager.error]);
416+
360417
// Load joined rooms when connected
361418
useEffect(() => {
362419
const loadRooms = async () => {
@@ -515,6 +572,9 @@ const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
515572
setSearchQuery("");
516573
setShowSearchResults(false);
517574

575+
// Trigger room joined event
576+
triggerEvent("roomJoined");
577+
518578
console.log('[ChatBox] 📋 Room join completed successfully');
519579
} else {
520580
console.log('[ChatBox] ❌ Failed to join room:', roomId);
@@ -535,6 +595,9 @@ const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
535595
const updatedJoinedRooms = joinedRooms.filter((room: any) => room.id !== roomId);
536596
setJoinedRooms(updatedJoinedRooms);
537597

598+
// Trigger room left event
599+
triggerEvent("roomLeft");
600+
538601
// If user left the current room, switch to another joined room or clear chat
539602
if (currentRoom?.id === roomId) {
540603
if (updatedJoinedRooms.length > 0) {
@@ -652,6 +715,26 @@ const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
652715
stopTyping
653716
} = chatManager;
654717

718+
// Message received event
719+
useEffect(() => {
720+
if (messages.length > 0) {
721+
const lastMessage = messages[messages.length - 1];
722+
if (lastMessage && lastMessage.authorId !== props.userId.value) {
723+
triggerEvent("messageReceived");
724+
}
725+
}
726+
}, [messages]);
727+
728+
// Typing events
729+
useEffect(() => {
730+
if (typingUsers && typingUsers.length > 0) {
731+
triggerEvent("typingStarted");
732+
} else {
733+
triggerEvent("typingStopped");
734+
}
735+
}, [typingUsers]);
736+
737+
655738
useEffect(() => {
656739
if (chatAreaRef.current) {
657740
chatAreaRef.current.scrollTop = chatAreaRef.current.scrollHeight;
@@ -716,7 +799,8 @@ const ChatBoxView = React.memo((props: ToViewReturn<ChatCompChildrenType>) => {
716799

717800
if (success) {
718801
setCurrentMessage("");
719-
handleClickEvent();
802+
handleClickEvent();
803+
triggerEvent("messageSent");
720804
}
721805
}
722806
};
@@ -1332,6 +1416,29 @@ ChatBoxTmpComp = class extends ChatBoxTmpComp {
13321416
}
13331417
};
13341418

1419+
// Add method exposing
1420+
ChatBoxTmpComp = withMethodExposing(ChatBoxTmpComp, [
1421+
{
1422+
method: {
1423+
name: "joinUser",
1424+
description: "Allow users to join the chat server with their own credentials",
1425+
params: [
1426+
{
1427+
name: "userId",
1428+
type: "string",
1429+
},
1430+
{
1431+
name: "userName",
1432+
type: "string",
1433+
},
1434+
],
1435+
},
1436+
execute: async (comp: any, values: any) => {
1437+
return await handleJoinUser(comp, values?.[0], values?.[1]);
1438+
},
1439+
},
1440+
]);
1441+
13351442
export const ChatBoxComp = withExposingConfigs(ChatBoxTmpComp, [
13361443
new NameConfig("chatName", "Chat name displayed in header"),
13371444
new NameConfig("userId", "Unique identifier for current user"),

client/packages/lowcoder/src/comps/comps/chatBoxComponent/chatControllerComp.tsx

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ const handleJoinRoom = async (
6969
const chatManager = comp.children.chatManager.getView() as unknown as UseChatManagerReturn;
7070
try {
7171
const success = await chatManager.joinRoom(roomId);
72-
if (!success) {
72+
if (success) {
73+
// Note: Event will be triggered by the component's useEffect hooks
74+
console.log('[ChatController] ✅ Successfully joined room:', roomId);
75+
} else {
7376
console.error('[ChatBox] ❌ Failed to join room:', roomId);
7477
}
7578
} catch (error) {
@@ -87,6 +90,10 @@ const handleLeaveRoom = async (
8790
console.log('[ChatBox] 🚪 Attempting to leave room:', roomId);
8891

8992
const success = await chatManager.leaveRoom(roomId);
93+
if (success) {
94+
// Note: Event will be triggered by the component's useEffect hooks
95+
console.log('[ChatController] ✅ Successfully left room:', roomId);
96+
}
9097
return success;
9198
} catch (error) {
9299
console.error('[ChatBox] 💥 Error leaving room:', error);
@@ -113,6 +120,10 @@ const handleSendMessage = async (
113120
const chatManager = comp.children.chatManager.getView() as unknown as UseChatManagerReturn;
114121
if (currentMessage.trim()) {
115122
const success = await chatManager.sendMessage(currentMessage.trim());
123+
if (success) {
124+
// Note: Event will be triggered by the component's useEffect hooks
125+
console.log('[ChatController] ✅ Message sent successfully');
126+
}
116127
return success;
117128
}
118129
} catch (error) {
@@ -194,6 +205,13 @@ const ChatBoxView = React.memo((
194205
const [currentRoomParticipants, setCurrentRoomParticipants] = useState<Array<{ id: string; name: string }>>([]);
195206
const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent});
196207

208+
// Helper function to trigger custom events
209+
const triggerEvent = (eventName: string) => {
210+
if (props.onEvent) {
211+
props.onEvent(eventName);
212+
}
213+
};
214+
197215
// Initialize chat manager
198216
const modeValue = props.mode as 'local' | 'collaborative' | 'hybrid';
199217

@@ -259,6 +277,15 @@ const ChatBoxView = React.memo((
259277
}
260278
}, [props.userId.value, props.userName.value]);
261279

280+
// Chat event handlers
281+
useEffect(() => {
282+
if (chatManager.isConnected) {
283+
triggerEvent("connected");
284+
} else if (chatManager.error) {
285+
triggerEvent("error");
286+
}
287+
}, [chatManager.isConnected, chatManager.error]);
288+
262289
// Refresh joined rooms periodically
263290
useEffect(() => {
264291
if (!chatManager.isConnected) return;
@@ -324,6 +351,11 @@ const ChatBoxView = React.memo((
324351
dispatch(
325352
changeChildAction("currentRoom", currentRoom as any, false)
326353
);
354+
355+
// Trigger room joined event when currentRoom changes to a new room
356+
if (currentRoom) {
357+
triggerEvent("roomJoined");
358+
}
327359
}, [currentRoom]);
328360

329361
// Update typingUsers state
@@ -335,6 +367,31 @@ const ChatBoxView = React.memo((
335367
);
336368
}, [typingUsers]);
337369

370+
// Message events
371+
useEffect(() => {
372+
if (messages.length > 0) {
373+
const lastMessage = messages[messages.length - 1];
374+
if (lastMessage) {
375+
if (lastMessage.authorId === props.userId.value) {
376+
// Message sent by current user
377+
triggerEvent("messageSent");
378+
} else {
379+
// Message received from another user
380+
triggerEvent("messageReceived");
381+
}
382+
}
383+
}
384+
}, [messages, props.userId.value]);
385+
386+
// Typing events
387+
useEffect(() => {
388+
if (typingUsers && typingUsers.length > 0) {
389+
triggerEvent("typingStarted");
390+
} else {
391+
triggerEvent("typingStopped");
392+
}
393+
}, [typingUsers]);
394+
338395
return (
339396
<BackgroundColorContext.Provider value={props.style.background}>
340397
{/* <DrawerWrapper> */}
@@ -510,6 +567,25 @@ ChatControllerComp = withMethodExposing(ChatControllerComp, [
510567
handleSendMessage(comp, values?.[0]);
511568
},
512569
},
570+
{
571+
method: {
572+
name: "getRoomParticipants",
573+
description: "Get participants of a room with their ID and name",
574+
params: [
575+
{
576+
name: "roomId",
577+
type: "string",
578+
},
579+
],
580+
},
581+
execute: async (comp: ConstructorToComp<typeof ChatControllerComp>, values: any) => {
582+
const chatManager = comp.children.chatManager.getView() as any;
583+
if (chatManager && chatManager.getRoomParticipants) {
584+
return await chatManager.getRoomParticipants(values?.[0]);
585+
}
586+
return [];
587+
},
588+
},
513589
{
514590
method: {
515591
name: "joinUser",

client/packages/lowcoder/src/comps/comps/chatBoxComponent/chatUtils.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,21 @@ import React, { useContext, useMemo } from "react";
1616
import { trans } from "i18n";
1717

1818
// Event options for the chat component
19-
const EventOptions = [clickEvent, doubleClickEvent] as const;
19+
const EventOptions = [
20+
clickEvent,
21+
doubleClickEvent,
22+
{ label: trans("chatBox.connected"), value: "connected", description: trans("chatBox.connectedDesc") },
23+
{ label: trans("chatBox.disconnected"), value: "disconnected", description: trans("chatBox.disconnectedDesc") },
24+
{ label: trans("chatBox.messageReceived"), value: "messageReceived", description: trans("chatBox.messageReceivedDesc") },
25+
{ label: trans("chatBox.messageSent"), value: "messageSent", description: trans("chatBox.messageSentDesc") },
26+
{ label: trans("chatBox.userJoined"), value: "userJoined", description: trans("chatBox.userJoinedDesc") },
27+
{ label: trans("chatBox.userLeft"), value: "userLeft", description: trans("chatBox.userLeftDesc") },
28+
{ label: trans("chatBox.typingStarted"), value: "typingStarted", description: trans("chatBox.typingStartedDesc") },
29+
{ label: trans("chatBox.typingStopped"), value: "typingStopped", description: trans("chatBox.typingStoppedDesc") },
30+
{ label: trans("chatBox.roomJoined"), value: "roomJoined", description: trans("chatBox.roomJoinedDesc") },
31+
{ label: trans("chatBox.roomLeft"), value: "roomLeft", description: trans("chatBox.roomLeftDesc") },
32+
{ label: trans("chatBox.error"), value: "error", description: trans("chatBox.errorDesc") },
33+
] as const;
2034

2135
// Define the component's children map
2236
export const chatCompChildrenMap = {

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,32 @@ export const en = {
14821482
"conversationHistory": "Full conversation history as JSON array",
14831483
"databaseNameExposed": "Database name for SQL queries (ChatDB_<componentName>)"
14841484
},
1485+
1486+
"chatBox": {
1487+
// Event Labels & Descriptions
1488+
"connected": "Connected",
1489+
"connectedDesc": "Triggered when the chat connects to the server",
1490+
"disconnected": "Disconnected",
1491+
"disconnectedDesc": "Triggered when the chat disconnects from the server",
1492+
"messageReceived": "Message Received",
1493+
"messageReceivedDesc": "Triggered when a new message is received from another user",
1494+
"messageSent": "Message Sent",
1495+
"messageSentDesc": "Triggered when the current user sends a message",
1496+
"userJoined": "User Joined",
1497+
"userJoinedDesc": "Triggered when a new user joins the current room",
1498+
"userLeft": "User Left",
1499+
"userLeftDesc": "Triggered when a user leaves the current room",
1500+
"typingStarted": "Typing Started",
1501+
"typingStartedDesc": "Triggered when someone starts typing in the current room",
1502+
"typingStopped": "Typing Stopped",
1503+
"typingStoppedDesc": "Triggered when someone stops typing in the current room",
1504+
"roomJoined": "Room Joined",
1505+
"roomJoinedDesc": "Triggered when the current user joins a room",
1506+
"roomLeft": "Room Left",
1507+
"roomLeftDesc": "Triggered when the current user leaves a room",
1508+
"error": "Error",
1509+
"errorDesc": "Triggered when an error occurs in the chat system"
1510+
},
14851511
// eighth part
14861512

14871513
"comp": {

0 commit comments

Comments
 (0)