mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
feat: show upload success in upload dialog title
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
"jotai": "^2.14.0",
|
"jotai": "^2.14.0",
|
||||||
"jotai-effect": "^2.1.3",
|
"jotai-effect": "^2.1.3",
|
||||||
"jotai-scope": "^0.9.5",
|
"jotai-scope": "^0.9.5",
|
||||||
|
"jotai-tanstack-query": "^0.11.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"motion": "^12.23.16",
|
"motion": "^12.23.16",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
|
|||||||
@@ -26,46 +26,46 @@ export type FileUploadStatus =
|
|||||||
| FileUploadError
|
| FileUploadError
|
||||||
| FileUploadSuccess
|
| FileUploadSuccess
|
||||||
|
|
||||||
export const fileUploadsAtom = atom<Record<string, FileUploadStatus>>({})
|
export const fileUploadStatusesAtom = atom<Record<string, FileUploadStatus>>({})
|
||||||
|
|
||||||
export const fileUploadStatusAtomFamily = atomFamily((id: string) =>
|
export const fileUploadStatusAtomFamily = atomFamily((id: string) =>
|
||||||
atom(
|
atom(
|
||||||
(get) => get(fileUploadsAtom)[id],
|
(get) => get(fileUploadStatusesAtom)[id],
|
||||||
(get, set, status: FileUploadStatus) => {
|
(get, set, status: FileUploadStatus) => {
|
||||||
const fileUploads = { ...get(fileUploadsAtom) }
|
const fileUploads = { ...get(fileUploadStatusesAtom) }
|
||||||
fileUploads[id] = status
|
fileUploads[id] = status
|
||||||
set(fileUploadsAtom, fileUploads)
|
set(fileUploadStatusesAtom, fileUploads)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const clearFileUploadAtom = atom(null, (get, set, id: string) => {
|
|
||||||
const fileUploads = { ...get(fileUploadsAtom) }
|
|
||||||
delete fileUploads[id]
|
|
||||||
fileUploadStatusAtomFamily.remove(id)
|
|
||||||
set(fileUploadsAtom, fileUploads)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const clearFileUploadStatusesAtom = atom(
|
export const clearFileUploadStatusesAtom = atom(
|
||||||
null,
|
null,
|
||||||
(get, set, ids: string[]) => {
|
(get, set, ids: string[]) => {
|
||||||
const fileUploads = { ...get(fileUploadsAtom) }
|
const fileUploads = { ...get(fileUploadStatusesAtom) }
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
if (fileUploads[id]) {
|
if (fileUploads[id]) {
|
||||||
delete fileUploads[id]
|
delete fileUploads[id]
|
||||||
}
|
}
|
||||||
fileUploadStatusAtomFamily.remove(id)
|
fileUploadStatusAtomFamily.remove(id)
|
||||||
}
|
}
|
||||||
set(fileUploadsAtom, fileUploads)
|
set(fileUploadStatusesAtom, fileUploads)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const clearAllFileUploadStatusesAtom = atom(
|
||||||
|
null,
|
||||||
|
(get, set) => {
|
||||||
|
set(fileUploadStatusesAtom, {})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const fileUploadCountAtom = atom(
|
export const fileUploadCountAtom = atom(
|
||||||
(get) => Object.keys(get(fileUploadsAtom)).length,
|
(get) => Object.keys(get(fileUploadStatusesAtom)).length,
|
||||||
)
|
)
|
||||||
|
|
||||||
export const inProgressFileUploadCountAtom = atom((get) => {
|
export const inProgressFileUploadCountAtom = atom((get) => {
|
||||||
const statuses = get(fileUploadsAtom)
|
const statuses = get(fileUploadStatusesAtom)
|
||||||
let count = 0
|
let count = 0
|
||||||
for (const status in statuses) {
|
for (const status in statuses) {
|
||||||
if (statuses[status]?.kind === FileUploadStatusKind.InProgress) {
|
if (statuses[status]?.kind === FileUploadStatusKind.InProgress) {
|
||||||
@@ -76,7 +76,7 @@ export const inProgressFileUploadCountAtom = atom((get) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const successfulFileUploadCountAtom = atom((get) => {
|
export const successfulFileUploadCountAtom = atom((get) => {
|
||||||
const statuses = get(fileUploadsAtom)
|
const statuses = get(fileUploadStatusesAtom)
|
||||||
let count = 0
|
let count = 0
|
||||||
for (const status in statuses) {
|
for (const status in statuses) {
|
||||||
if (statuses[status]?.kind === FileUploadStatusKind.Success) {
|
if (statuses[status]?.kind === FileUploadStatusKind.Success) {
|
||||||
@@ -87,7 +87,7 @@ export const successfulFileUploadCountAtom = atom((get) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const hasFileUploadsErrorAtom = atom((get) => {
|
export const hasFileUploadsErrorAtom = atom((get) => {
|
||||||
const statuses = get(fileUploadsAtom)
|
const statuses = get(fileUploadStatusesAtom)
|
||||||
for (const status in statuses) {
|
for (const status in statuses) {
|
||||||
if (statuses[status]?.kind === FileUploadStatusKind.Error) {
|
if (statuses[status]?.kind === FileUploadStatusKind.Error) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Doc } from "@fileone/convex/_generated/dataModel"
|
import type { Doc } from "@fileone/convex/_generated/dataModel"
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { mutationOptions } from "@tanstack/react-query"
|
||||||
import { atom, useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
import { atom, useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
||||||
import { atomEffect } from "jotai-effect"
|
import { atomEffect } from "jotai-effect"
|
||||||
|
import { atomWithMutation } from "jotai-tanstack-query"
|
||||||
import {
|
import {
|
||||||
CircleAlertIcon,
|
CircleAlertIcon,
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
@@ -28,15 +29,16 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip"
|
} from "@/components/ui/tooltip"
|
||||||
import { WithAtom } from "@/components/with-atom"
|
|
||||||
import { formatError } from "@/lib/error"
|
import { formatError } from "@/lib/error"
|
||||||
import {
|
import {
|
||||||
|
clearAllFileUploadStatusesAtom,
|
||||||
clearFileUploadStatusesAtom,
|
clearFileUploadStatusesAtom,
|
||||||
FileUploadStatusKind,
|
FileUploadStatusKind,
|
||||||
fileUploadCountAtom,
|
fileUploadCountAtom,
|
||||||
fileUploadStatusAtomFamily,
|
fileUploadStatusAtomFamily,
|
||||||
fileUploadsAtom,
|
fileUploadStatusesAtom,
|
||||||
hasFileUploadsErrorAtom,
|
hasFileUploadsErrorAtom,
|
||||||
|
successfulFileUploadCountAtom,
|
||||||
} from "./store"
|
} from "./store"
|
||||||
import useUploadFile from "./use-upload-file"
|
import useUploadFile from "./use-upload-file"
|
||||||
|
|
||||||
@@ -53,6 +55,88 @@ export type PickedFile = {
|
|||||||
|
|
||||||
export const pickedFilesAtom = atom<PickedFile[]>([])
|
export const pickedFilesAtom = atom<PickedFile[]>([])
|
||||||
|
|
||||||
|
function useUploadFilesAtom({
|
||||||
|
targetDirectory,
|
||||||
|
}: {
|
||||||
|
targetDirectory: Doc<"directories">
|
||||||
|
}) {
|
||||||
|
const uploadFile = useUploadFile({ targetDirectory })
|
||||||
|
const store = useStore()
|
||||||
|
const options = useMemo(
|
||||||
|
() =>
|
||||||
|
mutationOptions({
|
||||||
|
mutationFn: async (files: PickedFile[]) => {
|
||||||
|
const promises = files.map((pickedFile) =>
|
||||||
|
uploadFile({
|
||||||
|
file: pickedFile.file,
|
||||||
|
onStart: () => {
|
||||||
|
store.set(
|
||||||
|
fileUploadStatusAtomFamily(pickedFile.id),
|
||||||
|
{
|
||||||
|
kind: FileUploadStatusKind.InProgress,
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onProgress: (progress) => {
|
||||||
|
store.set(
|
||||||
|
fileUploadStatusAtomFamily(pickedFile.id),
|
||||||
|
{
|
||||||
|
kind: FileUploadStatusKind.InProgress,
|
||||||
|
progress,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log("error", error)
|
||||||
|
store.set(
|
||||||
|
fileUploadStatusAtomFamily(pickedFile.id),
|
||||||
|
{
|
||||||
|
kind: FileUploadStatusKind.Error,
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return await Promise.allSettled(promises)
|
||||||
|
},
|
||||||
|
onSuccess: (results, files) => {
|
||||||
|
const remainingPickedFiles: PickedFile[] = []
|
||||||
|
results.forEach((result, i) => {
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: results lenght must match input files array length
|
||||||
|
const pickedFile = files[i]!
|
||||||
|
const statusAtom = fileUploadStatusAtomFamily(
|
||||||
|
pickedFile.id,
|
||||||
|
)
|
||||||
|
switch (result.status) {
|
||||||
|
case "fulfilled":
|
||||||
|
store.set(statusAtom, {
|
||||||
|
kind: FileUploadStatusKind.Success,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "rejected":
|
||||||
|
store.set(statusAtom, {
|
||||||
|
kind: FileUploadStatusKind.Error,
|
||||||
|
error: result.reason,
|
||||||
|
})
|
||||||
|
remainingPickedFiles.push(pickedFile)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// setPickedFiles(remainingPickedFiles)
|
||||||
|
|
||||||
|
if (remainingPickedFiles.length === 0) {
|
||||||
|
toast.success("All files uploaded successfully")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[uploadFile, store.set],
|
||||||
|
)
|
||||||
|
return useMemo(() => atomWithMutation(() => options), [options])
|
||||||
|
}
|
||||||
|
type UploadFilesAtom = ReturnType<typeof useUploadFilesAtom>
|
||||||
|
|
||||||
export function UploadFileDialog({
|
export function UploadFileDialog({
|
||||||
targetDirectory,
|
targetDirectory,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -79,69 +163,9 @@ export function UploadFileDialog({
|
|||||||
)
|
)
|
||||||
useAtom(updateFileInputEffect)
|
useAtom(updateFileInputEffect)
|
||||||
|
|
||||||
const uploadFile = useUploadFile({
|
const uploadFilesAtom = useUploadFilesAtom({
|
||||||
targetDirectory,
|
targetDirectory,
|
||||||
})
|
})
|
||||||
const { mutate: uploadFiles, isPending: isUploading } = useMutation({
|
|
||||||
mutationFn: async (files: PickedFile[]) => {
|
|
||||||
const promises = files.map((pickedFile) =>
|
|
||||||
uploadFile({
|
|
||||||
file: pickedFile.file,
|
|
||||||
onStart: () => {
|
|
||||||
store.set(fileUploadStatusAtomFamily(pickedFile.id), {
|
|
||||||
kind: FileUploadStatusKind.InProgress,
|
|
||||||
progress: 0,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onProgress: (progress) => {
|
|
||||||
store.set(fileUploadStatusAtomFamily(pickedFile.id), {
|
|
||||||
kind: FileUploadStatusKind.InProgress,
|
|
||||||
progress,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// clearFileUpload(pickedFile.id)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("error", error)
|
|
||||||
store.set(fileUploadStatusAtomFamily(pickedFile.id), {
|
|
||||||
kind: FileUploadStatusKind.Error,
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return await Promise.allSettled(promises)
|
|
||||||
},
|
|
||||||
onSuccess: (results, files) => {
|
|
||||||
const remainingPickedFiles: PickedFile[] = []
|
|
||||||
results.forEach((result, i) => {
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: results lenght must match input files array length
|
|
||||||
const pickedFile = files[i]!
|
|
||||||
const statusAtom = fileUploadStatusAtomFamily(pickedFile.id)
|
|
||||||
switch (result.status) {
|
|
||||||
case "fulfilled":
|
|
||||||
store.set(statusAtom, {
|
|
||||||
kind: FileUploadStatusKind.Success,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "rejected":
|
|
||||||
store.set(statusAtom, {
|
|
||||||
kind: FileUploadStatusKind.Error,
|
|
||||||
error: result.reason,
|
|
||||||
})
|
|
||||||
remainingPickedFiles.push(pickedFile)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// setPickedFiles(remainingPickedFiles)
|
|
||||||
|
|
||||||
if (remainingPickedFiles.length === 0) {
|
|
||||||
toast.success("All files uploaded successfully")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -162,9 +186,11 @@ export function UploadFileDialog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onUploadButtonClick() {
|
function onUploadButtonClick() {
|
||||||
const uploadStatuses = store.get(fileUploadsAtom)
|
const uploadStatuses = store.get(fileUploadStatusesAtom)
|
||||||
const fileUploadCount = store.get(fileUploadCountAtom)
|
const fileUploadCount = store.get(fileUploadCountAtom)
|
||||||
const pickedFiles = store.get(pickedFilesAtom)
|
const pickedFiles = store.get(pickedFilesAtom)
|
||||||
|
const { mutate: uploadFiles, reset: restUploadFilesMutation } =
|
||||||
|
store.get(uploadFilesAtom)
|
||||||
|
|
||||||
if (pickedFiles.length === 0) {
|
if (pickedFiles.length === 0) {
|
||||||
// no files are picked, nothing to upload
|
// no files are picked, nothing to upload
|
||||||
@@ -203,24 +229,11 @@ export function UploadFileDialog({
|
|||||||
} else {
|
} else {
|
||||||
// some files were not successfully uploaded, set the next picked files
|
// some files were not successfully uploaded, set the next picked files
|
||||||
setPickedFiles(nextPickedFiles)
|
setPickedFiles(nextPickedFiles)
|
||||||
|
restUploadFilesMutation()
|
||||||
uploadFiles(nextPickedFiles)
|
uploadFiles(nextPickedFiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dialogTitle: string
|
|
||||||
let dialogDescription: string
|
|
||||||
if (isUploading) {
|
|
||||||
dialogTitle = "Uploading files"
|
|
||||||
dialogDescription =
|
|
||||||
"You can close the dialog while they are being uploaded in the background."
|
|
||||||
} else if (targetDirectory.name) {
|
|
||||||
dialogTitle = `Upload file to "${targetDirectory.name}"`
|
|
||||||
dialogDescription = "Drag and drop files here or click to select files"
|
|
||||||
} else {
|
|
||||||
dialogTitle = "Upload file"
|
|
||||||
dialogDescription = "Drag and drop files here or click to select files"
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open
|
open
|
||||||
@@ -229,10 +242,10 @@ export function UploadFileDialog({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<UploadDialogHeader
|
||||||
<DialogTitle>{dialogTitle}</DialogTitle>
|
uploadFilesAtom={uploadFilesAtom}
|
||||||
<DialogDescription>{dialogDescription}</DialogDescription>
|
targetDirectory={targetDirectory}
|
||||||
</DialogHeader>
|
/>
|
||||||
|
|
||||||
<form id={formId} onSubmit={handleSubmit}>
|
<form id={formId} onSubmit={handleSubmit}>
|
||||||
<input
|
<input
|
||||||
@@ -250,44 +263,141 @@ export function UploadFileDialog({
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<WithAtom atom={pickedFilesAtom}>
|
<ContinueUploadAfterSuccessfulUploadButton
|
||||||
{(pickedFiles) => (
|
uploadFilesAtom={uploadFilesAtom}
|
||||||
<>
|
/>
|
||||||
{pickedFiles.length > 0 ? (
|
<SelectMoreFilesButton
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={openFilePicker}
|
onClick={openFilePicker}
|
||||||
disabled={isUploading}
|
uploadFilesAtom={uploadFilesAtom}
|
||||||
>
|
/>
|
||||||
Select more files
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
<UploadButton
|
<UploadButton
|
||||||
disabled={isUploading}
|
uploadFilesAtom={uploadFilesAtom}
|
||||||
loading={isUploading}
|
|
||||||
onClick={onUploadButtonClick}
|
onClick={onUploadButtonClick}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</WithAtom>
|
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function UploadDialogHeader({
|
||||||
|
uploadFilesAtom,
|
||||||
|
targetDirectory,
|
||||||
|
}: {
|
||||||
|
uploadFilesAtom: UploadFilesAtom
|
||||||
|
targetDirectory: Doc<"directories">
|
||||||
|
}) {
|
||||||
|
const { data: uploadResults, isPending: isUploading } =
|
||||||
|
useAtomValue(uploadFilesAtom)
|
||||||
|
const successfulUploadCount = useAtomValue(successfulFileUploadCountAtom)
|
||||||
|
|
||||||
|
let dialogTitle: string
|
||||||
|
let dialogDescription: string
|
||||||
|
if (isUploading) {
|
||||||
|
dialogTitle = "Uploading files"
|
||||||
|
dialogDescription =
|
||||||
|
"You can close the dialog while they are being uploaded in the background."
|
||||||
|
} else if (
|
||||||
|
uploadResults &&
|
||||||
|
uploadResults.length > 0 &&
|
||||||
|
successfulUploadCount === uploadResults.length
|
||||||
|
) {
|
||||||
|
dialogTitle = "Files uploaded"
|
||||||
|
dialogDescription =
|
||||||
|
"Click 'Done' to close the dialog, or select more files to upload."
|
||||||
|
} else if (targetDirectory.name) {
|
||||||
|
dialogTitle = `Upload file to "${targetDirectory.name}"`
|
||||||
|
dialogDescription = "Drag and drop files here or click to select files"
|
||||||
|
} else {
|
||||||
|
dialogTitle = "Upload file"
|
||||||
|
dialogDescription = "Drag and drop files here or click to select files"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||||
|
<DialogDescription>{dialogDescription}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContinueUploadAfterSuccessfulUploadButton({
|
||||||
|
uploadFilesAtom,
|
||||||
|
}: {
|
||||||
|
uploadFilesAtom: UploadFilesAtom
|
||||||
|
}) {
|
||||||
|
const setPickedFiles = useSetAtom(pickedFilesAtom)
|
||||||
|
const clearAllFileUploadStatuses = useSetAtom(
|
||||||
|
clearAllFileUploadStatusesAtom,
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
data: uploadResults,
|
||||||
|
isPending: isUploading,
|
||||||
|
reset: resetUploadFilesMutation,
|
||||||
|
} = useAtomValue(uploadFilesAtom)
|
||||||
|
const successfulUploadCount = useAtomValue(successfulFileUploadCountAtom)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!uploadResults ||
|
||||||
|
uploadResults.length === 0 ||
|
||||||
|
successfulUploadCount !== uploadResults.length
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUploadState() {
|
||||||
|
setPickedFiles([])
|
||||||
|
clearAllFileUploadStatuses()
|
||||||
|
resetUploadFilesMutation()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={resetUploadState}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
Upload more files
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows the user to select more files after they have selected some files for upload. only visible before any upload has been started.
|
||||||
|
*/
|
||||||
|
function SelectMoreFilesButton({
|
||||||
|
onClick,
|
||||||
|
uploadFilesAtom,
|
||||||
|
}: {
|
||||||
|
onClick: () => void
|
||||||
|
uploadFilesAtom: UploadFilesAtom
|
||||||
|
}) {
|
||||||
|
const pickedFiles = useAtomValue(pickedFilesAtom)
|
||||||
|
const { data: uploadResults, isPending: isUploading } =
|
||||||
|
useAtomValue(uploadFilesAtom)
|
||||||
|
|
||||||
|
if (pickedFiles.length === 0 || uploadResults) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant="outline" onClick={onClick} disabled={isUploading}>
|
||||||
|
Select more files
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function UploadButton({
|
function UploadButton({
|
||||||
disabled,
|
uploadFilesAtom,
|
||||||
loading,
|
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
disabled: boolean
|
uploadFilesAtom: UploadFilesAtom
|
||||||
loading: boolean
|
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}) {
|
}) {
|
||||||
const pickedFiles = useAtomValue(pickedFilesAtom)
|
const pickedFiles = useAtomValue(pickedFilesAtom)
|
||||||
const hasUploadErrors = useAtomValue(hasFileUploadsErrorAtom)
|
const hasUploadErrors = useAtomValue(hasFileUploadsErrorAtom)
|
||||||
const fileUploadCount = useAtomValue(fileUploadCountAtom)
|
const fileUploadCount = useAtomValue(fileUploadCountAtom)
|
||||||
|
const { isPending: isUploading } = useAtomValue(uploadFilesAtom)
|
||||||
|
|
||||||
let label: string
|
let label: string
|
||||||
if (hasUploadErrors) {
|
if (hasUploadErrors) {
|
||||||
@@ -303,7 +413,7 @@ function UploadButton({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={onClick} disabled={disabled} loading={loading}>
|
<Button onClick={onClick} disabled={isUploading} loading={isUploading}>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -464,7 +574,14 @@ function PickedFileItem({
|
|||||||
break
|
break
|
||||||
case FileUploadStatusKind.Success:
|
case FileUploadStatusKind.Success:
|
||||||
statusIndicator = (
|
statusIndicator = (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
<CircleCheckIcon className="pr-2 text-green-500" />
|
<CircleCheckIcon className="pr-2 text-green-500" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>File uploaded</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -472,10 +589,10 @@ function PickedFileItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className="pl-3 pr-1 py-0.5 h-8 hover:bg-muted flex justify-between items-center"
|
className="pl-3 pr-1 py-0.5 h-8 hover:bg-muted flex justify-between items-center border-b border-border"
|
||||||
key={id}
|
key={id}
|
||||||
>
|
>
|
||||||
<span>{file.name}</span>
|
<p>{file.name} </p>
|
||||||
{statusIndicator}
|
{statusIndicator}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user