This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

28
frontend/node_modules/react-dropzone/.babelrc.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
// https://babeljs.io/docs/en/configuration
const presets = ['@babel/preset-react']
if (process.env['BABEL_ENV'] === 'es') {
presets.unshift(['@babel/preset-env', { modules: false }])
} else {
presets.unshift('@babel/preset-env')
}
const plugins = [
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-logical-assignment-operators',
['@babel/plugin-proposal-optional-chaining', { loose: false }],
['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }],
['@babel/plugin-proposal-nullish-coalescing-operator', { loose: false }],
'@babel/plugin-proposal-do-expressions',
'add-module-exports'
]
module.exports = {
presets,
plugins,
env: {
test: {
presets,
plugins: [...plugins, '@babel/plugin-transform-runtime', 'dynamic-import-node']
}
}
}

15
frontend/node_modules/react-dropzone/.codeclimate.yml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
engines:
duplication:
enabled: true
config:
languages:
- javascript
eslint:
enabled: true
channel: "eslint-3"
ratings:
paths:
- "**.js"
exclude_paths:
- "dist/"
- "src/*.spec.js"

13
frontend/node_modules/react-dropzone/.editorconfig generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

3
frontend/node_modules/react-dropzone/.eslintignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
coverage/
dist/
styleguide/*

37
frontend/node_modules/react-dropzone/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,37 @@
{
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"env": {
"es6": true,
"node": true,
"jest": true
},
"plugins": [
"import",
"node",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"rules": {
"strict": 0,
"node/no-unpublished-require": 0,
// Import
"import/no-extraneous-dependencies": [
2,
{
"devDependencies": [
"webpack*.js",
"**/*.spec.js",
"**/*.config.js",
"**/testSetup.js"
]
}
]
}
}

6
frontend/node_modules/react-dropzone/.gitpod.yml generated vendored Normal file
View File

@@ -0,0 +1,6 @@
tasks:
- init: yarn install && yarn run build
command: yarn run start
ports:
- port: 8080
onOpen: open-preview

5
frontend/node_modules/react-dropzone/.husky/commit-msg generated vendored Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Check commit msg format
yarn commitlint --edit $1

5
frontend/node_modules/react-dropzone/.husky/pre-commit generated vendored Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Check whatever is staged for issues
yarn lint-staged

1
frontend/node_modules/react-dropzone/.nvmrc generated vendored Normal file
View File

@@ -0,0 +1 @@
20.17.0

22
frontend/node_modules/react-dropzone/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 Param Aggarwal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

444
frontend/node_modules/react-dropzone/README.md generated vendored Normal file
View File

