Skip to content

Commit

Permalink
Removed import alias and improvedÏ FileUpload
Browse files Browse the repository at this point in the history
  • Loading branch information
NicholasMata committed Jul 14, 2023
1 parent 3756d00 commit 2890672
Show file tree
Hide file tree
Showing 18 changed files with 98 additions and 94 deletions.
1 change: 1 addition & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mui-file-upload",
"version": "1.0.0-alpha.1",
"version": "1.0.0-alpha.2",
"description": "A library of components that are related to file uploads built on top of MUI",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
9 changes: 8 additions & 1 deletion src/components/FileUpload/FileUploadResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ import { Box, Fade, Grow, Stack, StackProps } from "@mui/material";
import { FileUploadCard, FileUploadCardActions } from "../FileUploadCard";
import { RejectedFileUploadAlert } from "../RejectedFileUploadAlert";
import { forwardRef } from "react";
import { FileUpload } from "types";
import { FileUpload } from "../../types";

type Props = {
/** A list of files that have been rejected. */
rejected: File[];
/** A list of file uploads that are completed and have failed. */
failed: FileUpload<any>[];
/** A list of file uploads that are in progress. */
inProgress: FileUpload<any>[];
/** A list of fiel uploads that are compelted and successful */
successful: FileUpload<any>[];
/** Called when a rejected file alert should be dismissed aka removed from `rejected` */
onDismissRejected?: (file: File) => void;
/** Called when a failed file upload should be retried. */
onRetry?: (fileUpload: FileUpload<any>) => void;
/** Called when a file upload needs to be removed. */
onRemoveFileUpload?: (fileUpload: FileUpload<any>) => void;
} & StackProps;
export const FileUploadResults = forwardRef<HTMLDivElement, Props>(
Expand Down
75 changes: 33 additions & 42 deletions src/components/FileUpload/MultiFileUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import { Stack, Box, Grow, Slide } from "@mui/material";
import { FileUploadCard, FileUploadCardActions } from "../FileUploadCard";
import { RejectedFileUploadAlert } from "../RejectedFileUploadAlert";
import { FileUploadService, useFileUploader } from "@hooks/useFileUploader";
import { useFileUploaderManager } from "@hooks/useFileUploaderManager";
import { Stack } from "@mui/material";
import { useFileUploaderManager, useFileUploader } from "../../hooks";
import {
useRejectedFileManager,
FileDropzone,
FileDropzoneBody,
} from "@components/FileDropzone";
} from "../FileDropzone";
import { BaseFileUploadProps } from "./types";
import { FileUploadResults } from "./FileUploadResults";

