From 75f9ff39ae3179631221b417f0a85aa91c54b010 Mon Sep 17 00:00:00 2001 From: david_bai Date: Sun, 17 Aug 2025 10:10:40 +0800 Subject: [PATCH] fix:Now there is a progress bar for file transfer --- frontend/components/ClipboardApp.tsx | 6 - frontend/hooks/useRoomManager.ts | 57 ++++----- frontend/hooks/useWebRTCConnection.ts | 75 ++++++----- frontend/stores/fileTransferStore.ts | 177 ++++++++++++++++---------- 4 files changed, 182 insertions(+), 133 deletions(-) diff --git a/frontend/components/ClipboardApp.tsx b/frontend/components/ClipboardApp.tsx index bbc63b3..3b47868 100644 --- a/frontend/components/ClipboardApp.tsx +++ b/frontend/components/ClipboardApp.tsx @@ -57,9 +57,6 @@ const ClipboardApp = () => { updateShareContent, addFilesToSend, removeFileToSend, - onStringDataReceived, - onFileMetadataReceived, - onFileFullyReceived, handleDownloadFile, } = useFileTransferHandler({ messages, putMessageInMs }); @@ -91,9 +88,6 @@ const ClipboardApp = () => { isContentPresent, messages, putMessageInMs, - onStringReceived: onStringDataReceived, - onFileMetaReceived: onFileMetadataReceived, - onFileReceived: onFileFullyReceived, }); const resetAppState = useCallback(async () => { diff --git a/frontend/hooks/useRoomManager.ts b/frontend/hooks/useRoomManager.ts index ed892c3..0377575 100644 --- a/frontend/hooks/useRoomManager.ts +++ b/frontend/hooks/useRoomManager.ts @@ -42,6 +42,8 @@ export function useRoomManager({ sharePeerCount, retrievePeerCount, senderDisconnected, + isSenderInRoom, + isReceiverInRoom, setShareRoomId, setInitShareRoomId, setShareLink, @@ -261,12 +263,16 @@ export function useRoomManager({ 6000 ); + // 更新 Store 中的房间状态 if (isSenderSide) { + useFileTransferStore.getState().setIsSenderInRoom(true); const link = `${window.location.origin}${window.location.pathname}?roomId=${actualRoomIdForSenderJoin}`; setShareLink(link); if (actualRoomIdForSenderJoin !== shareRoomId) { setShareRoomId(actualRoomIdForSenderJoin); } + } else { + useFileTransferStore.getState().setIsReceiverInRoom(true); } } catch (error) { let errorMsgToShow = messages.text.ClipboardApp.joinRoom.failMsg; @@ -313,51 +319,44 @@ export function useRoomManager({ // useEffect for room status text useEffect(() => { - if ( - !messages || - (activeTab === "send" && !sender) || - (activeTab === "retrieve" && !receiver) - ) { + if (!messages) { if (activeTab === "send") setShareRoomStatusText(""); else setRetrieveRoomStatusText(""); return; } - const currentPeer = activeTab === "send" ? sender : receiver; + const isInRoom = activeTab === "send" ? isSenderInRoom : isReceiverInRoom; const currentPeerCount = activeTab === "send" ? sharePeerCount : retrievePeerCount; let statusText = ""; - if (currentPeer) { - if (!currentPeer.isInRoom) { - statusText = - activeTab === "retrieve" - ? messages.text.ClipboardApp.roomStatus.receiverEmptyMsg - : messages.text.ClipboardApp.roomStatus.senderEmptyMsg; - } else if (currentPeerCount === 0) { - statusText = messages.text.ClipboardApp.roomStatus.onlyOneMsg; - } else { - 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 (!isInRoom) { + statusText = + activeTab === "retrieve" + ? messages.text.ClipboardApp.roomStatus.receiverEmptyMsg + : messages.text.ClipboardApp.roomStatus.senderEmptyMsg; + } else if (currentPeerCount === 0) { + statusText = messages.text.ClipboardApp.roomStatus.onlyOneMsg; + } else { + 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); }, [ activeTab, sharePeerCount, retrievePeerCount, - sender, - receiver, messages, senderDisconnected, - sender?.isInRoom, - receiver?.isInRoom, + isSenderInRoom, + isReceiverInRoom, setShareRoomStatusText, setRetrieveRoomStatusText, ]); diff --git a/frontend/hooks/useWebRTCConnection.ts b/frontend/hooks/useWebRTCConnection.ts index 3e1670d..b6cc18b 100644 --- a/frontend/hooks/useWebRTCConnection.ts +++ b/frontend/hooks/useWebRTCConnection.ts @@ -23,10 +23,6 @@ interface UseWebRTCConnectionProps { shareContent: string; sendFiles: CustomFile[]; isContentPresent: boolean; - // Callbacks for data received from peers - onStringReceived: (data: string, peerId: string) => void; - onFileMetaReceived: (meta: fileMetadata, peerId: string) => void; - onFileReceived: (file: CustomFile, peerId: string) => void; // For user feedback and messages from the hook, if any (mostly for console for now) messages: Messages | null; @@ -41,9 +37,6 @@ export function useWebRTCConnection({ shareContent, sendFiles, isContentPresent, - onStringReceived, - onFileMetaReceived, - onFileReceived, messages, putMessageInMs, }: UseWebRTCConnectionProps) { @@ -77,12 +70,10 @@ export function useWebRTCConnection({ ]; return allProgress.some((fileProgress: unknown) => { const typedFileProgress = fileProgress as FileProgressPeers; - return Object.values(typedFileProgress).some( - (progress: unknown) => { - const typedProgress = progress as PeerProgressDetails; - return typedProgress.progress > 0 && typedProgress.progress < 1; - } - ); + return Object.values(typedFileProgress).some((progress: unknown) => { + const typedProgress = progress as PeerProgressDetails; + return typedProgress.progress > 0 && typedProgress.progress < 1; + }); }); }, [sendProgress, receiveProgress]); @@ -174,22 +165,31 @@ export function useWebRTCConnection({ sender.onConnectionStateChange = (state, peerId) => { if (developmentEnv) console.log(`Sender connection state with ${peerId}: ${state}`); + // 更新连接状态 + useFileTransferStore.getState().setShareConnectionState(state as any); setSharePeerCount(sender.peerConnections.size); if (state === "connected") { - senderFileTransfer.setProgressCallback((fileId, progress: number, speed: number) => { - setSendProgress((prev: ProgressState) => ({ - ...prev, - [fileId]: { ...prev[fileId], [peerId]: { progress, speed } }, - })); - }, peerId); + senderFileTransfer.setProgressCallback( + (fileId, progress: number, speed: number) => { + useFileTransferStore.getState().updateSendProgress(fileId, peerId, { progress, speed }); + }, + peerId + ); } }; - sender.onDataChannelOpen = () => + sender.onDataChannelOpen = () => { + // 当数据通道打开时,标记发送方已加入房间 + useFileTransferStore.getState().setIsSenderInRoom(true); broadcastDataToAllPeers(shareContent, sendFiles); + } sender.onPeerDisconnected = (peerId) => { setTimeout(() => { setSharePeerCount(sender.peerConnections.size); + // 检查是否所有 peer 都已断开连接 + if (sender.peerConnections.size === 0) { + useFileTransferStore.getState().setIsSenderInRoom(false); + } }, 0); }; @@ -203,14 +203,13 @@ export function useWebRTCConnection({ receiver.onConnectionStateChange = (state, peerId) => { if (developmentEnv) console.log(`Receiver connection state with ${peerId}: ${state}`); + // 更新连接状态 + useFileTransferStore.getState().setRetrieveConnectionState(state as any); setRetrievePeerCount(receiver.peerConnections.size); if (state === "connected") { receiverFileTransfer.setProgressCallback( (fileId, progress: number, speed: number) => { - setReceiveProgress((prev: ProgressState) => ({ - ...prev, - [fileId]: { ...prev[fileId], [peerId]: { progress, speed } }, - })); + useFileTransferStore.getState().updateReceiveProgress(fileId, peerId, { progress, speed }); } ); } else if (state === "failed" || state === "disconnected") { @@ -223,21 +222,30 @@ export function useWebRTCConnection({ receiverFileTransfer.onStringReceived = (data) => { const peerId = "testId"; if (developmentEnv) console.log(`String received from peer ${peerId}`); - onStringReceived(data, peerId || "unknown_peer"); + useFileTransferStore.getState().setRetrievedContent(data); }; + receiverFileTransfer.onFileMetaReceived = (meta) => { const peerId = "testId"; if (developmentEnv) console.log( `File meta received from peer ${peerId} for: ${meta.name}` ); - onFileMetaReceived(meta, peerId || "unknown_peer"); + const { type, ...metaWithoutType } = meta; + const store = useFileTransferStore.getState(); + // Filter out existing file with same ID and add the new one + const DPrev = store.retrievedFileMetas.filter( + (existingFile) => existingFile.fileId !== metaWithoutType.fileId + ); + store.setRetrievedFileMetas([...DPrev, metaWithoutType]); }; + receiverFileTransfer.onFileReceived = async (file) => { - const peerId = "testId"; + const peerId = "testId"; // This should be dynamic in a multi-peer scenario if (developmentEnv) console.log(`File received from peer ${peerId}: ${file.name}`); - onFileReceived(file, peerId || "unknown_peer"); + // Directly call the store action + useFileTransferStore.getState().addRetrievedFile(file); }; receiver.onPeerDisconnected = (peerId) => { @@ -245,12 +253,14 @@ export function useWebRTCConnection({ console.log(`Receiver peer ${peerId} disconnected.`); setSenderDisconnected(true); setRetrievePeerCount(0); + useFileTransferStore.getState().setIsReceiverInRoom(false); }; receiver.onConnectionEstablished = (peerId) => { if (developmentEnv) console.log(`Receiver connection established with ${peerId}.`); setSenderDisconnected(false); + useFileTransferStore.getState().setIsReceiverInRoom(true); }; receiver.onError = (error) => { @@ -263,9 +273,6 @@ export function useWebRTCConnection({ senderFileTransfer, receiver, receiverFileTransfer, - onStringReceived, - onFileMetaReceived, - onFileReceived, putMessageInMs, broadcastDataToAllPeers, shareContent, @@ -273,8 +280,6 @@ export function useWebRTCConnection({ isAnyFileTransferring, setSharePeerCount, setRetrievePeerCount, - setSendProgress, - setReceiveProgress, setSenderDisconnected, ]); @@ -342,6 +347,7 @@ export function useWebRTCConnection({ if (receiver) { setSenderDisconnected(false); setRetrievePeerCount(0); + useFileTransferStore.getState().setIsReceiverInRoom(false); await receiver.leaveRoomAndCleanup(); } }, [receiver, setSenderDisconnected, setRetrievePeerCount]); @@ -351,6 +357,7 @@ export function useWebRTCConnection({ if (sender) { await sender.leaveRoomAndCleanup(); setSharePeerCount(0); + useFileTransferStore.getState().setIsSenderInRoom(false); } }, [sender, setSharePeerCount]); @@ -386,4 +393,4 @@ export function useWebRTCConnection({ resetSenderConnection, manualSafeSave, }; -} \ No newline at end of file +} diff --git a/frontend/stores/fileTransferStore.ts b/frontend/stores/fileTransferStore.ts index 74cf44e..c2ff376 100644 --- a/frontend/stores/fileTransferStore.ts +++ b/frontend/stores/fileTransferStore.ts @@ -1,5 +1,5 @@ -import { create } from 'zustand'; -import { CustomFile, FileMeta } from '@/types/webrtc'; +import { create } from "zustand"; +import { CustomFile, FileMeta } from "@/types/webrtc"; interface FileTransferState { // 房间相关状态 @@ -8,33 +8,39 @@ interface FileTransferState { shareLink: string; shareRoomStatusText: string; retrieveRoomStatusText: string; - - // WebRTC 连接状态 + + // WebRTC 连接状态 - 发送方 + shareConnectionState: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed'; + isSenderInRoom: boolean; sharePeerCount: number; + + // WebRTC 连接状态 - 接收方 + retrieveConnectionState: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed'; + isReceiverInRoom: boolean; retrievePeerCount: number; senderDisconnected: boolean; - + // 文件传输状态 shareContent: string; sendFiles: CustomFile[]; retrievedContent: string; retrievedFiles: CustomFile[]; retrievedFileMetas: FileMeta[]; - + // 传输进度状态 sendProgress: Record; receiveProgress: Record; isAnyFileTransferring: boolean; - + // UI 状态 - activeTab: 'send' | 'retrieve'; + activeTab: "send" | "retrieve"; retrieveRoomIdInput: string; isDragging: boolean; - + // 消息状态 shareMessage: string; retrieveMessage: string; - + // Actions // 房间相关 actions setShareRoomId: (id: string) => void; @@ -42,12 +48,16 @@ interface FileTransferState { setShareLink: (link: string) => void; setShareRoomStatusText: (text: string) => void; setRetrieveRoomStatusText: (text: string) => void; - + // WebRTC 连接相关 actions + setShareConnectionState: (state: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed') => void; + setIsSenderInRoom: (isInRoom: boolean) => void; setSharePeerCount: (count: number) => void; + setRetrieveConnectionState: (state: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed') => void; + setIsReceiverInRoom: (isInRoom: boolean) => void; setRetrievePeerCount: (count: number) => void; setSenderDisconnected: (disconnected: boolean) => void; - + // 文件传输相关 actions setShareContent: (content: string) => void; setSendFiles: (files: CustomFile[]) => void; @@ -56,22 +66,25 @@ interface FileTransferState { setRetrievedContent: (content: string) => void; setRetrievedFiles: (files: CustomFile[]) => void; setRetrievedFileMetas: (metas: FileMeta[]) => void; - + addRetrievedFile: (file: CustomFile) => void; + // 传输进度相关 actions setSendProgress: (progress: Record) => void; setReceiveProgress: (progress: Record) => void; + updateSendProgress: (fileId: string, peerId: string, progress: { progress: number; speed: number }) => void; + updateReceiveProgress: (fileId: string, peerId: string, progress: { progress: number; speed: number }) => void; setIsAnyFileTransferring: (transferring: boolean) => void; - + // UI 状态相关 actions - setActiveTab: (tab: 'send' | 'retrieve') => void; + setActiveTab: (tab: "send" | "retrieve") => void; setRetrieveRoomIdInput: (input: string) => void; setIsDragging: (dragging: boolean) => void; - + // 消息相关 actions setShareMessage: (message: string) => void; setRetrieveMessage: (message: string) => void; setRetrieveRoomId: (input: string) => void; - + // 重置相关 actions resetReceiverState: () => void; resetSenderApp: () => void; @@ -79,81 +92,117 @@ interface FileTransferState { export const useFileTransferStore = create()((set, get) => ({ // 初始状态 - shareRoomId: '', - initShareRoomId: '', - shareLink: '', - shareRoomStatusText: '', - retrieveRoomStatusText: '', + shareRoomId: "", + initShareRoomId: "", + shareLink: "", + shareRoomStatusText: "", + retrieveRoomStatusText: "", + shareConnectionState: 'idle', + isSenderInRoom: false, sharePeerCount: 0, + retrieveConnectionState: 'idle', + isReceiverInRoom: false, retrievePeerCount: 0, senderDisconnected: false, - shareContent: '', + shareContent: "", sendFiles: [], - retrievedContent: '', + retrievedContent: "", retrievedFiles: [], retrievedFileMetas: [], sendProgress: {}, receiveProgress: {}, isAnyFileTransferring: false, - activeTab: 'send', - retrieveRoomIdInput: '', + activeTab: "send", + retrieveRoomIdInput: "", isDragging: false, - shareMessage: '', - retrieveMessage: '', - + shareMessage: "", + retrieveMessage: "", + // Actions 实现 setShareRoomId: (id) => set({ shareRoomId: id }), setInitShareRoomId: (id) => set({ initShareRoomId: id }), setShareLink: (link) => set({ shareLink: link }), setShareRoomStatusText: (text) => set({ shareRoomStatusText: text }), setRetrieveRoomStatusText: (text) => set({ retrieveRoomStatusText: text }), - + + // WebRTC 连接相关 actions + setShareConnectionState: (state) => set({ shareConnectionState: state }), + setIsSenderInRoom: (isInRoom) => set({ isSenderInRoom: isInRoom }), setSharePeerCount: (count) => set({ sharePeerCount: count }), + setRetrieveConnectionState: (state) => set({ retrieveConnectionState: state }), + setIsReceiverInRoom: (isInRoom) => set({ isReceiverInRoom: isInRoom }), setRetrievePeerCount: (count) => set({ retrievePeerCount: count }), - setSenderDisconnected: (disconnected) => set({ senderDisconnected: disconnected }), - + setSenderDisconnected: (disconnected) => + set({ senderDisconnected: disconnected }), + setShareContent: (content) => set({ shareContent: content }), setSendFiles: (files) => set({ sendFiles: files }), - addSendFiles: (files) => set((state) => ({ sendFiles: [...state.sendFiles, ...files] })), - removeSendFile: (meta) => set((state) => { - if (meta.folderName && meta.folderName !== '') { - return { - sendFiles: state.sendFiles.filter( - (file) => file.folderName !== meta.folderName - ) - }; - } else { - return { - sendFiles: state.sendFiles.filter((file) => file.name !== meta.name) - }; - } - }), + addSendFiles: (files) => + set((state) => ({ sendFiles: [...state.sendFiles, ...files] })), + removeSendFile: (meta) => + set((state) => { + if (meta.folderName && meta.folderName !== "") { + return { + sendFiles: state.sendFiles.filter( + (file) => file.folderName !== meta.folderName + ), + }; + } else { + return { + sendFiles: state.sendFiles.filter((file) => file.name !== meta.name), + }; + } + }), setRetrievedContent: (content) => set({ retrievedContent: content }), setRetrievedFiles: (files) => set({ retrievedFiles: files }), setRetrievedFileMetas: (metas) => set({ retrievedFileMetas: metas }), - + addRetrievedFile: (file) => + set((state) => ({ retrievedFiles: [...state.retrievedFiles, file] })), + setSendProgress: (progress) => set({ sendProgress: progress }), setReceiveProgress: (progress) => set({ receiveProgress: progress }), - setIsAnyFileTransferring: (transferring) => set({ isAnyFileTransferring: transferring }), - + updateSendProgress: (fileId, peerId, progress) => + set((state) => ({ + sendProgress: { + ...state.sendProgress, + [fileId]: { ...state.sendProgress[fileId], [peerId]: progress }, + }, + })), + updateReceiveProgress: (fileId, peerId, progress) => + set((state) => ({ + receiveProgress: { + ...state.receiveProgress, + [fileId]: { ...state.receiveProgress[fileId], [peerId]: progress }, + }, + })), + setIsAnyFileTransferring: (transferring) => + set({ isAnyFileTransferring: transferring }), + setActiveTab: (tab) => set({ activeTab: tab }), setRetrieveRoomIdInput: (input) => set({ retrieveRoomIdInput: input }), setRetrieveRoomId: (input) => set({ retrieveRoomIdInput: input }), setIsDragging: (dragging) => set({ isDragging: dragging }), - + setShareMessage: (message) => set({ shareMessage: message }), setRetrieveMessage: (message) => set({ retrieveMessage: message }), - - resetReceiverState: () => set({ - retrievedContent: '', - retrievedFiles: [], - retrievedFileMetas: [] - }), - - resetSenderApp: () => set({ - shareLink: '', - sendProgress: {}, - receiveProgress: {}, - isAnyFileTransferring: false - }) -})); \ No newline at end of file + + resetReceiverState: () => + set({ + retrievedContent: "", + retrievedFiles: [], + retrievedFileMetas: [], + retrievePeerCount: 0, + senderDisconnected: false, + retrieveRoomIdInput: "", + receiveProgress: {}, + retrieveRoomStatusText: "", + }), + + resetSenderApp: () => + set({ + shareLink: "", + sendProgress: {}, + receiveProgress: {}, + isAnyFileTransferring: false, + }), +}));