@@ -0,0 +1,444 @@
![react-dropzone logo](https://raw.githubusercontent.com/react-dropzone/react-dropzone/master/logo/logo.png)
# react-dropzone
[![npm](https://img.shields.io/npm/v/react-dropzone.svg?style=flat-square)](https://www.npmjs.com/package/react-dropzone)
![Tests](https://img.shields.io/github/actions/workflow/status/react-dropzone/react-dropzone/test.yml?branch=master&style=flat-square&label=tests)
[![codecov](https://img.shields.io/codecov/c/gh/react-dropzone/react-dropzone/master.svg?style=flat-square)](https://codecov.io/gh/react-dropzone/react-dropzone)
[![Open Collective Backers](https://img.shields.io/opencollective/backers/react-dropzone.svg?style=flat-square)](#backers)
[![Open Collective Sponsors](https://img.shields.io/opencollective/sponsors/react-dropzone.svg?style=flat-square)](#sponsors)
[![Gitpod](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/react-dropzone/react-dropzone)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=flat-square)](https://github.com/react-dropzone/.github/blob/main/CODE_OF_CONDUCT.md)
Simple React hook to create a HTML5-compliant drag'n'drop zone for files.
Documentation and examples at https://react-dropzone.js.org. Source code at https://github.com/react-dropzone/react-dropzone/.
## Installation
Install it from npm and include it in your React build process (using [Webpack](http://webpack.github.io/), [Browserify](http://browserify.org/), etc).
```bash
npm install --save react-dropzone
```
or:
```bash
yarn add react-dropzone
```
## Usage
You can either use the hook:
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
)
}
```
Or the wrapper component for the hook:
```jsx static
import React from 'react'
import Dropzone from 'react-dropzone'
<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
)}
</Dropzone>
```
If you want to access file contents you have to use the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader):
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback((acceptedFiles) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader()
reader.onabort = () => console.log('file reading was aborted')
reader.onerror = () => console.log('file reading has failed')
reader.onload = () => {
// Do whatever you want with the file contents
const binaryStr = reader.result
console.log(binaryStr)
}
reader.readAsArrayBuffer(file)
})
}, [])
const {getRootProps, getInputProps} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
```
## Dropzone Props Getters
The dropzone property getters are just two functions that return objects with properties which you need to use to create the drag 'n' drop zone.
The root properties can be applied to whatever element you want, whereas the input properties must be applied to an `<input>`:
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps} = useDropzone()
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
```
Note that whatever other props you want to add to the element where the props from `getRootProps()` are set, you should always pass them through that function rather than applying them on the element itself.
This is in order to avoid your props being overridden (or overriding the props returned by `getRootProps()`):
```jsx static
<div
{...getRootProps({
onClick: event => console.log(event),
role: 'button',
'aria-label': 'drag and drop area',
...
})}
/>
```
In the example above, the provided `{onClick}` handler will be invoked before the internal one, therefore, internal callbacks can be prevented by simply using [stopPropagation](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation).
See [Events](https://react-dropzone.js.org#events) for more examples.
*Important*: if you omit rendering an `<input>` and/or binding the props from `getInputProps()`, opening a file dialog will not be possible.
## Refs
Both `getRootProps` and `getInputProps` accept a custom `refKey` (defaults to `ref`) as one of the attributes passed down in the parameter.
This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g:
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
// NOTE: After v4.0.0, styled components exposes a ref using forwardRef,
// therefore, no need for using innerRef as refKey
import styled from 'styled-components'
const StyledDiv = styled.div`
// Some styling here
`
function Example() {
const {getRootProps, getInputProps} = useDropzone()
<StyledDiv {...getRootProps({ refKey: 'innerRef' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</StyledDiv>
}
```
If you're working with [Material UI v4](https://v4.mui.com/) and would like to apply the root props on some component that does not expose a ref, use [RootRef](https://v4.mui.com/api/root-ref/):
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
import RootRef from '@material-ui/core/RootRef'
function PaperDropzone() {
const {getRootProps, getInputProps} = useDropzone()
const {ref, ...rootProps} = getRootProps()
<RootRef rootRef={ref}>
<Paper {...rootProps}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</Paper>
</RootRef>
}
```
**IMPORTANT**: do not set the `ref` prop on the elements where `getRootProps()`/`getInputProps()` props are set, instead, get the refs from the hook itself:
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
function Refs() {
const {
getRootProps,
getInputProps,
rootRef, // Ref to the `<div>`
inputRef // Ref to the `<input>`
} = useDropzone()
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
}
```
If you're using the `<Dropzone>` component, though, you can set the `ref` prop on the component itself which will expose the `{open}` prop that can be used to open the file dialog programmatically:
```jsx static
import React, {createRef} from 'react'
import Dropzone from 'react-dropzone'
const dropzoneRef = createRef()
<Dropzone ref={dropzoneRef}>
{({getRootProps, getInputProps}) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)}
</Dropzone>
dropzoneRef.open()
```
## Testing
`react-dropzone` makes some of its drag 'n' drop callbacks asynchronous to enable promise based `getFilesFromEvent()` functions. In order to test components that use this library, you need to use the [react-testing-library](https://github.com/testing-library/react-testing-library):
```js static
import React from 'react'
import Dropzone from 'react-dropzone'
import {act, fireEvent, render} from '@testing-library/react'
test('invoke onDragEnter when dragenter event occurs', async () => {
const file = new File([
JSON.stringify({ping: true})
], 'ping.json', { type: 'application/json' })
const data = mockData([file])
const onDragEnter = jest.fn()
const ui = (
<Dropzone onDragEnter={onDragEnter}>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
)
const { container } = render(ui)
await act(
() => fireEvent.dragEnter(
container.querySelector('div'),
data,
)
);
expect(onDragEnter).toHaveBeenCalled()
})
function mockData(files) {
return {
dataTransfer: {
files,
items: files.map(file => ({
kind: 'file',
type: file.type,
getAsFile: () => file
})),
types: ['Files']
}
}
}
```
**NOTE**: using [Enzyme](https://airbnb.io/enzyme) for testing is not supported at the moment, see [#2011](https://github.com/airbnb/enzyme/issues/2011).
More examples for this can be found in `react-dropzone`'s own [test suites](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.spec.js).
## Caveats
### Required React Version
React [16.8](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html) or above is required because we use [hooks](https://reactjs.org/docs/hooks-intro.html) (the lib itself is a hook).
### File Paths
Files returned by the hook or passed as arg to the `onDrop` cb won't have the properties `path` or `fullPath`.
For more inf check [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).
### Not a File Uploader
This lib is not a file uploader; as such, it does not process files or provide any way to make HTTP requests to some server; if you're looking for that, checkout [filepond](https://pqina.nl/filepond) or [uppy.io](https://uppy.io/).
### Using \<label\> as Root
If you use [\<label\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) as the root element, the file dialog will be opened twice; see [#1107](https://github.com/react-dropzone/react-dropzone/issues/1107) why. To avoid this, use `noClick`:
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps} = useDropzone({noClick: true})
return (
<label {...getRootProps()}>
<input {...getInputProps()} />
</label>
)
}
```
### Using open() on Click
If you bind a click event on an inner element and use `open()`, it will trigger a click on the root element too, resulting in the file dialog opening twice. To prevent this, use the `noClick` on the root:
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps, open} = useDropzone({noClick: true})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button type="button" onClick={open}>
Open
</button>
</div>
)
}
```
### File Dialog Cancel Callback
The `onFileDialogCancel()` cb is unstable in most browsers, meaning, there's a good chance of it being triggered even though you have selected files.
We rely on using a timeout of `300ms` after the window is focused (the window `onfocus` event is triggered when the file select dialog is closed) to check if any files were selected and trigger `onFileDialogCancel` if none were selected.
As one can imagine, this doesn't really work if there's a lot of files or large files as by the time we trigger the check, the browser is still processing the files and no `onchange` events are triggered yet on the input. Check [#1031](https://github.com/react-dropzone/react-dropzone/issues/1031) for more info.
Fortunately, there's the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API), which is currently a working draft and some browsers support it (see [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker#browser_compatibility)), that provides a reliable way to prompt the user for file selection and capture cancellation.
Also keep in mind that the FS access API can only be used in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
**NOTE** You can enable using the FS access API with the `useFsAccessApi` property: `useDropzone({useFsAccessApi: true})`.
### File System Access API
When setting `useFsAccessApi` to `true`, you're switching to the [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) (see the [file system access](https://wicg.github.io/file-system-access/) RFC).
What this essentially does is that it will use the [showOpenFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker) method to open the file picker window so that the user can select files.
In contrast, the traditional way (when the `useFsAccessApi` is not set to `true` or not specified) uses an `<input type="file">` (see [docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file)) on which a click event is triggered.
With the use of the file system access API enabled, there's a couple of caveats to keep in mind:
1. The users will not be able to select directories
2. It requires the app to run in a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)
3. In [Electron](https://www.electronjs.org/), the path may not be set (see [#1249](https://github.com/react-dropzone/react-dropzone/issues/1249))
## Supported Browsers
We use [browserslist](https://github.com/browserslist/browserslist) config to state the browser support for this lib, so check it out on [browserslist.dev](https://browserslist.dev/?q=ZGVmYXVsdHM%3D).
## Need image editing?
React Dropzone integrates perfectly with [Pintura Image Editor](https://pqina.nl/pintura/?ref=react-dropzone), creating a modern image editing experience. Pintura supports crop aspect ratios, resizing, rotating, cropping, annotating, filtering, and much more.
Checkout the [Pintura integration example](https://codesandbox.io/s/react-dropzone-pintura-40xh4?file=/src/App.js).
## Support
### Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/react-dropzone#backer)]
<a href="https://opencollective.com/react-dropzone/backer/0/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/1/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/2/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/3/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/4/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/5/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/6/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/7/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/8/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/9/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/10/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/11/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/12/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/13/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/14/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/15/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/16/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/17/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/18/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/19/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/20/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/21/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/22/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/23/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/24/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/25/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/26/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/27/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/28/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/backer/29/website" target="_blank"><img src="https://opencollective.com/react-dropzone/backer/29/avatar.svg"></a>
### Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/react-dropzone#sponsor)]
<a href="https://opencollective.com/react-dropzone/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/10/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/11/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/12/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/13/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/14/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/15/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/16/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/17/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/18/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/19/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/20/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/21/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/22/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/23/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/24/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/25/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/26/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/27/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/28/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/react-dropzone/sponsor/29/website" target="_blank"><img src="https://opencollective.com/react-dropzone/sponsor/29/avatar.svg"></a>
### Hosting
[react-dropzone.js.org](https://react-dropzone.js.org/) hosting provided by [netlify](https://www.netlify.com/).
## Contribute
Checkout the organization [CONTRIBUTING.md](https://github.com/react-dropzone/.github/blob/main/CONTRIBUTING.md).
## License
MIT

View File

@@ -0,0 +1 @@
module.exports = { extends: ["@commitlint/config-angular"] };

983
frontend/node_modules/react-dropzone/dist/es/index.js generated vendored Executable file
View File

@@ -0,0 +1,983 @@
var _excluded = ["children"],
_excluded2 = ["open"],
_excluded3 = ["refKey", "role", "onKeyDown", "onFocus", "onBlur", "onClick", "onDragEnter", "onDragOver", "onDragLeave", "onDrop"],
_excluded4 = ["refKey", "onChange", "onClick"];
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
/* eslint prefer-template: 0 */
import React, { forwardRef, Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from "react";
import PropTypes from "prop-types";
import { fromEvent } from "file-selector";
import { acceptPropAsAcceptAttr, allFilesAccepted, composeEventHandlers, fileAccepted, fileMatchSize, canUseFileSystemAccessAPI, isAbort, isEvtWithFiles, isIeOrEdge, isPropagationStopped, isSecurityError, onDocumentDragOver, pickerOptionsFromAccept, TOO_MANY_FILES_REJECTION } from "./utils/index.js";
/**
* Convenience wrapper component for the `useDropzone` hook
*
* ```jsx
* <Dropzone>
* {({getRootProps, getInputProps}) => (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag 'n' drop some files here, or click to select files</p>
* </div>
* )}
* </Dropzone>
* ```
*/
var Dropzone = /*#__PURE__*/forwardRef(function (_ref, ref) {
var children = _ref.children,
params = _objectWithoutProperties(_ref, _excluded);
var _useDropzone = useDropzone(params),
open = _useDropzone.open,
props = _objectWithoutProperties(_useDropzone, _excluded2);
useImperativeHandle(ref, function () {
return {
open: open
};
}, [open]); // TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element
return /*#__PURE__*/React.createElement(Fragment, null, children(_objectSpread(_objectSpread({}, props), {}, {
open: open
})));
});
Dropzone.displayName = "Dropzone"; // Add default props for react-docgen
var defaultProps = {
disabled: false,
getFilesFromEvent: fromEvent,
maxSize: Infinity,
minSize: 0,
multiple: true,
maxFiles: 0,
preventDropOnDocument: true,
noClick: false,
noKeyboard: false,
noDrag: false,
noDragEventsBubbling: false,
validator: null,
useFsAccessApi: false,
autoFocus: false
};
Dropzone.defaultProps = defaultProps;
Dropzone.propTypes = {
/**
* Render function that exposes the dropzone state and prop getter fns
*
* @param {object} params
* @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
* @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
* @param {Function} params.open Open the native file selection dialog
* @param {boolean} params.isFocused Dropzone area is in focus
* @param {boolean} params.isFileDialogActive File dialog is opened
* @param {boolean} params.isDragActive Active drag is in progress
* @param {boolean} params.isDragAccept Dragged files are accepted
* @param {boolean} params.isDragReject Some dragged files are rejected
* @param {File[]} params.acceptedFiles Accepted files
* @param {FileRejection[]} params.fileRejections Rejected files and why they were rejected
*/
children: PropTypes.func,
/**
* Set accepted file types.
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
*/
accept: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
/**
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
*/
multiple: PropTypes.bool,
/**
* If false, allow dropped items to take over the current browser window
*/
preventDropOnDocument: PropTypes.bool,
/**
* If true, disables click to open the native file selection dialog
*/
noClick: PropTypes.bool,
/**
* If true, disables SPACE/ENTER to open the native file selection dialog.
* Note that it also stops tracking the focus state.
*/
noKeyboard: PropTypes.bool,
/**
* If true, disables drag 'n' drop
*/
noDrag: PropTypes.bool,
/**
* If true, stops drag event propagation to parents
*/
noDragEventsBubbling: PropTypes.bool,
/**
* Minimum file size (in bytes)
*/
minSize: PropTypes.number,
/**
* Maximum file size (in bytes)
*/
maxSize: PropTypes.number,
/**
* Maximum accepted number of files
* The default value is 0 which means there is no limitation to how many files are accepted.
*/
maxFiles: PropTypes.number,
/**
* Enable/disable the dropzone
*/
disabled: PropTypes.bool,
/**
* Use this to provide a custom file aggregator
*
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
*/
getFilesFromEvent: PropTypes.func,
/**
* Cb for when closing the file dialog with no selection
*/
onFileDialogCancel: PropTypes.func,
/**
* Cb for when opening the file dialog
*/
onFileDialogOpen: PropTypes.func,
/**
* Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
* to open the file picker instead of using an `<input type="file">` click event.
*/
useFsAccessApi: PropTypes.bool,
/**
* Set to true to focus the root element on render
*/
autoFocus: PropTypes.bool,
/**
* Cb for when the `dragenter` event occurs.
*
* @param {DragEvent} event
*/
onDragEnter: PropTypes.func,
/**
* Cb for when the `dragleave` event occurs
*
* @param {DragEvent} event
*/
onDragLeave: PropTypes.func,
/**
* Cb for when the `dragover` event occurs
*
* @param {DragEvent} event
*/
onDragOver: PropTypes.func,
/**
* Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
* If `multiple` is set to false and additional files are dropped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
*
* @param {File[]} acceptedFiles
* @param {FileRejection[]} fileRejections
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
onDrop: PropTypes.func,
/**
* Cb for when the `drop` event occurs.
* Note that if no files are accepted, this callback is not invoked.
*
* @param {File[]} files
* @param {(DragEvent|Event)} event
*/
onDropAccepted: PropTypes.func,
/**
* Cb for when the `drop` event occurs.
* Note that if no files are rejected, this callback is not invoked.
*
* @param {FileRejection[]} fileRejections
* @param {(DragEvent|Event)} event
*/
onDropRejected: PropTypes.func,
/**
* Cb for when there's some error from any of the promises.
*
* @param {Error} error
*/
onError: PropTypes.func,
/**
* Custom validation function. It must return null if there's no errors.
* @param {File} file
* @returns {FileError|FileError[]|null}
*/
validator: PropTypes.func
};
export default Dropzone;
/**
* A function that is invoked for the `dragenter`,
* `dragover` and `dragleave` events.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dragCb
* @param {DragEvent} event
*/
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dropCb
* @param {File[]} acceptedFiles List of accepted files
* @param {FileRejection[]} fileRejections List of rejected files and why they were rejected
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are files (such as link, text, etc.).
*
* @callback dropAcceptedCb
* @param {File[]} files List of accepted files that meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
/**
* A function that is invoked for the `drop` or input change event.
*
* @callback dropRejectedCb
* @param {File[]} files List of rejected files that do not meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
/**
* A function that is used aggregate files,
* in a asynchronous fashion, from drag or input change events.
*
* @callback getFilesFromEvent
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
* @returns {(File[]|Promise<File[]>)}
*/
/**
* An object with the current dropzone state.
*
* @typedef {object} DropzoneState
* @property {boolean} isFocused Dropzone area is in focus
* @property {boolean} isFileDialogActive File dialog is opened
* @property {boolean} isDragActive Active drag is in progress
* @property {boolean} isDragAccept Dragged files are accepted
* @property {boolean} isDragReject Some dragged files are rejected
* @property {File[]} acceptedFiles Accepted files
* @property {FileRejection[]} fileRejections Rejected files and why they were rejected
*/
/**
* An object with the dropzone methods.
*
* @typedef {object} DropzoneMethods
* @property {Function} getRootProps Returns the props you should apply to the root drop container you render
* @property {Function} getInputProps Returns the props you should apply to hidden file input you render
* @property {Function} open Open the native file selection dialog
*/
var initialState = {
isFocused: false,
isFileDialogActive: false,
isDragActive: false,
isDragAccept: false,
isDragReject: false,
acceptedFiles: [],
fileRejections: []
};
/**
* A React hook that creates a drag 'n' drop area.
*
* ```jsx
* function MyDropzone(props) {
* const {getRootProps, getInputProps} = useDropzone({
* onDrop: acceptedFiles => {
* // do something with the File objects, e.g. upload to some server
* }
* });
* return (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag and drop some files here, or click to select files</p>
* </div>
* )
* }
* ```
*
* @function useDropzone
*
* @param {object} props
* @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
* @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
* @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
* @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
* @param {boolean} [props.noKeyboard=false] If true, disables SPACE/ENTER to open the native file selection dialog.
* Note that it also stops tracking the focus state.
* @param {boolean} [props.noDrag=false] If true, disables drag 'n' drop
* @param {boolean} [props.noDragEventsBubbling=false] If true, stops drag event propagation to parents
* @param {number} [props.minSize=0] Minimum file size (in bytes)
* @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
* @param {boolean} [props.useFsAccessApi] Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
* to open the file picker instead of using an `<input type="file">` click event.
* @param {boolean} autoFocus Set to true to auto focus the root element.
* @param {Function} [props.onFileDialogOpen] Cb for when opening the file dialog
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
* @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
* @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be an object with keys as a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) and the value an array of file extensions (optional).
* If `multiple` is set to false and additional files are dropped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
* @param {dropAcceptedCb} [props.onDropAccepted]
* @param {dropRejectedCb} [props.onDropRejected]
* @param {(error: Error) => void} [props.onError]
*
* @returns {DropzoneState & DropzoneMethods}
*/
export function useDropzone() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _defaultProps$props = _objectSpread(_objectSpread({}, defaultProps), props),
accept = _defaultProps$props.accept,
disabled = _defaultProps$props.disabled,
getFilesFromEvent = _defaultProps$props.getFilesFromEvent,
maxSize = _defaultProps$props.maxSize,
minSize = _defaultProps$props.minSize,
multiple = _defaultProps$props.multiple,
maxFiles = _defaultProps$props.maxFiles,
onDragEnter = _defaultProps$props.onDragEnter,
onDragLeave = _defaultProps$props.onDragLeave,
onDragOver = _defaultProps$props.onDragOver,
onDrop = _defaultProps$props.onDrop,
onDropAccepted = _defaultProps$props.onDropAccepted,
onDropRejected = _defaultProps$props.onDropRejected,
onFileDialogCancel = _defaultProps$props.onFileDialogCancel,
onFileDialogOpen = _defaultProps$props.onFileDialogOpen,
useFsAccessApi = _defaultProps$props.useFsAccessApi,
autoFocus = _defaultProps$props.autoFocus,
preventDropOnDocument = _defaultProps$props.preventDropOnDocument,
noClick = _defaultProps$props.noClick,
noKeyboard = _defaultProps$props.noKeyboard,
noDrag = _defaultProps$props.noDrag,
noDragEventsBubbling = _defaultProps$props.noDragEventsBubbling,
onError = _defaultProps$props.onError,
validator = _defaultProps$props.validator;
var acceptAttr = useMemo(function () {
return acceptPropAsAcceptAttr(accept);
}, [accept]);
var pickerTypes = useMemo(function () {
return pickerOptionsFromAccept(accept);
}, [accept]);
var onFileDialogOpenCb = useMemo(function () {
return typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop;
}, [onFileDialogOpen]);
var onFileDialogCancelCb = useMemo(function () {
return typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop;
}, [onFileDialogCancel]);
/**
* @constant
* @type {React.MutableRefObject<HTMLElement>}
*/
var rootRef = useRef(null);
var inputRef = useRef(null);
var _useReducer = useReducer(reducer, initialState),
_useReducer2 = _slicedToArray(_useReducer, 2),
state = _useReducer2[0],
dispatch = _useReducer2[1];
var isFocused = state.isFocused,
isFileDialogActive = state.isFileDialogActive;
var fsAccessApiWorksRef = useRef(typeof window !== "undefined" && window.isSecureContext && useFsAccessApi && canUseFileSystemAccessAPI()); // Update file dialog active state when the window is focused on
var onWindowFocus = function onWindowFocus() {
// Execute the timeout only if the file dialog is opened in the browser
if (!fsAccessApiWorksRef.current && isFileDialogActive) {
setTimeout(function () {
if (inputRef.current) {
var files = inputRef.current.files;
if (!files.length) {
dispatch({
type: "closeDialog"
});
onFileDialogCancelCb();
}
}
}, 300);
}
};
useEffect(function () {
window.addEventListener("focus", onWindowFocus, false);
return function () {
window.removeEventListener("focus", onWindowFocus, false);
};
}, [inputRef, isFileDialogActive, onFileDialogCancelCb, fsAccessApiWorksRef]);
var dragTargetsRef = useRef([]);
var onDocumentDrop = function onDocumentDrop(event) {
if (rootRef.current && rootRef.current.contains(event.target)) {
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
return;
}
event.preventDefault();
dragTargetsRef.current = [];
};
useEffect(function () {
if (preventDropOnDocument) {
document.addEventListener("dragover", onDocumentDragOver, false);
document.addEventListener("drop", onDocumentDrop, false);
}
return function () {
if (preventDropOnDocument) {
document.removeEventListener("dragover", onDocumentDragOver);
document.removeEventListener("drop", onDocumentDrop);
}
};
}, [rootRef, preventDropOnDocument]); // Auto focus the root when autoFocus is true
useEffect(function () {
if (!disabled && autoFocus && rootRef.current) {
rootRef.current.focus();
}
return function () {};
}, [rootRef, autoFocus, disabled]);
var onErrCb = useCallback(function (e) {
if (onError) {
onError(e);
} else {
// Let the user know something's gone wrong if they haven't provided the onError cb.
console.error(e);
}
}, [onError]);
var onDragEnterCb = useCallback(function (event) {
event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
event.persist();
stopPropagation(event);
dragTargetsRef.current = [].concat(_toConsumableArray(dragTargetsRef.current), [event.target]);
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(function (files) {
if (isPropagationStopped(event) && !noDragEventsBubbling) {
return;
}
var fileCount = files.length;
var isDragAccept = fileCount > 0 && allFilesAccepted({
files: files,
accept: acceptAttr,
minSize: minSize,
maxSize: maxSize,
multiple: multiple,
maxFiles: maxFiles,
validator: validator
});
var isDragReject = fileCount > 0 && !isDragAccept;
dispatch({
isDragAccept: isDragAccept,
isDragReject: isDragReject,
isDragActive: true,
type: "setDraggedFiles"
});
if (onDragEnter) {
onDragEnter(event);
}
}).catch(function (e) {
return onErrCb(e);
});
}
}, [getFilesFromEvent, onDragEnter, onErrCb, noDragEventsBubbling, acceptAttr, minSize, maxSize, multiple, maxFiles, validator]);
var onDragOverCb = useCallback(function (event) {
event.preventDefault();
event.persist();
stopPropagation(event);
var hasFiles = isEvtWithFiles(event);
if (hasFiles && event.dataTransfer) {
try {
event.dataTransfer.dropEffect = "copy";
} catch (_unused) {}
/* eslint-disable-line no-empty */
}
if (hasFiles && onDragOver) {
onDragOver(event);
}
return false;
}, [onDragOver, noDragEventsBubbling]);
var onDragLeaveCb = useCallback(function (event) {
event.preventDefault();
event.persist();
stopPropagation(event); // Only deactivate once the dropzone and all children have been left
var targets = dragTargetsRef.current.filter(function (target) {
return rootRef.current && rootRef.current.contains(target);
}); // Make sure to remove a target present multiple times only once
// (Firefox may fire dragenter/dragleave multiple times on the same element)
var targetIdx = targets.indexOf(event.target);
if (targetIdx !== -1) {
targets.splice(targetIdx, 1);
}
dragTargetsRef.current = targets;
if (targets.length > 0) {
return;
}
dispatch({
type: "setDraggedFiles",
isDragActive: false,
isDragAccept: false,
isDragReject: false
});
if (isEvtWithFiles(event) && onDragLeave) {
onDragLeave(event);
}
}, [rootRef, onDragLeave, noDragEventsBubbling]);
var setFiles = useCallback(function (files, event) {
var acceptedFiles = [];
var fileRejections = [];
files.forEach(function (file) {
var _fileAccepted = fileAccepted(file, acceptAttr),
_fileAccepted2 = _slicedToArray(_fileAccepted, 2),
accepted = _fileAccepted2[0],
acceptError = _fileAccepted2[1];
var _fileMatchSize = fileMatchSize(file, minSize, maxSize),
_fileMatchSize2 = _slicedToArray(_fileMatchSize, 2),
sizeMatch = _fileMatchSize2[0],
sizeError = _fileMatchSize2[1];
var customErrors = validator ? validator(file) : null;
if (accepted && sizeMatch && !customErrors) {
acceptedFiles.push(file);
} else {
var errors = [acceptError, sizeError];
if (customErrors) {
errors = errors.concat(customErrors);
}
fileRejections.push({
file: file,
errors: errors.filter(function (e) {
return e;
})
});
}
});
if (!multiple && acceptedFiles.length > 1 || multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles) {
// Reject everything and empty accepted files
acceptedFiles.forEach(function (file) {
fileRejections.push({
file: file,
errors: [TOO_MANY_FILES_REJECTION]
});
});
acceptedFiles.splice(0);
}
dispatch({
acceptedFiles: acceptedFiles,
fileRejections: fileRejections,
isDragReject: fileRejections.length > 0,
type: "setFiles"
});
if (onDrop) {
onDrop(acceptedFiles, fileRejections, event);
}
if (fileRejections.length > 0 && onDropRejected) {
onDropRejected(fileRejections, event);
}
if (acceptedFiles.length > 0 && onDropAccepted) {
onDropAccepted(acceptedFiles, event);
}
}, [dispatch, multiple, acceptAttr, minSize, maxSize, maxFiles, onDrop, onDropAccepted, onDropRejected, validator]);
var onDropCb = useCallback(function (event) {
event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
event.persist();
stopPropagation(event);
dragTargetsRef.current = [];
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(function (files) {
if (isPropagationStopped(event) && !noDragEventsBubbling) {
return;
}
setFiles(files, event);
}).catch(function (e) {
return onErrCb(e);
});
}
dispatch({
type: "reset"
});
}, [getFilesFromEvent, setFiles, onErrCb, noDragEventsBubbling]); // Fn for opening the file dialog programmatically
var openFileDialog = useCallback(function () {
// No point to use FS access APIs if context is not secure
// https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#feature_detection
if (fsAccessApiWorksRef.current) {
dispatch({
type: "openDialog"
});
onFileDialogOpenCb(); // https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
var opts = {
multiple: multiple,
types: pickerTypes
};
window.showOpenFilePicker(opts).then(function (handles) {
return getFilesFromEvent(handles);
}).then(function (files) {
setFiles(files, null);
dispatch({
type: "closeDialog"
});
}).catch(function (e) {
// AbortError means the user canceled
if (isAbort(e)) {
onFileDialogCancelCb(e);
dispatch({
type: "closeDialog"
});
} else if (isSecurityError(e)) {
fsAccessApiWorksRef.current = false; // CORS, so cannot use this API
// Try using the input
if (inputRef.current) {
inputRef.current.value = null;
inputRef.current.click();
} else {
onErrCb(new Error("Cannot open the file picker because the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API is not supported and no <input> was provided."));
}
} else {
onErrCb(e);
}
});
return;
}
if (inputRef.current) {
dispatch({
type: "openDialog"
});
onFileDialogOpenCb();
inputRef.current.value = null;
inputRef.current.click();
}
}, [dispatch, onFileDialogOpenCb, onFileDialogCancelCb, useFsAccessApi, setFiles, onErrCb, pickerTypes, multiple]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
var onKeyDownCb = useCallback(function (event) {
// Ignore keyboard events bubbling up the DOM tree
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
return;
}
if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
event.preventDefault();
openFileDialog();
}
}, [rootRef, openFileDialog]); // Update focus state for the dropzone
var onFocusCb = useCallback(function () {
dispatch({
type: "focus"
});
}, []);
var onBlurCb = useCallback(function () {
dispatch({
type: "blur"
});
}, []); // Cb to open the file dialog when click occurs on the dropzone
var onClickCb = useCallback(function () {
if (noClick) {
return;
} // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
// to ensure React can handle state changes
// See: https://github.com/react-dropzone/react-dropzone/issues/450
if (isIeOrEdge()) {
setTimeout(openFileDialog, 0);
} else {
openFileDialog();
}
}, [noClick, openFileDialog]);
var composeHandler = function composeHandler(fn) {
return disabled ? null : fn;
};
var composeKeyboardHandler = function composeKeyboardHandler(fn) {
return noKeyboard ? null : composeHandler(fn);
};
var composeDragHandler = function composeDragHandler(fn) {
return noDrag ? null : composeHandler(fn);
};
var stopPropagation = function stopPropagation(event) {
if (noDragEventsBubbling) {
event.stopPropagation();
}
};
var getRootProps = useMemo(function () {
return function () {
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref2$refKey = _ref2.refKey,
refKey = _ref2$refKey === void 0 ? "ref" : _ref2$refKey,
role = _ref2.role,
onKeyDown = _ref2.onKeyDown,
onFocus = _ref2.onFocus,
onBlur = _ref2.onBlur,
onClick = _ref2.onClick,
onDragEnter = _ref2.onDragEnter,
onDragOver = _ref2.onDragOver,
onDragLeave = _ref2.onDragLeave,
onDrop = _ref2.onDrop,
rest = _objectWithoutProperties(_ref2, _excluded3);
return _objectSpread(_objectSpread(_defineProperty({
onKeyDown: composeKeyboardHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
onFocus: composeKeyboardHandler(composeEventHandlers(onFocus, onFocusCb)),
onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
onDragEnter: composeDragHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
onDragOver: composeDragHandler(composeEventHandlers(onDragOver, onDragOverCb)),
onDragLeave: composeDragHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
role: typeof role === "string" && role !== "" ? role : "presentation"
}, refKey, rootRef), !disabled && !noKeyboard ? {
tabIndex: 0
} : {}), rest);
};
}, [rootRef, onKeyDownCb, onFocusCb, onBlurCb, onClickCb, onDragEnterCb, onDragOverCb, onDragLeaveCb, onDropCb, noKeyboard, noDrag, disabled]);
var onInputElementClick = useCallback(function (event) {
event.stopPropagation();
}, []);
var getInputProps = useMemo(function () {
return function () {
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref3$refKey = _ref3.refKey,
refKey = _ref3$refKey === void 0 ? "ref" : _ref3$refKey,
onChange = _ref3.onChange,
onClick = _ref3.onClick,
rest = _objectWithoutProperties(_ref3, _excluded4);
var inputProps = _defineProperty({
accept: acceptAttr,
multiple: multiple,
type: "file",
style: {
border: 0,
clip: "rect(0, 0, 0, 0)",
clipPath: "inset(50%)",
height: "1px",
margin: "0 -1px -1px 0",
overflow: "hidden",
padding: 0,
position: "absolute",
width: "1px",
whiteSpace: "nowrap"
},
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
tabIndex: -1
}, refKey, inputRef);
return _objectSpread(_objectSpread({}, inputProps), rest);
};
}, [inputRef, accept, multiple, onDropCb, disabled]);
return _objectSpread(_objectSpread({}, state), {}, {
isFocused: isFocused && !disabled,
getRootProps: getRootProps,
getInputProps: getInputProps,
rootRef: rootRef,
inputRef: inputRef,
open: composeHandler(openFileDialog)
});
}
/**
* @param {DropzoneState} state
* @param {{type: string} & DropzoneState} action
* @returns {DropzoneState}
*/
function reducer(state, action) {
/* istanbul ignore next */
switch (action.type) {
case "focus":
return _objectSpread(_objectSpread({}, state), {}, {
isFocused: true
});
case "blur":
return _objectSpread(_objectSpread({}, state), {}, {
isFocused: false
});
case "openDialog":
return _objectSpread(_objectSpread({}, initialState), {}, {
isFileDialogActive: true
});
case "closeDialog":
return _objectSpread(_objectSpread({}, state), {}, {
isFileDialogActive: false
});
case "setDraggedFiles":
return _objectSpread(_objectSpread({}, state), {}, {
isDragActive: action.isDragActive,
isDragAccept: action.isDragAccept,
isDragReject: action.isDragReject
});
case "setFiles":
return _objectSpread(_objectSpread({}, state), {}, {
acceptedFiles: action.acceptedFiles,
fileRejections: action.fileRejections,
isDragReject: action.isDragReject
});
case "reset":
return _objectSpread({}, initialState);
default:
return state;
}
}
function noop() {}
export { ErrorCode } from "./utils/index.js";

View File

@@ -0,0 +1 @@
{"type":"module"}

View File

@@ -0,0 +1,342 @@
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import _accepts from "attr-accept";
var accepts = typeof _accepts === "function" ? _accepts : _accepts.default; // Error codes
export var FILE_INVALID_TYPE = "file-invalid-type";
export var FILE_TOO_LARGE = "file-too-large";
export var FILE_TOO_SMALL = "file-too-small";
export var TOO_MANY_FILES = "too-many-files";
export var ErrorCode = {
FileInvalidType: FILE_INVALID_TYPE,
FileTooLarge: FILE_TOO_LARGE,
FileTooSmall: FILE_TOO_SMALL,
TooManyFiles: TOO_MANY_FILES
};
/**
*
* @param {string} accept
*/
export var getInvalidTypeRejectionErr = function getInvalidTypeRejectionErr() {
var accept = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
var acceptArr = accept.split(",");
var msg = acceptArr.length > 1 ? "one of ".concat(acceptArr.join(", ")) : acceptArr[0];
return {
code: FILE_INVALID_TYPE,
message: "File type must be ".concat(msg)
};
};
export var getTooLargeRejectionErr = function getTooLargeRejectionErr(maxSize) {
return {
code: FILE_TOO_LARGE,
message: "File is larger than ".concat(maxSize, " ").concat(maxSize === 1 ? "byte" : "bytes")
};
};
export var getTooSmallRejectionErr = function getTooSmallRejectionErr(minSize) {
return {
code: FILE_TOO_SMALL,
message: "File is smaller than ".concat(minSize, " ").concat(minSize === 1 ? "byte" : "bytes")
};
};
export var TOO_MANY_FILES_REJECTION = {
code: TOO_MANY_FILES,
message: "Too many files"
};
/**
* Check if file is accepted.
*
* Firefox versions prior to 53 return a bogus MIME type for every file drag,
* so dragovers with that MIME type will always be accepted.
*
* @param {File} file
* @param {string} accept
* @returns
*/
export function fileAccepted(file, accept) {
var isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept);
return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
}
export function fileMatchSize(file, minSize, maxSize) {
if (isDefined(file.size)) {
if (isDefined(minSize) && isDefined(maxSize)) {
if (file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
if (file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
} else if (isDefined(minSize) && file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];else if (isDefined(maxSize) && file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
}
return [true, null];
}
function isDefined(value) {
return value !== undefined && value !== null;
}
/**
*
* @param {object} options
* @param {File[]} options.files
* @param {string} [options.accept]
* @param {number} [options.minSize]
* @param {number} [options.maxSize]
* @param {boolean} [options.multiple]
* @param {number} [options.maxFiles]
* @param {(f: File) => FileError|FileError[]|null} [options.validator]
* @returns
*/
export function allFilesAccepted(_ref) {
var files = _ref.files,
accept = _ref.accept,
minSize = _ref.minSize,
maxSize = _ref.maxSize,
multiple = _ref.multiple,
maxFiles = _ref.maxFiles,
validator = _ref.validator;
if (!multiple && files.length > 1 || multiple && maxFiles >= 1 && files.length > maxFiles) {
return false;
}
return files.every(function (file) {
var _fileAccepted = fileAccepted(file, accept),
_fileAccepted2 = _slicedToArray(_fileAccepted, 1),
accepted = _fileAccepted2[0];
var _fileMatchSize = fileMatchSize(file, minSize, maxSize),
_fileMatchSize2 = _slicedToArray(_fileMatchSize, 1),
sizeMatch = _fileMatchSize2[0];
var customErrors = validator ? validator(file) : null;
return accepted && sizeMatch && !customErrors;
});
} // React's synthetic events has event.isPropagationStopped,
// but to remain compatibility with other libs (Preact) fall back
// to check event.cancelBubble
export function isPropagationStopped(event) {
if (typeof event.isPropagationStopped === "function") {
return event.isPropagationStopped();
} else if (typeof event.cancelBubble !== "undefined") {
return event.cancelBubble;
}
return false;
}
export function isEvtWithFiles(event) {
if (!event.dataTransfer) {
return !!event.target && !!event.target.files;
} // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
return Array.prototype.some.call(event.dataTransfer.types, function (type) {
return type === "Files" || type === "application/x-moz-file";
});
}
export function isKindFile(item) {
return _typeof(item) === "object" && item !== null && item.kind === "file";
} // allow the entire document to be a drag target
export function onDocumentDragOver(event) {
event.preventDefault();
}
function isIe(userAgent) {
return userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1;
}
function isEdge(userAgent) {
return userAgent.indexOf("Edge/") !== -1;
}
export function isIeOrEdge() {
var userAgent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.navigator.userAgent;
return isIe(userAgent) || isEdge(userAgent);
}
/**
* This is intended to be used to compose event handlers
* They are executed in order until one of them calls `event.isPropagationStopped()`.
* Note that the check is done on the first invoke too,
* meaning that if propagation was stopped before invoking the fns,
* no handlers will be executed.
*
* @param {Function} fns the event hanlder functions
* @return {Function} the event handler to add to an element
*/
export function composeEventHandlers() {
for (var _len = arguments.length, fns = new Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];
}
return function (event) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return fns.some(function (fn) {
if (!isPropagationStopped(event) && fn) {
fn.apply(void 0, [event].concat(args));
}
return isPropagationStopped(event);
});
};
}
/**
* canUseFileSystemAccessAPI checks if the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
* is supported by the browser.
* @returns {boolean}
*/
export function canUseFileSystemAccessAPI() {
return "showOpenFilePicker" in window;
}
/**
* Convert the `{accept}` dropzone prop to the
* `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
*
* @param {AcceptProp} accept
* @returns {{accept: string[]}[]}
*/
export function pickerOptionsFromAccept(accept) {
if (isDefined(accept)) {
var acceptForPicker = Object.entries(accept).filter(function (_ref2) {
var _ref3 = _slicedToArray(_ref2, 2),
mimeType = _ref3[0],
ext = _ref3[1];
var ok = true;
if (!isMIMEType(mimeType)) {
console.warn("Skipped \"".concat(mimeType, "\" because it is not a valid MIME type. Check https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types for a list of valid MIME types."));
ok = false;
}
if (!Array.isArray(ext) || !ext.every(isExt)) {
console.warn("Skipped \"".concat(mimeType, "\" because an invalid file extension was provided."));
ok = false;
}
return ok;
}).reduce(function (agg, _ref4) {
var _ref5 = _slicedToArray(_ref4, 2),
mimeType = _ref5[0],
ext = _ref5[1];
return _objectSpread(_objectSpread({}, agg), {}, _defineProperty({}, mimeType, ext));
}, {});
return [{
// description is required due to https://crbug.com/1264708
description: "Files",
accept: acceptForPicker
}];
}
return accept;
}
/**
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
* @param {AcceptProp} accept
* @returns {string}
*/
export function acceptPropAsAcceptAttr(accept) {
if (isDefined(accept)) {
return Object.entries(accept).reduce(function (a, _ref6) {
var _ref7 = _slicedToArray(_ref6, 2),
mimeType = _ref7[0],
ext = _ref7[1];
return [].concat(_toConsumableArray(a), [mimeType], _toConsumableArray(ext));
}, []) // Silently discard invalid entries as pickerOptionsFromAccept warns about these
.filter(function (v) {
return isMIMEType(v) || isExt(v);
}).join(",");
}
return undefined;
}
/**
* Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
*
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
* @param {any} v
* @returns {boolean} True if v is an abort exception.
*/
export function isAbort(v) {
return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
}
/**
* Check if v is a security error.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
* @param {any} v
* @returns {boolean} True if v is a security error.
*/
export function isSecurityError(v) {
return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
}
/**
* Check if v is a MIME type string.
*
* See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
*
* @param {string} v
*/
export function isMIMEType(v) {
return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || v === "application/*" || /\w+\/[-+.\w]+/g.test(v);
}
/**
* Check if v is a file extension.
* @param {string} v
*/
export function isExt(v) {
return /^.*\.[\w]+$/.test(v);
}
/**
* @typedef {Object.<string, string[]>} AcceptProp
*/
/**
* @typedef {object} FileError
* @property {string} message
* @property {ErrorCode|string} code
*/
/**
* @typedef {"file-invalid-type"|"file-too-large"|"file-too-small"|"too-many-files"} ErrorCode
*/

2
frontend/node_modules/react-dropzone/dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
{
"globals": {
"useDropzone": true
}
}

View File

@@ -0,0 +1,144 @@
By providing `accept` prop you can make the dropzone accept specific file types and reject the others.
The value must be an object with a common [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) as keys and an array of file extensions as values (similar to [showOpenFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker)'s types `accept` option).
```js static
useDropzone({
accept: {
'image/png': ['.png'],
'text/html': ['.html', '.htm'],
}
})
```
For more information see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input.
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Accept(props) {
const {
acceptedFiles,
fileRejections,
getRootProps,
getInputProps
} = useDropzone({
accept: {
'image/jpeg': [],
'image/png': []
}
});
const acceptedFileItems = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
const fileRejectionItems = fileRejections.map(({ file, errors }) => (
<li key={file.path}>
{file.path} - {file.size} bytes
<ul>
{errors.map(e => (
<li key={e.code}>{e.message}</li>
))}
</ul>
</li>
));
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
<em>(Only *.jpeg and *.png images will be accepted)</em>
</div>
<aside>
<h4>Accepted files</h4>
<ul>{acceptedFileItems}</ul>
<h4>Rejected files</h4>
<ul>{fileRejectionItems}</ul>
</aside>
</section>
);
}
<Accept />
```
### Browser limitations
Because of HTML5 File API differences across different browsers during the drag, Dropzone might not be able to detect whether the files are accepted or rejected in Safari nor IE.
Furthermore, at this moment it's not possible to read file names (and thus, file extensions) during the drag operation. For that reason, if you want to react on different file types _during_ the drag operation, _you have to use_ mime types and not extensions! For example, the following example won't work even in Chrome:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Accept(props) {
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject
} = useDropzone({
accept: {
'image/jpeg': ['.jpeg', '.png']
}
});
return (
<div className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
{isDragAccept && (<p>All files will be accepted</p>)}
{isDragReject && (<p>Some files will be rejected</p>)}
{!isDragActive && (<p>Drop some files here ...</p>)}
</div>
</div>
);
}
<Accept />
```
but this one will:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Accept(props) {
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject
} = useDropzone({
accept: {
'image/*': ['.jpeg', '.png']
}
});
return (
<div className="container">
<div {...getRootProps({className: "dropzone"})}>
<input {...getInputProps()} />
{isDragAccept && (<p>All files will be accepted</p>)}
{isDragReject && (<p>Some files will be rejected</p>)}
{!isDragActive && (<p>Drop some files here ...</p>)}
</div>
</div>
);
}
<Accept />
```
### Notes
Mime type determination is not reliable across platforms. CSV files, for example, are reported as text/plain under macOS but as application/vnd.ms-excel under Windows. In some cases there might not be a mime type set at all.

View File

@@ -0,0 +1,70 @@
The `useDropzone` hook just binds the necessary handlers to create a drag 'n' drop zone.
Use the `getRootProps()` fn to get the props required for drag 'n' drop and use them on any element.
For click and keydown behavior, use the `getInputProps()` fn and use the returned props on an `<input>`.
Furthermore, the hook supports folder drag 'n' drop by default. See [file-selector](https://github.com/react-dropzone/file-selector) for more info about supported browsers.
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Basic(props) {
const {acceptedFiles, getRootProps, getInputProps} = useDropzone();
const files = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<Basic />
```
Dropzone with `disabled` property:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Basic(props) {
const {acceptedFiles, getRootProps, getInputProps} = useDropzone({
disabled: true
});
const files = acceptedFiles.map(file => (
<li key={file.name}>
{file.name} - {file.size} bytes
</li>
));
return (
<section className="container">
<div {...getRootProps({className: 'dropzone disabled'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<Basic />
```

View File

@@ -0,0 +1,45 @@
If you're still using class components, you can use the [`<Dropzone>`](https://react-dropzone.js.org/#components) component provided by the lib:
```jsx harmony
import React, {Component} from 'react';
import Dropzone from 'react-dropzone';
class Basic extends Component {
constructor() {
super();
this.onDrop = (files) => {
this.setState({files})
};
this.state = {
files: []
};
}
render() {
const files = this.state.files.map(file => (
<li key={file.name}>
{file.name} - {file.size} bytes
</li>
));
return (
<Dropzone onDrop={this.onDrop}>
{({getRootProps, getInputProps}) => (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
)}
</Dropzone>
);
}
}
<Basic />
```

View File

@@ -0,0 +1,164 @@
If you'd like to prevent drag events propagation from the child to parent, you can use the `{noDragEventsBubbling}` property on the child:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function OuterDropzone(props) {
const {getRootProps} = useDropzone({
// Note how this callback is never invoked if drop occurs on the inner dropzone
onDrop: files => console.log(files)
});
return (
<div className="container">
<div {...getRootProps({className: 'dropzone'})}>
<InnerDropzone />
<p>Outer dropzone</p>
</div>
</div>
);
}
function InnerDropzone(props) {
const {getRootProps} = useDropzone({noDragEventsBubbling: true});
return (
<div {...getRootProps({className: 'dropzone'})}>
<p>Inner dropzone</p>
</div>
);
}
<OuterDropzone />
```
Note that internally we use `event.stopPropagation()` to achieve the behavior illustrated above, but this comes with its own [drawbacks](https://javascript.info/bubbling-and-capturing#stopping-bubbling).
If you'd like to selectively turn off the default dropzone behavior for `onClick`, use the `{noClick}` property:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function DropzoneWithoutClick(props) {
const {getRootProps, getInputProps, acceptedFiles} = useDropzone({noClick: true});
const files = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Dropzone without click events</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<DropzoneWithoutClick />
```
If you'd like to selectively turn off the default dropzone behavior for `onKeyDown`, `onFocus` and `onBlur`, use the `{noKeyboard}` property:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function DropzoneWithoutKeyboard(props) {
const {getRootProps, getInputProps, acceptedFiles} = useDropzone({noKeyboard: true});
const files = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Dropzone without keyboard events</p>
<em>(SPACE/ENTER and focus events are disabled)</em>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<DropzoneWithoutKeyboard />
```
Or you can prevent the default behavior for both click and keyboard events if you omit the input:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function DropzoneWithoutClick(props) {
const {getRootProps, acceptedFiles} = useDropzone();
const files = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<p>Dropzone without click events</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<DropzoneWithoutClick />
```
**NOTE** If the browser supports the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) and you've set the `useFsAccessApi` to true, removing the `<input>` has no effect.
If you'd like to selectively turn off the default dropzone behavior for drag events, use the `{noDrag}` property:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function DropzoneWithoutDrag(props) {
const {getRootProps, getInputProps, acceptedFiles} = useDropzone({noDrag: true});
const files = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Dropzone with no drag events</p>
<em>(Drag 'n' drop is disabled)</em>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
<DropzoneWithoutDrag />
```
Keep in mind that if you provide your own callback handlers as well and use `event.stopPropagation()`, it will prevent the default dropzone behavior:
```jsx harmony
import React from 'react';
import Dropzone from 'react-dropzone';
// Note that there will be nothing logged when files are dropped
<Dropzone onDrop={files => console.log(files)}>
{({getRootProps, getInputProps}) => (
<div className="container">
<div
{...getRootProps({
className: 'dropzone',
onDrop: event => event.stopPropagation()
})}
>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</div>
)}
</Dropzone>
```

View File

@@ -0,0 +1,90 @@
You can programmatically invoke the default OS file prompt; just use the `open` method returned by the hook.
**Note** that for security reasons most browsers require popups and dialogues to originate from a direct user interaction (i.e. click).
If you are calling `open()` asynchronously, theres a good chance its going to be blocked by the browser. So if you are calling `open()` asynchronously, be sure there is no more than *1000ms* delay between user interaction and `open()` call.
Due to the lack of official docs on this (at least we havent found any. If you know one, feel free to open PR), there is no guarantee that **allowed delay duration** will not be changed in later browser versions. Since implementations may differ between different browsers, avoid calling open asynchronously if possible.
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Dropzone(props) {
const {getRootProps, getInputProps, open, acceptedFiles} = useDropzone({
// Disable click and keydown behavior
noClick: true,
noKeyboard: true
});
const files = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<div className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here</p>
<button type="button" onClick={open}>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</div>
);
}
<Dropzone />
```
Or use the `ref` exposed by the `<Dropzone>` component:
```jsx harmony
import React, {createRef} from 'react';
import Dropzone from 'react-dropzone';
const dropzoneRef = createRef();
const openDialog = () => {
// Note that the ref is set async,
// so it might be null at some point
if (dropzoneRef.current) {
dropzoneRef.current.open()
}
};
// Disable click and keydown behavior on the <Dropzone>
<Dropzone ref={dropzoneRef} noClick noKeyboard>
{({getRootProps, getInputProps, acceptedFiles}) => {
return (
<div className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here</p>
<button
type="button"
onClick={openDialog}
>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>
{acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
))}
</ul>
</aside>
</div>
);
}}
</Dropzone>
```

View File

@@ -0,0 +1,69 @@
React-dropzone does not submit the files in form submissions by default.
If you need this behavior, you can add a hidden file input, and set the files into it.
```jsx harmony
import React, {useRef} from 'react';
import {useDropzone} from 'react-dropzone';
function Dropzone(props) {
const {required, name} = props;
const hiddenInputRef = useRef(null);
const {getRootProps, getInputProps, open, acceptedFiles} = useDropzone({
onDrop: (incomingFiles) => {
if (hiddenInputRef.current) {
// Note the specific way we need to munge the file into the hidden input
// https://stackoverflow.com/a/68182158/1068446
const dataTransfer = new DataTransfer();
incomingFiles.forEach((v) => {
dataTransfer.items.add(v);
});
hiddenInputRef.current.files = dataTransfer.files;
}
}
});
const files = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<div className="container">
<div {...getRootProps({className: 'dropzone'})}>
{/*
Add a hidden file input
Best to use opacity 0, so that the required validation message will appear on form submission
*/}
<input type ="file" name={name} required={required} style ={{opacity: 0}} ref={hiddenInputRef}/>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here</p>
<button type="button" onClick={open}>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</div>
);
}
<form onSubmit={(e) => {
e.preventDefault();
// Now get the form data as you regularly would
const formData = new FormData(e.currentTarget);
const file = formData.get("my-file");
alert(file.name);
}}>
<Dropzone name ="my-file" required/>
<button type="submit">Submit</button>
</form>
```

View File

@@ -0,0 +1,58 @@
By providing `maxFiles` prop you can limit how many files the dropzone accepts.
**Note** that this prop is enabled when the `multiple` prop is enabled.
The default value for this prop is 0, which means there's no limitation to how many files are accepted.
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function AcceptMaxFiles(props) {
const {
acceptedFiles,
fileRejections,
getRootProps,
getInputProps
} = useDropzone({
maxFiles:2
});
const acceptedFileItems = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
const fileRejectionItems = fileRejections.map(({ file, errors }) => {
return (
<li key={file.path}>
{file.path} - {file.size} bytes
<ul>
{errors.map(e => <li key={e.code}>{e.message}</li>)}
</ul>
</li>
)
});
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
<em>(2 files are the maximum number of files you can drop here)</em>
</div>
<aside>
<h4>Accepted files</h4>
<ul>{acceptedFileItems}</ul>
<h4>Rejected files</h4>
<ul>{fileRejectionItems}</ul>
</aside>
</section>
);
}
<AcceptMaxFiles />
```

View File

@@ -0,0 +1,32 @@
If you'd like to use [react without JSX](https://reactjs.org/docs/react-without-jsx.html) you can:
```js harmony
import React, {useCallback, useState} from 'react';
import {useDropzone} from 'react-dropzone';
const e = React.createElement
function Basic () {
const [files, setFiles] = useState([]);
const onDrop = useCallback(files => setFiles(files), [setFiles]);
const {getRootProps, getInputProps} = useDropzone({onDrop});
const fileList = files.map(
file => React.createElement('li', {key: file.name}, `${file.name} - ${file.size} bytes`)
);
return e('section', {className: 'container'}, [
e('div', getRootProps({className: 'dropzone', key: 'dropzone'}), [
e('input', getInputProps({key: 'input'})),
e('p', {key: 'desc'}, "Drag 'n' drop some files here, or click to select files")
]),
e('aside', {key: 'filesContainer'}, [
e('h4', {key: 'title'}, 'Files'),
e('ul', {key: 'fileList'}, fileList)
])
]);
}
Basic()
```

View File

@@ -0,0 +1,146 @@
If you'd like to integrate the dropzone with the [Pintura](https://pqina.nl/pintura/?ref=react-dropzone) image editor, you just need to pass either of the selected images to the `openDefaultEditor()` method exported by Pintura:
```jsx static
import React, { useState, useEffect } from 'react';
// React Dropzone
import { useDropzone } from 'react-dropzone';
// Pintura Image Editor
import 'pintura/pintura.css';
import { openDefaultEditor } from 'pintura';
// Based on the default React Dropzone image thumbnail example
// The `thumbButton` style positions the edit button in the bottom right corner of the thumbnail
const thumbsContainer = {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 16,
padding: 20,
};
const thumb = {
position: 'relative',
display: 'inline-flex',
borderRadius: 2,
border: '1px solid #eaeaea',
marginBottom: 8,
marginRight: 8,
width: 100,
height: 100,
padding: 4,
boxSizing: 'border-box',
};
const thumbInner = {
display: 'flex',
minWidth: 0,
overflow: 'hidden',
};
const img = {
display: 'block',
width: 'auto',
height: '100%',
};
const thumbButton = {
position: 'absolute',
right: 10,
bottom: 10,
};
// This function is called when the user taps the edit button.
// It opens the editor and returns the modified file when done
const editImage = (image, done) => {
const imageFile = image.pintura ? image.pintura.file : image;
const imageState = image.pintura ? image.pintura.data : {};
const editor = openDefaultEditor({
src: imageFile,
imageState,
});
editor.on('close', () => {
// the user cancelled editing the image
});
editor.on('process', ({ dest, imageState }) => {
Object.assign(dest, {
pintura: { file: imageFile, data: imageState },
});
done(dest);
});
};
function App() {
const [files, setFiles] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': [],
},
onDrop: (acceptedFiles) => {
setFiles(
acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
)
);
},
});
const thumbs = files.map((file, index) => (
<div style={thumb} key={file.name}>
<div style={thumbInner}>
<img src={file.preview} style={img} alt="" />
</div>
<button
style={thumbButton}
onClick={() =>
editImage(file, (output) => {
const updatedFiles = [...files];
// replace original image with new image
updatedFiles[index] = output;
// revoke preview URL for old image
if (file.preview) URL.revokeObjectURL(file.preview);
// set new preview URL
Object.assign(output, {
preview: URL.createObjectURL(output),
});
// update view
setFiles(updatedFiles);
})
}
>
Edit
</button>
</div>
));
useEffect(
() => () => {
// Make sure to revoke the Object URL to avoid memory leaks
files.forEach((file) => URL.revokeObjectURL(file.preview));
},
[files]
);
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside style={thumbsContainer}>{thumbs}</aside>
</section>
);
}
export default App;
```

View File

@@ -0,0 +1,55 @@
The hook accepts a `getFilesFromEvent` prop that enhances the handling of dropped file system objects and allows more flexible use of them e.g. passing a function that accepts drop event of a folder and resolves it to an array of files adds plug-in functionality of folders drag-and-drop.
Though, note that the provided `getFilesFromEvent` fn must return a `Promise` with a list of `File` objects (or `DataTransferItem` of `{kind: 'file'}` when data cannot be accessed).
Otherwise, the results will be discarded and none of the drag events callbacks will be invoked.
In case you need to extend the `File` with some additional properties, you should use [Object.defineProperty()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) so that the result will still pass through our filter:
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
function Plugin(props) {
const {acceptedFiles, getRootProps, getInputProps} = useDropzone({
getFilesFromEvent: event => myCustomFileGetter(event)
});
const files = acceptedFiles.map(f => (
<li key={f.name}>
{f.name} has <strong>myProps</strong>: {f.myProp === true ? 'YES' : ''}
</li>
));
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</section>
);
}
async function myCustomFileGetter(event) {
const files = [];
const fileList = event.dataTransfer ? event.dataTransfer.files : event.target.files;
for (var i = 0; i < fileList.length; i++) {
const file = fileList.item(i);
Object.defineProperty(file, 'myProp', {
value: true
});
files.push(file);
}
return files;
}
<Plugin />
```

View File

@@ -0,0 +1,86 @@
Starting with version 7.0.0, the `{preview}` property generation on the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects and the `{disablePreview}` property on the `<Dropzone>` have been removed.
If you need the `{preview}`, it can be easily achieved in the `onDrop()` callback:
```jsx harmony
import React, {useEffect, useState} from 'react';
import {useDropzone} from 'react-dropzone';
const thumbsContainer = {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 16
};
const thumb = {
display: 'inline-flex',
borderRadius: 2,
border: '1px solid #eaeaea',
marginBottom: 8,
marginRight: 8,
width: 100,
height: 100,
padding: 4,
boxSizing: 'border-box'
};
const thumbInner = {
display: 'flex',
minWidth: 0,
overflow: 'hidden'
};
const img = {
display: 'block',
width: 'auto',
height: '100%'
};
function Previews(props) {
const [files, setFiles] = useState([]);
const {getRootProps, getInputProps} = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
setFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}
});
const thumbs = files.map(file => (
<div style={thumb} key={file.name}>
<div style={thumbInner}>
<img
src={file.preview}
style={img}
// Revoke data uri after image is loaded
onLoad={() => { URL.revokeObjectURL(file.preview) }}
/>
</div>
</div>
));
useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, [files]);
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside style={thumbsContainer}>
{thumbs}
</aside>
</section>
);
}
<Previews />
```

View File

@@ -0,0 +1,126 @@
The hook fn doesn't set any styles on either of the prop fns (`getRootProps()`/`getInputProps()`).
### Using inline styles
```jsx harmony
import React, {useMemo} from 'react';
import {useDropzone} from 'react-dropzone';
const baseStyle = {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '20px',
borderWidth: 2,
borderRadius: 2,
borderColor: '#eeeeee',
borderStyle: 'dashed',
backgroundColor: '#fafafa',
color: '#bdbdbd',
outline: 'none',
transition: 'border .24s ease-in-out'
};
const focusedStyle = {
borderColor: '#2196f3'
};
const acceptStyle = {
borderColor: '#00e676'
};
const rejectStyle = {
borderColor: '#ff1744'
};
function StyledDropzone(props) {
const {
getRootProps,
getInputProps,
isFocused,
isDragAccept,
isDragReject
} = useDropzone({accept: {'image/*': []}});
const style = useMemo(() => ({
...baseStyle,
...(isFocused ? focusedStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}), [
isFocused,
isDragAccept,
isDragReject
]);
return (
<div className="container">
<div {...getRootProps({style})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</div>
);
}
<StyledDropzone />
```
### Using styled-components
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
import styled from 'styled-components';
const getColor = (props) => {
if (props.isDragAccept) {
return '#00e676';
}
if (props.isDragReject) {
return '#ff1744';
}
if (props.isFocused) {
return '#2196f3';
}
return '#eeeeee';
}
const Container = styled.div`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border-width: 2px;
border-radius: 2px;
border-color: ${props => getColor(props)};
border-style: dashed;
background-color: #fafafa;
color: #bdbdbd;
outline: none;
transition: border .24s ease-in-out;
`;
function StyledDropzone(props) {
const {
getRootProps,
getInputProps,
isFocused,
isDragAccept,
isDragReject
} = useDropzone({accept: {'image/*': []}});
return (
<div className="container">
<Container {...getRootProps({isFocused, isDragAccept, isDragReject})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</Container>
</div>
);
}
<StyledDropzone />
```

View File

@@ -0,0 +1,37 @@
.container {
display: flex;
flex-direction: column;
font-family: sans-serif;
}
.container > p {
font-size: 1rem;
}
.container > em {
font-size: .8rem;
}
.dropzone {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border-width: 2px;
border-radius: 2px;
border-color: #eeeeee;
border-style: dashed;
background-color: #fafafa;
color: #bdbdbd;
outline: none;
transition: border .24s ease-in-out;
}
.dropzone:focus {
border-color: #2196f3;
}
.dropzone.disabled {
opacity: 0.6;
}

View File

@@ -0,0 +1,68 @@
By providing `validator` prop you can specify custom validation for files.
The value must be a function that accepts File object and returns null if file should be accepted or error object/array of error objects if file should be rejected.
```jsx harmony
import React from 'react';
import {useDropzone} from 'react-dropzone';
const maxLength = 20;
function nameLengthValidator(file) {
if (file.name.length > maxLength) {
return {
code: "name-too-large",
message: `Name is larger than ${maxLength} characters`
};
}
return null
}
function CustomValidation(props) {
const {
acceptedFiles,
fileRejections,
getRootProps,
getInputProps
} = useDropzone({
validator: nameLengthValidator
});
const acceptedFileItems = acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
const fileRejectionItems = fileRejections.map(({ file, errors }) => (
<li key={file.path}>
{file.path} - {file.size} bytes
<ul>
{errors.map(e => (
<li key={e.code}>{e.message}</li>
))}
</ul>
</li>
));
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
<em>(Only files with name less than 20 characters will be accepted)</em>
</div>
<aside>
<h4>Accepted files</h4>
<ul>{acceptedFileItems}</ul>
<h4>Rejected files</h4>
<ul>{fileRejectionItems}</ul>
</aside>
</section>
);
}
<CustomValidation />
```

205
frontend/node_modules/react-dropzone/package.json generated vendored Normal file
View File

@@ -0,0 +1,205 @@
{
"name": "react-dropzone",
"description": "Simple HTML5 drag-drop zone with React.js",
"main": "dist/index.js",
"module": "dist/es/index.js",
"exports": {
".": {
"import": {
"types": "./typings/react-dropzone.d.ts",
"default": "./dist/es/index.js"
},
"require": {
"types": "./typings/react-dropzone.d.ts",
"default": "./dist/index.js"
}
}
},
"typesVersions": {
"*": {
".": [
"./typings/react-dropzone.d.ts"
]
}
},
"sideEffects": false,
"scripts": {
"cz": "git-cz",
"clean": "rimraf ./dist",
"build": "yarn clean && yarn build:umd && yarn build:es && yarn build:es-package",
"build:umd": "cross-env NODE_ENV=es rollup -c",
"build:es": "cross-env BABEL_ENV=es babel ./src --out-dir ./dist/es --ignore '**/*.spec.js'",
"build:es-package": "echo '{\"type\":\"module\"}' > dist/es/package.json",
"start": "styleguidist server",
"styleguide": "styleguidist build",
"test": "cross-env NODE_ENV=test yarn lint && jest --coverage && yarn typescript",
"test:watch": "cross-env NODE_ENV=test jest --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"commitmsg": "commitlint -e",
"prepublish": "yarn build && yarn size",
"_postinstall": "husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable",
"logo": "cd logo && sketchtool export artboards logo.sketch",
"imagemin": "imagemin --out-dir=logo --plugin=pngquant --plugin=svgo",
"size": "size-limit",
"size:why": "size-limit --why",
"typescript": "tsc --project ./typings/tests"
},
"size-limit": [
{
"path": "dist/index.js",
"limit": "17 KB"
},
{
"path": "dist/es/index.js",
"limit": "17 KB"
}
],
"lint-staged": {
"*.js": [
"eslint . --fix"
],
"*.ts": [
"eslint ."
],
"*.{svg,png}": [
"imagemin"
]
},
"config": {
"commitizen": {
"path": "@commitlint/prompt"
}
},
"jest": {
"testEnvironment": "jsdom",
"clearMocks": true,
"setupFilesAfterEnv": [
"<rootDir>/testSetup.js"
],
"coveragePathIgnorePatterns": [
"/dist/",
"/node_modules/",
"<rootDir>/testSetup.js"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/dist/"
]
},
"keywords": [
"react-component",
"react",
"drag",
"drop",
"upload"
],
"repository": {
"type": "git",
"url": "https://github.com/react-dropzone/react-dropzone.git"
},
"bugs": {
"url": "https://github.com/react-dropzone/react-dropzone/issues"
},
"homepage": "https://github.com/react-dropzone/react-dropzone",
"author": "Param Aggarwal",
"contributors": [
"Andrey Okonetchnikov <andrey@okonet.ru> (http://okonet.ru)",
"Mike Olson <me@mwolson.org>",
"Param Aggarwal",
"Tyler Waters <tyler.waters@gmail.com>",
"Rick Markins <rmarkins@gmail.com>",
"Roland Groza <rolandjitsu@gmail.com>"
],
"license": "MIT",
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
},
"dependencies": {
"attr-accept": "^2.2.4",
"file-selector": "^2.1.0",
"prop-types": "^15.8.1"
},
"devDependencies": {
"@babel/cli": "^7.16.8",
"@babel/core": "^7.16.12",
"@babel/eslint-parser": "^7.16.5",
"@babel/plugin-external-helpers": "^7.16.7",
"@babel/plugin-proposal-do-expressions": "^7.16.7",
"@babel/plugin-proposal-export-default-from": "^7.16.7",
"@babel/plugin-proposal-logical-assignment-operators": "^7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@babel/plugin-proposal-pipeline-operator": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.10",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/register": "^7.16.9",
"@commitlint/cli": "^16.1.0",
"@commitlint/config-angular": "^16.0.0",
"@commitlint/prompt": "^16.1.0",
"@commitlint/prompt-cli": "^16.1.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.1.3",
"@size-limit/preset-small-lib": "^7.0.5",
"@size-limit/webpack": "^7.0.5",
"@size-limit/webpack-why": "^7.0.5",
"@testing-library/dom": "^8.11.3",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"babel-jest": "^27.4.6",
"babel-plugin-add-module-exports": "^1.0.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
"commitizen": "^4.2.4",
"cross-env": "^7.0.3",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"husky": "^7.0.4",
"imagemin-cli": "^8.0.0",
"imagemin-pngquant": "^10.0.0",
"jest": "^27.4.7",
"lint-staged": "^12.3.2",
"markdownlint-cli": "^0.30.0",
"pinst": "^2.1.6",
"prettier": "^2.5.1",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-styleguidist": "^11.2.0",
"react-test-renderer": "^18.1.0",
"rimraf": "^3.0.2",
"rollup": "^2.66.1",
"rollup-plugin-terser": "^7.0.2",
"size-limit": "^7.0.5",
"style-loader": "^3.3.1",
"styled-components": "^5.3.3",
"typescript": "^5.6.3",
"webpack": "^5.67.0",
"webpack-blocks": "^2.1.0"
},
"typings": "typings/react-dropzone.d.ts",
"version": "14.3.8",
"engines": {
"node": ">= 10.13"
},
"browserslist": [
"defaults"
],
"packageManager": "yarn@1.22.22",
"resolutions": {
"node-sass": "^9.0.0"
}
}

33
frontend/node_modules/react-dropzone/rollup.config.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
const { nodeResolve } = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
const { babel } = require("@rollup/plugin-babel");
const { terser } = require("rollup-plugin-terser");
const umdGlobals = {
react: "React",
"prop-types": "PropTypes",
};
module.exports = [
{
input: "./src/index.js",
output: {
file: "dist/index.js",
format: "umd",
name: "reactDropzone",
globals: umdGlobals,
sourcemap: "inline",
exports: "named",
},
external: Object.keys(umdGlobals),
plugins: [
nodeResolve(),
commonjs({ include: "**/node_modules/**" }),
babel({
exclude: "**/node_modules/**",
babelHelpers: "bundled",
}),
terser(),
],
},
];

37
frontend/node_modules/react-dropzone/src/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,37 @@
{
"root": true,
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jest": true
},
"plugins": [
"import",
"jsx-a11y",
"prettier",
"react",
"react-hooks"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended"
],
"rules": {
"react-hooks/rules-of-hooks": 2,
"react/forbid-prop-types": 0,
"react/require-default-props": 0
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`useDropzone() hook behavior renders the root and input nodes with the necessary props 1`] = `"<div role=\\"presentation\\" tabindex=\\"0\\"><input multiple=\\"\\" type=\\"file\\" style=\\"border: 0px; clip: rect(0px, 0px, 0px, 0px); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;\\" tabindex=\\"-1\\"></div>"`;

1042
frontend/node_modules/react-dropzone/src/index.js generated vendored Executable file

File diff suppressed because it is too large Load Diff

3640
frontend/node_modules/react-dropzone/src/index.spec.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

336
frontend/node_modules/react-dropzone/src/utils/index.js generated vendored Normal file
View File

@@ -0,0 +1,336 @@
import _accepts from "attr-accept";
const accepts = typeof _accepts === "function" ? _accepts : _accepts.default;
// Error codes
export const FILE_INVALID_TYPE = "file-invalid-type";
export const FILE_TOO_LARGE = "file-too-large";
export const FILE_TOO_SMALL = "file-too-small";
export const TOO_MANY_FILES = "too-many-files";
export const ErrorCode = {
FileInvalidType: FILE_INVALID_TYPE,
FileTooLarge: FILE_TOO_LARGE,
FileTooSmall: FILE_TOO_SMALL,
TooManyFiles: TOO_MANY_FILES,
};
/**
*
* @param {string} accept
*/
export const getInvalidTypeRejectionErr = (accept = "") => {
const acceptArr = accept.split(",");
const msg =
acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
return {
code: FILE_INVALID_TYPE,
message: `File type must be ${msg}`,
};
};
export const getTooLargeRejectionErr = (maxSize) => {
return {
code: FILE_TOO_LARGE,
message: `File is larger than ${maxSize} ${
maxSize === 1 ? "byte" : "bytes"
}`,
};
};
export const getTooSmallRejectionErr = (minSize) => {
return {
code: FILE_TOO_SMALL,
message: `File is smaller than ${minSize} ${
minSize === 1 ? "byte" : "bytes"
}`,
};
};
export const TOO_MANY_FILES_REJECTION = {
code: TOO_MANY_FILES,
message: "Too many files",
};
/**
* Check if file is accepted.
*
* Firefox versions prior to 53 return a bogus MIME type for every file drag,
* so dragovers with that MIME type will always be accepted.
*
* @param {File} file
* @param {string} accept
* @returns
*/
export function fileAccepted(file, accept) {
const isAcceptable =
file.type === "application/x-moz-file" || accepts(file, accept);
return [
isAcceptable,
isAcceptable ? null : getInvalidTypeRejectionErr(accept),
];
}
export function fileMatchSize(file, minSize, maxSize) {
if (isDefined(file.size)) {
if (isDefined(minSize) && isDefined(maxSize)) {
if (file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
if (file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
} else if (isDefined(minSize) && file.size < minSize)
return [false, getTooSmallRejectionErr(minSize)];
else if (isDefined(maxSize) && file.size > maxSize)
return [false, getTooLargeRejectionErr(maxSize)];
}
return [true, null];
}
function isDefined(value) {
return value !== undefined && value !== null;
}
/**
*
* @param {object} options
* @param {File[]} options.files
* @param {string} [options.accept]
* @param {number} [options.minSize]
* @param {number} [options.maxSize]
* @param {boolean} [options.multiple]
* @param {number} [options.maxFiles]
* @param {(f: File) => FileError|FileError[]|null} [options.validator]
* @returns
*/
export function allFilesAccepted({
files,
accept,
minSize,
maxSize,
multiple,
maxFiles,
validator,
}) {
if (
(!multiple && files.length > 1) ||
(multiple && maxFiles >= 1 && files.length > maxFiles)
) {
return false;
}
return files.every((file) => {
const [accepted] = fileAccepted(file, accept);
const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
const customErrors = validator ? validator(file) : null;
return accepted && sizeMatch && !customErrors;
});
}
// React's synthetic events has event.isPropagationStopped,
// but to remain compatibility with other libs (Preact) fall back
// to check event.cancelBubble
export function isPropagationStopped(event) {
if (typeof event.isPropagationStopped === "function") {
return event.isPropagationStopped();
} else if (typeof event.cancelBubble !== "undefined") {
return event.cancelBubble;
}
return false;
}
export function isEvtWithFiles(event) {
if (!event.dataTransfer) {
return !!event.target && !!event.target.files;
}
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
return Array.prototype.some.call(
event.dataTransfer.types,
(type) => type === "Files" || type === "application/x-moz-file"
);
}
export function isKindFile(item) {
return typeof item === "object" && item !== null && item.kind === "file";
}
// allow the entire document to be a drag target
export function onDocumentDragOver(event) {
event.preventDefault();
}
function isIe(userAgent) {
return (
userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1
);
}
function isEdge(userAgent) {
return userAgent.indexOf("Edge/") !== -1;
}
export function isIeOrEdge(userAgent = window.navigator.userAgent) {
return isIe(userAgent) || isEdge(userAgent);
}
/**
* This is intended to be used to compose event handlers
* They are executed in order until one of them calls `event.isPropagationStopped()`.
* Note that the check is done on the first invoke too,
* meaning that if propagation was stopped before invoking the fns,
* no handlers will be executed.
*
* @param {Function} fns the event hanlder functions
* @return {Function} the event handler to add to an element
*/
export function composeEventHandlers(...fns) {
return (event, ...args) =>
fns.some((fn) => {
if (!isPropagationStopped(event) && fn) {
fn(event, ...args);
}
return isPropagationStopped(event);
});
}
/**
* canUseFileSystemAccessAPI checks if the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
* is supported by the browser.
* @returns {boolean}
*/
export function canUseFileSystemAccessAPI() {
return "showOpenFilePicker" in window;
}
/**
* Convert the `{accept}` dropzone prop to the
* `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
*
* @param {AcceptProp} accept
* @returns {{accept: string[]}[]}
*/
export function pickerOptionsFromAccept(accept) {
if (isDefined(accept)) {
const acceptForPicker = Object.entries(accept)
.filter(([mimeType, ext]) => {
let ok = true;
if (!isMIMEType(mimeType)) {
console.warn(
`Skipped "${mimeType}" because it is not a valid MIME type. Check https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types for a list of valid MIME types.`
);
ok = false;
}
if (!Array.isArray(ext) || !ext.every(isExt)) {
console.warn(
`Skipped "${mimeType}" because an invalid file extension was provided.`
);
ok = false;
}
return ok;
})
.reduce(
(agg, [mimeType, ext]) => ({
...agg,
[mimeType]: ext,
}),
{}
);
return [
{
// description is required due to https://crbug.com/1264708
description: "Files",
accept: acceptForPicker,
},
];
}
return accept;
}
/**
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
* @param {AcceptProp} accept
* @returns {string}
*/
export function acceptPropAsAcceptAttr(accept) {
if (isDefined(accept)) {
return (
Object.entries(accept)
.reduce((a, [mimeType, ext]) => [...a, mimeType, ...ext], [])
// Silently discard invalid entries as pickerOptionsFromAccept warns about these
.filter((v) => isMIMEType(v) || isExt(v))
.join(",")
);
}
return undefined;
}
/**
* Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
*
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
* @param {any} v
* @returns {boolean} True if v is an abort exception.
*/
export function isAbort(v) {
return (
v instanceof DOMException &&
(v.name === "AbortError" || v.code === v.ABORT_ERR)
);
}
/**
* Check if v is a security error.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
* @param {any} v
* @returns {boolean} True if v is a security error.
*/
export function isSecurityError(v) {
return (
v instanceof DOMException &&
(v.name === "SecurityError" || v.code === v.SECURITY_ERR)
);
}
/**
* Check if v is a MIME type string.
*
* See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
*
* @param {string} v
*/
export function isMIMEType(v) {
return (
v === "audio/*" ||
v === "video/*" ||
v === "image/*" ||
v === "text/*" ||
v === "application/*" ||
/\w+\/[-+.\w]+/g.test(v)
);
}
/**
* Check if v is a file extension.
* @param {string} v
*/
export function isExt(v) {
return /^.*\.[\w]+$/.test(v);
}
/**
* @typedef {Object.<string, string[]>} AcceptProp
*/
/**
* @typedef {object} FileError
* @property {string} message
* @property {ErrorCode|string} code
*/
/**
* @typedef {"file-invalid-type"|"file-too-large"|"file-too-small"|"too-many-files"} ErrorCode
*/

View File

@@ -0,0 +1,600 @@
beforeEach(() => {
jest.resetModules();
});
describe("fileMatchSize()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true if the file object doesn't have a {size} property", () => {
expect(utils.fileMatchSize({})).toEqual([true, null]);
expect(utils.fileMatchSize({ size: null })).toEqual([true, null]);
});
it("should return true if the minSize and maxSize were not provided", () => {
expect(utils.fileMatchSize({ size: 100 })).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, undefined, undefined)).toEqual([
true,
null,
]);
expect(utils.fileMatchSize({ size: 100 }, null, null)).toEqual([
true,
null,
]);
});
it("should return true if the file {size} is within the [minSize, maxSize] range", () => {
expect(utils.fileMatchSize({ size: 100 }, 10, 200)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, 10, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
expect(utils.fileMatchSize({ size: 100 }, 101, 200)).toEqual([
false,
{ code: "file-too-small", message: "File is smaller than 101 bytes" },
]);
});
it("should return true if the file {size} is more than minSize", () => {
expect(utils.fileMatchSize({ size: 100 }, 100)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, 101)).toEqual([
false,
{ code: "file-too-small", message: "File is smaller than 101 bytes" },
]);
});
it("should return true if the file {size} is less than maxSize", () => {
expect(utils.fileMatchSize({ size: 100 }, undefined, 100)).toEqual([
true,
null,
]);
expect(utils.fileMatchSize({ size: 100 }, null, 100)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, undefined, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
expect(utils.fileMatchSize({ size: 100 }, null, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
});
});
describe("isIeOrEdge", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true for IE10", () => {
const userAgent =
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return true for IE11", () => {
const userAgent =
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return true for Edge", () => {
const userAgent =
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16258";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return false for Chrome", () => {
const userAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36";
expect(utils.isIeOrEdge(userAgent)).toBe(false);
});
});
describe("isKindFile()", () => {
it('should return true for DataTransferItem of kind "file"', async () => {
/**
* @constant
* @type {import('./index')}
*/
const utils = await import("./index");
expect(utils.isKindFile({ kind: "file" })).toBe(true);
expect(utils.isKindFile({ kind: "text/html" })).toBe(false);
expect(utils.isKindFile({})).toBe(false);
expect(utils.isKindFile(null)).toBe(false);
});
});
describe("isPropagationStopped()", () => {
const trueFn = jest.fn(() => true);
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return result of isPropagationStopped() if isPropagationStopped exists", () => {
expect(utils.isPropagationStopped({ isPropagationStopped: trueFn })).toBe(
true
);
});
it("should return value of cancelBubble if isPropagationStopped doesnt exist and cancelBubble exists", () => {
expect(utils.isPropagationStopped({ cancelBubble: true })).toBe(true);
});
it("should return false if isPropagationStopped and cancelBubble are missing", () => {
expect(utils.isPropagationStopped({})).toBe(false);
});
});
describe("isEvtWithFiles()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true if some dragged types are files", () => {
expect(utils.isEvtWithFiles({ dataTransfer: { types: ["Files"] } })).toBe(
true
);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["application/x-moz-file"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["Files", "application/x-moz-file"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({ dataTransfer: { types: ["text/plain"] } })
).toBe(false);
expect(
utils.isEvtWithFiles({ dataTransfer: { types: ["text/html"] } })
).toBe(false);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["Files", "application/test"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["application/x-moz-file", "application/test"] },
})
).toBe(true);
});
it("should return true if the event has a target with files", () => {
expect(utils.isEvtWithFiles({ target: { files: [] } })).toBe(true);
});
it("should return false otherwise", () => {
expect(utils.isEvtWithFiles({})).toBe(false);
});
});
describe("composeEventHandlers()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("returns a fn", () => {
const fn = utils.composeEventHandlers(() => {});
expect(typeof fn).toBe("function");
});
it("runs every passed fn in order", () => {
const fn1 = jest.fn();
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = { type: "click" };
const data = { ping: true };
fn(event, data);
expect(fn1).toHaveBeenCalledWith(event, data);
expect(fn2).toHaveBeenCalledWith(event, data);
});
it("stops after first fn that calls stopPropagation()", () => {
const fn1 = jest.fn().mockImplementation((event) => {
Object.defineProperty(event, "cancelBubble", { value: true });
return event;
});
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = new MouseEvent("click");
fn(event);
expect(fn1).toHaveBeenCalledWith(event);
expect(fn2).not.toHaveBeenCalled();
});
it("stops before first fn if bubble is already canceled", () => {
const fn1 = jest.fn();
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = new MouseEvent("click");
Object.defineProperty(event, "cancelBubble", { value: true });
fn(event);
expect(fn1).not.toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
});
});
describe("fileAccepted()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("accepts bogus firefox file", () => {
const file = createFile("bogus.png", 100, "application/x-moz-file");
expect(utils.fileAccepted(file, ".pdf")).toEqual([true, null]);
});
it("accepts file when single accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".pdf")).toEqual([true, null]);
});
it("accepts file when multiple accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".pdf,.png")).toEqual([true, null]);
});
it("rejects file when single accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".png")).toEqual([
false,
{ code: "file-invalid-type", message: "File type must be .png" },
]);
});
it("rejects file when multiple accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".gif,.png")).toEqual([
false,
{
code: "file-invalid-type",
message: "File type must be one of .gif, .png",
},
]);
});
});
describe("getTooLargeRejectionErr()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("prints byte when maxSize is 1", () => {
expect(utils.getTooLargeRejectionErr(1).message).toEqual(
"File is larger than 1 byte"
);
});
it("prints bytes when maxSize > 1", () => {
expect(utils.getTooLargeRejectionErr(100).message).toEqual(
"File is larger than 100 bytes"
);
});
});
describe("getTooSmallRejectionErr()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("prints byte when minSize is 1", () => {
expect(utils.getTooSmallRejectionErr(1).message).toEqual(
"File is smaller than 1 byte"
);
});
it("prints bytes when minSize > 1", () => {
expect(utils.getTooSmallRejectionErr(100).message).toEqual(
"File is smaller than 100 bytes"
);
});
});
describe("allFilesAccepted()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("rejects file when multiple accept criteria", () => {
const files = [
createFile("hamster.pdf", 100, "application/pdf"),
createFile("fish.pdf", 100, "application/pdf"),
];
const images = [
createFile("cats.gif", 1234, "image/gif"),
createFile("dogs.gif", 2345, "image/jpeg"),
];
expect(utils.allFilesAccepted({ files, multiple: true })).toEqual(true);
expect(
utils.allFilesAccepted({ files, multiple: true, maxFiles: 10 })
).toEqual(true);
expect(
utils.allFilesAccepted({ files, multiple: false, maxFiles: 10 })
).toEqual(false);
expect(
utils.allFilesAccepted({ files, multiple: true, accept: "image/jpeg" })
).toEqual(false);
expect(
utils.allFilesAccepted({
files: images,
multiple: true,
accept: "image/*",
})
).toEqual(true);
expect(
utils.allFilesAccepted({ files, multiple: true, minSize: 110 })
).toEqual(false);
expect(
utils.allFilesAccepted({ files, multiple: true, maxSize: 99 })
).toEqual(false);
expect(
utils.allFilesAccepted({ files, multiple: true, maxFiles: 1 })
).toEqual(false);
expect(
utils.allFilesAccepted({
files,
validator: () => ({ code: "not-allowed", message: "Cannot do this!" }),
})
).toEqual(false);
});
});
describe("ErrorCode", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should exist and have known error code properties", () => {
expect(utils.ErrorCode.FileInvalidType).toEqual(utils.FILE_INVALID_TYPE);
expect(utils.ErrorCode.FileTooLarge).toEqual(utils.FILE_TOO_LARGE);
expect(utils.ErrorCode.FileTooSmall).toEqual(utils.FILE_TOO_SMALL);
expect(utils.ErrorCode.TooManyFiles).toEqual(utils.TOO_MANY_FILES);
});
});
describe("canUseFileSystemAccessAPI()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return false if not", () => {
expect(utils.canUseFileSystemAccessAPI()).toBe(false);
});
it("should return true if yes", () => {
// TODO: If we use these in other tests, restore once test is done
window.showOpenFilePicker = jest.fn();
expect(utils.canUseFileSystemAccessAPI()).toBe(true);
});
});
describe("pickerOptionsFromAccept()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("converts the {accept} prop to file picker options", () => {
expect(
utils.pickerOptionsFromAccept({
"image/*": [".png", ".jpg"], // ok
"text/*": [".txt", ".pdf"], // ok
"audio/*": ["mp3"], // not ok
"*": [".p12"], // not ok
})
).toEqual([
{
description: "Files",
accept: {
"image/*": [".png", ".jpg"],
"text/*": [".txt", ".pdf"],
},
},
]);
});
});
describe("acceptPropAsAcceptAttr()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("converts {accept} to an array of strings", () => {
expect(
utils.acceptPropAsAcceptAttr({
"image/*": [".png", ".jpg"],
"text/*": [".txt", ".pdf"],
"audio/*": ["mp3"], // `mp3` not ok
"*": [".p12"], // `*` not ok
})
).toEqual("image/*,.png,.jpg,text/*,.txt,.pdf,audio/*,.p12");
});
});
describe("isMIMEType()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("checks that the value is a valid MIME type string", () => {
expect(utils.isMIMEType("text/html")).toBe(true);
expect(utils.isMIMEType("text/*")).toBe(true);
expect(utils.isMIMEType("image/*")).toBe(true);
expect(utils.isMIMEType("video/*")).toBe(true);
expect(utils.isMIMEType("audio/*")).toBe(true);
expect(utils.isMIMEType("application/*")).toBe(true);
expect(utils.isMIMEType("test/*")).toBe(false);
expect(utils.isMIMEType("text")).toBe(false);
expect(utils.isMIMEType("")).toBe(false);
expect(utils.isMIMEType(undefined)).toBe(false);
});
});
describe("isExt()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("checks that the value is a valid file extension", () => {
expect(utils.isExt(".jpg")).toBe(true);
expect(utils.isExt("me.jpg")).toBe(true);
expect(utils.isExt("me.prev.png")).toBe(true);
expect(utils.isExt("")).toBe(false);
expect(utils.isExt("text")).toBe(false);
expect(utils.isExt(undefined)).toBe(false);
});
});
describe("isAbort()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should work as expected", () => {
expect(utils.isAbort(new DOMException())).toBe(false);
expect(utils.isAbort(new DOMException("some err"))).toBe(false);
expect(utils.isAbort(new DOMException("some err", "Noop"))).toBe(false);
expect(utils.isAbort(new DOMException("some err", "AbortError"))).toBe(
true
);
const err = new DOMException("some err");
const e = new Proxy(err, {
get(t, p) {
if (p === "code") {
return 20;
}
return t[p];
},
});
expect(utils.isAbort(e)).toBe(true);
});
});
describe("isSecurityError()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should work as expected", () => {
expect(utils.isSecurityError(new DOMException())).toBe(false);
expect(utils.isSecurityError(new DOMException("some err"))).toBe(false);
expect(utils.isSecurityError(new DOMException("some err", "Noop"))).toBe(
false
);
expect(
utils.isSecurityError(new DOMException("some err", "SecurityError"))
).toBe(true);
const err = new DOMException("some err");
const e = new Proxy(err, {
get(t, p) {
if (p === "code") {
return 18;
}
return t[p];
},
});
expect(utils.isSecurityError(e)).toBe(true);
});
});
function createFile(name, size, type) {
const file = new File([], name, { type });
Object.defineProperty(file, "size", {
get() {
return size;
},
});
return file;
}

