This commit is contained in:
2024-04-21 14:42:52 +02:00
parent 4b69674ede
commit 8a25f53c99
10700 changed files with 55767 additions and 14201 deletions

View File

@ -0,0 +1,105 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
CircularProgress,
IconButton,
} from '@chakra-ui/react'
import cx from 'classnames'
import { IconClose, IconSchedule, IconCheckCircle, IconError } from '@/lib'
import {
Upload,
UploadDecorator,
uploadRemoved,
} from '@/store/entities/uploads'
import { useAppDispatch } from '@/store/hook'
export type UploadItemProps = {
upload: Upload
}
const UploadItem = ({ upload: uploadProp }: UploadItemProps) => {
const dispatch = useAppDispatch()
const upload = new UploadDecorator(uploadProp)
return (
<div className={cx('flex', 'flex-col', 'gap-1')}>
<div
className={cx(
'flex',
'flex-row',
'items-center',
'gap-0.5',
'justify-between',
'h-2.5',
)}
>
{upload.isProgressing && (
<CircularProgress
value={upload.progress}
max={100}
isIndeterminate={upload.progress === 100 && !upload.error}
className={cx('text-black')}
size="20px"
/>
)}
{upload.isPending && (
<IconSchedule className={cx('shrink-0', 'text-gray-500')} />
)}
{upload.isSucceeded && (
<IconCheckCircle
className={cx('shrink-0', 'text-green-500')}
filled={true}
/>
)}
{upload.isFailed && (
<div className={cx('shrink-0', 'text-red-500')}>
<IconError filled={true} />
</div>
)}
<span
className={cx(
'grow',
'text-ellipsis',
'overflow-hidden',
'whitespace-nowrap',
)}
>
{upload.file.name}
</span>
<IconButton
icon={<IconClose />}
size="xs"
variant="outline"
colorScheme={upload.isProgressing ? 'red' : 'gray'}
aria-label=""
onClick={() => {
upload.request?.abort()
dispatch(uploadRemoved(upload.id))
}}
/>
</div>
{upload.isFailed && (
<Accordion allowMultiple>
<AccordionItem className={cx('border-none')}>
<AccordionButton className={cx('p-0.5', 'hover:bg-red-50')}>
<div className={cx('flex', 'flex-row', 'w-full')}>
<span className={cx('text-red-500', 'text-left', 'grow')}>
Upload failed. Click to expand.
</span>
<AccordionIcon className={cx('text-red-500')} />
</div>
</AccordionButton>
<AccordionPanel className={cx('p-0.5')}>
{upload.error}
</AccordionPanel>
</AccordionItem>
</Accordion>
)}
</div>
)
}
export default UploadItem

View File

@ -0,0 +1,44 @@
import { useEffect } from 'react'
import { Divider } from '@chakra-ui/react'
import cx from 'classnames'
import { useAppDispatch, useAppSelector } from '@/store/hook'
import { uploadsDrawerClosed } from '@/store/ui/uploads-drawer'
import UploadItem from './upload-item'
import { queue } from './upload-worker'
const UploadList = () => {
const items = useAppSelector((state) => state.entities.uploads.items)
const dispatch = useAppDispatch()
useEffect(() => {
for (const upload of items) {
if (
queue.findIndex((e) => e.id === upload.id) !== -1 ||
upload.completed
) {
continue
}
queue.push(upload)
}
if (items.length === 0) {
dispatch(uploadsDrawerClosed())
}
}, [items, dispatch])
if (items.length === 0) {
return <span>There are no uploads.</span>
}
return (
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
{items.map((u, index) => (
<div key={u.id} className={cx('flex', 'flex-col', 'gap-1.5')}>
<UploadItem upload={u} />
{index !== items.length - 1 && <Divider />}
</div>
))}
</div>
)
}
export default UploadList

View File

@ -0,0 +1,49 @@
import { FileWithPath } from 'react-dropzone'
import FileAPI from '@/client/api/file'
import { errorToString } from '@/client/error'
import store from '@/store/configure-store'
import {
Upload,
uploadCompleted,
uploadUpdated,
} from '@/store/entities/uploads'
export const queue: Upload[] = []
let working = false
setInterval(async () => {
if (queue.length === 0 || working) {
return
}
working = true
const upload = queue.at(0) as Upload
try {
const request = new XMLHttpRequest()
store.dispatch(uploadUpdated({ id: upload.id, request }))
await FileAPI.upload({
workspaceId: upload.workspaceId,
parentId: upload.parentId,
name:
(upload.file as FileWithPath).path ||
upload.file.webkitRelativePath ||
upload.file.name,
request,
file: upload.file,
onProgress: (progress) => {
store.dispatch(uploadUpdated({ id: upload.id, progress }))
},
})
store.dispatch(uploadCompleted(upload.id))
} catch (error) {
store.dispatch(
uploadUpdated({
id: upload.id,
completed: true,
error: errorToString(error),
}),
)
} finally {
queue.shift()
working = false
}
}, 1000)