more progress

pull/134/head
Alex Kern 1 year ago
parent bebf6c319a
commit 2959e72bb4
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -8,7 +8,7 @@ export default function DownloadButton({
return (
<button
onClick={onClick}
className="px-4 py-2 bg-green-500 text-white font-semibold rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"
className="px-4 py-2 bg-gradient-to-b from-green-500 to-green-600 text-white rounded-md hover:from-green-500 hover:to-green-700 transition-all duration-200 border border-green-600 shadow-sm hover:shadow-md text-shadow"
>
Download
</button>

@ -11,6 +11,7 @@ import StopButton from './StopButton'
import ProgressBar from './ProgressBar'
import TitleText from './TitleText'
import ReturnHome from './ReturnHome'
import { pluralize } from '../utils/pluralize'
interface FileInfo {
fileName: string
@ -29,7 +30,9 @@ export function DownloadComplete({
}): JSX.Element {
return (
<>
<TitleText>You downloaded {filesInfo.length} files.</TitleText>
<TitleText>
You downloaded {pluralize(filesInfo.length, 'file', 'files')}.
</TitleText>
<div className="flex flex-col space-y-5 w-full">
<UploadFileList files={filesInfo} />
<div className="w-full">
@ -55,14 +58,16 @@ export function DownloadInProgress({
return (
<>
<TitleText>
You are about to start downloading {filesInfo.length} files.
You are downloading {pluralize(filesInfo.length, 'file', 'files')}.
</TitleText>
<div className="flex flex-col space-y-5 w-full">
<UploadFileList files={filesInfo} />
<div className="w-full">
<ProgressBar value={bytesDownloaded} max={totalSize} />
</div>
<StopButton onClick={onStop} isDownloading />
<div className="flex justify-center w-full">
<StopButton onClick={onStop} isDownloading />
</div>
</div>
</>
)
@ -78,7 +83,8 @@ export function ReadyToDownload({
return (
<>
<TitleText>
You are about to start downloading {filesInfo.length} files.
You are about to start downloading{' '}
{pluralize(filesInfo.length, 'file', 'files')}.
</TitleText>
<div className="flex flex-col space-y-5 w-full">
<UploadFileList files={filesInfo} />
@ -106,9 +112,7 @@ export function PasswordEntry({
return (
<>
<TitleText>
{errorMessage || 'This download requires a password.'}
</TitleText>
<TitleText>This download requires a password.</TitleText>
<div className="flex flex-col space-y-5 w-full">
<form
action="#"
@ -127,6 +131,14 @@ export function PasswordEntry({
</div>
</form>
</div>
{errorMessage && (
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline">{errorMessage}</span>
</div>
)}
</>
)
}

@ -2,11 +2,17 @@ import React from 'react'
export default function InputLabel({
children,
hasError = false,
}: {
children: React.ReactNode
hasError?: boolean
}): JSX.Element {
return (
<label className="text-[10px] text-stone-400 mb-0.5 font-bold">
<label
className={`text-[10px] mb-0.5 font-bold ${
hasError ? 'text-red-500' : 'text-stone-400'
}`}
>
{children}
</label>
)

@ -1,7 +1,12 @@
import React, { useCallback } from 'react'
import InputLabel from './InputLabel'
export default function PasswordField(props: {
export default function PasswordField({
value,
onChange,
isRequired = false,
isInvalid = false,
}: {
value: string
onChange: (v: string) => void
isRequired?: boolean
@ -9,24 +14,24 @@ export default function PasswordField(props: {
}): JSX.Element {
const handleChange = useCallback(
function (e: React.ChangeEvent<HTMLInputElement>): void {
props.onChange(e.target.value)
onChange(e.target.value)
},
[props.onChange],
[onChange],
)
return (
<div className="flex flex-col w-full">
<InputLabel>
{props.isRequired ? 'Password' : 'Password (optional)'}
<InputLabel hasError={isInvalid}>
{isRequired ? 'Password' : 'Password (optional)'}
</InputLabel>
<input
autoFocus
type="password"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
props.isInvalid ? 'border-red-500' : 'border-stone-300'
isInvalid ? 'border-red-500' : 'border-stone-300'
}`}
placeholder="Enter a secret password for this FilePizza..."
value={props.value}
value={value}
onChange={handleChange}
/>
</div>

@ -8,7 +8,7 @@ export default function StartButton({
return (
<button
onClick={onClick}
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors duration-200"
className="px-4 py-2 bg-gradient-to-b from-green-500 to-green-600 text-white rounded-md hover:from-green-500 hover:to-green-700 transition-all duration-200 border border-green-600 shadow-sm hover:shadow-md text-shadow"
>
Start
</button>

@ -4,11 +4,11 @@ export default function UnlockButton({
onClick,
}: {
onClick?: React.MouseEventHandler<HTMLButtonElement>
}): React.ReactElement {
}): JSX.Element {
return (
<button
onClick={onClick}
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors duration-200"
className="px-4 py-2 bg-gradient-to-b from-green-500 to-green-600 text-white rounded-md hover:from-green-500 hover:to-green-700 transition-all duration-200 border border-green-600 shadow-sm hover:shadow-md text-shadow"
>
Unlock
</button>

@ -37,7 +37,6 @@ export function useDownloader(uploaderPeerID: string): {
bytesDownloaded: number
} {
const peer = useWebRTC()
const [password, setPassword] = useState('')
const [dataConnection, setDataConnection] = useState<DataConnection | null>(
null,
)
@ -50,11 +49,11 @@ export function useDownloader(uploaderPeerID: string): {
((message: z.infer<typeof ChunkMessage>) => void) | null
>(null)
const [isConnected, setIsConnected] = useState(false)
const [isDownloading, setDownloading] = useState(false)
const [isPasswordRequired, setIsPasswordRequired] = useState(false)
const [isDownloading, setIsDownloading] = useState(false)
const [isDone, setDone] = useState(false)
const [bytesDownloaded, setBytesDownloaded] = useState(0)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [isPasswordRequired, setIsPasswordRequired] = useState(false)
useEffect(() => {
const conn = peer.connect(uploaderPeerID, { reliable: true })
@ -79,6 +78,7 @@ export function useDownloader(uploaderPeerID: string): {
switch (message.type) {
case MessageType.PasswordRequired:
setIsPasswordRequired(true)
if (message.errorMessage) setErrorMessage(message.errorMessage)
break
case MessageType.Info:
setFilesInfo(message.files)
@ -100,7 +100,7 @@ export function useDownloader(uploaderPeerID: string): {
const handleClose = () => {
setDataConnection(null)
setIsConnected(false)
setDownloading(false)
setIsDownloading(false)
}
const handleError = (err: Error) => {
@ -124,7 +124,7 @@ export function useDownloader(uploaderPeerID: string): {
conn.off('close', handleClose)
peer.off('error', handleError)
}
}, [peer, password, uploaderPeerID])
}, [peer, uploaderPeerID])
const submitPassword = useCallback(
(pass: string) => {
@ -134,12 +134,12 @@ export function useDownloader(uploaderPeerID: string): {
password: pass,
} as z.infer<typeof Message>)
},
[dataConnection, password],
[dataConnection],
)
const startDownload = useCallback(() => {
if (!filesInfo || !dataConnection) return
setDownloading(true)
setIsDownloading(true)
const fileStreamByPath: Record<
string,
@ -213,8 +213,23 @@ export function useDownloader(uploaderPeerID: string): {
}, [dataConnection, filesInfo])
const stopDownload = useCallback(() => {
// TODO(@kern): Implement me
}, [])
// TODO(@kern): Continue here with stop / pause logic
if (dataConnection) {
dataConnection.send({ type: MessageType.Pause } as z.infer<
typeof Message
>)
dataConnection.close()
}
setIsDownloading(false)
setDone(false)
setBytesDownloaded(0)
setErrorMessage(null)
// fileStreams.forEach((stream) => stream.cancel())
// fileStreams.length = 0
// Object.values(fileStreamByPath).forEach((stream) => stream.cancel())
// Object.keys(fileStreamByPath).forEach((key) => delete fileStreamByPath[key])
// }, [dataConnection, fileStreams, fileStreamByPath])
}, [dataConnection])
return {
filesInfo,

@ -57,13 +57,19 @@ export function useUploaderConnections(
const message = decodeMessage(data)
switch (message.type) {
case MessageType.RequestInfo: {
const newConnectionState = {
browserName: message.browserName,
browserVersion: message.browserVersion,
osName: message.osName,
osVersion: message.osVersion,
mobileVendor: message.mobileVendor,
mobileModel: message.mobileModel,
}
if (password) {
// TODO(@kern): Check password
const request: Message = {
type: MessageType.Error,
error: 'Invalid password',
type: MessageType.PasswordRequired,
}
conn.send(request)
updateConnection((draft) => {
@ -73,13 +79,8 @@ export function useUploaderConnections(
return {
...draft,
status: UploaderConnectionStatus.InvalidPassword,
browserName: message.browserName,
browserVersion: message.browserVersion,
osName: message.osName,
osVersion: message.osVersion,
mobileVendor: message.mobileVendor,
mobileModel: message.mobileModel,
...newConnectionState,
status: UploaderConnectionStatus.Authenticating,
}
})
@ -93,13 +94,8 @@ export function useUploaderConnections(
return {
...draft,
...newConnectionState,
status: UploaderConnectionStatus.Paused,
browserName: message.browserName,
browserVersion: message.browserVersion,
osName: message.osName,
osVersion: message.osVersion,
mobileVendor: message.mobileVendor,
mobileModel: message.mobileModel,
}
})
@ -120,6 +116,57 @@ export function useUploaderConnections(
break
}
case MessageType.UsePassword: {
const { password: submittedPassword } = message
if (submittedPassword === password) {
updateConnection((draft) => {
if (
draft.status !== UploaderConnectionStatus.Authenticating
) {
return draft
}
return {
...draft,
status: UploaderConnectionStatus.Paused,
}
})
const fileInfo = files.map((f) => ({
fileName: getFileName(f),
size: f.size,
type: f.type,
}))
const request: Message = {
type: MessageType.Info,
files: fileInfo,
}
conn.send(request)
} else {
updateConnection((draft) => {
if (
draft.status !== UploaderConnectionStatus.Authenticating
) {
return draft
}
return {
...draft,
status: UploaderConnectionStatus.InvalidPassword,
}
})
const request: Message = {
type: MessageType.PasswordRequired,
errorMessage: 'Invalid password',
}
conn.send(request)
}
break
}
case MessageType.Start: {
const fileName = message.fileName
let offset = message.offset

@ -57,6 +57,7 @@ export const ErrorMessage = z.object({
export const PasswordRequiredMessage = z.object({
type: z.literal(MessageType.PasswordRequired),
errorMessage: z.string().optional(),
})
export const UsePasswordMessage = z.object({

@ -7,6 +7,7 @@ export enum UploaderConnectionStatus {
Paused = 'PAUSED',
Uploading = 'UPLOADING',
Done = 'DONE',
Authenticating = 'AUTHENTICATING',
InvalidPassword = 'INVALID_PASSWORD',
Closed = 'CLOSED',
}

@ -0,0 +1,7 @@
export function pluralize(
count: number,
singular: string,
plural: string,
): string {
return `${count} ${count === 1 ? singular : plural}`
}
Loading…
Cancel
Save