View File

@@ -0,0 +1,103 @@
/* eslint import/no-extraneous-dependencies: 0 */
const path = require("path");
const { createConfig, babel, css, devServer } = require("webpack-blocks");
// https://react-styleguidist.js.org/docs/configuration.html
module.exports = {
title: "react-dropzone",
styleguideDir: path.join(__dirname, "styleguide"),
template: {
favicon:
"https://github.com/react-dropzone/react-dropzone/raw/master/logo/logo.png",
},
webpackConfig: createConfig([
babel(),
css(),
devServer({
disableHostCheck: true,
host: "0.0.0.0",
}),
]),
exampleMode: "expand",
usageMode: "expand",
showSidebar: true,
serverPort: 8080,
moduleAliases: {
"react-dropzone": path.resolve(__dirname, "./src"),
},
require: [path.join(__dirname, "examples/theme.css")],
sections: [
{
name: "",
content: "README.md",
},
// TODO: Figure out how to document the hook
// See https://github.com/reactjs/react-docgen/issues/332
{
name: "Components",
components: "./src/index.js",
},
{
name: "Examples",
sections: [
{
name: "Basic example",
content: "examples/basic/README.md",
},
{
name: "Event Propagation",
content: "examples/events/README.md",
},
{
name: "Forms",
content: "examples/forms/README.md",
},
{
name: "Styling Dropzone",
content: "examples/styling/README.md",
},
{
name: "Accepting specific file types",
content: "examples/accept/README.md",
},
{
name: "Accepting specific number of files",
content: "examples/maxFiles/README.md",
},
{
name: "Custom validation",
content: "examples/validator/README.md",
},
{
name: "Opening File Dialog Programmatically",
content: "examples/file-dialog/README.md",
},
{
name: "Previews",
content: "examples/previews/README.md",
},
{
name: "Class Components",
content: "examples/class-component/README.md",
},
{
name: "No JSX",
content: "examples/no-jsx/README.md",
},
{
name: "Extending Dropzone",
content: "examples/plugins/README.md",
},
],
},
{
name: "Integrations",
sections: [
{
name: "Pintura",
content: "examples/pintura/README.md",
},
],
},
],
};

