add more feedback

This commit is contained in:
Matthias Kretschmann 2019-10-18 11:32:36 +02:00
parent 6d377c19cd
commit f998a90546
Signed by: m
GPG Key ID: 606EEEF3C479A91F
7 changed files with 155 additions and 47 deletions

View File

@ -1,9 +1,15 @@
dist: xenial
sudo: required
language: node_js
node_js: node
cache: npm
before_install:
# Fixes an issue where the max file watch count is exceeded, triggering ENOSPC
# https://stackoverflow.com/questions/22475849/node-js-error-enospc#32600959
- echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
# will run `npm install` automatically here
script:

View File

@ -1,3 +1,5 @@
@import '../styles/_variables.css';
.add {
max-width: 40rem;
width: 100%;
@ -5,3 +7,9 @@
word-wrap: break-word;
word-break: break-all;
}
.error {
font-size: var(--font-size-small);
color: var(--red);
margin-top: var(--spacer);
}

View File

@ -1,25 +1,85 @@
import React, { useState } from 'react'
import { saveToIpfs } from '../ipfs'
import { ipfsGateway } from '../../site.config'
import React, { useState, useEffect } from 'react'
import { ipfsNodeUri, ipfsGateway } from '../../site.config'
import Dropzone from './Dropzone'
import styles from './Add.module.css'
import Spinner from './Spinner'
import useIpfsApi, { IpfsConfig } from '../hooks/use-ipfs-api'
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]
}
const { hostname, port, protocol } = new URL(ipfsNodeUri)
const ipfsConfig: IpfsConfig = {
protocol: protocol.replace(':', ''),
host: hostname,
port: port || '443'
}
async function addToIpfs(
files: File[],
setFileSizeReceived: (size: string) => void
) {
const { ipfs } = useIpfsApi(ipfsConfig)
const file = [...files][0]
const fileDetails = { path: file.name, content: file }
const response = await ipfs.add(fileDetails, {
wrapWithDirectory: true,
progress: (length: number) => setFileSizeReceived(formatBytes(length, 0))
})
// CID of wrapping directory is returned last
const cid = response[response.length - 1].hash
return cid
}
export default function Add() {
const { isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig)
const [fileHash, setFileHash] = useState()
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState()
const [error, setError] = useState()
const [fileSize, setFileSize] = useState()
const [fileSizeReceived, setFileSizeReceived] = useState('')
useEffect(() => {
setMessage(
`Adding to IPFS<br />
<small>${fileSizeReceived || 0}/${fileSize}</small><br />`
)
}, [fileSize, fileSizeReceived])
async function handleOnDrop(acceptedFiles: File[]) {
if (!acceptedFiles[0]) return
const handleCaptureFile = async (files: File[]) => {
setLoading(true)
const cid = await saveToIpfs(files)
setError(null)
const totalSize = formatBytes(acceptedFiles[0].size, 0)
setFileSize(totalSize)
try {
const cid = await addToIpfs(acceptedFiles, setFileSizeReceived)
if (!cid) return
setFileHash(cid)
setLoading(false)
} catch (error) {
setError(`Adding to IPFS failed: ${error.message}`)
return null
}
}
return (
<div className={styles.add}>
{loading ? (
<Spinner />
<Spinner message={message} />
) : fileHash ? (
<a
target="_blank"
@ -29,7 +89,16 @@ export default function Add() {
ipfs://{fileHash}
</a>
) : (
<Dropzone multiple={false} handleOnDrop={handleCaptureFile} />
<>
<Dropzone
multiple={false}
handleOnDrop={handleOnDrop}
disabled={!isIpfsReady}
/>
{(error || ipfsError) && (
<div className={styles.error}>{error || ipfsError}</div>
)}
</>
)}
</div>
)

View File

@ -22,7 +22,7 @@
.disabled {
composes: dropzone;
opacity: 0.5;
opacity: 0.3;
pointer-events: none;
}

View File

@ -1,6 +1,17 @@
import React from 'react'
import styles from './Spinner.module.css'
export default function Spinner() {
return <div className={styles.spinner} />
const Spinner = ({ message }: { message?: string }) => {
return (
<div className={styles.spinner}>
{message && (
<div
className={styles.spinnerMessage}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
</div>
)
}
export default Spinner

View File

@ -0,0 +1,49 @@
import { useEffect, useState } from 'react'
import ipfsClient from 'ipfs-http-client'
let ipfs: any = null
let ipfsVersion = ''
export interface IpfsConfig {
protocol: string
host: string
port: string
}
export default function useIpfsApi(config: IpfsConfig) {
const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs))
const [ipfsError, setIpfsError] = useState('')
async function initIpfs() {
if (ipfs !== null) return
// eslint-disable-next-line
ipfs = await ipfsClient(config)
try {
const version = await ipfs.version()
ipfsVersion = version.version
} catch (error) {
setIpfsError(`IPFS connection error: ${error.message}`)
return
}
setIpfsReady(Boolean(await ipfs.id()))
}
useEffect(() => {
initIpfs()
}, [config])
useEffect(() => {
// just like componentWillUnmount()
return function cleanup() {
if (ipfs) {
setIpfsReady(false)
ipfs = null
ipfsVersion = ''
setIpfsError('')
}
}
}, [])
return { ipfs, ipfsVersion, isIpfsReady, ipfsError }
}

View File

@ -1,35 +0,0 @@
import ipfsClient from 'ipfs-http-client'
import { ipfsNodeUri } from '../site.config'
export async function saveToIpfs(files: File[]) {
const { hostname, port, protocol } = new URL(ipfsNodeUri)
const ipfsConfig = {
protocol: protocol.replace(':', ''),
host: hostname,
port: port || '443'
}
const ipfs = ipfsClient(ipfsConfig)
const file = [...files][0]
let ipfsId
const fileDetails = {
path: file.name,
content: file
}
const options = {
wrapWithDirectory: true,
progress: (prog: number) => console.log(`received: ${prog}`)
}
try {
const response = await ipfs.add(fileDetails, options)
// CID of wrapping directory is returned last
ipfsId = `${response[response.length - 1].hash}/${fileDetails.path}`
return ipfsId
} catch (error) {
console.error(error.message)
}
}