mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
Split useChatInputWidget into state + actions (flat hooks layout)
Continues the proposal #4 split pattern (doorbell, poll, furni-chooser, user-chooser, friend-request) for the chat-input widget. Splits the 334-line useChatInputWidget along the natural seam: - useChatInputState — selectedUsername / floodBlocked / floodBlockedSeconds / isTyping / isIdle state plus the three event listeners (FLOOD_EVENT, ObjectSelected, ObjectDeselected) and the three lifecycle effects (flood-countdown, idle-auto-clear, typing-indicator sync). - useChatInputActions — sendChat(text, chatType, recipientName, styleId). Carries the slash-command handler (":shake", ":rotate", ":zoom", ":screenshot", ":pickall", etc.) and the chat-vs-shout-vs-whisper dispatch path, with the optional outgoing-translation hook. - useChatInputWidget — deprecated shim that composes both into the historical { selectedUsername, floodBlocked, floodBlockedSeconds, setIsTyping, setIsIdle, sendChat } shape so ChatInputView keeps working unchanged. Bonus while in here: - Guarded all roomSession reads in actions with optional chaining (the hook can be called during the brief no-room window between enter and leave). - Dropped the useless 'if(isIdle)' inside the idle effect body — the early return guard above it already covers that branch.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { RoomEngineObjectEvent, RoomObjectCategory, RoomSessionChatEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNitroEvent } from '../../events';
|
||||
import { useObjectSelectedEvent } from '../engine';
|
||||
import { useRoom } from '../useRoom';
|
||||
|
||||
/**
|
||||
* State + event subscriptions for the chat-input widget. Pure
|
||||
* imperative dispatch (sendChat) lives in useChatInputActions.
|
||||
*
|
||||
* - selectedUsername → tracks the last avatar the user clicked,
|
||||
* used by `/whisper` shortcuts.
|
||||
* - floodBlocked / → flood-throttle banner state, driven by the
|
||||
* floodBlockedSeconds renderer's FLOOD_EVENT plus a 1s tick.
|
||||
* - isTyping / → typing indicator + 10s idle auto-clear, with
|
||||
* isIdle an internal `typingStartedSent` ref so the
|
||||
* outgoing sendChatTypingMessage only fires on
|
||||
* state edges (start / stop), not every render.
|
||||
*/
|
||||
export const useChatInputState = () =>
|
||||
{
|
||||
const [ selectedUsername, setSelectedUsername ] = useState('');
|
||||
const [ isTyping, setIsTyping ] = useState<boolean>(false);
|
||||
const [ typingStartedSent, setTypingStartedSent ] = useState(false);
|
||||
const [ isIdle, setIsIdle ] = useState(false);
|
||||
const [ floodBlocked, setFloodBlocked ] = useState(false);
|
||||
const [ floodBlockedSeconds, setFloodBlockedSeconds ] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
useNitroEvent<RoomSessionChatEvent>(RoomSessionChatEvent.FLOOD_EVENT, event =>
|
||||
{
|
||||
setFloodBlocked(true);
|
||||
setFloodBlockedSeconds(parseFloat(event.message));
|
||||
});
|
||||
|
||||
useObjectSelectedEvent(event =>
|
||||
{
|
||||
if(event.category !== RoomObjectCategory.UNIT) return;
|
||||
|
||||
const userData = roomSession?.userDataManager?.getUserDataByIndex(event.id);
|
||||
|
||||
if(!userData) return;
|
||||
|
||||
setSelectedUsername(userData.name);
|
||||
});
|
||||
|
||||
useNitroEvent<RoomEngineObjectEvent>(RoomEngineObjectEvent.DESELECTED, () => setSelectedUsername(''));
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!floodBlocked) return;
|
||||
|
||||
let seconds = 0;
|
||||
|
||||
const interval = setInterval(() =>
|
||||
{
|
||||
setFloodBlockedSeconds(prevValue =>
|
||||
{
|
||||
seconds = ((prevValue || 0) - 1);
|
||||
|
||||
return seconds;
|
||||
});
|
||||
|
||||
if(seconds < 0)
|
||||
{
|
||||
clearInterval(interval);
|
||||
|
||||
setFloodBlocked(false);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [ floodBlocked ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isIdle) return;
|
||||
|
||||
const timeout = setTimeout(() =>
|
||||
{
|
||||
setIsIdle(false);
|
||||
setIsTyping(false);
|
||||
}, 10000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ isIdle ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!roomSession) return;
|
||||
|
||||
if(isTyping)
|
||||
{
|
||||
if(!typingStartedSent)
|
||||
{
|
||||
setTypingStartedSent(true);
|
||||
|
||||
roomSession.sendChatTypingMessage(isTyping);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(typingStartedSent)
|
||||
{
|
||||
setTypingStartedSent(false);
|
||||
|
||||
roomSession.sendChatTypingMessage(isTyping);
|
||||
}
|
||||
}
|
||||
}, [ roomSession, isTyping, typingStartedSent ]);
|
||||
|
||||
return { selectedUsername, floodBlocked, floodBlockedSeconds, setIsTyping, setIsIdle };
|
||||
};
|
||||
Reference in New Issue
Block a user