8
frontend/node_modules/react-dropzone/testSetup.js generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// https://www.npmjs.com/package/jest-dom
require("@testing-library/jest-dom/extend-expect");
Object.defineProperty(global, "isSecureContext", {
value: true,
writable: true,
enumerable: true,
});

28
frontend/node_modules/react-dropzone/typings/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"es6": true,
"browser": true
},
"plugins": [
"import",
"prettier",
"react",
"react-hooks",
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}

View File

@@ -0,0 +1,101 @@
import * as React from "react";
import { FileWithPath } from "file-selector";
export { FileWithPath };
export default function Dropzone(
props: DropzoneProps & React.RefAttributes<DropzoneRef>
): React.ReactElement;
export function useDropzone(options?: DropzoneOptions): DropzoneState;
export interface DropzoneProps extends DropzoneOptions {
children?(state: DropzoneState): React.ReactElement;
}
export enum ErrorCode {
FileInvalidType = "file-invalid-type",
FileTooLarge = "file-too-large",
FileTooSmall = "file-too-small",
TooManyFiles = "too-many-files",
}
export interface FileError {
message: string;
code: ErrorCode | string;
}
export interface FileRejection {
file: FileWithPath;
errors: readonly FileError[];
}
export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
accept?: Accept;
minSize?: number;
maxSize?: number;
maxFiles?: number;
preventDropOnDocument?: boolean;
noClick?: boolean;
noKeyboard?: boolean;
noDrag?: boolean;
noDragEventsBubbling?: boolean;
disabled?: boolean;
onDrop?: <T extends File>(
acceptedFiles: T[],
fileRejections: FileRejection[],
event: DropEvent
) => void;
onDropAccepted?: <T extends File>(files: T[], event: DropEvent) => void;
onDropRejected?: (fileRejections: FileRejection[], event: DropEvent) => void;
getFilesFromEvent?: (
event: DropEvent
) => Promise<Array<File | DataTransferItem>>;
onFileDialogCancel?: () => void;
onFileDialogOpen?: () => void;
onError?: (err: Error) => void;
validator?: <T extends File>(
file: T
) => FileError | readonly FileError[] | null;
useFsAccessApi?: boolean;
autoFocus?: boolean;
};
export type DropEvent =
| React.DragEvent<HTMLElement>
| React.ChangeEvent<HTMLInputElement>
| DragEvent
| Event
| Array<FileSystemFileHandle>;
export type DropzoneState = DropzoneRef & {
isFocused: boolean;
isDragActive: boolean;
isDragAccept: boolean;
isDragReject: boolean;
isFileDialogActive: boolean;
acceptedFiles: readonly FileWithPath[];
fileRejections: readonly FileRejection[];
rootRef: React.RefObject<HTMLElement>;
inputRef: React.RefObject<HTMLInputElement>;
getRootProps: <T extends DropzoneRootProps>(props?: T) => T;
getInputProps: <T extends DropzoneInputProps>(props?: T) => T;
};
export interface DropzoneRef {
open: () => void;
}
export interface DropzoneRootProps extends React.HTMLAttributes<HTMLElement> {
refKey?: string;
[key: string]: any;
}
export interface DropzoneInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
refKey?: string;
}
type PropTypes = "multiple" | "onDragEnter" | "onDragOver" | "onDragLeave";
export interface Accept {
[key: string]: readonly string[];
}

