From d8d4d3d7d065710060106e33fb76e785f5c6fff2 Mon Sep 17 00:00:00 2001 From: Alessandro Menduni Date: Tue, 7 Mar 2023 11:05:42 +0100 Subject: [PATCH 1/2] Deprecate HiddenUploader component --- frontend/src/components/FileChooser.tsx | 8 +++- frontend/src/components/HiddenFileUpload.tsx | 48 ++++++++++++++++++++ frontend/src/old-pages/Configure/Source.tsx | 4 +- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/HiddenFileUpload.tsx diff --git a/frontend/src/components/FileChooser.tsx b/frontend/src/components/FileChooser.tsx index 78402e84..dedc08ce 100644 --- a/frontend/src/components/FileChooser.tsx +++ b/frontend/src/components/FileChooser.tsx @@ -17,7 +17,11 @@ import {Button} from '@cloudscape-design/components' import {setState, getState} from '../store' import {useTranslation} from 'react-i18next' -function HiddenUploader({callbackPath, handleData, handleCancel}: any) { +function DeprecatedHiddenUploader({ + callbackPath, + handleData, + handleCancel, +}: any) { const hiddenFileInput = React.useRef(null) const handleClick = React.useCallback( event => { @@ -83,4 +87,4 @@ function FileUploadButton(props: any) { ) } -export {FileUploadButton as default, HiddenUploader} +export {FileUploadButton as default, DeprecatedHiddenUploader} diff --git a/frontend/src/components/HiddenFileUpload.tsx b/frontend/src/components/HiddenFileUpload.tsx new file mode 100644 index 00000000..93fab26f --- /dev/null +++ b/frontend/src/components/HiddenFileUpload.tsx @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance +// with the License. A copy of the License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES +// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react' +import {getState, setState} from '../store' + +export function HiddenUploader({callbackPath, handleData, handleCancel}: any) { + const hiddenFileInput = React.useRef(null) + const handleClick = React.useCallback( + event => { + // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. + hiddenFileInput.current.click() + }, + [hiddenFileInput], + ) + + const handleChange = (event: any) => { + var file = event.target.files[0] + var reader = new FileReader() + reader.onload = function (e) { + // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. + handleData(e.target.result) + } + reader.readAsText(file) + } + + React.useEffect(() => { + if (!getState(callbackPath) || getState(callbackPath) !== handleClick) + setState(callbackPath, handleClick) + }, [callbackPath, handleClick]) + + return ( + + ) +} diff --git a/frontend/src/old-pages/Configure/Source.tsx b/frontend/src/old-pages/Configure/Source.tsx index f17a897b..da9efc67 100644 --- a/frontend/src/old-pages/Configure/Source.tsx +++ b/frontend/src/old-pages/Configure/Source.tsx @@ -34,7 +34,7 @@ import { } from '@cloudscape-design/components' // Components -import {HiddenUploader} from '../../components/FileChooser' +import {DeprecatedHiddenUploader} from '../../components/FileChooser' import Loading from '../../components/Loading' // Types @@ -294,7 +294,7 @@ function Source() { }, ]} /> - From 7c6c58369857468eaacda067270414fe5a467605 Mon Sep 17 00:00:00 2001 From: Alessandro Menduni Date: Tue, 7 Mar 2023 11:14:28 +0100 Subject: [PATCH 2/2] Refactor HiddenFileUpload to be controlled via props --- frontend/src/components/HiddenFileUpload.tsx | 67 +++++++++++++------ .../__tests__/HiddenFileUpload.test.tsx | 66 ++++++++++++++++++ 2 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/__tests__/HiddenFileUpload.test.tsx diff --git a/frontend/src/components/HiddenFileUpload.tsx b/frontend/src/components/HiddenFileUpload.tsx index 93fab26f..c65bb5a4 100644 --- a/frontend/src/components/HiddenFileUpload.tsx +++ b/frontend/src/components/HiddenFileUpload.tsx @@ -9,39 +9,62 @@ // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and // limitations under the License. -import React from 'react' -import {getState, setState} from '../store' +import {useRef, useCallback, ChangeEventHandler, useEffect} from 'react' -export function HiddenUploader({callbackPath, handleData, handleCancel}: any) { - const hiddenFileInput = React.useRef(null) - const handleClick = React.useCallback( +interface Props { + open: boolean + onChange: (data: string) => void + onDismiss: () => void +} + +export function HiddenFileUpload({onChange, onDismiss, open}: Props) { + const hiddenFileInput = useRef(null) + + const handleClick = useCallback(() => { + if (!hiddenFileInput?.current) return + + hiddenFileInput.current.click() + }, [hiddenFileInput]) + + const onFileChange: ChangeEventHandler = useCallback( event => { - // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. - hiddenFileInput.current.click() + const { + target: {files}, + } = event + + if (!files || files.length < 1) { + return + } + + const [file] = files + const reader = new FileReader() + reader.onload = function () { + onChange(reader.result as string) + } + reader.readAsText(file) }, - [hiddenFileInput], + [onChange], ) - const handleChange = (event: any) => { - var file = event.target.files[0] - var reader = new FileReader() - reader.onload = function (e) { - // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. - handleData(e.target.result) + useEffect(() => { + if (open) { + handleClick() + /** + * Immediately dismiss the selector, + * to allow callers to reset the `open` prop. + * + * This is needed as there is no way to intercept + * actual cancel event on the brower's file picker + */ + onDismiss() } - reader.readAsText(file) - } - - React.useEffect(() => { - if (!getState(callbackPath) || getState(callbackPath) !== handleClick) - setState(callbackPath, handleClick) - }, [callbackPath, handleClick]) + }, [handleClick, onDismiss, open]) return ( ) diff --git a/frontend/src/components/__tests__/HiddenFileUpload.test.tsx b/frontend/src/components/__tests__/HiddenFileUpload.test.tsx new file mode 100644 index 00000000..8642db44 --- /dev/null +++ b/frontend/src/components/__tests__/HiddenFileUpload.test.tsx @@ -0,0 +1,66 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance +// with the License. A copy of the License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES +// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +import {render, RenderResult} from '@testing-library/react' +import {HiddenFileUpload} from '../HiddenFileUpload' + +/** + * This test is here to make sure that + * users of the component can reset the `open` prop + * after the file selector dialog has been opened. + * + * This is needed as there is no way to intercept + * actual cancel event on the brower's file picker + * + * The test can be removed when this quirk has been resolved. + */ + +describe('given a hidden file input field', () => { + let screen: RenderResult + let mockOnDismiss: jest.Mock + let mockOnChange: jest.Mock + + describe('when open is set to true', () => { + beforeEach(() => { + mockOnDismiss = jest.fn() + mockOnChange = jest.fn() + + screen = render( + , + ) + }) + it('should call the onDismiss handler', () => { + expect(mockOnDismiss).toHaveBeenCalledTimes(1) + }) + }) + describe('when open is set to false', () => { + beforeEach(() => { + mockOnDismiss = jest.fn() + mockOnChange = jest.fn() + + screen = render( + , + ) + }) + + it('should not call any handler', () => { + expect(mockOnDismiss).toHaveBeenCalledTimes(0) + }) + }) +})