import {
VStack,
Input,
Heading,
HStack,
IconButton,
Checkbox,
Text,
Badge,
Progress,
ProgressIndicator,
Button,
Box,
} from "@hope-ui/solid"
import { createSignal, For, onCleanup, Show } from "solid-js"
import { usePath, useRouter, useT } from "~/hooks"
import { getMainColor } from "~/store"
import {
RiDocumentFolderUploadFill,
RiDocumentFileUploadFill,
} from "solid-icons/ri"
import { Resp, TaskInfo } from "~/types"
import { getFileSize, joinBase, notify, pathJoin, r } from "~/utils"
import { asyncPool } from "~/utils/async_pool"
import { createStore } from "solid-js/store"
import { UploadFileProps, StatusBadge } from "./types"
import { File2Upload, traverseFileTree } from "./util"
import { SelectWrapper } from "~/components"
import { getUploads } from "./uploads"
import { TaskState } from "~/pages/manage/tasks/Task"
enum TaskStateEnum {
Pending,
Running,
Succeeded,
Canceling,
Canceled,
Errored,
Failing,
Failed,
WaitingRetry,
BeforeRetry,
}
const UploadFile = (props: UploadFileProps) => {
const t = useT()
return (
{props.path}
{t(`home.upload.${props.status}`)}
}
>
{getFileSize(props.speed)}/s
{props.task_id}
{props.msg}
)
}
const Upload = () => {
const t = useT()
const { pathname } = useRouter()
const { refresh } = usePath()
const [drag, setDrag] = createSignal(false)
const [uploading, setUploading] = createSignal(false)
const [asTask, setAsTask] = createSignal(false)
const [overwrite, setOverwrite] = createSignal(false)
const [rapid, setRapid] = createSignal(true)
const [uploadFiles, setUploadFiles] = createStore<{
uploads: UploadFileProps[]
}>({
uploads: [],
})
const taskPollers = new Map()
const allDone = () => {
return uploadFiles.uploads.every(({ status }) =>
["success", "error"].includes(status),
)
}
const hasBackgroundTask = () =>
uploadFiles.uploads.some(({ task_id }) => !!task_id)
let fileInput: HTMLInputElement
let folderInput: HTMLInputElement
const clearTaskPoller = (path: string) => {
const timer = taskPollers.get(path)
if (timer !== undefined) {
window.clearTimeout(timer)
taskPollers.delete(path)
}
}
const scheduleTaskPoll = (path: string, taskID: string, delay = 1500) => {
clearTaskPoller(path)
const timer = window.setTimeout(() => {
void pollTask(path, taskID)
}, delay)
taskPollers.set(path, timer)
}
const setUpload = (path: string, key: keyof UploadFileProps, value: any) => {
setUploadFiles("uploads", (upload) => upload.path === path, key, value)
}
const syncTask = (path: string, task: TaskInfo) => {
setUpload(path, "task", task)
setUpload(path, "task_id", task.id)
setUpload(path, "progress", task.progress)
if (task.state === TaskStateEnum.Succeeded) {
clearTaskPoller(path)
setUpload(path, "status", "success")
setUpload(path, "progress", 100)
setUpload(path, "msg", undefined)
refresh(undefined, true)
return
}
if (
task.state === TaskStateEnum.Failed ||
task.state === TaskStateEnum.Canceled
) {
clearTaskPoller(path)
setUpload(path, "status", "error")
setUpload(path, "msg", task.error || task.status)
return
}
setUpload(path, "status", "backending")
setUpload(path, "msg", undefined)
scheduleTaskPoll(path, task.id)
}
const pollTask = async (path: string, taskID: string) => {
const resp: Resp = await r.post(`/task/upload/info?tid=${taskID}`)
if (resp.code !== 200 || !resp.data) {
scheduleTaskPoll(path, taskID, 3000)
return
}
syncTask(path, resp.data)
}
onCleanup(() => {
taskPollers.forEach((timer) => window.clearTimeout(timer))
taskPollers.clear()
})
const handleAddFiles = async (files: File[]) => {
if (files.length === 0) return
setUploading(true)
for (const file of files) {
const upload = File2Upload(file)
setUploadFiles("uploads", (uploads) => [...uploads, upload])
}
for await (const ms of asyncPool(3, files, handleFile)) {
console.log(ms)
}
refresh(undefined, true)
}
const uploaders = getUploads()
const [curUploader, setCurUploader] = createSignal(uploaders[0])
const handleFile = async (file: File) => {
const path = file.webkitRelativePath ? file.webkitRelativePath : file.name
setUpload(path, "status", "uploading")
const uploadPath = pathJoin(pathname(), path)
try {
const result = await curUploader().upload(
uploadPath,
file,
(key, value) => {
setUpload(path, key, value)
},
asTask(),
overwrite(),
rapid(),
)
if (result.error) {
setUpload(path, "status", "error")
setUpload(path, "msg", result.error.message)
} else if (result.task) {
syncTask(path, result.task)
} else {
setUpload(path, "status", "success")
setUpload(path, "progress", 100)
}
} catch (e: any) {
console.error(e)
setUpload(path, "status", "error")
setUpload(path, "msg", e.message)
}
}
return (
{(upload) => }
>
}
>
{
// @ts-ignore
handleAddFiles(Array.from(e.target.files ?? []))
}}
/>
{
// @ts-ignore
handleAddFiles(Array.from(e.target.files ?? []))
}}
/>
{
e.preventDefault()
setDrag(true)
}}
onDragLeave={() => {
setDrag(false)
}}
onDrop={async (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
setDrag(false)
const res: File[] = []
const items = Array.from(e.dataTransfer?.items ?? [])
const files = Array.from(e.dataTransfer?.files ?? [])
let itemLength = items.length
const folderEntries = []
for (let i = 0; i < itemLength; i++) {
const item = items[i]
const entry = item.webkitGetAsEntry()
if (entry?.isFile) {
res.push(files[i])
} else if (entry?.isDirectory) {
folderEntries.push(entry)
}
}
for (const entry of folderEntries) {
const innerFiles = await traverseFileTree(entry)
res.push(...innerFiles)
}
if (res.length === 0) {
notify.warning(t("home.upload.no_files_drag"))
}
handleAddFiles(res)
}}
spacing="$4"
// py="$4"
h="$56"
>
{t("home.upload.release")}}
>
{t("home.upload.upload-tips")}
{
setCurUploader(
uploaders.find((uploader) => uploader.name === name)!,
)
}}
options={uploaders.map((uploader) => {
return {
label: uploader.name,
value: uploader.name,
}
})}
/>
}
onClick={() => {
folderInput.click()
}}
/>
}
onClick={() => {
fileInput.click()
}}
/>
{
setAsTask(!asTask())
}}
>
{t("home.upload.add_as_task")}
{
setOverwrite(!overwrite())
}}
>
{t("home.conflict_policy.overwrite_existing")}
{
setRapid(!rapid())
}}
>
{t("home.upload.try_rapid")}
)
}
export default Upload