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",
|
"resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz",
|
||||||
"integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY="
|
"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": {
|
"autoprefixer": {
|
||||||
"version": "9.6.1",
|
"version": "9.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
|
||||||
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
|
"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": {
|
"file-type": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||||
@ -20906,6 +20929,16 @@
|
|||||||
"object.pick": "^1.3.0"
|
"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": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.1.tgz",
|
||||||
@ -24177,8 +24210,7 @@
|
|||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"tsutils": {
|
"tsutils": {
|
||||||
"version": "3.17.1",
|
"version": "3.17.1",
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"react-datepicker": "^2.8.0",
|
"react-datepicker": "^2.8.0",
|
||||||
"react-dom": "16.8.6",
|
"react-dom": "16.8.6",
|
||||||
"react-dotdotdot": "^1.3.1",
|
"react-dotdotdot": "^1.3.1",
|
||||||
|
"react-dropzone": "^10.1.8",
|
||||||
"react-ga": "^2.6.0",
|
"react-ga": "^2.6.0",
|
||||||
"react-helmet": "^5.2.1",
|
"react-helmet": "^5.2.1",
|
||||||
"react-markdown": "^4.1.0",
|
"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 ipfsMessage = ''
|
||||||
let ipfsVersion = ''
|
let ipfsVersion = ''
|
||||||
|
|
||||||
export default function useIpfsApi(config: {
|
interface IpfsConfig {
|
||||||
host: string
|
host: string
|
||||||
port: string
|
port: string
|
||||||
protocol: string
|
protocol: string
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export default function useIpfsApi(config: IpfsConfig) {
|
||||||
const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs))
|
const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs))
|
||||||
const [ipfsError, setIpfsError] = useState(null)
|
const [ipfsError, setIpfsError] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initIpfs() {
|
async function initIpfs() {
|
||||||
ipfsMessage = 'Checking IPFS gateway...'
|
if (ipfs !== null) return
|
||||||
|
|
||||||
if (ipfs) return
|
ipfsMessage = 'Checking IPFS gateway...'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const message = 'Connected to IPFS gateway'
|
const message = 'Connected to IPFS gateway'
|
||||||
@ -35,8 +37,11 @@ export default function useIpfsApi(config: {
|
|||||||
}
|
}
|
||||||
setIpfsReady(Boolean(ipfs))
|
setIpfsReady(Boolean(ipfs))
|
||||||
}
|
}
|
||||||
initIpfs()
|
|
||||||
|
|
||||||
|
initIpfs()
|
||||||
|
}, [config])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
// just like componentWillUnmount()
|
// just like componentWillUnmount()
|
||||||
return function cleanup() {
|
return function cleanup() {
|
||||||
if (ipfs) {
|
if (ipfs) {
|
||||||
@ -47,7 +52,7 @@ export default function useIpfsApi(config: {
|
|||||||
setIpfsError(null)
|
setIpfsError(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [config])
|
}, [])
|
||||||
|
|
||||||
return { ipfs, ipfsVersion, isIpfsReady, ipfsError, ipfsMessage }
|
return { ipfs, ipfsVersion, isIpfsReady, ipfsError, ipfsMessage }
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@import '../../../styles/variables';
|
@import '../../../../styles/variables';
|
||||||
|
|
||||||
.ipfsForm {
|
.ipfsForm {
|
||||||
margin-top: $spacer / 2;
|
margin-top: $spacer / 2;
|
@ -1,12 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Ipfs from './Ipfs'
|
import Ipfs from '.'
|
||||||
|
|
||||||
const addFile = jest.fn()
|
const addFile = jest.fn()
|
||||||
|
|
||||||
describe('Ipfs', () => {
|
describe('Ipfs', () => {
|
||||||
|
const ui = <Ipfs addFile={addFile} />
|
||||||
|
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', async () => {
|
||||||
const { container, findByText } = render(<Ipfs addFile={addFile} />)
|
const { container, findByText } = render(ui)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
|
||||||
// wait for IPFS node
|
// wait for IPFS node
|
@ -1,33 +1,12 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import axios from 'axios'
|
import useIpfsApi from '../../../../hooks/use-ipfs-api'
|
||||||
import useIpfsApi from '../../../hooks/use-ipfs-api'
|
import Label from '../../../../components/atoms/Form/Label'
|
||||||
import Label from '../../../components/atoms/Form/Label'
|
import Spinner from '../../../../components/atoms/Spinner'
|
||||||
import Spinner from '../../../components/atoms/Spinner'
|
import Dropzone from '../../../../components/molecules/Dropzone'
|
||||||
import styles from './Ipfs.module.scss'
|
import { formatBytes, pingUrl } from './utils'
|
||||||
|
import styles from './index.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]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
||||||
const config = {
|
const config = {
|
||||||
@ -80,15 +59,17 @@ export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCaptureFile(files: FileList | null) {
|
function handleOnDrop(files: any) {
|
||||||
const reader: any = new window.FileReader()
|
const reader: any = new FileReader()
|
||||||
const file = files && files[0]
|
|
||||||
|
|
||||||
|
files &&
|
||||||
|
files.forEach((file: File) => {
|
||||||
reader.readAsArrayBuffer(file)
|
reader.readAsArrayBuffer(file)
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
const buffer: any = Buffer.from(reader.result)
|
const buffer: any = Buffer.from(reader.result)
|
||||||
saveToIpfs(buffer)
|
saveToIpfs(buffer)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -99,13 +80,7 @@ export default function Ipfs({ addFile }: { addFile(url: string): void }) {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Spinner message={message} />
|
<Spinner message={message} />
|
||||||
) : (
|
) : (
|
||||||
<input
|
<Dropzone handleOnDrop={handleOnDrop} disabled={!isIpfsReady} />
|
||||||
type="file"
|
|
||||||
name="fileUpload"
|
|
||||||
id="fileUpload"
|
|
||||||
onChange={e => handleCaptureFile(e.target.files)}
|
|
||||||
disabled={!isIpfsReady}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{ipfsMessage !== '' && (
|
{ipfsMessage !== '' && (
|
||||||
<div className={styles.message} title={ipfsVersion}>
|
<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