Skip to content

Commit

Permalink
fix: intercept react-butterfiles and use a patched implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel910 committed Jul 23, 2024
1 parent 60e8e8f commit deb698c
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"apollo-link-http-common": "^0.2.16",
"apollo-utilities": "^1.3.4",
"boolean": "^3.0.1",
"bytes": "^3.0.0",
"graphql": "^15.7.2",
"invariant": "^2.2.4",
"lodash": "^4.17.21",
"minimatch": "^5.1.0",
"nanoid": "^3.3.7",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
272 changes: 272 additions & 0 deletions packages/app/src/react-butterfiles/Files.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import React from "react";
import bytes from "bytes";
import minimatch from "minimatch";
import { readFileContent } from "./utils/readFileContent";
import { generateId } from "./utils/generateId";

export type SelectedFile = {
id: string;
name: string;
type: string;
size: number;
src: {
file: File;
base64: string | null;
};
};

export type FileError = {
id: string;
type:
| "unsupportedFileType"
| "maxSizeExceeded"
| "multipleMaxSizeExceeded"
| "multipleMaxCountExceeded"
| "multipleNotAllowed";
index?: number;
file?: SelectedFile | File;
multipleFileSize?: number;
multipleMaxSize?: number;
multipleMaxCount?: number;
multipleCount?: number;
};

export type BrowseFilesParams = {
onSuccess?: (files: SelectedFile[]) => void;
onError?: (errors: FileError[], files: SelectedFile[]) => void;
};

export type RenderPropParams = {
browseFiles: (params: BrowseFilesParams) => void;
getDropZoneProps: (additionalProps: any) => any;
getLabelProps: (additionalProps: any) => any;
validateFiles: (files: SelectedFile[] | File[]) => FileError[];
};

export type FilesRules = {
accept: string[];
multiple: boolean;
maxSize: string;
multipleMaxSize: string;
multipleMaxCount: number | null;
convertToBase64: boolean;
onSuccess?: (files: SelectedFile[]) => void;
onError?: (errors: FileError[], files: SelectedFile[]) => void;
};

export type Props = FilesRules & {
children: (params: RenderPropParams) => React.ReactNode;
id?: string;
};