View File

@@ -0,0 +1,54 @@
import React from "react";
import Dropzone from "../../";
export default class Accept extends React.Component {
state = {
accepted: [],
rejected: [],
};
render() {
return (
<section>
<div className="dropzone">
<Dropzone
accept={{
"image/*": [".jpeg", ".png"],
}}
onDrop={(accepted, rejected) => {
this.setState({ accepted, rejected });
}}
>
{({ getRootProps }) => (
<div {...getRootProps()}>
<p>
Try dropping some files here, or click to select files to
upload.
</p>
<p>Only *.jpeg and *.png images will be accepted</p>
</div>
)}
</Dropzone>
</div>
<aside>
<h2>Accepted files</h2>
<ul>
{this.state.accepted.map((f) => (
<li key={f.name}>
{f.name} - {f.size} bytes
</li>
))}
</ul>
<h2>Rejected files</h2>
<ul>
{this.state.rejected.map((f) => (
<li key={f.name}>
{f.name} - {f.size} bytes
</li>
))}
</ul>
</aside>
</section>
);
}
}

View File

@@ -0,0 +1,46 @@
import React from "react";
import Dropzone from "../../";
export default class Test extends React.Component {
render() {
return (
<div>
<Dropzone
onDrop={(acceptedFiles, fileRejections, event) =>
console.log(acceptedFiles, fileRejections, event)
}
onDragEnter={(event) => console.log(event)}
onDragOver={(event) => console.log(event)}
onDragLeave={(event) => console.log(event)}
onDropAccepted={(files, event) => console.log(files, event)}
onDropRejected={(files, event) => console.log(files, event)}
onFileDialogCancel={() => console.log("onFileDialogCancel invoked")}
onFileDialogOpen={() => console.log("onFileDialogOpen invoked")}
onError={(e) => console.log(e)}
validator={(f) => ({ message: f.name, code: "" })}
minSize={2000}
maxSize={Infinity}
maxFiles={100}
preventDropOnDocument
noClick={false}
noKeyboard={false}
noDrag={false}
noDragEventsBubbling={false}
disabled
multiple={false}
accept={{
"image/*": [".png"],
}}
useFsAccessApi={false}
autoFocus
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
</div>
);
}
}

