Skip to content

Commit

Permalink
Add upload progress, auto append .zip on compress.
Browse files Browse the repository at this point in the history
  • Loading branch information
retrixe committed Aug 13, 2023
1 parent a92b6a9 commit 28a9e1b
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 14 deletions.
16 changes: 9 additions & 7 deletions imports/dashboard/files/fileManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import useKy from '../../helpers/useKy'

import Editor from './editor'
import Overlay from './overlay'
import { joinPath, normalisePath, parentPath } from './fileUtils'
import { joinPath, normalisePath, parentPath, uploadFormData } from './fileUtils'
import UploadButton from './uploadButton'
import FileList, { type File } from './fileList'
import MassActionDialog from './massActionDialog'
Expand Down Expand Up @@ -49,7 +49,7 @@ const FileManager = (props: {
const [search, setSearch] = useState<string | null>(null)
const [searchApplies, setSearchApplies] = useState(true)

const [overlay, setOverlay] = useState('')
const [overlay, setOverlay] = useState<string | { text: string, progress: number }>('')
const [message, setMessage] = useState('')
const [fetching, setFetching] = useState(true)
const [error, setError] = useState<null | 'folderNotExist' | 'pathNotFolder' | 'outsideServerDir'>(null)
Expand Down Expand Up @@ -242,19 +242,21 @@ const FileManager = (props: {
;(async () => {
for (let i = 0; i < files.length; i++) {
const file = files[i]
setOverlay(`Uploading ${file.name} to ${path}`)
setOverlay({ text: `Uploading ${file.name} to ${path}`, progress: 0 })
// Save the file.
const formData = new FormData()
formData.append('upload', file, file.name)
const r = await ky.post(`server/${server}/file?path=${euc(path)}`, { body: formData, timeout: false })
const r = await uploadFormData(`${ip}/server/${server}/file?path=${euc(path)}`, formData, progress => {
setOverlay({ text: `Uploading ${file.name} to ${path}`, progress: progress * 100 })
})
if (r.status !== 200) {
setMessage(`Error uploading ${file.name}\n${(await r.json<{ error: string }>()).error}`)
setMessage(`Error uploading ${file.name}\n${JSON.parse(r.body).error}`)
}
setOverlay('')
}
setMessage('Uploaded all files successfully!')
if (path === prevPath.current) fetchFiles() // prevPath is current path after useEffect call.
})().catch(e => { console.error(e); setMessage(`Failed to upload files: ${e.message}`) })
})().catch(e => { console.error(e); setOverlay(''); setMessage(`Failed to upload files: ${e.message}`) })
}
// Single file logic.
const handleDeleteMenuButton = (): void => {
Expand Down Expand Up @@ -539,7 +541,7 @@ const FileManager = (props: {
</Menu>
)}
{message && <Message message={message} setMessage={setMessage} />}
{overlay && <Overlay message={overlay} />}
{overlay && <Overlay display={overlay} />}
</>
)
}
Expand Down
19 changes: 19 additions & 0 deletions imports/dashboard/files/fileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@ export const joinPath = (a: string, b: string): string => {
const normalisedPathB = normalisePath(b)
return normalisePath(normalisedPathA + normalisedPathB)
}

export const uploadFormData = async (
url: string,
formData: FormData,
onProgress: (progress: number) => void
): Promise<{ status: number, body: string }> =>
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.upload.addEventListener('progress', e => onProgress(e.loaded / e.total))
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status !== 0) resolve({ status: xhr.status, body: xhr.responseText })
else reject(new Error(xhr.statusText))
}
}
xhr.open('POST', url, true)
xhr.setRequestHeader('Authorization', localStorage.getItem('ecthelion:token') ?? '')
xhr.send(formData)
})
2 changes: 1 addition & 1 deletion imports/dashboard/files/massActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const MassActionDialog = ({
if (operation === 'compress') {
setOverlay(`Compressing ${files.length} files on the server.`)
const json = files.map(f => path + f)
ky.post(`${endpoint}?path=${encodeURIComponent(path + newPath)}`, { json }).then(res => {
ky.post(`${endpoint}?path=${encodeURIComponent(path + newPath + '.zip')}`, { json }).then(res => {
setOverlay('')
if (res.ok) {
reload()
Expand Down
40 changes: 34 additions & 6 deletions imports/dashboard/files/overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Paper, Typography, LinearProgress } from '@mui/material'
import { Paper, Typography, LinearProgress, type LinearProgressProps, Box } from '@mui/material'
import styled from '@emotion/styled'

const OverlayContainer = styled.div({
Expand All @@ -16,13 +16,41 @@ const OverlayContainer = styled.div({
pointerEvents: 'none'
})

const Overlay = ({ message }: { message: string }): JSX.Element => (
// https://github.com/mui/material-ui/blob/v5.14.4/docs/data/material/components/progress/LinearWithValueLabel.tsx
function LinearProgressWithLabel (props: LinearProgressProps & { value: number }): JSX.Element {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant='determinate' {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant='body2' color='text.secondary'>
{`${Math.round(props.value)}%`}
</Typography>
</Box>
</Box>
)
}

const Overlay = (props: { display: string | { text: string, progress: number } }): JSX.Element => (
<OverlayContainer>
<div style={{ flex: 1 }} />
<Paper elevation={3} sx={{ height: '80px', m: '28px', p: '20px', ml: { xs: '28px', sm: '228px' } }}>
<LinearProgress color='secondary' />
<br />
<Typography variant='body1'>{message}</Typography>
<Paper
elevation={3} sx={{
height: '80px',
m: '28px',
p: '20px',
ml: { xs: '28px', sm: '228px' },
pt: typeof props.display === 'string' ? '20px' : '14px'
}}
>
{typeof props.display === 'string'
? <LinearProgress color='secondary' />
: <LinearProgressWithLabel color='secondary' value={props.display.progress} />}
<div style={{ height: typeof props.display === 'string' ? '1rem' : '0.6rem' }} />
<Typography variant='body1'>
{typeof props.display === 'string' ? props.display : props.display.text}
</Typography>
</Paper>
</OverlayContainer>
)
Expand Down

0 comments on commit 28a9e1b

Please sign in to comment.