feat(site): support zip upload for template files (#12323)

Related to #11687
This commit is contained in:
Bruno Quaresma
2024-02-27 18:00:23 -03:00
committed by GitHub
parent 0f9c142ea6
commit 0fc1a9164e
3 changed files with 94 additions and 40 deletions
@@ -0,0 +1,41 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { FileUpload } from "./FileUpload";
import { ThemeProvider } from "contexts/ThemeProvider";
test("accepts files with the correct extension", async () => {
const onUpload = jest.fn();
render(
<ThemeProvider>
<FileUpload
isUploading={false}
onUpload={onUpload}
removeLabel="Remove file"
title="Upload file"
extensions={["tar", "zip"]}
/>
</ThemeProvider>,
);
const dropZone = screen.getByTestId("drop-zone");
const tarFile = new File([""], "file.tar");
fireEvent.drop(dropZone, {
dataTransfer: { files: [tarFile] },
});
expect(onUpload).toBeCalledWith(tarFile);
onUpload.mockClear();
const zipFile = new File([""], "file.zip");
fireEvent.drop(dropZone, {
dataTransfer: { files: [zipFile] },
});
expect(onUpload).toBeCalledWith(zipFile);
onUpload.mockClear();
const unsupportedFile = new File([""], "file.mp4");
fireEvent.drop(dropZone, {
dataTransfer: { files: [unsupportedFile] },
});
expect(onUpload).not.toHaveBeenCalled();
});
+51 -37
View File
@@ -8,36 +8,6 @@ import RemoveIcon from "@mui/icons-material/DeleteOutline";
import FileIcon from "@mui/icons-material/FolderOutlined";
import { css, type Interpolation, type Theme } from "@emotion/react";
const useFileDrop = (
callback: (file: File) => void,
fileTypeRequired?: string,
): {
onDragOver: (e: DragEvent<HTMLDivElement>) => void;
onDrop: (e: DragEvent<HTMLDivElement>) => void;
} => {
const onDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
const onDrop = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- file can be undefined
if (!file) {
return;
}
if (fileTypeRequired && file.type !== fileTypeRequired) {
return;
}
callback(file);
};
return {
onDragOver,
onDrop,
};
};
export interface FileUploadProps {
isUploading: boolean;
onUpload: (file: File) => void;
@@ -46,8 +16,7 @@ export interface FileUploadProps {
removeLabel: string;
title: string;
description?: ReactNode;
extension?: string;
fileTypeRequired?: string;
extensions?: string[];
}
export const FileUpload: FC<FileUploadProps> = ({
@@ -58,10 +27,9 @@ export const FileUpload: FC<FileUploadProps> = ({
removeLabel,
title,
description,
extension,
fileTypeRequired,
extensions,
}) => {
const tarDrop = useFileDrop(onUpload, fileTypeRequired);
const fileDrop = useFileDrop(onUpload, extensions);
const inputRef = useRef<HTMLInputElement>(null);
const clickable = useClickable<HTMLDivElement>(
() => inputRef.current?.click(),
@@ -90,9 +58,10 @@ export const FileUpload: FC<FileUploadProps> = ({
return (
<>
<div
data-testid="drop-zone"
css={[styles.root, isUploading && styles.disabled]}
{...clickable}
{...tarDrop}
{...fileDrop}
>
<Stack alignItems="center" spacing={1}>
{isUploading ? (
@@ -113,7 +82,7 @@ export const FileUpload: FC<FileUploadProps> = ({
data-testid="file-upload"
ref={inputRef}
css={styles.input}
accept={extension}
accept={extensions?.map((ext) => `.${ext}`).join(",")}
onChange={(event) => {
const file = event.currentTarget.files?.[0];
if (file) {
@@ -125,6 +94,47 @@ export const FileUpload: FC<FileUploadProps> = ({
);
};
const useFileDrop = (
callback: (file: File) => void,
extensions?: string[],
): {
onDragOver: (e: DragEvent<HTMLDivElement>) => void;
onDrop: (e: DragEvent<HTMLDivElement>) => void;
} => {
const onDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
const onDrop = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const file = e.dataTransfer.files[0] as File | undefined;
if (!file) {
return;
}
if (!extensions) {
callback(file);
return;
}
const extension = file.name.split(".").pop();
if (!extension) {
throw new Error(`File has no extension to compare with ${extensions}`);
}
if (extensions.includes(extension)) {
callback(file);
}
};
return {
onDragOver,
onDrop,
};
};
const styles = {
root: (theme) => css`
display: flex;
@@ -151,12 +161,16 @@ const styles = {
title: {
fontSize: 16,
lineHeight: "1",
},
description: (theme) => ({
color: theme.palette.text.secondary,
textAlign: "center",
maxWidth: 400,
fontSize: 14,
lineHeight: "1.5",
marginTop: 4,
}),
input: {
@@ -18,7 +18,7 @@ export const TemplateUpload: FC<TemplateUploadProps> = ({
}) => {
const description = (
<>
The template has to be a .tar file. You can also use our{" "}
The template has to be a .tar or .zip file. You can also use our{" "}
<Link
component={RouterLink}
to="/starter-templates"
@@ -42,8 +42,7 @@ export const TemplateUpload: FC<TemplateUploadProps> = ({
removeLabel="Remove file"
title="Upload template"
description={description}
extension=".tar"
fileTypeRequired="application/x-tar"
extensions={["tar", "zip"]}
/>
);
};