export type MultiFileUploadProps<Response = string> = {
uploadService: FileUploadService<Response>;
acceptsOnly?: string;
};
export type MultiFileUploadProps<Response = string> =
BaseFileUploadProps<Response>;
export const MultiFileUpload = <Response = string,>(
props: MultiFileUploadProps<Response>
) => {
const { uploadService, acceptsOnly } = props;
const {
uploadService,
acceptsOnly,
onSuccessfulUpload,
fileManager,
body = <FileDropzoneBody />,
} = props;
const { rejectedFiles, addRejected, removeRejected } =
useRejectedFileManager();

const { fileUploads, removeFileUpload, handlers } =
useFileUploaderManager<Response>();
const { upload } = useFileUploader(uploadService, handlers);
fileManager ?? useFileUploaderManager<Response>();
const { upload } = useFileUploader(uploadService, {
onFileUploadStart: handlers.onFileUploadStart,
onFileProgressUpdate: handlers.onFileProgressUpdate,
onFileUploadComplete: (fu) => {
onSuccessfulUpload?.(fu);
handlers.onFileUploadComplete(fu);
},
});

return (
<Stack spacing={2}>
Expand All @@ -31,36 +41,17 @@ export const MultiFileUpload = <Response = string,>(
onFilesRejected={addRejected}
acceptsOnly={acceptsOnly}
>
<FileDropzoneBody />
{body}
</FileDropzone>
<Stack spacing={1}>
{rejectedFiles.map((f, i) => (
<Grow in key={`rejected-${i}`}>
<RejectedFileUploadAlert
filename={f.name}
severity="warning"
onClose={() => removeRejected(f)}
/>
</Grow>
))}
{fileUploads.failed.map((f) => (
<FileUploadCard
key={f.id}
fileUpload={f}
actions={<FileUploadCardActions onRetry={() => upload(f)} />}
/>
))}
{fileUploads.inProgress.map((f) => (
<Slide direction="right" in={true} key={f.id}>
<Box>
<FileUploadCard fileUpload={f} />
</Box>
</Slide>
))}
{fileUploads.successful.map((f) => (
<FileUploadCard key={f.id} fileUpload={f} />
))}
</Stack>
<FileUploadResults
rejected={rejectedFiles}
failed={fileUploads.failed}
inProgress={fileUploads.inProgress}
successful={onSuccessfulUpload ? [] : fileUploads.successful}
onRetry={upload}
onDismissRejected={removeRejected}
onRemoveFileUpload={removeFileUpload}
/>
</Stack>
);
};
6 changes: 2 additions & 4 deletions src/components/FileUpload/SingleFileUpload.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { Meta, StoryObj } from "@storybook/react";
import React, { PropsWithChildren } from "react";
import { useFakeService } from "../../stories/utils";
import { SingleFileUpload, SingleFileUploadProps } from ".";
import { useFileUploaderManager } from "../FileDropzone";
import { Box, Slide, Stack } from "@mui/material";
import { FileUploadCard, FileUploadCardActions } from "../FileUploadCard";
import { useFileUploaderManager } from "../../hooks";

type StoryArgs = PropsWithChildren<
SingleFileUploadProps & { failureRate: number }
Expand All @@ -22,7 +20,7 @@ type Story = StoryObj<StoryType>;

export const Input: Story = (args: StoryArgs) => {
const uploadService = useFakeService({ failureRate: args.failureRate });
const fileManager = useFileUploaderManager<void>();
const fileManager = useFileUploaderManager();
return (
<SingleFileUpload
acceptsOnly={args.acceptsOnly}
Expand Down
21 changes: 6 additions & 15 deletions src/components/FileUpload/SingleFileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,13 @@ import {
useRejectedFileManager,
FileDropzone,
FileDropzoneInputBody,
FileDropzoneInputBodyProps,
} from "../FileDropzone";
import { FileUploadResults } from "./FileUploadResults";
import { FileUploadService, useFileUploader } from "@hooks/useFileUploader";
import {
FileUploadManager,
useFileUploaderManager,
} from "@hooks/useFileUploaderManager";
import { FileUpload } from "types";
import { useFileUploaderManager, useFileUploader } from "../../hooks";
import { BaseFileUploadProps } from "./types";

export type SingleFileUploadProps<Response = string> = {
uploadService: FileUploadService<Response>;
onSuccessfulUpload?: (fileUpload: FileUpload<Response>) => void;
fileManager?: FileUploadManager<Response>;
acceptsOnly?: string;
} & FileDropzoneInputBodyProps;
export type SingleFileUploadProps<Response = string> =
BaseFileUploadProps<Response>;

export const SingleFileUpload = <Response = string,>(
props: SingleFileUploadProps<Response>
Expand All @@ -28,7 +19,7 @@ export const SingleFileUpload = <Response = string,>(
acceptsOnly,
onSuccessfulUpload,
fileManager,
...fileDropzoneInputBodyProps
body = <FileDropzoneInputBody />,
} = props;
const { rejectedFiles, addRejected, removeRejected } =
useRejectedFileManager();
Expand Down Expand Up @@ -67,7 +58,7 @@ export const SingleFileUpload = <Response = string,>(
onFilesRejected={addRejected}
acceptsOnly={acceptsOnly}
>
<FileDropzoneInputBody {...fileDropzoneInputBodyProps} />
{body}
</FileDropzone>
</Box>
</Fade>
Expand Down
16 changes: 16 additions & 0 deletions src/components/FileUpload/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ReactNode } from "react";
import { FileUploadService, FileUploadManager } from "../../hooks";
import { FileUpload } from "../../types";

export type BaseFileUploadProps<Response = string> = {
/** A service that is responsible for handling file uploads. */
uploadService: FileUploadService<Response>;
/** Called when a upload was successful. If this is provided then successful file uploads need to be rendered externally. */
onSuccessfulUpload?: (fileUpload: FileUpload<Response>) => void;
/** A file manager responsible for handling different states. */
fileManager?: FileUploadManager<Response>;
/** A accept string which states which file types are allowed to be uploaded. */
acceptsOnly?: string;

body?: ReactNode;
};
2 changes: 1 addition & 1 deletion src/components/FileUploadCard/FileUploadCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@mui/material";
import { ReactNode } from "react";
import { FileUploadUtils, FileUtils } from "../../utils";
import { FileUpload } from "types";
import { FileUpload } from "../../types";

export type FileUploadCardProps<FileUploadResponse = string> = {
/** The icon that will be displayed */
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./FileDropzone";
export * from "./FileUploadCard/FileUploadCard";
export * from "./FileUploadCard";
export * from "./RejectedFileUploadAlert";
export * from "./FileUpload";
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./useFileUploader";
export * from "./useXMLHttpService";
export * from "./useFileUploaderManager";
export * from "./types";
10 changes: 10 additions & 0 deletions src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FileUpload } from "../types";

export type FileUploaderObservers<Response = string> = {
onFileUploadStart: (
newFileUpload: FileUpload<Response>,
isRetry: boolean
) => void;
onFileProgressUpdate: (updatedFileUpload: FileUpload<Response>) => void;
onFileUploadComplete: (completedFileUpload: FileUpload<Response>) => void;
};
12 changes: 2 additions & 10 deletions src/hooks/useFileUploader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback } from "react";
import { FileUpload } from "types";
import { FileUploaderObservers } from "./types";
import { FileUpload } from "../types";

export type FileUploader<Response> = {
/** A function that can be called to upload a file or retry a failed file upload. */
Expand All @@ -12,15 +13,6 @@ export type FileUploadService<Response> = (
onProgress: (progress: number) => void
) => Promise<Response>;

export type FileUploaderObservers<Response = string> = {
onFileUploadStart: (
newFileUpload: FileUpload<Response>,
isRetry: boolean
) => void;
onFileProgressUpdate: (updatedFileUpload: FileUpload<Response>) => void;
onFileUploadComplete: (completedFileUpload: FileUpload<Response>) => void;
};

export const useFileUploader = <Response = string>(
networkService: FileUploadService<Response>,
observers: FileUploaderObservers<Response>
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useFileUploaderManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { FileUpload } from "types";
import { FileUploaderObservers } from "./useFileUploader";
import { FileUploaderObservers } from "./types";
import { FileUpload } from "../types";

export type FileUploadManager<Response = string> = {
fileUploads: {
Expand Down
15 changes: 9 additions & 6 deletions src/hooks/useXMLHttpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { FileUploadService } from "./useFileUploader";

export const useXMLHttpService = <Response = string>(
endpoint: string,
method: string | undefined = "POST",
modifyRequest?: (xhr: XMLHttpRequest) => Promise<void> | void,
responseTransformer?: (responseText: string) => Response
): FileUploadService<Response> => {
return useCallback<FileUploadService<Response>>(
(file, onProgress) => {
return new Promise<Response>((resolve, reject) => {
const formData = new FormData();
formData.append("file", file);

const xhr = new XMLHttpRequest();
xhr.open("POST", endpoint, true);
const xhr = new XMLHttpRequest();
xhr.open(method, endpoint, true);
return new Promise<Response>(async (resolve, reject) => {
await modifyRequest?.(xhr);

xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
Expand All @@ -38,6 +38,9 @@ export const useXMLHttpService = <Response = string>(
reject();
};

const formData = new FormData();
formData.append("file", file);

xhr.send(formData);
});
},
Expand Down
2 changes: 1 addition & 1 deletion src/stories/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FileUploadService } from "@hooks/useFileUploader";
import { FileUploadService } from "../hooks/useFileUploader";

export type FakeServiceOptions = {
milliseconds?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/file-upload-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FileUpload } from "types";
import { FileUpload } from "../types";

export type FileUploadStatus = "Uploading" | "Failed" | "Completed";

Expand Down
8 changes: 1 addition & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@
"moduleResolution": "node",
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": "./src",
"rootDir": "./src",
"paths": {
"@components/*": ["components/*"],
"@hooks/*": ["hooks/*"]
}
"forceConsistentCasingInFileNames": true
},
"exclude": [
"dist",
Expand Down

0 comments on commit 2890672

Please sign in to comment.