Skip to content

Commit

Permalink
feat(verified-fetch): improve UX (#286)
Browse files Browse the repository at this point in the history
  • Loading branch information
SgtPooki authored Feb 8, 2024
1 parent 6b5e262 commit 72f3d59
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 20 deletions.
3 changes: 2 additions & 1 deletion examples/helia-browser-react-verified-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@helia/verified-fetch": "^0.0.0-6c88ee1",
"@helia/verified-fetch": "0.0.0-3851fe2",
"@sgtpooki/file-type": "^1.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
196 changes: 177 additions & 19 deletions examples/helia-browser-react-verified-fetch/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,182 @@
import { useCallback, useState } from 'react'
// import { verifiedFetch, createVerifiedFetch } from '@helia/verified-fetch'
import { verifiedFetch } from '@helia/verified-fetch'
import { fileTypeFromBuffer } from '@sgtpooki/file-type'
import { useCallback, useState } from 'react'
import { helpText } from './constants'

function App() {
function renderOutput (output: string | JSX.Element, err: string): JSX.Element {
console.log('err: ', err)
console.log('output: ', output)
if (err.length > 0) {
return (
<div className="bg-red-300">
<pre className="bg-black text-red-300 rounded p-4">{err}</pre>
</div>
)
}

if (typeof output === 'string') {
return (
<div className="bg-violet-300">
{output != null && (
<pre className="bg-black text-teal-300 rounded p-4">
<code id="output" className="language-json">{`${output}`}</code>
</pre>
)}
</div>
)
}

return output
}

function loadingIndicator (message: string): JSX.Element {
return (
<div className="bg-yellow-300">
<pre className="bg-black text-yellow-300 rounded p-4">Loading... {message}</pre>
</div>
)
}

function App (): JSX.Element {
const [path, setPath] = useState<string>('')
const [output, setOutput] = useState<string>('')
const [output, setOutput] = useState<string | JSX.Element>('')
const [err, setErr] = useState<string>('')
const [loading, setLoadingTo] = useState<JSX.Element | null>(null)

const setSuccess = useCallback((message: string | JSX.Element) => {
setOutput(message)
setLoadingTo(null)
setErr('')
}, [])
const setError = useCallback((message: string) => {
setOutput('')
setLoadingTo(null)
setErr(message)
}, [])
const setLoading = useCallback((message: string) => {
setErr('')
setLoadingTo(loadingIndicator(message))
}, [])

const handleImageType = useCallback(async (resp: Response) => {
try {
setLoading('Waiting for full image data...')
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
setSuccess(<img src={url} alt="fetched image content" />)
} catch (err) {
setError((err as Error).message)
}
}, [])

const handleJsonType = useCallback(async (resp: Response) => {
try {
setLoading('Waiting for full JSON data...')
const json = await resp.json()
setSuccess(JSON.stringify(json, null, 2))
} catch (err) {
setError((err as Error).message)

}
}, [])

const handleVideoType = useCallback(async (resp: Response) => {
try {
setLoading('Waiting for full video data...')
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
setSuccess(<video controls src={url} />)
} catch (err) {
setError((err as Error).message)
}
}, [])

const onFetchJson = useCallback(async () => {
if (!path) {
setErr('Invalid path')
if (path == null) {
setError('Invalid path')
return
}
const resp = await verifiedFetch(path)
const json = await resp.json()
try {
setLoading('Fetching json response...')
const resp = await verifiedFetch(path)
await handleJsonType(resp)
} catch (err) {
setError((err as Error).message)
}

}, [path, handleJsonType])

setOutput(json)
const onFetchImage = useCallback(async () => {
if (path == null) {
setError('Invalid path')
return
}
try {
setLoading('Fetching image response...')
const resp = await verifiedFetch(path)
await handleImageType(resp)
} catch (err) {
setError((err as Error).message)
}
}, [path, handleImageType])

const onFetchFile = useCallback(async () => {
if (path == null) {
setError('Invalid path')
return
}
try {
setLoading('Fetching content to download...')
const resp = await verifiedFetch(path)
const blob = await resp.blob()
const url = URL.createObjectURL(blob)
const downloadLink = document.createElement('a')
downloadLink.href = url
downloadLink.download = 'download'
setSuccess('') // clear output
downloadLink.click()
} catch (err) {
setError((err as Error).message)
}
}, [path])

const onFetchImage = useCallback(async () => {}, [path])
const onFetchFile = useCallback(async () => {}, [path])
const onFetchAuto = useCallback(async () => {
if (path == null) {
setError('Invalid path')
return
}
try {
setLoading('Fetching auto content...')
const resp = await verifiedFetch(path)
const buffer = await resp.clone().arrayBuffer()
let contentType = (await fileTypeFromBuffer(new Uint8Array(buffer)))?.mime
if (contentType == null) {
try {
// see if we can parse as json
await resp.clone().json()
contentType = 'application/json'
} catch (err) {
// ignore
}
}
switch (true) {
case contentType.includes('image'):
await handleImageType(resp)
break
case contentType.includes('json'):
await handleJsonType(resp)
break
case contentType.includes('video'):
await handleVideoType(resp)
break
default:
setError(`Unknown content-type: ${contentType}`)
}
} catch (err) {
setError((err as Error).message)
}
}, [path, handleImageType, handleJsonType, handleVideoType])

return (
<div className="">
Expand All @@ -37,7 +194,7 @@ function App() {
type="text"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="ipfs://... or ipns://"
onChange={(e) => setPath(e.target.value)}
onChange={(e) => { setPath(e.target.value) }}
value={path}
/>
<button
Expand All @@ -59,21 +216,22 @@ function App() {
id="button-resolve-ipns"
onClick={onFetchFile}
>
🔑 Fetch as file
🔑 Fetch & Download
</button>
<button
className="my-2 mr-2 btn btn-blue bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
id="button-resolve-ipns"
onClick={onFetchAuto}
>
🔑 Fetch auto
</button>

<pre className="bg-black text-teal-300 rounded p-4">{helpText}</pre>
</div>
{/* Left */}

{/* Right */}
<div className="bg-violet-300">
{output && (
<pre className="bg-black text-teal-300 rounded p-4">
<code id="output" className="language-json"></code>
</pre>
)}
</div>
{renderOutput(loading ?? output, err)}
</div>
</section>
</div>
Expand Down

0 comments on commit 72f3d59

Please sign in to comment.