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..c65bb5a4 --- /dev/null +++ b/frontend/src/components/HiddenFileUpload.tsx @@ -0,0 +1,71 @@ +// 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 {useRef, useCallback, ChangeEventHandler, useEffect} from 'react' + +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 => { + 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) + }, + [onChange], + ) + + 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() + } + }, [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) + }) + }) +}) 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() { }, ]} /> -