export class Files extends React.Component<Props> {
static defaultProps = {
accept: [],
multiple: false,
maxSize: "2mb",
multipleMaxSize: "10mb",
multipleMaxCount: null,
convertToBase64: false
};

input: HTMLInputElement | null = null;
browseFilesPassedParams: BrowseFilesParams | null = null;
id: string = generateId();

validateFiles = (files: SelectedFile[] | File[]): FileError[] => {
const { multiple, multipleMaxSize, multipleMaxCount, accept, maxSize } = this.props;

const errors: FileError[] = [];
let multipleFileSize = 0;

if (!multiple && files.length > 1) {
errors.push({
id: generateId(),
type: "multipleNotAllowed"
});

return errors;
}

for (let index = 0; index < files.length; index++) {
const file = files[index];

if (
Array.isArray(accept) &&
accept.length &&
!accept.some(type => minimatch(file.type, type))
) {
errors.push({
id: generateId(),
index,
file,
type: "unsupportedFileType"
});
} else if (maxSize) {
if (file.size > bytes(maxSize)) {
errors.push({
id: generateId(),
index,
file,
type: "maxSizeExceeded"
});
}
}

if (multiple) {
multipleFileSize += file.size;
}
}

if (multiple) {
if (multipleMaxSize && multipleFileSize > bytes(multipleMaxSize)) {
errors.push({
id: generateId(),
type: "multipleMaxSizeExceeded",
multipleFileSize,
multipleMaxSize: bytes(multipleMaxSize)
});
}

if (multipleMaxCount && files.length > multipleMaxCount) {
errors.push({
id: generateId(),
type: "multipleMaxCountExceeded",
multipleCount: files.length,
multipleMaxCount
});
}
}

return errors;
};

processSelectedFiles = async (eventFiles: Array<File>) => {
if (eventFiles.length === 0) {
return;
}

const { convertToBase64, onSuccess, onError } = this.props;
const { browseFilesPassedParams } = this;
const callbacks = {
onSuccess,
onError
};

if (browseFilesPassedParams && browseFilesPassedParams.onSuccess) {
callbacks.onSuccess = browseFilesPassedParams.onSuccess;
}

if (browseFilesPassedParams && browseFilesPassedParams.onError) {
callbacks.onError = browseFilesPassedParams.onError;
}

const files: SelectedFile[] = [...eventFiles].map(file => {
return {
id: generateId(),
name: file.name,
type: file.type,
size: file.size,
src: {
file,
base64: null
}
};
});

const errors = this.validateFiles(files);

if (errors.length) {
callbacks.onError && callbacks.onError(errors, files);
} else {
if (convertToBase64) {
for (let i = 0; i < files.length; i++) {
const file = files[i].src.file;
files[i].src.base64 = await readFileContent(file);
}
}

callbacks.onSuccess && callbacks.onSuccess(files);
}

// Reset the browseFiles arguments.
if (this.input) {
this.input.value = "";
}
this.browseFilesPassedParams = null;
};

/**
* Extracted into a separate method just for testing purposes.
*/
onDropFilesHandler = async ({ e, onSuccess, onError }: any) => {
this.browseFilesPassedParams = { onSuccess, onError };
e.dataTransfer &&
e.dataTransfer.files &&
(await this.processSelectedFiles(e.dataTransfer.files));
};

/**
* Extracted into a separate method just for testing purposes.
*/
browseFilesHandler = ({ onSuccess, onError }: any) => {
this.browseFilesPassedParams = { onSuccess, onError };
this.input && this.input.click();
};

override render() {
const { multiple, accept, id } = this.props;
return (
<React.Fragment>
{this.props.children({
getLabelProps: (props: any) => {
return {
...props,
htmlFor: id || this.id
};
},
validateFiles: this.validateFiles,
browseFiles: ({ onSuccess, onError }: BrowseFilesParams = {}) => {
this.browseFilesHandler({ onSuccess, onError });
},
getDropZoneProps: ({
onSuccess,
onError,
onDragOver,
onDrop,
...rest
}: any = {}) => {
return {
...rest,
onDragOver: (e: DragEvent) => {
e.preventDefault();
typeof onDragOver === "function" && onDragOver();
},
onDrop: async (e: DragEvent) => {
e.preventDefault();
typeof onDrop === "function" && onDrop();
this.onDropFilesHandler({ e, onSuccess, onError });
}
};
}
})}

<input
id={id || this.id}
ref={ref => {
if (ref) {
this.input = ref;
}
}}
accept={accept.join(",")}
style={{ display: "none" }}
type="file"
multiple={multiple}
onChange={e =>
this.processSelectedFiles((e.target.files as any as Array<File>) ?? [])
}
/>
</React.Fragment>
);
}
}
3 changes: 3 additions & 0 deletions packages/app/src/react-butterfiles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Files } from "./Files";

export default Files;
3 changes: 3 additions & 0 deletions packages/app/src/react-butterfiles/utils/generateId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const generateId = () => {
return "_" + Math.random().toString(36).substr(2, 9);
};
14 changes: 14 additions & 0 deletions packages/app/src/react-butterfiles/utils/readFileContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const readFileContent = async (file: File) => {
return new Promise<string>((resolve, reject) => {
const reader = new window.FileReader();
reader.onload = function (e) {
if (e.target) {
resolve(e.target.result as string);
} else {
reject(`Unable to read file contents!`);
}
};

reader.readAsDataURL(file);
});
};
2 changes: 2 additions & 0 deletions packages/project-utils/bundling/app/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ module.exports = function (webpackEnv, { paths, options }) {
"react-dom$": require.resolve("react-dom/profiling"),
"scheduler/tracing": require.resolve("scheduler/tracing-profiling")
}),
// This is a temporary fix, until we sort out the `react-butterfiles` dependency.
"react-butterfiles": require.resolve("@webiny/app/react-butterfiles"),
...(modules.webpackAliases || {})
},
fallback: {
Expand Down
1 change: 1 addition & 0 deletions packages/project-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"src": [
"!!raw-loader!",
"@material/base",
"@webiny/app",
"@webiny/api",
"@webiny/tasks",
"@webiny/handler",
Expand Down

0 comments on commit deb698c

Please sign in to comment.