checkpoint

pull/134/head
Alex Kern 1 year ago
parent 4cb02da415
commit 3c17d2604f
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -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 (
<PageWrapper>
<p className="text-lg text-center text-stone-800 max-w-md">
You are about to start uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} onChange={onFileListChange} />
<UploadFileList files={fileListData} onChange={onFileListChange} />
<PasswordField value={password} onChange={onChangePassword} />
<div className="flex space-x-4">
<CancelButton onClick={onCancel} />
@ -93,13 +105,14 @@ function UploadingState({
password: string
onStop: () => void
}): JSX.Element {
const fileListData = useUploaderFileListData(uploadedFiles)
return (
<PageWrapper isRotating={true}>
<p className="text-lg text-center text-stone-800 max-w-md">
You are uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} />
<UploadFileList files={fileListData} />
<WebRTCProvider>
<Uploader files={uploadedFiles} password={password} onStop={onStop} />
</WebRTCProvider>

@ -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:

@ -105,7 +105,7 @@ export default function Downloader({
null,
)
const [filesInfo, setFilesInfo] = useState<Array<{
fullPath: string
fileName: string
size: number
type: string
}> | 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<typeof Message> = {
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<typeof ChunkMessage>): 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],
}))

@ -11,13 +11,19 @@ export default function ProgressBar({
const isComplete = value === max
return (
<div className="w-full h-12 bg-stone-200 rounded-md overflow-hidden">
<div className="w-full h-12 bg-stone-200 rounded-md overflow-hidden relative">
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-black font-bold">{Math.round(percentage)}%</span>
</div>
<div
className={`h-full ${
isComplete ? 'bg-green-500' : 'bg-blue-500'
} transition-all duration-300 ease-in-out`}
style={{ width: `${percentage}%` }}
/>
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-white font-bold">{Math.round(percentage)}%</span>
</div>
</div>
)
}

@ -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) => (
<div
key={getFileName(f)}
key={f.fileName}
className="w-full border border-stone-300 rounded-md mb-2 group"
>
<div className="flex justify-between items-center py-2 px-2.5">
<p className="truncate text-sm font-medium">{getFileName(f)}</p>
<p className="truncate text-sm font-medium">{f.fileName}</p>
<div className="flex items-center">
<TypeBadge type={f.type} />
{onChange && (

@ -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 <Loading text="Creating channel" />
}
const activeDownloaders = connections.filter(
(conn) => conn.status === UploaderConnectionStatus.Uploading,
).length
return (
<>
<div className="flex w-full items-center">
@ -46,8 +52,7 @@ export default function Uploader({
<div className="mt-6 pt-4 border-t border-gray-200 w-full">
<div className="flex justify-between items-center mb-2">
<h2 className="text-lg font-semibold text-stone-400">
{connections.length}{' '}
{connections.length === 1 ? 'Downloader' : 'Downloaders'}
{activeDownloaders} Downloading, {connections.length} Total
</h2>
<StopButton onClick={onStop} />
</div>

@ -3,7 +3,7 @@ import { UploadedFile } from './types'
const getAsFile = (entry: any): Promise<File> =>
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 ?? ''
}

@ -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<typeof Message> = {
type: MessageType.Chunk,
fullPath,
fileName,
offset,
bytes: file.slice(offset, end),
final,

@ -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,

@ -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

Loading…
Cancel
Save