chat, nicknames, user list.

This commit is contained in:
legop3
2025-11-21 04:32:49 -05:00
parent 3d5df5938d
commit 7dde0d0599
16 changed files with 587 additions and 22 deletions
+90
View File
@@ -0,0 +1,90 @@
/* eslint-disable react-refresh/only-export-components */
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useSocket } from './SocketContext.jsx';
import { useSession } from './SessionContext.jsx';
const ChatContext = createContext({
messages: [],
sendMessage: async () => {},
focusChat: () => {},
blurChat: () => {},
registerInputRef: () => {},
onInputFocus: () => {},
onInputBlur: () => {},
isChatFocused: false,
});
export function ChatProvider({ children }) {
const socket = useSocket();
const { session } = useSession();
const [messages, setMessages] = useState([]);
const [isChatFocused, setIsChatFocused] = useState(false);
const inputRef = useRef(null);
useEffect(() => {
function handleMessage(payload = {}) {
setMessages((prev) => [...prev.slice(-99), payload]);
}
socket.on('chat:message', handleMessage);
return () => {
socket.off('chat:message', handleMessage);
};
}, [socket]);
const sendMessage = useCallback(
(text) =>
new Promise((resolve, reject) => {
socket.emit('chat:send', { text }, (resp = {}) => {
if (resp.error) {
reject(new Error(resp.error));
} else {
resolve(resp);
}
});
}),
[socket],
);
const registerInputRef = useCallback((el) => {
inputRef.current = el;
}, []);
const focusChat = useCallback(() => {
setIsChatFocused(true);
inputRef.current?.focus();
}, []);
const blurChat = useCallback(() => {
setIsChatFocused(false);
inputRef.current?.blur();
}, []);
const onInputFocus = useCallback(() => setIsChatFocused(true), []);
const onInputBlur = useCallback(() => setIsChatFocused(false), []);
const value = useMemo(
() => ({
messages,
sendMessage,
focusChat,
blurChat,
registerInputRef,
onInputFocus,
onInputBlur,
isChatFocused,
selfSocketId: session?.socketId || null,
}),
[blurChat, focusChat, isChatFocused, messages, onInputBlur, onInputFocus, registerInputRef, sendMessage, session?.socketId],
);
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
}
export function useChat() {
const ctx = useContext(ChatContext);
if (!ctx) {
throw new Error('useChat must be used inside ChatProvider');
}
return ctx;
}
+2
View File
@@ -14,6 +14,7 @@ const SessionContext = createContext({
subscribeAll: async () => {},
homeAssistantToggle: async () => {},
homeAssistantSetState: async () => {},
setNickname: async () => {},
});
function useAckEmitter(socket) {
@@ -94,6 +95,7 @@ export function SessionProvider({ children }) {
homeAssistantToggle: (entityId) => emitWithAck('homeAssistant:toggle', { entityId }),
homeAssistantSetState: (entityId, state) =>
emitWithAck('homeAssistant:setState', { entityId, state }),
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
}),
[emitWithAck],
);