diff --git a/package-lock.json b/package-lock.json index 4bae6f7ed..cb94de91f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4002,6 +4002,21 @@ "react-dom": "^16.8.0 || 17.x" } }, + "node_modules/@react-dnd/asap": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.0.tgz", + "integrity": "sha512-czNGSkNPZgxapKz1a8zj/C5me5MpVpN4wlXDQIMF4wDUuFJ37x7beakc4S7C1xKilHA4tgT9zZb4U64BdT/E5g==" + }, + "node_modules/@react-dnd/invariant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.0.tgz", + "integrity": "sha512-tjPrh294NbH6Gj1YP1of6JYEe3+sm0Wy7YA1ImG6YghlZe3n8F+fBx1yIrA5dVJCuUh6pBp4XO7/lcSq7oLL0A==" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.0.tgz", + "integrity": "sha512-Yykovind6xzqAqd0t5umrdAGPlGLTE80cy80UkEnbt8Zv5zEYTFzJSNPQ81TY8BSpRreubu1oE54iHBv2UVnTQ==" + }, "node_modules/@reduxjs/toolkit": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", @@ -8413,6 +8428,16 @@ "node": ">=8" } }, + "node_modules/dnd-core": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.0.tgz", + "integrity": "sha512-8cGtybb5LBjG2euYgVv3amk49F+9dH3l5TuuGQf0mhFr+KWIPE1qPxB8VpPDov74ZevUAxVDxadL2zN7I0oQ1Q==", + "dependencies": { + "@react-dnd/asap": "^5.0.0", + "@react-dnd/invariant": "^4.0.0", + "redux": "^4.1.2" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -18518,6 +18543,43 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/react-dnd": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.0.tgz", + "integrity": "sha512-RCoeWRWhuwSoqdLaJV8N/weARLyXqsf43OC3QiBWPORIIGGovF/EqI8ckf14ca3bl6oZNI/igtxX49+IDmNDeQ==", + "dependencies": { + "@react-dnd/invariant": "^4.0.0", + "@react-dnd/shallowequal": "^4.0.0", + "dnd-core": "^16.0.0", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.0.tgz", + "integrity": "sha512-be3lKEbbT8FQcoTjFlQ4ZXG/NVrFNJu9W0INc5rm/5EFQpHCkz+xpbB2U8j9uh5Bvk7AsJyQrZznEut0hrNPIA==", + "dependencies": { + "dnd-core": "^16.0.0" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -22869,6 +22931,31 @@ } } }, + "node_modules/use-file-picker": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/use-file-picker/-/use-file-picker-1.4.2.tgz", + "integrity": "sha512-RkJlsHko25I2zZOAMhph1ZDfmnDtWwkFZqJzmD71vM5chIECogWt0qmyGIsn+gnCYbwsWdiGHfI6LjpGgWu1TQ==", + "dependencies": { + "file-selector": "0.2.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/use-file-picker/node_modules/file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/use-sidecar": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", @@ -23132,6 +23219,12 @@ "vega-lite": "*" } }, + "node_modules/vega-embed/node_modules/yallist": { + "version": "4.0.0", + "extraneous": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/vega-encode": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.9.0.tgz", @@ -24479,6 +24572,8 @@ "react-confetti": "6.0.1", "react-container-dimensions": "1.4.1", "react-datepicker": "4.6.0", + "react-dnd": "16.0.0", + "react-dnd-html5-backend": "16.0.0", "react-dom": "17.0.2", "react-dropzone": "12.0.5", "react-error-boundary": "3.1.4", @@ -24508,6 +24603,7 @@ "unist-util-visit": "4.0.0", "url-join": "4.0.1", "urql": "2.2.0", + "use-file-picker": "1.4.2", "use-trace-update": "1.3.2", "vega": "5.22.1", "vega-lite": "5.2.0" @@ -27292,6 +27388,8 @@ "react-confetti": "6.0.1", "react-container-dimensions": "1.4.1", "react-datepicker": "4.6.0", + "react-dnd": "16.0.0", + "react-dnd-html5-backend": "16.0.0", "react-dom": "17.0.2", "react-dropzone": "12.0.5", "react-error-boundary": "3.1.4", @@ -27323,6 +27421,7 @@ "unist-util-visit": "4.0.0", "url-join": "4.0.1", "urql": "2.2.0", + "use-file-picker": "*", "use-trace-update": "1.3.2", "vega": "5.22.1", "vega-lite": "5.2.0", @@ -28072,6 +28171,21 @@ "tslib": "^2.1.0" } }, + "@react-dnd/asap": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.0.tgz", + "integrity": "sha512-czNGSkNPZgxapKz1a8zj/C5me5MpVpN4wlXDQIMF4wDUuFJ37x7beakc4S7C1xKilHA4tgT9zZb4U64BdT/E5g==" + }, + "@react-dnd/invariant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.0.tgz", + "integrity": "sha512-tjPrh294NbH6Gj1YP1of6JYEe3+sm0Wy7YA1ImG6YghlZe3n8F+fBx1yIrA5dVJCuUh6pBp4XO7/lcSq7oLL0A==" + }, + "@react-dnd/shallowequal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.0.tgz", + "integrity": "sha512-Yykovind6xzqAqd0t5umrdAGPlGLTE80cy80UkEnbt8Zv5zEYTFzJSNPQ81TY8BSpRreubu1oE54iHBv2UVnTQ==" + }, "@reduxjs/toolkit": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", @@ -31630,6 +31744,16 @@ "path-type": "^4.0.0" } }, + "dnd-core": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.0.tgz", + "integrity": "sha512-8cGtybb5LBjG2euYgVv3amk49F+9dH3l5TuuGQf0mhFr+KWIPE1qPxB8VpPDov74ZevUAxVDxadL2zN7I0oQ1Q==", + "requires": { + "@react-dnd/asap": "^5.0.0", + "@react-dnd/invariant": "^4.0.0", + "redux": "^4.1.2" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -39070,6 +39194,26 @@ } } }, + "react-dnd": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.0.tgz", + "integrity": "sha512-RCoeWRWhuwSoqdLaJV8N/weARLyXqsf43OC3QiBWPORIIGGovF/EqI8ckf14ca3bl6oZNI/igtxX49+IDmNDeQ==", + "requires": { + "@react-dnd/invariant": "^4.0.0", + "@react-dnd/shallowequal": "^4.0.0", + "dnd-core": "^16.0.0", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + } + }, + "react-dnd-html5-backend": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.0.tgz", + "integrity": "sha512-be3lKEbbT8FQcoTjFlQ4ZXG/NVrFNJu9W0INc5rm/5EFQpHCkz+xpbB2U8j9uh5Bvk7AsJyQrZznEut0hrNPIA==", + "requires": { + "dnd-core": "^16.0.0" + } + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -42351,6 +42495,24 @@ "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==", "requires": {} }, + "use-file-picker": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/use-file-picker/-/use-file-picker-1.4.2.tgz", + "integrity": "sha512-RkJlsHko25I2zZOAMhph1ZDfmnDtWwkFZqJzmD71vM5chIECogWt0qmyGIsn+gnCYbwsWdiGHfI6LjpGgWu1TQ==", + "requires": { + "file-selector": "0.2.4" + }, + "dependencies": { + "file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "requires": { + "tslib": "^2.0.3" + } + } + } + }, "use-sidecar": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", @@ -42568,6 +42730,13 @@ "vega-schema-url-parser": "^2.2.0", "vega-themes": "^2.10.0", "vega-tooltip": "^0.27.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "bundled": true, + "extraneous": true + } } }, "vega-encode": { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index ae969ba64..f840fc8e4 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -35,6 +35,8 @@ "react-confetti": "6.0.1", "react-container-dimensions": "1.4.1", "react-datepicker": "4.6.0", + "react-dnd": "16.0.0", + "react-dnd-html5-backend": "16.0.0", "react-dom": "17.0.2", "react-dropzone": "12.0.5", "react-error-boundary": "3.1.4", @@ -64,6 +66,7 @@ "unist-util-visit": "4.0.0", "url-join": "4.0.1", "urql": "2.2.0", + "use-file-picker": "1.4.2", "use-trace-update": "1.3.2", "vega": "5.22.1", "vega-lite": "5.2.0" diff --git a/packages/frontend/src/components/file-browser/file-browser.tsx b/packages/frontend/src/components/file-browser/file-browser.tsx index 3bbd57cd6..ac7e6a489 100644 --- a/packages/frontend/src/components/file-browser/file-browser.tsx +++ b/packages/frontend/src/components/file-browser/file-browser.tsx @@ -19,7 +19,6 @@ import { useColorModeValue } from '@chakra-ui/react'; import { Badge, Box, - Button, Collapse, Editable, EditableInput, @@ -29,17 +28,18 @@ import { IconButton, Tooltip, useDisclosure, - useEditableControls, VStack } from '@chakra-ui/react'; import { useState } from 'react'; -import { useDropzone } from 'react-dropzone'; +import { useDrag, useDrop } from 'react-dnd'; +import { NativeTypes } from 'react-dnd-html5-backend'; import type { FileOrFolder, InsightFile, InsightFolder } from '../../models/file-tree'; import { fileIconFactoryAs } from '../../shared/file-icon-factory'; import type { InsightFileTree } from '../../shared/file-tree'; import { isFolder } from '../../shared/file-tree'; import { iconFactoryAs } from '../../shared/icon-factory'; +import { useFilePicker } from '../../shared/use-file-picker'; import { EditableControls } from './components/editable-controls/editable-controls'; @@ -47,12 +47,13 @@ type FileOrFolderSelect = (fileOrFolder: FileOrFolder) => void; export type FileBrowserActions = { onDelete?: (fileOrFolder: FileOrFolder, force?: boolean) => void; + onFilePicker?: () => void; + onMove?: (fileOrFolder: FileOrFolder, newPath: string) => void; onNewFile?: (parent?: InsightFolder) => void; onNewFolder?: (parent?: InsightFolder) => void; onRename?: (fileOrFolder: FileOrFolder, newName: string) => void; onSelect?: (fileOrFolder: FileOrFolder | undefined) => void; onUndelete?: (fileOrFolder: FileOrFolder) => void; - onFilePicker?: () => void; onUpload?: (acceptedFiles: any[], parent?: InsightFolder) => void; }; @@ -129,20 +130,46 @@ const FolderRenderer = ({ } }; - const { - getInputProps, - getRootProps, - isDragActive, - open: openFilePicker - } = useDropzone({ - onDrop: (acceptedFiles, rejectedFile, event) => { - event.stopPropagation(); + const [{ isDragOver }, drop] = useDrop( + () => ({ + accept: ['file', NativeTypes.FILE], + drop: (droppedItem, monitor) => { + if (monitor.didDrop()) { + // Already dropped + return undefined; + } + + switch (monitor.getItemType()) { + case NativeTypes.FILE: + // Upload file and add to tree + if (actions.onUpload) { + actions.onUpload(droppedItem.files, item); + onOpen(); + } + break; + + case 'file': + // Move file to new location in tree + if (actions.onMove) { + actions.onMove(droppedItem, `${item.path}/${droppedItem.name}`); + onOpen(); + } + } + }, + collect: (monitor) => ({ + isDragOver: monitor.isOver({ shallow: true }) + }) + }), + [actions.onMove, item] + ); + + const [openFilePicker] = useFilePicker({ + onFilesPicked: (files) => { if (actions.onUpload) { - actions.onUpload(acceptedFiles, item); + actions.onUpload(files, item); + onOpen(); } - }, - noClick: true, - noDragEventsBubbling: true + } }); const dragBgColor = useColorModeValue('snowstorm.200', 'polar.100'); @@ -153,15 +180,14 @@ const FolderRenderer = ({ - ({ + type: 'file', + item: () => item, + collect: (monitor) => ({ + isDragging: monitor.isDragging() + }), + canDrag: () => !item.readonly + })); + const [name, setName] = useState(item.name); const onRename = (newName: string) => { @@ -264,61 +299,70 @@ const FileRenderer = ({ const selectedBgColor = useColorModeValue('snowstorm.300', 'polar.300'); return ( - - - + { + onFileSelect(item); + }} + _hover={item.readonly ? {} : { '& > .actions': { display: 'flex' } }} + > + {fileIconFactoryAs( + { + mimeType: item.mimeType, + fileName: item.name, + isFolder: false, + isOpen, + isSelected + }, + { fontSize: '1rem', mr: '0.5rem' } + )} + { - onFileSelect(item); - }} - _hover={item.readonly ? {} : { '& > .actions': { display: 'flex' } }} - > - {fileIconFactoryAs( - { - mimeType: item.mimeType, - fileName: item.name, - isFolder: false, - isOpen, - isSelected - }, - { fontSize: '1rem', mr: '0.5rem' } - )} - - {item.readonly && readonly} - + flexGrow={2} + isTruncated={true} + cursor="unset" + /> + {item.readonly && readonly} + - - - - + + + ); }; @@ -337,24 +381,20 @@ export const FileBrowser = ({ tree, actions = {}, ...boxProps }: Props & BoxProp } }; - const { - getInputProps, - getRootProps, - isDragActive, - open: openFilePicker - } = useDropzone({ - onDrop: (acceptedFiles) => { + const [openFilePicker] = useFilePicker({ + onFilesPicked: (files) => { if (actions.onUpload) { - actions.onUpload(acceptedFiles, undefined); + actions.onUpload(files, undefined); } - }, - noClick: true + } }); + const dragBgColor = useColorModeValue('snowstorm.200', 'polar.100'); + const dragBorderColor = useColorModeValue('polar.200', 'snowstorm.300'); + return ( - + - - + + + ); }; diff --git a/packages/frontend/src/pages/insight-editor/components/insight-editor-sidebar/sidebar-files.tsx b/packages/frontend/src/pages/insight-editor/components/insight-editor-sidebar/sidebar-files.tsx index bebca0c6f..fcd663c87 100644 --- a/packages/frontend/src/pages/insight-editor/components/insight-editor-sidebar/sidebar-files.tsx +++ b/packages/frontend/src/pages/insight-editor/components/insight-editor-sidebar/sidebar-files.tsx @@ -22,7 +22,8 @@ import { Divider } from '@chakra-ui/react'; import { Box, Collapse, Flex, IconButton, Text, useToast, HStack } from '@chakra-ui/react'; import { nanoid } from 'nanoid'; import { useCallback, useState } from 'react'; -import { useDropzone } from 'react-dropzone'; +import { useDrop } from 'react-dnd'; +import { NativeTypes } from 'react-dnd-html5-backend'; import { gql } from 'urql'; import { FileBrowser } from '../../../../components/file-browser/file-browser'; @@ -69,7 +70,7 @@ export const SidebarFiles = ({ const toast = useToast(); const [uploading, setUploading] = useState(false); - const onDropFile = useCallback( + const onUploadFile = useCallback( async (acceptedFiles: any[], item?: FileOrFolder) => { setUploading(true); const uploadedFiles = await Promise.all( @@ -137,6 +138,11 @@ export const SidebarFiles = ({ onTreeChanged(tree); }; + const onMove = (fileOrFolder: FileOrFolder, newPath: string) => { + tree.moveItem(fileOrFolder, newPath); + onTreeChanged(tree); + }; + const onRename = (fileOrFolder: FileOrFolder, newName: string) => { tree.updateItemById({ id: fileOrFolder.id, @@ -177,15 +183,32 @@ export const SidebarFiles = ({ md: isFilesOpen ? iconFactoryAs('chevronRight') : iconFactoryAs('folderOpened') }); - const { - getInputProps, - getRootProps, - isDragActive, - open: openFilePicker - } = useDropzone({ - onDrop: (acceptedFiles) => onDropFile(acceptedFiles, undefined), - noClick: true - }); + const [{ isDragOver }, drop] = useDrop( + () => ({ + accept: ['file', NativeTypes.FILE], + drop: (droppedItem, monitor) => { + if (monitor.didDrop()) { + // Already dropped + return undefined; + } + + switch (monitor.getItemType()) { + case NativeTypes.FILE: + // Upload file and add to tree + onUploadFile(droppedItem.files, undefined); + break; + + case 'file': + // Move file to new location in tree + onMove(droppedItem, droppedItem.name); + } + }, + collect: (monitor) => ({ + isDragOver: monitor.isOver({ shallow: true }) + }) + }), + [] + ); const dragBgColor = useColorModeValue('snowstorm.200', 'polar.100'); const dragBorderColor = useColorModeValue('polar.200', 'snowstorm.300'); @@ -195,16 +218,15 @@ export const SidebarFiles = ({ direction="column" align="stretch" flexGrow={1} - {...(isDragActive && { + ref={drop} + {...(isDragOver && { bg: dragBgColor, borderWidth: '1px', borderStyle: 'dashed', borderColor: dragBorderColor })} {...flexProps} - {...getRootProps()} > - {(isFilesOpen || isMobile) && Files} + onDelete(f, true, force), + onMove, onNewFile, onNewFolder, onRename, onUndelete: (f) => onDelete(f, false, false), - onUpload: onDropFile + onUpload: onUploadFile }} /> diff --git a/packages/frontend/src/shared/file-tree.ts b/packages/frontend/src/shared/file-tree.ts index 2d884b9e7..79b3c56de 100644 --- a/packages/frontend/src/shared/file-tree.ts +++ b/packages/frontend/src/shared/file-tree.ts @@ -17,7 +17,8 @@ import { freeze, produce } from 'immer'; import { nanoid } from 'nanoid'; -import type { FileOrFolder, InsightFile, InsightFileAction, InsightFolder } from '../models/file-tree'; +import type { FileOrFolder, InsightFile, InsightFolder } from '../models/file-tree'; +import { InsightFileAction } from '../models/file-tree'; const PATH_SEPARATOR = '/'; @@ -100,6 +101,28 @@ export class InsightFileTree { }); } + /** + * Moves an item in the tree by following its `path` + * + * @param partialItem (Partial) Item to move + * @param newPath New path + */ + moveItem(partialItem: Pick & Partial, newPath: string) { + this.files = produce(this.files, (draft) => { + const item = findById(draft, partialItem.id) as InsightFile; + + if (item) { + const paths = item.path.split(PATH_SEPARATOR); + const newPaths = newPath.split(PATH_SEPARATOR); + + removeFileFromTree(draft, item, paths); + item.originalPath ??= item.path; + item.path = newPath; + item.action ??= InsightFileAction.RENAME; + addFileToTree(draft, item, newPaths); + } + }); + } /** * Updates an item by ID. * @@ -230,6 +253,7 @@ export function addFileToTree( export function removeFileFromTree(tree: FileOrFolder[], file: FileOrFolder, remainingPathParts: string[]) { if (remainingPathParts.length === 1) { // This is a file, find it + const existingFileIndex = tree.findIndex((f) => f.name === file.name); if (existingFileIndex !== undefined) { tree.splice(existingFileIndex, 1); @@ -239,7 +263,7 @@ export function removeFileFromTree(tree: FileOrFolder[], file: FileOrFolder, rem // Check for an existing folder const existingFolder: InsightFolder | undefined = tree.filter(isFolder).find((folder) => { - return folder.path === current; + return folder.name === current; }); if (existingFolder) { diff --git a/packages/frontend/src/shared/use-file-picker.ts b/packages/frontend/src/shared/use-file-picker.ts new file mode 100644 index 000000000..01849a669 --- /dev/null +++ b/packages/frontend/src/shared/use-file-picker.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEffect, useRef } from 'react'; +import { useFilePicker as ufp } from 'use-file-picker'; + +export const useFilePicker = ({ onFilesPicked }): [() => void, Record] => { + const previousFile = useRef(undefined); + const [openFileSelector, { loading, errors, plainFiles, clear }] = ufp({ + multiple: true, + maxFileSize: 100, // in MB + limitFilesConfig: { max: 10 }, + readFilesContent: true + }); + + useEffect(() => { + if (previousFile.current != plainFiles && plainFiles.length > 0) { + previousFile.current = plainFiles; + onFilesPicked(plainFiles); + clear(); + } + }, [clear, onFilesPicked, plainFiles]); + + return [ + openFileSelector, + { + loading, + errors + } + ]; +};