mirror of
https://github.com/oceanprotocol/commons.git
synced 2023-03-15 18:03:00 +01:00
dropzone component
This commit is contained in:
parent
a258f6b94b
commit
a2a6720fd8
36
client/package-lock.json
generated
36
client/package-lock.json
generated
@ -3870,6 +3870,21 @@
|
||||
"resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz",
|
||||
"integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY="
|
||||
},
|
||||
"attr-accept": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz",
|
||||
"integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==",
|
||||
"requires": {
|
||||
"core-js": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
|
||||
"integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
|
||||
@ -8657,6 +8672,14 @@
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
|
||||
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
|
||||
},
|
||||
"file-selector": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.12.tgz",
|
||||
"integrity": "sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"file-type": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||
@ -20906,6 +20929,16 @@
|
||||
"object.pick": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"react-dropzone": {
|
||||
"version": "10.1.8",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.1.8.tgz",
|
||||
"integrity": "sha512-Lm6+TxIDf/my4i3VdYmufRcrJ4SUbSTJP3HB49V2+HNjZwLI4NKVkaNRHwwSm9CEuzMP+6SW7pT1txc1uBPfDg==",
|
||||
"requires": {
|
||||
"attr-accept": "^1.1.3",
|
||||
"file-selector": "^0.1.11",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-error-overlay": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.1.tgz",
|
||||
@ -24177,8 +24210,7 @@
|
||||
"tslib": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "3.17.1",
|
||||
|
@ -33,6 +33,7 @@
|
||||
"react-datepicker": "^2.8.0",
|
||||
"react-dom": "16.8.6",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-dropzone": "^10.1.8",
|
||||
"react-ga": "^2.6.0",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-markdown": "^4.1.0",
|
||||
|
36
client/src/components/molecules/Dropzone.module.scss
Normal file
36
client/src/components/molecules/Dropzone.module.scss
Normal file
@ -0,0 +1,36 @@
|
||||
@import '../../styles/variables';
|
||||
|
||||
.dropzone {
|
||||
margin-top: $spacer;
|
||||
margin-bottom: $spacer;
|
||||
border: .2rem dashed $brand-grey-lighter;
|
||||
border-radius: 1rem;
|
||||
padding: $spacer;
|
||||
background: $brand-white;
|
||||
transition: .2s ease-out;
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
font-size: $font-size-small;
|
||||
color: $brand-grey-light;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: $brand-grey-light;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dragover {
|
||||
composes: dropzone;
|
||||
border-color: $brand-pink;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
composes: dropzone;
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
52
client/src/components/molecules/Dropzone.test.tsx
Normal file
52
client/src/components/molecules/Dropzone.test.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import { fireEvent, render } from '@testing-library/react'
|
||||
import Dropzone from './Dropzone'
|
||||
|
||||
function mockData(files: any) {
|
||||
return {
|
||||
dataTransfer: {
|
||||
files,
|
||||
items: files.map((file: any) => ({
|
||||
kind: 'file',
|
||||
type: file.type,
|
||||
getAsFile: () => file
|
||||
})),
|
||||
types: ['Files']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushPromises(ui: any, container: any) {
|
||||
return new Promise(resolve =>
|
||||
setImmediate(() => {
|
||||
render(ui, { container })
|
||||
resolve(container)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function dispatchEvt(node: any, type: string, data: any) {
|
||||
const event = new Event(type, { bubbles: true })
|
||||
Object.assign(event, data)
|
||||
fireEvent(node, event)
|
||||
}
|
||||
|
||||
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 handleOnDrop = jest.fn()
|
||||
|
||||
const ui = <Dropzone handleOnDrop={handleOnDrop} />
|
||||
const { container } = render(ui)
|
||||
|
||||
// drop a file
|
||||
const dropzone = container.querySelector('div')
|
||||
dispatchEvt(dropzone, 'dragenter', data)
|
||||
dispatchEvt(dropzone, 'dragover', data)
|
||||
dispatchEvt(dropzone, 'drop', data)
|
||||
await flushPromises(ui, container)
|
||||
|
||||
expect(handleOnDrop).toHaveBeenCalled()
|
||||
})
|
34
client/src/components/molecules/Dropzone.tsx
Normal file
34
client/src/components/molecules/Dropzone.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import styles from './Dropzone.module.scss'
|
||||
|
||||
export default function Dropzone({
|
||||
handleOnDrop,
|
||||
disabled
|
||||
}: {
|
||||
handleOnDrop(files: FileList): void
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const onDrop = useCallback(acceptedFiles => handleOnDrop(acceptedFiles), [
|
||||
handleOnDrop
|
||||
])
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
{...getRootProps({
|
||||
className: isDragActive
|
||||
? styles.dragover
|
||||
: disabled
|
||||
? styles.disabled
|
||||
: styles.dropzone
|
||||
})}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<p>{`Drag 'n' drop some files here, or click to select files`}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -7,19 +7,21 @@ let ipfs: any = null
|
||||
let ipfsMessage = ''
|
||||
let ipfsVersion = ''
|
||||
|
||||
export default function useIpfsApi(config: {
|
||||
interface IpfsConfig {
|
||||
host: string
|
||||
port: string
|
||||
protocol: string
|
||||
}) {
|
||||
}
|
||||
|
||||
export default function useIpfsApi(config: IpfsConfig) {
|
||||
const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs))
|
||||
const [ipfsError, setIpfsError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
async function initIpfs() {
|
||||
ipfsMessage = 'Checking IPFS gateway...'
|
||||
if (ipfs !== null) return
|
||||
|
||||
if (ipfs) return
|
||||
ipfsMessage = 'Checking IPFS gateway...'
|
||||
|
||||
try {
|
||||
const message = 'Connected to IPFS gateway'
|
||||
@ -35,8 +37,11 @@ export default function useIpfsApi(config: {
|
||||
}
|
||||
setIpfsReady(Boolean(ipfs))
|
||||
}
|
||||
initIpfs()
|
||||
|
||||
initIpfs()
|
||||
}, [config])
|
||||
|
||||
useEffect(() => {
|
||||
// just like componentWillUnmount()
|
||||
return function cleanup() {
|
||||
if (ipfs) {
|
||||
@ -47,7 +52,7 @@ export default function useIpfsApi(config: {
|
||||
setIpfsError(null)
|
||||
}
|
||||
}
|
||||
}, [config])
|
||||
}, [])
|
||||
|
||||
return { ipfs, ipfsVersion, isIpfsReady, ipfsError, ipfsMessage }
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import '../../../styles/variables';
|
||||
@import '../../../../styles/variables';
|
||||
|
||||
.ipfsForm {
|
||||
margin-top: $spacer / 2;
|
@ -1,12 +1,14 @@
|
||||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import Ipfs from './Ipfs'
|
||||
import Ipfs from '.'
|
||||
|
||||
const addFile = jest.fn()
|
||||
|
||||
describe('Ipfs', () => {
|
||||
const ui = <Ipfs addFile={addFile} />
|
||||
|
||||
it('renders without crashing', async () => {
|
||||
const { container, findByText } = render(<Ipfs addFile={addFile} />)
|
||||
const { container, findByText } = render(ui)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
|
||||
// wait for IPFS node
|
@ -1,33 +1,12 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import useIpfsApi from '../../../hooks/use-ipfs-api'
|
||||
import Label from '../../../components/atoms/Form/Label'
|
||||
import Spinner from '../../../components/atoms/Spinner'
|
||||
import styles from './Ipfs.module.scss'
|
||||
|
||||
async function pingUrl(url: string) {
|
||||
try {
|
||||
const response = await axios(url)
|
||||
if (response.status !== 200) console.error(`Not found: ${url}`)
|
||||
|
||||
console.log(`File found: ${url}`)
|
||||
return
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(a: number, b: number) {
|
||||
if (a === 0) return '0 Bytes'
|
||||
const c = 1024
|
||||
const d = b || 2
|
||||
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const f = Math.floor(Math.log(a) / Math.log(c))
|
||||
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f]
|
||||
}
|
||||
import useIpfsApi from '../../../../hooks/use-ipfs-api'
|
||||
import Label from '../../../../components/atoms/Form/Label'
|
||||
import Spinner from '../../../../components/atoms/Spinner'
|
||||
import Dropzone from '../../../../components/molecules/Dropzone'
|
||||
import { formatBytes, pingUrl } from './utils'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
||||
const config = {
|
||||
@ -80,15 +59,17 @@ export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleCaptureFile(files: FileList | null) {
|
||||
const reader: any = new window.FileReader()
|
||||
const file = files && files[0]
|
||||
function handleOnDrop(files: any) {
|
||||
const reader: any = new FileReader()
|
||||
|
||||
files &&
|
||||
files.forEach((file: File) => {
|
||||
reader.readAsArrayBuffer(file)
|
||||
reader.onloadend = () => {
|
||||
const buffer: any = Buffer.from(reader.result)
|
||||
saveToIpfs(buffer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@ -99,13 +80,7 @@ export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
||||
{loading ? (
|
||||
<Spinner message={message} />
|
||||
) : (
|
||||
<input
|
||||
type="file"
|
||||
name="fileUpload"
|
||||
id="fileUpload"
|
||||
onChange={e => handleCaptureFile(e.target.files)}
|
||||
disabled={!isIpfsReady}
|
||||
/>
|
||||
<Dropzone handleOnDrop={handleOnDrop} disabled={!isIpfsReady} />
|
||||
)}
|
||||
{ipfsMessage !== '' && (
|
||||
<div className={styles.message} title={ipfsVersion}>
|
9
client/src/routes/Publish/Files/Ipfs/utils.test.tsx
Normal file
9
client/src/routes/Publish/Files/Ipfs/utils.test.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { formatBytes } from './utils'
|
||||
|
||||
describe('utils', () => {
|
||||
it('formatBytes outputs as expected', () => {
|
||||
const number = 1024
|
||||
const output = formatBytes(number, 0)
|
||||
expect(output).toBe('1 KB')
|
||||
})
|
||||
})
|
24
client/src/routes/Publish/Files/Ipfs/utils.tsx
Normal file
24
client/src/routes/Publish/Files/Ipfs/utils.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from 'axios'
|
||||
|
||||
export async function pingUrl(url: string) {
|
||||
try {
|
||||
const response = await axios(url)
|
||||
if (response.status !== 200) console.error(`Not found: ${url}`)
|
||||
|
||||
console.log(`File found: ${url}`)
|
||||
return
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export function formatBytes(a: number, b: number) {
|
||||
if (a === 0) return '0 Bytes'
|
||||
const c = 1024
|
||||
const d = b || 2
|
||||
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const f = Math.floor(Math.log(a) / Math.log(c))
|
||||
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f]
|
||||
}
|
Loading…
Reference in New Issue
Block a user