From 3c17d2604f482b89b071368d8b00e8057b38da7c Mon Sep 17 00:00:00 2001 From: Alex Kern Date: Mon, 9 Sep 2024 17:41:49 -0700 Subject: [PATCH] checkpoint --- src/app/page.tsx | 17 +++++++++++++++-- src/components/ConnectionListItem.tsx | 4 ++-- src/components/Downloader.tsx | 12 ++++++------ src/components/ProgressBar.tsx | 8 +++++++- src/components/UploadFileList.tsx | 14 +++----------- src/components/Uploader.tsx | 11 ++++++++--- src/fs.ts | 6 +++++- src/hooks/useUploaderConnections.ts | 17 ++++++++--------- src/messages.ts | 6 +++--- src/types.ts | 4 ++-- 10 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 272f017..ca2e032 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -12,6 +12,8 @@ import { UploadedFile } from '../types' import Spinner from '../components/Spinner' import Wordmark from '../components/Wordmark' import CancelButton from '../components/CancelButton' +import { useMemo } from 'react' +import { getFileName } from '../fs' const queryClient = new QueryClient() @@ -53,6 +55,15 @@ function InitialState({ ) } +function useUploaderFileListData(uploadedFiles: UploadedFile[]) { + return useMemo(() => { + return uploadedFiles.map((item) => ({ + fileName: getFileName(item), + type: item.type, + })) + }, [uploadedFiles]) +} + function ConfirmUploadState({ uploadedFiles, password, @@ -68,13 +79,14 @@ function ConfirmUploadState({ onStart: () => void onFileListChange: (updatedFiles: UploadedFile[]) => void }): JSX.Element { + const fileListData = useUploaderFileListData(uploadedFiles) return (

You are about to start uploading {uploadedFiles.length}{' '} {uploadedFiles.length === 1 ? 'file' : 'files'}.

- +
@@ -93,13 +105,14 @@ function UploadingState({ password: string onStop: () => void }): JSX.Element { + const fileListData = useUploaderFileListData(uploadedFiles) return (

You are uploading {uploadedFiles.length}{' '} {uploadedFiles.length === 1 ? 'file' : 'files'}.

- + diff --git a/src/components/ConnectionListItem.tsx b/src/components/ConnectionListItem.tsx index 27b5a17..5145298 100644 --- a/src/components/ConnectionListItem.tsx +++ b/src/components/ConnectionListItem.tsx @@ -10,11 +10,11 @@ export function ConnectionListItem({ const getStatusColor = (status: UploaderConnectionStatus) => { switch (status) { case UploaderConnectionStatus.Uploading: - return 'bg-green-500' + return 'bg-blue-500' case UploaderConnectionStatus.Paused: return 'bg-yellow-500' case UploaderConnectionStatus.Done: - return 'bg-blue-500' + return 'bg-green-500' case UploaderConnectionStatus.Closed: return 'bg-red-500' default: diff --git a/src/components/Downloader.tsx b/src/components/Downloader.tsx index 9895d0c..0983f36 100644 --- a/src/components/Downloader.tsx +++ b/src/components/Downloader.tsx @@ -105,7 +105,7 @@ export default function Downloader({ null, ) const [filesInfo, setFilesInfo] = useState | null>(null) @@ -234,7 +234,7 @@ export default function Downloader({ close = () => ctrl.close() }, }) - fileStreamByPath[info.fullPath] = { + fileStreamByPath[info.fileName] = { stream, enqueue, close, @@ -250,7 +250,7 @@ export default function Downloader({ const request: t.TypeOf = { type: MessageType.Start, - fullPath: filesInfo[nextFileIndex].fullPath, + fileName: filesInfo[nextFileIndex].fileName, offset: 0, } dataConnection.send(request) @@ -258,9 +258,9 @@ export default function Downloader({ } const processChunkFunc = (message: t.TypeOf): void => { - const fileStream = fileStreamByPath[message.fullPath] + const fileStream = fileStreamByPath[message.fileName] if (!fileStream) { - console.error('no stream found for ' + message.fullPath) + console.error('no stream found for ' + message.fileName) return } @@ -275,7 +275,7 @@ export default function Downloader({ processChunk.current = processChunkFunc const downloads = filesInfo.map((info, i) => ({ - name: info.fullPath.replace(/^\//, ''), + name: info.fileName.replace(/^\//, ''), size: info.size, stream: () => fileStreams[i], })) diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index 708ee87..c57087a 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -11,13 +11,19 @@ export default function ProgressBar({ const isComplete = value === max return ( -
+
+
+ {Math.round(percentage)}% +
+
+ {Math.round(percentage)}% +
) } diff --git a/src/components/UploadFileList.tsx b/src/components/UploadFileList.tsx index 7fe42d7..978c74c 100644 --- a/src/components/UploadFileList.tsx +++ b/src/components/UploadFileList.tsx @@ -2,18 +2,10 @@ import React from 'react' import TypeBadge from './TypeBadge' type UploadedFileLike = { - fullPath?: string - name?: string + fileName?: string type: string } -function getFileName(file: UploadedFileLike): string { - if (file.fullPath) { - return file.fullPath.slice(1) - } - return file.name || 'Unknown' -} - export default function UploadFileList({ files, onChange, @@ -30,11 +22,11 @@ export default function UploadFileList({ const items = files.map((f: UploadedFileLike, i: number) => (
-

{getFileName(f)}

+

{f.fileName}

{onChange && ( diff --git a/src/components/Uploader.tsx b/src/components/Uploader.tsx index a146b0f..88837c5 100644 --- a/src/components/Uploader.tsx +++ b/src/components/Uploader.tsx @@ -1,5 +1,7 @@ +'use client' + import React from 'react' -import { UploadedFile } from '../types' +import { UploadedFile, UploaderConnectionStatus } from '../types' import { useWebRTC } from './WebRTCProvider' import QRCode from 'react-qr-code' import Loading from './Loading' @@ -32,6 +34,10 @@ export default function Uploader({ return } + const activeDownloaders = connections.filter( + (conn) => conn.status === UploaderConnectionStatus.Uploading, + ).length + return ( <>
@@ -46,8 +52,7 @@ export default function Uploader({

- {connections.length}{' '} - {connections.length === 1 ? 'Downloader' : 'Downloaders'} + {activeDownloaders} Downloading, {connections.length} Total

diff --git a/src/fs.ts b/src/fs.ts index c1e779e..ed0ec1d 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -3,7 +3,7 @@ import { UploadedFile } from './types' const getAsFile = (entry: any): Promise => new Promise((resolve, reject) => { entry.file((file: UploadedFile) => { - file.fullPath = entry.fullPath + file.entryFullPath = entry.fullPath resolve(file) }, reject) }) @@ -77,3 +77,7 @@ export const formatSize = (bytes: number): string => { const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${(bytes / Math.pow(k, i)).toPrecision(3)} ${sizes[i]}` } + +export const getFileName = (file: UploadedFile): string => { + return file.name ?? file.entryFullPath ?? '' +} diff --git a/src/hooks/useUploaderConnections.ts b/src/hooks/useUploaderConnections.ts index c719018..a84ab72 100644 --- a/src/hooks/useUploaderConnections.ts +++ b/src/hooks/useUploaderConnections.ts @@ -7,19 +7,18 @@ import { } from '../types' import { decodeMessage, Message, MessageType } from '../messages' import * as t from 'io-ts' +import { getFileName } from '../fs' // TODO(@kern): Test for better values const MAX_CHUNK_SIZE = 10 * 1024 * 1024 // 10 Mi function validateOffset( files: UploadedFile[], - fullPath: string, + fileName: string, offset: number, ): UploadedFile { const validFile = files.find( - (file) => - (file.fullPath === fullPath || file.name === fullPath) && - offset <= file.size, + (file) => getFileName(file) === fileName && offset <= file.size, ) if (!validFile) { throw new Error('invalid file offset') @@ -106,7 +105,7 @@ export function useUploaderConnections( const fileInfo = files.map((f) => { return { - fullPath: f.fullPath ?? f.name ?? '', + fileName: f.fileName ?? f.name ?? '', size: f.size, type: f.type, } @@ -122,9 +121,9 @@ export function useUploaderConnections( } case MessageType.Start: { - const fullPath = message.fullPath + const fileName = message.fileName let offset = message.offset - const file = validateOffset(files, fullPath, offset) + const file = validateOffset(files, fileName, offset) updateConnection((draft) => { if (draft.status !== UploaderConnectionStatus.Paused) { return draft @@ -133,7 +132,7 @@ export function useUploaderConnections( return { ...draft, status: UploaderConnectionStatus.Uploading, - uploadingFullPath: fullPath, + uploadingFileName: fileName, uploadingOffset: offset, currentFileProgress: offset / file.size, } @@ -145,7 +144,7 @@ export function useUploaderConnections( const final = chunkSize < MAX_CHUNK_SIZE const request: t.TypeOf = { type: MessageType.Chunk, - fullPath, + fileName, offset, bytes: file.slice(offset, end), final, diff --git a/src/messages.ts b/src/messages.ts index 602bfb3..dfb2bf6 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -27,7 +27,7 @@ export const InfoMessage = t.type({ type: t.literal(MessageType.Info), files: t.array( t.type({ - fullPath: t.string, + fileName: t.string, size: t.number, type: t.string, }), @@ -36,13 +36,13 @@ export const InfoMessage = t.type({ export const StartMessage = t.type({ type: t.literal(MessageType.Start), - fullPath: t.string, + fileName: t.string, offset: t.number, }) export const ChunkMessage = t.type({ type: t.literal(MessageType.Chunk), - fullPath: t.string, + fileName: t.string, offset: t.number, bytes: t.unknown, final: t.boolean, diff --git a/src/types.ts b/src/types.ts index 08919d8..17a5ef2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import type { DataConnection } from 'peerjs' -export type UploadedFile = File & { fullPath?: string; name?: string } +export type UploadedFile = File & { entryFullPath?: string } export enum UploaderConnectionStatus { Pending = 'PENDING', @@ -20,7 +20,7 @@ export type UploaderConnection = { osVersion?: string mobileVendor?: string mobileModel?: string - uploadingFullPath?: string + uploadingFileName?: string uploadingOffset?: number completedFiles: number totalFiles: number