优化ID输入框反应速度

This commit is contained in:
david_bai
2025-05-28 23:37:50 +08:00
parent 0f9e65f4f8
commit 615f99fc8b
3 changed files with 158 additions and 103 deletions
+4 -4
View File
@@ -69,11 +69,11 @@ const ClipboardApp = () => {
// Initialize Room Manager Hook
const {
shareRoomId,
// setShareRoomId, // Keep if shareRoomId can be set from input/clipboard
initShareRoomId,
shareLink,
shareRoomStatusText,
retrieveRoomStatusText,
checkAndSetShareRoomId,
processRoomIdInput,
joinRoom,
generateShareLinkAndBroadcast,
} = useRoomManager({
@@ -137,12 +137,12 @@ const ClipboardApp = () => {
removeFileToSend={removeFileToSend}
richTextToPlainText={richTextToPlainText}
sendProgress={sendProgress}
shareRoomId={shareRoomId}
checkAndSetShareRoomId={checkAndSetShareRoomId}
processRoomIdInput={processRoomIdInput}
joinRoom={joinRoom}
generateShareLinkAndBroadcast={generateShareLinkAndBroadcast}
sender={sender}
shareMessage={shareMessage}
currentValidatedShareRoomId={shareRoomId}
/>
) : (
<RetrieveTabPanel
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import RichTextEditor from "@/components/Editor/RichTextEditor";
@@ -11,7 +11,7 @@ import FileListDisplay from "@/components/self_define/FileListDisplay";
import AnimatedButton from "@/components/self_define/AnimatedButton";
import type { Messages } from "@/types/messages";
import type { CustomFile, FileMeta } from "@/lib/types/file";
import type { ProgressState } from "@/hooks/useWebRTCConnection"; // 假设此类型已导出
import type { ProgressState } from "@/hooks/useWebRTCConnection";
import type WebRTC_Initiator from "@/lib/webrtc_Initiator";
interface SendTabPanelProps {
@@ -24,12 +24,16 @@ interface SendTabPanelProps {
removeFileToSend: (meta: FileMeta) => void;
richTextToPlainText: (html: string) => string;
sendProgress: ProgressState;
shareRoomId: string;
checkAndSetShareRoomId: (roomId: string) => void;
// shareRoomId: string; // 这个是从 useRoomManager 来的,代表已验证的ID
processRoomIdInput: (roomId: string) => void; // 从 useRoomManager 传入
joinRoom: (isSender: boolean, roomId: string) => void;
generateShareLinkAndBroadcast: () => void;
sender: WebRTC_Initiator | null;
shareMessage: string;
// Pass the validated/initial shareRoomId from useRoomManager for display/initialization
// Also, initShareRoomId can be useful if we want to reset the input to it.
currentValidatedShareRoomId: string;
// initShareRoomId: string; // If needed for reset logic
}
export function SendTabPanel({
@@ -42,18 +46,47 @@ export function SendTabPanel({
removeFileToSend,
richTextToPlainText,
sendProgress,
shareRoomId,
checkAndSetShareRoomId,
processRoomIdInput,
joinRoom,
generateShareLinkAndBroadcast,
sender,
shareMessage,
}: SendTabPanelProps) {
currentValidatedShareRoomId,
}: // initShareRoomId,
SendTabPanelProps) {
// 本地状态,用于输入框的即时响应
const [inputFieldValue, setInputFieldValue] = useState<string>(
currentValidatedShareRoomId
);
// 当来自父组件的 validatedShareRoomId 改变时(例如,初始获取后),同步本地输入框的值
useEffect(() => {
setInputFieldValue(currentValidatedShareRoomId);
}, [currentValidatedShareRoomId]);
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setInputFieldValue(newValue); // 立即更新输入框显示
processRoomIdInput(newValue); // 调用hook中的处理函数,它会进行防抖验证
},
[processRoomIdInput]
);
const handlePaste = useCallback(
(e: React.ClipboardEvent<HTMLInputElement>) => {
const pastedText = e.clipboardData.getData("text").trim();
setInputFieldValue(pastedText);
processRoomIdInput(pastedText);
},
[processRoomIdInput]
);
return (
<div id="send-panel" role="tabpanel" aria-labelledby="send-tab">
<div className="mb-3 text-sm text-gray-600">
{shareRoomStatusText ||
(sender && sender.isInRoom
(sender?.isInRoom
? messages.text.ClipboardApp.roomStatus.onlyOneMsg
: messages.text.ClipboardApp.roomStatus.senderEmptyMsg)}
</div>
@@ -83,17 +116,19 @@ export function SendTabPanel({
</span>
<Input
aria-label="Share Room ID"
value={shareRoomId}
onChange={(e) => checkAndSetShareRoomId(e.target.value)}
onPaste={(e) =>
checkAndSetShareRoomId(e.clipboardData.getData("text"))
}
value={inputFieldValue} // 绑定到本地状态
onChange={handleInputChange}
onPaste={handlePaste}
className="flex-grow min-w-0"
// placeholder={
// // messages.text.ClipboardApp.html.roomIdInput_placeholder ||
// "输入房间号或粘贴"
// }
/>
<Button
className="w-full sm:w-auto"
onClick={() => joinRoom(true, shareRoomId)}
disabled={!sender || sender.isInRoom || !shareRoomId.trim()}
onClick={() => joinRoom(true, inputFieldValue.trim())} // 使用当前输入框的值尝试加入
disabled={!sender || sender.isInRoom || !inputFieldValue.trim()}
>
{messages.text.ClipboardApp.html.joinRoom_dis}
</Button>
@@ -106,8 +141,9 @@ export function SendTabPanel({
disabled={
!sender ||
!sender.isInRoom ||
(sendFiles.length === 0 && shareContent.trim() === "")
}
(sendFiles.length === 0 && shareContent.trim() === "") ||
!currentValidatedShareRoomId.trim()
} // 确保有已验证的房间ID才可分享
>
{messages.text.ClipboardApp.html.startSending_dis}
</AnimatedButton>
+101 -82
View File
@@ -3,8 +3,8 @@ import { fetchRoom, createRoom, checkRoom } from "@/app/config/api";
import { debounce } from "lodash";
import { format_peopleMsg } from "@/utils/formatMessage";
import type { Messages } from "@/types/messages";
import type WebRTC_Initiator from "@/lib/webrtc_Initiator"; // Adjust path as needed
import type WebRTC_Recipient from "@/lib/webrtc_Recipient"; // Adjust path as needed
import type WebRTC_Initiator from "@/lib/webrtc_Initiator";
import type WebRTC_Recipient from "@/lib/webrtc_Recipient";
interface UseRoomManagerProps {
messages: Messages | null;
@@ -18,12 +18,6 @@ interface UseRoomManagerProps {
activeTab: "send" | "retrieve";
sharePeerCount: number;
retrievePeerCount: number;
// retrieveRoomId is managed by the main component and passed where needed
// shareContent and sendFiles are needed for `sendStringAndMetas` in handleShare,
// but handleShare's primary role here is room-related (generating link).
// `sendStringAndMetas` itself will be part of useWebRTCConnection or useFileTransfer.
// For now, we might need to pass a simplified broadcast function or rethink.
// Let's assume for now `broadcastData` is a function passed from the hook that will own `sendStringAndMetas`.
broadcastDataToPeers: () => Promise<boolean>;
}
@@ -37,39 +31,68 @@ export function useRoomManager({
retrievePeerCount,
broadcastDataToPeers,
}: UseRoomManagerProps) {
const [shareRoomId, setShareRoomId] = useState("");
const [initShareRoomId, setInitShareRoomId] = useState("");
const [shareRoomId, setShareRoomId] = useState(""); // 代表已验证或初始获取的房间ID
const [initShareRoomId, setInitShareRoomId] = useState(""); // 存储初始获取的房间ID,用于比较
const [shareLink, setShareLink] = useState("");
const [shareRoomStatusText, setShareRoomStatusText] = useState("");
const [retrieveRoomStatusText, setRetrieveRoomStatusText] = useState("");
// Initialize shareRoomId on mount
useEffect(() => {
if (messages && putMessageInMs) {
const initRoom = async () => {
if (
messages &&
putMessageInMs &&
!initShareRoomId &&
activeTab === "send"
) {
// 确保只在发送端初次加载时获取
const initNewRoom = async () => {
try {
const newRoomId = await fetchRoom();
setShareRoomId(newRoomId);
setInitShareRoomId(newRoomId);
} catch (err) {
console.error("Error fetching room:", err);
console.error("Error fetching initial room:", err);
const errorMsg =
messages.text?.ClipboardApp?.fetchRoom_err ||
"Error fetching room ID.";
putMessageInMs(errorMsg, true); // true for shareEnd
putMessageInMs(errorMsg, true);
}
};
initRoom();
initNewRoom();
}
}, [messages]);
}, [messages, initShareRoomId]);
const debouncedCheckRoom = useMemo(
// 防抖函数,用于实际检查房间ID并更新状态
const performDebouncedRoomCheck = useMemo(
() =>
debounce(async (roomId: string): Promise<boolean | undefined> => {
if (!messages) return undefined;
debounce(async (roomIdToCheck: string) => {
if (!messages || !putMessageInMs) return;
if (!roomIdToCheck.trim()) {
// 如果清空了输入,不进行检查,但可以清除提示或做其他处理
// putMessageInMs(messages.text.ClipboardApp.roomCheck.empty_msg, true);
// 考虑是否要重置 shareRoomId 为 initShareRoomId,如果希望清空输入时恢复默认
// setShareRoomId(initShareRoomId);
return;
}
try {
const available = await checkRoom(roomId);
return available;
const available = await checkRoom(roomIdToCheck);
if (available) {
setShareRoomId(roomIdToCheck); // 更新已验证的 shareRoomId
putMessageInMs(
messages.text.ClipboardApp.roomCheck.available_msg,
true
);
} else {
// 房间不可用,不更新 shareRoomId,它将保持上一个有效值或初始值
// 用户输入框中的值由 SendTabPanel 的本地状态管理,不会因此回滚
putMessageInMs(
messages.text.ClipboardApp.roomCheck.notAvailable_msg,
true
);
}
} catch (error) {
console.error("Error checking room availability:", error);
putMessageInMs(
@@ -77,36 +100,26 @@ export function useRoomManager({
"Error checking room.",
true
);
return undefined;
}
}, 50),
[messages, putMessageInMs]
}, 750), // 防抖延迟增加到 750ms
[messages, putMessageInMs, setShareRoomId]
);
const checkAndSetShareRoomId = useCallback(
async (roomId: string) => {
if (!messages || !putMessageInMs) return;
if (roomId.length === 0) {
// UI调用此函数来处理输入框ID的变化
const processRoomIdInput = useCallback(
(inputRoomId: string) => {
if (!inputRoomId.trim() && messages && putMessageInMs) {
// 如果用户清空了输入框
putMessageInMs(messages.text.ClipboardApp.roomCheck.empty_msg, true);
return;
performDebouncedRoomCheck.cancel();
// 并且,如果希望清空时,让 validated shareRoomId 回到初始状态:
// setShareRoomId(initShareRoomId); // 这会让二维码等内容更新为初始ID
return; // 不再继续执行防抖检查空字符串
}
const available = await debouncedCheckRoom(roomId);
if (available === true) {
putMessageInMs(
messages.text.ClipboardApp.roomCheck.available_msg,
true
);
setShareRoomId(roomId);
} else if (available === false) {
putMessageInMs(
messages.text.ClipboardApp.roomCheck.notAvailable_msg,
true
);
}
// If available is undefined, an error message was already shown by debouncedCheckRoom
performDebouncedRoomCheck(inputRoomId);
},
[messages, putMessageInMs, debouncedCheckRoom]
);
[performDebouncedRoomCheck, messages, putMessageInMs]
); // initShareRoomId从依赖中移除
const joinRoom = useCallback(
async (isSenderSide: boolean, currentRoomIdToJoin: string) => {
@@ -123,7 +136,7 @@ export function useRoomManager({
const peer = isSenderSide ? sender : receiver;
if (!peer) return; // Should be caught by above, but for type safety
if (!currentRoomIdToJoin) {
if (!currentRoomIdToJoin.trim()) {
putMessageInMs(
messages.text.ClipboardApp.joinRoom.EmptyMsg,
isSenderSide
@@ -143,8 +156,9 @@ export function useRoomManager({
);
return;
}
// 如果创建成功,WebRTC的joinRoom会使用这个ID,同时我们应该更新shareRoomId为这个新创建的ID
setShareRoomId(currentRoomIdToJoin);
} catch (error) {
console.error("Error creating room:", error);
putMessageInMs(
messages.text.ClipboardApp.joinRoom.failMsg +
` (Create room error)`,
@@ -156,27 +170,38 @@ export function useRoomManager({
}
try {
await peer.joinRoom(currentRoomIdToJoin, isSenderSide); // isInitiator flag
// WebRTC joinRoom 使用用户提供的ID(对接收方)或已验证/新创建的ID(对发送方)
// 对于发送方,如果上面 createRoom 成功并设置了 shareRoomId, peer.joinRoom 应使用它
// 但如果 currentRoomIdToJoin 是 initShareRoomId,则直接用它
const actualRoomIdForSenderJoin =
isSenderSide && currentRoomIdToJoin !== initShareRoomId
? currentRoomIdToJoin
: isSenderSide
? shareRoomId
: currentRoomIdToJoin;
await peer.joinRoom(actualRoomIdForSenderJoin, isSenderSide);
putMessageInMs(
messages.text.ClipboardApp.joinRoom.successMsg,
isSenderSide,
6000
);
if (isSenderSide) {
const link = `${window.location.origin}${window.location.pathname}?roomId=${currentRoomIdToJoin}`;
const link = `${window.location.origin}${window.location.pathname}?roomId=${actualRoomIdForSenderJoin}`;
setShareLink(link);
if (actualRoomIdForSenderJoin !== shareRoomId) {
// 如果是通过输入新ID并加入成功的,更新shareRoomId
setShareRoomId(actualRoomIdForSenderJoin);
}
}
} catch (error) {
console.error("Failed to join room:", error);
let errorMsgToShow = messages.text.ClipboardApp.joinRoom.failMsg;
if (error instanceof Error) {
if (error.message === "Room does not exist") {
errorMsgToShow = messages.text.ClipboardApp.joinRoom.notExist;
} else {
errorMsgToShow += ` ${error.message}`;
}
} else {
errorMsgToShow += " Unknown error";
errorMsgToShow =
error.message === "Room does not exist"
? messages.text.ClipboardApp.joinRoom.notExist
: `${messages.text.ClipboardApp.joinRoom.failMsg} ${error.message}`;
}
putMessageInMs(errorMsgToShow, isSenderSide);
}
@@ -188,22 +213,23 @@ export function useRoomManager({
receiver,
activeTab,
initShareRoomId,
shareRoomId,
setShareLink,
setShareRoomId,
]
);
const generateShareLinkAndBroadcast = useCallback(async () => {
if (!sender || !messages || !putMessageInMs) return;
if (!sender || !messages || !putMessageInMs || !shareRoomId) return; // 确保 shareRoomId 有效
if (sender.peerConnections.size === 0) {
putMessageInMs(messages.text.ClipboardApp.waitting_tips, true);
} else {
await broadcastDataToPeers(); // Call the passed broadcast function
await broadcastDataToPeers();
}
// Always generate/update the share link
const link = `${window.location.origin}${window.location.pathname}?roomId=${shareRoomId}`;
setShareLink(link);
}, [sender, messages, putMessageInMs, shareRoomId, setShareLink]);
}, [sender, messages, putMessageInMs, shareRoomId]);
// useEffect for room status text
useEffect(() => {
@@ -231,22 +257,16 @@ export function useRoomManager({
} else if (currentPeerCount === 0) {
statusText = messages.text.ClipboardApp.roomStatus.onlyOneMsg;
} else {
if (activeTab === "send") {
statusText = format_peopleMsg(
messages.text.ClipboardApp.roomStatus.peopleMsg_template,
currentPeerCount + 1
);
} else {
// retrieve tab
statusText = messages.text.ClipboardApp.roomStatus.connected_dis;
}
statusText =
activeTab === "send"
? format_peopleMsg(
messages.text.ClipboardApp.roomStatus.peopleMsg_template,
currentPeerCount + 1
)
: messages.text.ClipboardApp.roomStatus.connected_dis;
}
}
if (activeTab === "send") {
setShareRoomStatusText(statusText);
} else {
setRetrieveRoomStatusText(statusText);
if (activeTab === "send") setShareRoomStatusText(statusText);
else setRetrieveRoomStatusText(statusText);
}
}, [
activeTab,
@@ -258,14 +278,13 @@ export function useRoomManager({
]);
return {
shareRoomId,
initShareRoomId, // Mainly for internal logic or display if needed
setShareRoomId, // Expose setter if direct manipulation from component is needed (e.g. after reading from clipboard)
shareRoomId, // 这是已验证的或初始的房间ID
initShareRoomId, // 暴露以便UI可以比较或用于重置逻辑
shareLink,
shareRoomStatusText,
retrieveRoomStatusText,
checkAndSetShareRoomId, // Renamed from handleShareRoomCheck
joinRoom, // Renamed from handleJoinRoom
generateShareLinkAndBroadcast, // Renamed from handleShare
processRoomIdInput, // 新的处理输入函数
joinRoom,
generateShareLinkAndBroadcast,
};
}