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(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 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 && ( setShowPasswordPrompt(false)} onProceed={onFeedPasswordGiven} /> )} {identity && {`Update "${identity.name}"`}} {!identity && Upload} {(step === 0 || step === 2) && } {step === 1 && ( <> {hasAnyStamps && stampMode === 'SELECT' ? ( setStamp(stamp)} defaultValue={stamp?.batchID.toHex()} /> ) : ( setStampMode('SELECT')} /> )} Please refer to the{' '} official Bee documentation {' '} to understand these values. )} {step === 2 && stamp && ( <> Please do not close the application until your file is uploaded to your local node! )} 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} /> ) }