View File

@@ -0,0 +1,53 @@
import React from "react";
import Dropzone from "../../";
import { FileWithPath } from "file-selector";
export default class Basic extends React.Component {
state = { files: [] };
onDrop = (files: FileWithPath[]) => {
this.setState({
files,
});
};
render() {
return (
<section>
<div className="dropzone">
<Dropzone onDrop={this.onDrop}>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>
Try dropping some files here, or click to select files to
upload.
</p>
</div>
)}
</Dropzone>
</div>
<aside>
<h2>Dropped files</h2>
<ul>
{this.state.files.map((f) => (
<li key={f.name}>
{f.name} - {f.size} bytes
</li>
))}
</ul>
</aside>
</section>
);
}
}
export const optional = (
<Dropzone>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
);

View File

@@ -0,0 +1,31 @@
import React from "react";
import Dropzone from "../../";
export class Events extends React.Component {
render() {
return (
<section>
<div className="dropzone">
<Dropzone
onDrop={(acceptedFiles, fileRejections, event) =>
console.log(acceptedFiles, fileRejections, event)
}
onDragEnter={(event) => console.log(event)}
onDragOver={(event) => console.log(event)}
onDragLeave={(event) => console.log(event)}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>
Try dropping some files here, or click to select files to
upload.
</p>
</div>
)}
</Dropzone>
</div>
</section>
);
}
}

