From a875a4e1316e3af23d6e5fb4d67c57020f8a8083 Mon Sep 17 00:00:00 2001 From: djhi Date: Wed, 21 Aug 2019 17:02:15 +0200 Subject: [PATCH 1/3] [WIP] Migrate FileInput and related components to TypeScript - [x] FileInputPreview - [x] ImageInputPreview - [ ] FileInput - [ ] ImageInput --- ...view.spec.js => FileInputPreview.spec.tsx} | 37 +++++++++++-------- ...leInputPreview.js => FileInputPreview.tsx} | 19 ++++++---- ...geInputPreview.js => ImageInputPreview.ts} | 0 3 files changed, 32 insertions(+), 24 deletions(-) rename packages/ra-ui-materialui/src/input/{FileInputPreview.spec.js => FileInputPreview.spec.tsx} (71%) rename packages/ra-ui-materialui/src/input/{FileInputPreview.js => FileInputPreview.tsx} (82%) rename packages/ra-ui-materialui/src/input/{ImageInputPreview.js => ImageInputPreview.ts} (100%) diff --git a/packages/ra-ui-materialui/src/input/FileInputPreview.spec.js b/packages/ra-ui-materialui/src/input/FileInputPreview.spec.tsx similarity index 71% rename from packages/ra-ui-materialui/src/input/FileInputPreview.spec.js rename to packages/ra-ui-materialui/src/input/FileInputPreview.spec.tsx index 54f82fa9fc8..b698940d2ee 100644 --- a/packages/ra-ui-materialui/src/input/FileInputPreview.spec.js +++ b/packages/ra-ui-materialui/src/input/FileInputPreview.spec.tsx @@ -4,7 +4,21 @@ import { render, cleanup, fireEvent } from '@testing-library/react'; import FileInputPreview from './FileInputPreview'; describe('', () => { - afterEach(cleanup); + beforeAll(() => { + // @ts-ignore + global.URL.revokeObjectURL = jest.fn(); + }); + + afterAll(() => { + // @ts-ignore + delete global.URL.revokeObjectURL; + }); + + afterEach(() => { + // @ts-ignore + global.URL.revokeObjectURL.mockClear(); + cleanup(); + }); const file = { preview: 'previewUrl', @@ -13,7 +27,6 @@ describe('', () => { const defaultProps = { file, onRemove: jest.fn(), - revokeObjectURL: jest.fn(), }; it('should call `onRemove` prop when clicking on remove button', () => { @@ -41,36 +54,28 @@ describe('', () => { }); it('should clean up generated URLs for preview', () => { - const revokeObjectURL = jest.fn(); - const { unmount } = render( - +
Child
); unmount(); - expect(revokeObjectURL).toHaveBeenCalledWith('previewUrl'); + // @ts-ignore + expect(global.URL.revokeObjectURL).toHaveBeenCalledWith('previewUrl'); }); it('should not try to clean up preview urls if not passed a File object with a preview', () => { const file = {}; - const revokeObjectURL = jest.fn(); const { unmount } = render( - +
Child
); unmount(); - expect(revokeObjectURL).not.toHaveBeenCalled(); + // @ts-ignore + expect(global.URL.revokeObjectURL).not.toHaveBeenCalled(); }); }); diff --git a/packages/ra-ui-materialui/src/input/FileInputPreview.js b/packages/ra-ui-materialui/src/input/FileInputPreview.tsx similarity index 82% rename from packages/ra-ui-materialui/src/input/FileInputPreview.js rename to packages/ra-ui-materialui/src/input/FileInputPreview.tsx index 08961d2b1b3..04b856c2b0a 100644 --- a/packages/ra-ui-materialui/src/input/FileInputPreview.js +++ b/packages/ra-ui-materialui/src/input/FileInputPreview.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, ReactNode, FunctionComponent } from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core'; import RemoveCircle from '@material-ui/icons/RemoveCircle'; @@ -12,11 +12,17 @@ const useStyles = makeStyles(theme => ({ }, })); -const FileInputPreview = ({ +interface Props { + children: ReactNode; + className?: string; + onRemove: () => void; + file: any; +} + +const FileInputPreview: FunctionComponent = ({ children, className, onRemove, - revokeObjectURL, file, ...rest }) => { @@ -28,12 +34,10 @@ const FileInputPreview = ({ const preview = file.rawFile ? file.rawFile.preview : file.preview; if (preview) { - revokeObjectURL - ? revokeObjectURL(preview) - : window.URL.revokeObjectURL(preview); + window.URL.revokeObjectURL(preview); } }; - }, [file, revokeObjectURL]); + }, [file]); return (
@@ -55,7 +59,6 @@ FileInputPreview.propTypes = { className: PropTypes.string, file: PropTypes.object, onRemove: PropTypes.func.isRequired, - revokeObjectURL: PropTypes.func, }; FileInputPreview.defaultProps = { diff --git a/packages/ra-ui-materialui/src/input/ImageInputPreview.js b/packages/ra-ui-materialui/src/input/ImageInputPreview.ts similarity index 100% rename from packages/ra-ui-materialui/src/input/ImageInputPreview.js rename to packages/ra-ui-materialui/src/input/ImageInputPreview.ts From ef464e07c94d029c9053392c9c008fa860eab1aa Mon Sep 17 00:00:00 2001 From: djhi Date: Wed, 21 Aug 2019 17:02:44 +0200 Subject: [PATCH 2/3] FileInput --- .../{FileInput.spec.js => FileInput.spec.tsx} | 0 .../src/input/{FileInput.js => FileInput.tsx} | 65 ++++++++++++------- 2 files changed, 40 insertions(+), 25 deletions(-) rename packages/ra-ui-materialui/src/input/{FileInput.spec.js => FileInput.spec.tsx} (100%) rename packages/ra-ui-materialui/src/input/{FileInput.js => FileInput.tsx} (81%) diff --git a/packages/ra-ui-materialui/src/input/FileInput.spec.js b/packages/ra-ui-materialui/src/input/FileInput.spec.tsx similarity index 100% rename from packages/ra-ui-materialui/src/input/FileInput.spec.js rename to packages/ra-ui-materialui/src/input/FileInput.spec.tsx diff --git a/packages/ra-ui-materialui/src/input/FileInput.js b/packages/ra-ui-materialui/src/input/FileInput.tsx similarity index 81% rename from packages/ra-ui-materialui/src/input/FileInput.js rename to packages/ra-ui-materialui/src/input/FileInput.tsx index d68c53b3928..66d46f98f78 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.js +++ b/packages/ra-ui-materialui/src/input/FileInput.tsx @@ -1,11 +1,17 @@ -import React, { Children, cloneElement, isValidElement } from 'react'; +import React, { + FunctionComponent, + Children, + cloneElement, + isValidElement, + ReactElement, +} from 'react'; import PropTypes from 'prop-types'; import { shallowEqual } from 'recompose'; -import { useDropzone } from 'react-dropzone'; +import { useDropzone, DropzoneOptions } from 'react-dropzone'; import { makeStyles } from '@material-ui/core/styles'; import FormHelperText from '@material-ui/core/FormHelperText'; import classnames from 'classnames'; -import { useInput, useTranslate } from 'ra-core'; +import { useInput, useTranslate, InputProps } from 'ra-core'; import Labeled from './Labeled'; import FileInputPreview from './FileInputPreview'; @@ -25,20 +31,34 @@ const useStyles = makeStyles(theme => ({ root: { width: '100%' }, })); -const FileInput = ({ +export interface FileInputProps { + accept?: string; + labelMultiple?: string; + labelSingle?: string; + maxSize?: number; + minSize?: number; + multiple?: boolean; +} + +export interface FileInputOptions extends DropzoneOptions { + inputProps?: any; +} + +const FileInput: FunctionComponent< + FileInputProps & InputProps +> = ({ accept, children, - classes: classesOverride, className, - disableClick, + classes: classesOverride, helperText, label, - labelMultiple, - labelSingle, + labelMultiple = 'ra.input.file.upload_several', + labelSingle = 'ra.input.file.upload_single', maxSize, minSize, - multiple, - options = {}, + multiple = false, + options: { inputProps: inputPropsOptions, ...options } = {}, placeholder, resource, source, @@ -54,7 +74,9 @@ const FileInput = ({ return file; } - const { source, title } = Children.only(children).props; + const { source, title } = (Children.only(children) as ReactElement< + any + >).props; const preview = URL.createObjectURL(file); const transformedFile = { @@ -69,7 +91,7 @@ const FileInput = ({ return transformedFile; }; - const transformFiles = files => { + const transformFiles = (files: any[]) => { if (!files) { return multiple ? [] : null; } @@ -111,14 +133,14 @@ const FileInput = ({ const filteredFiles = files.filter( stateFile => !shallowEqual(stateFile, file) ); - onChange(filteredFiles); + onChange(filteredFiles as any); } else { onChange(null); } }; const childrenElement = isValidElement(Children.only(children)) - ? Children.only(children) + ? (Children.only(children) as ReactElement) : undefined; const { getRootProps, getInputProps } = useDropzone({ @@ -148,9 +170,10 @@ const FileInput = ({ > {placeholder ? ( placeholder @@ -196,7 +219,6 @@ FileInput.propTypes = { children: PropTypes.element, classes: PropTypes.object, className: PropTypes.string, - disableClick: PropTypes.bool, id: PropTypes.string, isRequired: PropTypes.bool, label: PropTypes.string, @@ -211,11 +233,4 @@ FileInput.propTypes = { placeholder: PropTypes.node, }; -FileInput.defaultProps = { - labelMultiple: 'ra.input.file.upload_several', - labelSingle: 'ra.input.file.upload_single', - multiple: false, - onUpload: () => {}, -}; - export default FileInput; From c5fb5bf0b1d8005177bac1faf7f631e8d0ac6b2a Mon Sep 17 00:00:00 2001 From: djhi Date: Wed, 21 Aug 2019 17:03:00 +0200 Subject: [PATCH 3/3] ImageInput --- .../src/input/{ImageInput.spec.js => ImageInput.spec.tsx} | 0 .../src/input/{ImageInput.js => ImageInput.tsx} | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename packages/ra-ui-materialui/src/input/{ImageInput.spec.js => ImageInput.spec.tsx} (100%) rename packages/ra-ui-materialui/src/input/{ImageInput.js => ImageInput.tsx} (85%) diff --git a/packages/ra-ui-materialui/src/input/ImageInput.spec.js b/packages/ra-ui-materialui/src/input/ImageInput.spec.tsx similarity index 100% rename from packages/ra-ui-materialui/src/input/ImageInput.spec.js rename to packages/ra-ui-materialui/src/input/ImageInput.spec.tsx diff --git a/packages/ra-ui-materialui/src/input/ImageInput.js b/packages/ra-ui-materialui/src/input/ImageInput.tsx similarity index 85% rename from packages/ra-ui-materialui/src/input/ImageInput.js rename to packages/ra-ui-materialui/src/input/ImageInput.tsx index 0675e5a47e7..47c26826b62 100644 --- a/packages/ra-ui-materialui/src/input/ImageInput.js +++ b/packages/ra-ui-materialui/src/input/ImageInput.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import FileInput from './FileInput'; +import FileInput, { FileInputProps, FileInputOptions } from './FileInput'; +import { InputProps } from 'ra-core'; const useStyles = makeStyles(theme => ({ root: { width: '100%' }, @@ -32,7 +33,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const ImageInput = props => { +const ImageInput = (props: FileInputProps & InputProps) => { const classes = useStyles(props); return (