Files
bee-dashboard/src/pages/files/Upload.tsx
T
Cafe137 1249c0df71 feat: bee-js revamp (#690)
* chore: initial commit

* refactor: remove unnecessary wrappers

* style: add missing newline

* chore: bump bee-js

* chore: ignore any cast in fdp

* fix: remove cid import

* fix: make TextEncoder and TextDecoder available in jest

* refactor: dedupe stamp ttl second conversion

* refactor: use convenience methods from bee-js

* feat: update to bee-js for restored ens support

* fix: bump bee-js to get download fix

* fix: resolve feed before downloading reference

* fix: fix token displays

* fix: fix token input modal error message

* refactor: remove wallet balance provider

* chore: remove dead code

* refactor: upcoming bee js 0.15.0 (#692)

* chore: initial commit

* fix: do not break page when duration seconds is 0

* ci: remove cache

* chore: upgrade bee-js

* feat: bee-js and bee v2.6 compatibility

* chore: switch upcoming/bee-js to ethersphere/bee-js
2025-07-16 17:10:14 +02:00

213 lines
7.6 KiB
TypeScript

import { Box } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { Context as FileContext } from '../../providers/File'
import { Context as SettingsContext } from '../../providers/Settings'
import { EnrichedPostageBatch, Context as StampsContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import { waitUntilStampUsable } from '../../utils'
import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
import { persistIdentity, updateFeed } from '../../utils/identity'
import { HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation'
import { PostageStampSelector } from '../stamps/PostageStampSelector'
import { AssetPreview } from './AssetPreview'
import { StampPreview } from './StampPreview'
import { UploadActionBar } from './UploadActionBar'
export function Upload(): ReactElement {
const [step, setStep] = useState(0)
const [stampMode, setStampMode] = useState<'SELECT' | 'BUY'>('SELECT')
const [stamp, setStamp] = useState<EnrichedPostageBatch | null>(null)
const [isUploading, setUploading] = useState(false)
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false)
const { stamps, refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
const { files, setFiles, uploadOrigin, metadata, previewUri } = useContext(FileContext)
const { identities, setIdentities } = useContext(IdentityContext)
const { status } = useContext(BeeContext)
const { enqueueSnackbar } = useSnackbar()
const navigate = useNavigate()
const hasAnyStamps = stamps !== null && stamps.length > 0
useEffect(() => {
refresh()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
if (!files.length) {
setFiles([])
navigate(ROUTES.UPLOAD, { replace: true })
return <></>
}
const identity = uploadOrigin.uuid ? identities.find(x => x.uuid === uploadOrigin.uuid) : null
const onUpload = () => {
if (uploadOrigin.origin === 'UPLOAD') {
uploadFiles()
} else {
if ((identity as Identity).type === 'PRIVATE_KEY') {
uploadFiles()
} else {
setShowPasswordPrompt(true)
}
}
}
const uploadFiles = async (password?: string) => {
if (!beeApi || !files.length || !stamp || !metadata) {
return
}
let fls: FilePath[] = files.map(f => packageFile(f)) // Apart from packaging, this is needed to not modify the original files array as it can trigger effects
let indexDocument: string | undefined = undefined // This means we assume it's folder
if (files.length === 1) indexDocument = unescape(encodeURIComponent(files[0].name))
else if (files.length > 1) {
const idx = detectIndexHtml(files)
// This is a website
if (idx) {
// The website is in some directory, remove it
if (idx.commonPrefix) {
const substrStart = idx.commonPrefix.length
indexDocument = idx.indexPath.slice(substrStart)
fls = files.map(f => {
const path = (f.path as string).slice(substrStart)
return packageFile(f, path)
})
} else {
// The website is not packed in a directory
indexDocument = idx.indexPath
}
}
}
const lastModified = files[0].lastModified
const metafile = new File([JSON.stringify(metadata)], META_FILE_NAME, {
type: 'application/json',
lastModified,
})
fls.push(packageFile(metafile))
setUploading(true)
await waitUntilStampUsable(stamp.batchID, beeApi)
beeApi
.uploadFiles(stamp.batchID, fls, { indexDocument, deferred: true })
.then(hash => {
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference.toHex(), getAssetNameFromFiles(files))
if (uploadOrigin.origin === 'UPLOAD') {
navigate(ROUTES.HASH.replace(':hash', hash.reference.toHex()), { replace: true })
} else {
updateFeed(beeApi, identity as Identity, hash.reference, stamp.batchID, password as string).then(() => {
persistIdentity(identities, identity as Identity)
setIdentities([...identities])
navigate(ROUTES.ACCOUNT_FEEDS_VIEW.replace(':uuid', uploadOrigin.uuid as string), { replace: true })
})
}
})
.catch(e => {
console.error(e) // eslint-disable-line
enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })
setUploading(false)
})
}
const reset = () => {
setStep(0)
setFiles([])
setStamp(null)
setUploading(false)
}
const onFeedPasswordGiven = (password: string) => {
uploadFiles(password)
}
return (
<>
{showPasswordPrompt && (
<FeedPasswordDialog
loading={isUploading}
feedName={(identity as Identity).name}
onCancel={() => setShowPasswordPrompt(false)}
onProceed={onFeedPasswordGiven}
/>
)}
{identity && <HistoryHeader>{`Update "${identity.name}"`}</HistoryHeader>}
{!identity && <HistoryHeader>Upload</HistoryHeader>}
<Box mb={4}>
<ProgressIndicator steps={['Preview', 'Add postage stamp', 'Upload to node']} index={step} />
</Box>
{(step === 0 || step === 2) && <AssetPreview metadata={metadata} previewUri={previewUri} />}
{step === 1 && (
<>
<Box mb={2}>
{hasAnyStamps && stampMode === 'SELECT' ? (
<PostageStampSelector onSelect={stamp => setStamp(stamp)} defaultValue={stamp?.batchID.toHex()} />
) : (
<PostageStampAdvancedCreation onFinished={() => setStampMode('SELECT')} />
)}
</Box>
<Box mb={4}>
<DocumentationText>
Please refer to the{' '}
<a
href="https://docs.ethswarm.org/debug-api/#tag/Postage-Stamps/paths/~1stamps~1{amount}~1{depth}/post"
target="_blank"
rel="noreferrer"
>
official Bee documentation
</a>{' '}
to understand these values.
</DocumentationText>
</Box>
</>
)}
{step === 2 && stamp && (
<>
<StampPreview stamp={stamp} />
<Box mb={4}>
<DocumentationText>
Please do not close the application until your file is uploaded to your local node!
</DocumentationText>
</Box>
</>
)}
<UploadActionBar
step={step}
onCancel={reset}
onGoBack={() => setStep(step => step - 1)}
onProceed={() => setStep(step => step + 1)}
onUpload={onUpload}
isUploading={isUploading}
hasStamp={Boolean(stamp)}
hasAnyStamps={hasAnyStamps}
uploadLabel={identity ? 'Update Feed' : 'Upload To Your Node'}
stampMode={stampMode}
setStampMode={setStampMode}
/>
</>
)
}