View File

@@ -0,0 +1,20 @@
import React from "react";
import Dropzone from "../../";
export const dropzone = (
<Dropzone
onDrop={(files) => console.log(files)}
onFileDialogCancel={() => console.log("onFileDialogCancel invoked")}
onFileDialogOpen={() => console.log("onFileDialogOpen invoked")}
>
{({ getRootProps, getInputProps, open }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drop some files here.</p>
<button type="button" onClick={open}>
Open file dialog
</button>
</div>
)}
</Dropzone>
);

View File

@@ -0,0 +1,15 @@
import React from "react";
import { useDropzone, DropzoneProps } from "../../";
export const Dropzone = ({ children, ...opts }: DropzoneProps) => {
const { ...state } = useDropzone(opts);
return children(state);
};
<Dropzone>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>;

View File

@@ -0,0 +1,87 @@
import React, { Component } from "react";
import Dropzone from "../../";
export class TestReactDragEvt extends Component {
getFiles = async (event: React.DragEvent<HTMLDivElement>) => {
const files = Array.from(event.dataTransfer.files);
return files;
};
render() {
return (
<div>
<Dropzone getFilesFromEvent={this.getFiles}>
{({ getRootProps }) => <div {...getRootProps()} />}
</Dropzone>
</div>
);
}
}
export class TestDataTransferItems extends Component {
getFiles = async (event: React.DragEvent<HTMLDivElement>) => {
const items = Array.from(event.dataTransfer.items);
return items;
};
render() {
return (
<div>
<Dropzone getFilesFromEvent={this.getFiles}>
{({ getRootProps }) => <div {...getRootProps()} />}
</Dropzone>
</div>
);
}
}
export class TestNativeDragEventEvt extends Component {
getFiles = async (event: DragEvent) => {
const files = Array.from(event.dataTransfer.files);
return files;
};
render() {
return (
<div>
<Dropzone getFilesFromEvent={this.getFiles}>
{({ getRootProps }) => <div {...getRootProps()} />}
</Dropzone>
</div>
);
}
}
export class TestChangeEvt extends Component {
getFiles = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files);
return files;
};
render() {
return (
<div>
<Dropzone getFilesFromEvent={this.getFiles}>
{({ getRootProps }) => <div {...getRootProps()} />}
</Dropzone>
</div>
);
}
}
export class TestNativeEvt extends Component {
getFiles = async (event: Event) => {
const files = Array.from((event.target as HTMLInputElement).files);
return files;
};
render() {
return (
<div>
<Dropzone getFilesFromEvent={this.getFiles}>
{({ getRootProps }) => <div {...getRootProps()} />}
</Dropzone>
</div>
);
}
}

View File

@@ -0,0 +1,18 @@
import React, { createRef } from "react";
import Dropzone, { DropzoneRef } from "../../";
const ref = createRef<DropzoneRef>();
export const dropzone = (
<Dropzone ref={ref}>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drop some files here.</p>
<button type="button" onClick={ref.current.open}>
Open file dialog
</button>
</div>
)}
</Dropzone>
);

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"baseUrl": ".",
"allowSyntheticDefaultImports": true,
"declaration": true,
"noEmit": true,
"jsx": "react",
"moduleResolution": "node",
"module": "es2015",
"target": "esnext",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2015",
"dom"
]
},
"include": [
"./*.tsx"
]
}