Support paste file to upload
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import React, { Dispatch, SetStateAction, useCallback } from "react";
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
|
||||
@@ -17,6 +24,8 @@ const DragAndDrop = ({
|
||||
bucketInfo: BucketInfo;
|
||||
}) => {
|
||||
const t = useTranslations("Components");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isPasteActive, setIsPasteActive] = useState(false);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[]) => {
|
||||
@@ -25,15 +34,63 @@ const DragAndDrop = ({
|
||||
[setSelectedFile],
|
||||
);
|
||||
|
||||
const handlePaste = useCallback(
|
||||
(event: ClipboardEvent) => {
|
||||
const clipboardData = event.clipboardData;
|
||||
if (!clipboardData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = clipboardData.items;
|
||||
const files: File[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
// 检查是否是文件类型
|
||||
if (item.kind === "file") {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
event.preventDefault();
|
||||
setSelectedFile(files);
|
||||
setIsPasteActive(true);
|
||||
setTimeout(() => setIsPasteActive(false), 2000);
|
||||
}
|
||||
},
|
||||
[setSelectedFile],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleGlobalPaste = (event: ClipboardEvent) => {
|
||||
handlePaste(event);
|
||||
};
|
||||
|
||||
document.addEventListener("paste", handleGlobalPaste);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("paste", handleGlobalPaste);
|
||||
};
|
||||
}, [handlePaste]);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
{...getRootProps()}
|
||||
className={`grids flex h-52 w-full cursor-pointer items-center justify-center rounded-lg border-2 border-dashed p-4 duration-150 ${
|
||||
tabIndex={0}
|
||||
className={`grids flex h-52 w-full cursor-pointer items-center justify-center rounded-lg border-2 border-dashed p-4 duration-150 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${
|
||||
isDragActive
|
||||
? "border-opacity-90 bg-muted/80 backdrop-blur-[2px]"
|
||||
: "border-opacity-50 bg-muted/10 backdrop-blur-[1px]"
|
||||
: isPasteActive
|
||||
? "border-green-500 bg-green-50 backdrop-blur-[2px]"
|
||||
: "border-opacity-50 bg-muted/10 backdrop-blur-[1px]"
|
||||
}`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
@@ -45,6 +102,10 @@ const DragAndDrop = ({
|
||||
<div className="animate-fade-in text-primary">
|
||||
{t("Drop files to upload them to")} {bucketInfo.bucket}
|
||||
</div>
|
||||
) : isPasteActive ? (
|
||||
<div className="animate-fade-in text-green-600">
|
||||
{t("Files pasted successfully")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="animate-fade-out">
|
||||
<p>{t("Drag and drop file(s) here")}</p>
|
||||
|
||||
+245
-245
@@ -25,6 +25,8 @@ import {
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerOverlay,
|
||||
DrawerPortal,
|
||||
DrawerTitle,
|
||||
} from "@/components/ui/drawer";
|
||||
import { CopyButton } from "@/components/shared/copy-button";
|
||||
@@ -91,270 +93,268 @@ export const FileUploader = ({
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Drawer open={isOpen} direction="right" onOpenChange={setIsOpen}>
|
||||
<DrawerContent className="h-screen w-full overflow-y-auto rounded-none sm:max-w-xl">
|
||||
<DrawerHeader className="flex items-center justify-between">
|
||||
<DrawerTitle className="flex items-center gap-1">
|
||||
{t("Upload Files")}
|
||||
</DrawerTitle>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="ghost" className="">
|
||||
<Icons.close className="size-4" />
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerHeader>
|
||||
<DrawerDescription className="flex items-center justify-between px-4">
|
||||
<div className="flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<div className="truncate">{bucketInfo.provider_name}</div>
|
||||
<Icons.arrowRight className="size-3" />
|
||||
<div className="font-medium text-blue-600 dark:text-blue-400">
|
||||
{bucketInfo.bucket}
|
||||
</div>
|
||||
<DrawerPortal>
|
||||
<DrawerContent className="h-screen w-full overflow-y-auto rounded-none sm:max-w-xl">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="mx-4 text-lg font-bold">{t("Upload Files")}</p>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="ghost">
|
||||
<Icons.close className="size-4" />
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</div>
|
||||
<Badge className="text-xs">
|
||||
{t("Limit")}:{" "}
|
||||
{formatFileSize(Number(plan?.stMaxFileSize || "0"), {
|
||||
precision: 0,
|
||||
})}{" "}
|
||||
/{" "}
|
||||
{formatFileSize(Number(plan?.stMaxTotalSize || "0"), {
|
||||
precision: 0,
|
||||
})}
|
||||
</Badge>
|
||||
</DrawerDescription>
|
||||
|
||||
<div className="space-y-3 p-4">
|
||||
<DragAndDrop
|
||||
setSelectedFile={setSelectedFile}
|
||||
bucketInfo={bucketInfo}
|
||||
/>
|
||||
|
||||
{/* 统计信息 */}
|
||||
{stats.total > 0 && (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="font-semibold">{t("Upload List")}</h2>
|
||||
<Badge className="flex items-center gap-1">
|
||||
{stats.completed}
|
||||
<span>/</span>
|
||||
{stats.total}
|
||||
</Badge>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div className="flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<div className="truncate">{bucketInfo.provider_name}</div>
|
||||
<Icons.arrowRight className="size-3" />
|
||||
<div className="font-medium text-blue-600 dark:text-blue-400">
|
||||
{bucketInfo.bucket}
|
||||
</div>
|
||||
</div>
|
||||
// <div className="mt-6 rounded-lg bg-gray-50 p-4">
|
||||
// <div className="flex flex-wrap gap-4 text-sm">
|
||||
// <span className="text-gray-600">总计: {stats.total}</span>
|
||||
// <span className="text-gray-600">等待: {stats.pending}</span>
|
||||
// <span className="text-blue-600">
|
||||
// 上传中: {stats.uploading}
|
||||
// </span>
|
||||
// <span className="text-green-600">
|
||||
// 完成: {stats.completed}
|
||||
// </span>
|
||||
// <span className="text-red-600">失败: {stats.error}</span>
|
||||
// <span className="text-orange-600">
|
||||
// 取消: {stats.cancelled}
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
)}
|
||||
<Badge className="text-xs">
|
||||
{t("Limit")}:{" "}
|
||||
{formatFileSize(Number(plan?.stMaxFileSize || "0"), {
|
||||
precision: 0,
|
||||
})}{" "}
|
||||
/{" "}
|
||||
{formatFileSize(Number(plan?.stMaxTotalSize || "0"), {
|
||||
precision: 0,
|
||||
})}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-3 p-4">
|
||||
<DragAndDrop
|
||||
setSelectedFile={setSelectedFile}
|
||||
bucketInfo={bucketInfo}
|
||||
/>
|
||||
|
||||
{/* 文件列表 */}
|
||||
{files.length > 0 && (
|
||||
<div className="space-y-2 rounded-lg">
|
||||
{files.some((file) => file.status === "uploading") && (
|
||||
<div className="flex items-center gap-1 rounded-md border border-dashed bg-yellow-100 p-2 text-sm text-muted-foreground dark:bg-neutral-600">
|
||||
<Icons.info className="size-4" />
|
||||
{t(
|
||||
"Do not close the window until the upload is complete",
|
||||
)}
|
||||
.
|
||||
</div>
|
||||
)}
|
||||
{/* 统计信息 */}
|
||||
{stats.total > 0 && (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="font-semibold">{t("Upload List")}</h2>
|
||||
<Badge className="flex items-center gap-1">
|
||||
{stats.completed}
|
||||
<span>/</span>
|
||||
{stats.total}
|
||||
</Badge>
|
||||
</div>
|
||||
// <div className="mt-6 rounded-lg bg-gray-50 p-4">
|
||||
// <div className="flex flex-wrap gap-4 text-sm">
|
||||
// <span className="text-gray-600">总计: {stats.total}</span>
|
||||
// <span className="text-gray-600">等待: {stats.pending}</span>
|
||||
// <span className="text-blue-600">
|
||||
// 上传中: {stats.uploading}
|
||||
// </span>
|
||||
// <span className="text-green-600">
|
||||
// 完成: {stats.completed}
|
||||
// </span>
|
||||
// <span className="text-red-600">失败: {stats.error}</span>
|
||||
// <span className="text-orange-600">
|
||||
// 取消: {stats.cancelled}
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
)}
|
||||
|
||||
{/* 文件列表 */}
|
||||
{files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-lg border",
|
||||
file.status === "uploading" &&
|
||||
"border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-800",
|
||||
file.status === "completed" &&
|
||||
"border-green-300 bg-green-50 dark:border-green-600 dark:bg-green-900/20",
|
||||
file.status === "error" &&
|
||||
"border-red-300 bg-red-50 dark:border-red-600 dark:bg-red-900/20",
|
||||
"backdrop-blur-sm transition-all duration-300",
|
||||
file.status === "cancelled" &&
|
||||
"border-yellow-300 bg-yellow-50 dark:border-yellow-600 dark:bg-yellow-900/20",
|
||||
)}
|
||||
>
|
||||
{/* 主进度条背景 */}
|
||||
{file.status === "uploading" && (
|
||||
<div className="absolute inset-0 overflow-hidden rounded-lg">
|
||||
<div
|
||||
className="h-full bg-gray-200 transition-all duration-500 ease-out dark:bg-gray-700"
|
||||
style={{ width: `${file.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* 文件列表 */}
|
||||
{files.length > 0 && (
|
||||
<div className="space-y-2 rounded-lg">
|
||||
{files.some((file) => file.status === "uploading") && (
|
||||
<div className="flex items-center gap-1 rounded-md border border-dashed bg-yellow-100 p-2 text-sm text-muted-foreground dark:bg-neutral-600">
|
||||
<Icons.info className="size-4" />
|
||||
{t(
|
||||
"Do not close the window until the upload is complete",
|
||||
)}
|
||||
.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="relative z-10 px-4 py-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-between gap-3",
|
||||
file.status === "uploading"
|
||||
? "items-start"
|
||||
: "items-center",
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-gray-900 dark:text-white">
|
||||
{file.originalName}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(file.file.size)}
|
||||
</p>
|
||||
{/* 文件列表 */}
|
||||
{files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-lg border",
|
||||
file.status === "uploading" &&
|
||||
"border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-800",
|
||||
file.status === "completed" &&
|
||||
"border-green-300 bg-green-50 dark:border-green-600 dark:bg-green-900/20",
|
||||
file.status === "error" &&
|
||||
"border-red-300 bg-red-50 dark:border-red-600 dark:bg-red-900/20",
|
||||
"backdrop-blur-sm transition-all duration-300",
|
||||
file.status === "cancelled" &&
|
||||
"border-yellow-300 bg-yellow-50 dark:border-yellow-600 dark:bg-yellow-900/20",
|
||||
)}
|
||||
>
|
||||
{/* 主进度条背景 */}
|
||||
{file.status === "uploading" && (
|
||||
<div className="absolute inset-0 overflow-hidden rounded-lg">
|
||||
<div
|
||||
className="h-full bg-gray-200 transition-all duration-500 ease-out dark:bg-gray-700"
|
||||
style={{ width: `${file.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 状态指示器和操作按钮 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{file.status === "pending" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-full bg-neutral-600 px-3 py-1 text-xs text-white dark:bg-neutral-700">
|
||||
<div className="h-2 w-2 rounded-full bg-neutral-300 dark:bg-neutral-400"></div>
|
||||
{t("Pending Upload")}
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeFile(file.id)}
|
||||
className="size-[30px] p-1.5 text-red-600 transition-colors hover:text-red-800"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : file.status === "uploading" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">
|
||||
{file.progress}%
|
||||
</span>
|
||||
<div className="flex items-center gap-1 rounded-full bg-gray-700 px-3 py-1 text-xs text-white dark:bg-gray-600">
|
||||
<div className="h-2 w-2 animate-pulse rounded-full bg-gray-300 dark:bg-gray-400"></div>
|
||||
{t("Uploading")}
|
||||
</div>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="destructive"
|
||||
onClick={() => cancelUpload(file.id)}
|
||||
title="取消上传"
|
||||
>
|
||||
<Icons.close className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : file.status === "completed" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-full bg-green-600 px-3 py-1 text-xs text-white dark:bg-green-700">
|
||||
<div className="h-2 w-2 rounded-full bg-green-300 dark:bg-green-400"></div>
|
||||
{t("Completed")}
|
||||
</div>
|
||||
<CopyButton
|
||||
value={`${bucketInfo.custom_domain}/${file.fileName}`}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeFile(file.id)}
|
||||
className="size-[30px] p-1.5 text-red-600 transition-colors hover:text-red-800"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
(file.status === "error" ||
|
||||
file.status === "cancelled") && (
|
||||
{/* 内容区域 */}
|
||||
<div className="relative z-10 px-4 py-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-between gap-3",
|
||||
file.status === "uploading"
|
||||
? "items-start"
|
||||
: "items-center",
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-gray-900 dark:text-white">
|
||||
{file.originalName}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(file.file.size)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 状态指示器和操作按钮 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{file.status === "pending" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-full bg-red-600 px-3 py-1 text-xs text-white dark:bg-red-700">
|
||||
<div className="h-2 w-2 rounded-full bg-red-300 dark:bg-red-400"></div>
|
||||
{t("Aborted")}
|
||||
<div className="flex items-center gap-1 rounded-full bg-neutral-600 px-3 py-1 text-xs text-white dark:bg-neutral-700">
|
||||
<div className="h-2 w-2 rounded-full bg-neutral-300 dark:bg-neutral-400"></div>
|
||||
{t("Pending Upload")}
|
||||
</div>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
onClick={() => retryUpload(file.id)}
|
||||
>
|
||||
<RotateCcw className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeFile(file.id)}
|
||||
className="size-[30px] p-1.5 text-red-600 transition-colors hover:text-red-800"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
{file.status === "uploading" && (
|
||||
<div className="mt-3">
|
||||
<div className="relative h-2 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full bg-gray-800 transition-all duration-300 ease-out dark:bg-gray-300"
|
||||
style={{ width: `${file.progress}%` }}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 h-full w-16 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-all duration-1000 ease-out dark:via-white/20"
|
||||
style={{
|
||||
left: `${Math.max(0, file.progress - 8)}%`,
|
||||
opacity:
|
||||
file.progress > 0 && file.progress < 100
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
/>
|
||||
) : file.status === "uploading" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">
|
||||
{file.progress}%
|
||||
</span>
|
||||
<div className="flex items-center gap-1 rounded-full bg-gray-700 px-3 py-1 text-xs text-white dark:bg-gray-600">
|
||||
<div className="h-2 w-2 animate-pulse rounded-full bg-gray-300 dark:bg-gray-400"></div>
|
||||
{t("Uploading")}
|
||||
</div>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="destructive"
|
||||
onClick={() => cancelUpload(file.id)}
|
||||
title="取消上传"
|
||||
>
|
||||
<Icons.close className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : file.status === "completed" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-full bg-green-600 px-3 py-1 text-xs text-white dark:bg-green-700">
|
||||
<div className="h-2 w-2 rounded-full bg-green-300 dark:bg-green-400"></div>
|
||||
{t("Completed")}
|
||||
</div>
|
||||
<CopyButton
|
||||
value={`${bucketInfo.custom_domain}/${file.fileName}`}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeFile(file.id)}
|
||||
className="size-[30px] p-1.5 text-red-600 transition-colors hover:text-red-800"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
(file.status === "error" ||
|
||||
file.status === "cancelled") && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-full bg-red-600 px-3 py-1 text-xs text-white dark:bg-red-700">
|
||||
<div className="h-2 w-2 rounded-full bg-red-300 dark:bg-red-400"></div>
|
||||
{t("Aborted")}
|
||||
</div>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
onClick={() => retryUpload(file.id)}
|
||||
>
|
||||
<RotateCcw className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
className="size-6"
|
||||
size="icon"
|
||||
variant="destructive"
|
||||
onClick={() => removeFile(file.id)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 进度条 */}
|
||||
{file.status === "uploading" && (
|
||||
<div className="mt-3">
|
||||
<div className="relative h-2 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full bg-gray-800 transition-all duration-300 ease-out dark:bg-gray-300"
|
||||
style={{ width: `${file.progress}%` }}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 h-full w-16 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-all duration-1000 ease-out dark:via-white/20"
|
||||
style={{
|
||||
left: `${Math.max(0, file.progress - 8)}%`,
|
||||
opacity:
|
||||
file.progress > 0 && file.progress < 100
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DrawerFooter className="sticky bottom-0 flex flex-row items-center justify-between gap-2 backdrop-blur-md">
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">{t("Cancel")}</Button>
|
||||
</DrawerClose>
|
||||
|
||||
<DrawerFooter className="sticky bottom-0 flex flex-row items-center justify-between gap-2 backdrop-blur-md">
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">{t("Cancel")}</Button>
|
||||
</DrawerClose>
|
||||
|
||||
{files.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={startUpload}
|
||||
disabled={isUploading || stats.pending === 0}
|
||||
className="flex items-center gap-2 rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600 disabled:cursor-not-allowed disabled:bg-gray-400"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
{t("Start Upload")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
clearAll();
|
||||
setSelectedFile(null);
|
||||
}}
|
||||
>
|
||||
{t("Clear")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
{files.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={startUpload}
|
||||
disabled={isUploading || stats.pending === 0}
|
||||
className="flex items-center gap-2 rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600 disabled:cursor-not-allowed disabled:bg-gray-400"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
{t("Start Upload")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
clearAll();
|
||||
setSelectedFile(null);
|
||||
}}
|
||||
>
|
||||
{t("Clear")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
|
||||
+2
-1
@@ -373,7 +373,8 @@
|
||||
"Do not close the window until the upload is complete": "Do not close the window until the upload is complete",
|
||||
"Pending Upload": "Pending Upload",
|
||||
"Start Upload": "Start Upload",
|
||||
"Limit": "Limit"
|
||||
"Limit": "Limit",
|
||||
"Files pasted successfully": "Files pasted successfully"
|
||||
},
|
||||
"Landing": {
|
||||
"settings": "Settings",
|
||||
|
||||
+2
-1
@@ -373,7 +373,8 @@
|
||||
"Do not close the window until the upload is complete": "在上传完成之前不要浏览器关闭窗口",
|
||||
"Pending Upload": "等待上传",
|
||||
"Start Upload": "开始上传",
|
||||
"Limit": "限制"
|
||||
"Limit": "限制",
|
||||
"Files pasted successfully": "文件粘贴成功"
|
||||
},
|
||||
"Landing": {
|
||||
"settings": "设置",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user