mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): support zip upload for template files (#12323)
Related to #11687
This commit is contained in:
@@ -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();
|
||||
});
|
||||
@@ -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"]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user