fix draggable file input

This commit is contained in:
Zhang Minghan 2023-09-13 21:39:19 +08:00
parent d9e821f43b
commit f0eacf2aba
2 changed files with 89 additions and 78 deletions

View File

@ -12,6 +12,7 @@ import {
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Alert, AlertTitle } from "./ui/alert.tsx"; import { Alert, AlertTitle } from "./ui/alert.tsx";
import { useToast } from "./ui/use-toast.ts"; import { useToast } from "./ui/use-toast.ts";
import {useDraggableInput} from "../utils.ts";
export type FileObject = { export type FileObject = {
name: string; name: string;
@ -19,7 +20,7 @@ export type FileObject = {
} }
type FileProviderProps = { type FileProviderProps = {
id?: string; id: string;
className?: string; className?: string;
maxLength?: number; maxLength?: number;
onChange?: (data: FileObject) => void; onChange?: (data: FileObject) => void;
@ -27,9 +28,10 @@ type FileProviderProps = {
}; };
type FileObjectProps = { type FileObjectProps = {
id?: string; id: string;
filename: string;
className?: string; className?: string;
onChange?: (filename: string, data: string) => void; onChange?: (filename?: string, data?: string) => void;
}; };
function FileProvider({ function FileProvider({
@ -43,7 +45,6 @@ function FileProvider({
const { toast } = useToast(); const { toast } = useToast();
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const [filename, setFilename] = useState<string>(""); const [filename, setFilename] = useState<string>("");
const ref = useRef<HTMLLabelElement | null>(null);
useEffect(() => { useEffect(() => {
setClearEvent && setClearEvent(() => clear); setClearEvent && setClearEvent(() => clear);
@ -53,48 +54,6 @@ function FileProvider({
} }
}, [setClearEvent]); }, [setClearEvent]);
useEffect(() => {
if (!ref.current) return;
const target = ref.current as HTMLLabelElement;
target.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
});
target.addEventListener("drop", (e) => {
e.preventDefault();
e.stopPropagation();
const file = e.dataTransfer?.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const data = e.target?.result as string;
if (!/^[\x00-\x7F]*$/.test(data)) {
toast({
title: t("file.parse-error"),
description: t("file.parse-error-prompt"),
});
handleChange();
} else {
handleChange(e.target?.result as string);
}
};
reader.readAsText(file);
} else {
handleChange();
}
});
target.addEventListener("dragleave", (e) => {
e.preventDefault();
e.stopPropagation();
});
return () => {
target.removeEventListener("dragover", () => {});
target.removeEventListener("drop", () => {});
}
}, [ref]);
function clear() { function clear() {
setFilename(""); setFilename("");
setActive(false); setActive(false);
@ -143,47 +102,43 @@ function FileProvider({
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>{t("file.type")}</AlertTitle> <AlertTitle>{t("file.type")}</AlertTitle>
</Alert> </Alert>
<label className={`drop-window`} htmlFor={id} ref={ref}> <FileObject
{filename ? ( id={id}
<div className={`file-object`}> filename={filename}
<File className={`h-4 w-4`} /> className={className}
<p>{filename}</p> onChange={handleChange}
<X
className={`h-3.5 w-3.5 ml-1 close`}
onClick={(e) => {
handleChange();
e.preventDefault();
}}
/> />
</div> </div>
) : (
<p>{t("file.drop")}</p>
)}
</label>
</div>
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<FileObject
id={id}
className={className}
onChange={handleChange}
/>
</> </>
); );
} }
function FileObject({ function FileObject({
id, id,
filename,
className, className,
onChange, onChange,
}: FileObjectProps) { }: FileObjectProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const ref = useRef(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { useEffect(() => {
const file = e.target.files?.[0]; if (!ref.current) return;
const target = ref.current as HTMLLabelElement;
onChange && useDraggableInput(t, toast, target, onChange);
return () => {
target.removeEventListener("dragover", () => {});
target.removeEventListener("drop", () => {});
}
}, [ref]);
const handleChange = (e?: React.ChangeEvent<HTMLInputElement>) => {
const file = e && e.target.files?.[0];
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
@ -203,7 +158,26 @@ function FileObject({
onChange?.("", ""); onChange?.("", "");
} }
}; };
return ( return (
<>
<label className={`drop-window`} htmlFor={id} ref={ref}>
{filename ? (
<div className={`file-object`}>
<File className={`h-4 w-4`} />
<p>{filename}</p>
<X
className={`h-3.5 w-3.5 ml-1 close`}
onClick={(e) => {
handleChange();
e.preventDefault();
}}
/>
</div>
) : (
<p>{t("file.drop")}</p>
)}
</label>
<input <input
id={id} id={id}
type="file" type="file"
@ -212,6 +186,7 @@ function FileObject({
multiple={false} multiple={false}
style={{ display: "none" }} style={{ display: "none" }}
/> />
</>
); );
} }

View File

@ -156,3 +156,39 @@ ${message}`;
return message; return message;
} }
} }
export function useDraggableInput(
t: any,
toast: any,
target: HTMLLabelElement,
handleChange: (filename?: string, content?: string) => void
) {
target.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
});
target.addEventListener("drop", (e) => {
e.preventDefault();
e.stopPropagation();
const file = e.dataTransfer?.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const data = e.target?.result as string;
if (!/^[\x00-\x7F]*$/.test(data)) {
toast({
title: t("file.parse-error"),
description: t("file.parse-error-prompt"),
});
handleChange();
} else {
handleChange(file.name, e.target?.result as string);
}
};
reader.readAsText(file);
} else {
handleChange();
}
});
}