Fix: file-manager and swarm-desktop bugs (#714)
- drive capacity display with stamp polling - download/upload progress handling - overlay and tooltip issues - FileMaganger readme - ultra-light mode handling - account feed view page - download media files - remove not found syncing link - fix ultra light node wallet page - tooltip issues --------- Co-authored-by: Andrei Mitrea <andrei.mitrea.hq@gmail.com> Co-authored-by: nidishk <nidishkrishnan45@gmail.com> Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com> Co-authored-by: Nándor Komlódi <nandor.komlodi@gmail.com> Co-authored-by: rolandlor <33499567+rolandlor@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { getHumanReadableFileSize } from '../../utils/file'
|
||||
import { shortenHash } from '../../utils/hash'
|
||||
import { AssetIcon } from './AssetIcon'
|
||||
import { FitVideo } from '../../components/FitVideo'
|
||||
import { FitAudio } from '../../components/FitAudio'
|
||||
|
||||
interface Props {
|
||||
previewUri?: string
|
||||
@@ -21,6 +22,10 @@ const getPreviewComponent = (previewUri?: string, metadata?: Metadata) => {
|
||||
return () => <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
|
||||
}
|
||||
|
||||
if (metadata?.isAudio) {
|
||||
return () => <FitAudio src={previewUri} maxWidth="250px" />
|
||||
}
|
||||
|
||||
if (metadata?.isImage) {
|
||||
return () => <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
|
||||
}
|
||||
|
||||
@@ -83,7 +83,8 @@ export function AssetSyncing({ reference }: Props): ReactElement {
|
||||
<DocumentationText>
|
||||
Files are not immediately accessible on the Swarm network. Please wait until your upload is synced to the
|
||||
network.{' '}
|
||||
<a href="https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing">Learn more about syncing</a>.
|
||||
{/* TODO: syncing article was removed, now the only available doc. is at https://docs.ethswarm.org/api/#tag/Tag */}
|
||||
{/* <a href="https://docs.ethswarm.org/docs/develop/access-the-swarm/syncing">Learn more about syncing</a>. */}
|
||||
</DocumentationText>
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Box, Typography } from '@material-ui/core'
|
||||
import { MantarayNode, NULL_ADDRESS } from '@ethersphere/bee-js'
|
||||
import { Bytes, MantarayNode, NULL_ADDRESS } from '@ethersphere/bee-js'
|
||||
import { saveAs } from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { Loading } from '../../components/Loading'
|
||||
@@ -36,6 +36,8 @@ export function Share(): ReactElement {
|
||||
const [preview, setPreview] = useState<string | undefined>(undefined)
|
||||
const [metadata, setMetadata] = useState<Metadata | undefined>()
|
||||
|
||||
const isMountedRef = useRef(true)
|
||||
|
||||
async function prepare() {
|
||||
if (!beeApi || !status.all) {
|
||||
return
|
||||
@@ -56,37 +58,53 @@ export function Share(): ReactElement {
|
||||
|
||||
const entries = manifest.collectAndMap()
|
||||
delete entries[META_FILE_NAME]
|
||||
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setSwarmEntries(entries)
|
||||
|
||||
const docsMetadata = manifest.getDocsMetadata()
|
||||
|
||||
// needed in catch block, shadows the outer variable
|
||||
const indexDocument = docsMetadata.indexDocument
|
||||
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setIndexDocument(indexDocument)
|
||||
|
||||
try {
|
||||
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
|
||||
const formattedMetadata = remoteMetadata.data.toJSON() as Metadata
|
||||
|
||||
if (formattedMetadata.isVideo || formattedMetadata.isImage) {
|
||||
if (formattedMetadata.isVideo || formattedMetadata.isAudio || formattedMetadata.isImage) {
|
||||
if (!isMountedRef.current) return
|
||||
setPreview(`${apiUrl}/bzz/${reference}`)
|
||||
}
|
||||
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setMetadata({ ...formattedMetadata, hash })
|
||||
} catch (e) {
|
||||
// if metadata is not available or invalid go with the default one
|
||||
const count = Object.keys(entries).length
|
||||
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setMetadata({
|
||||
hash,
|
||||
type: count > 1 ? 'folder' : 'unknown',
|
||||
name: reference,
|
||||
count,
|
||||
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
|
||||
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogg|mp3|ogg|wav)$/i.test(indexDocument)),
|
||||
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogv)$/i.test(indexDocument)),
|
||||
isAudio: Boolean(indexDocument && /.*\.(mp3|ogg|oga|wav|webm|m4a|aac|flac)$/i.test(indexDocument)),
|
||||
isImage: Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument)),
|
||||
// naive assumption based on indexDocument, we don't want to download the whole manifest
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setNotFound(true)
|
||||
enqueueSnackbar('The specified hash does not contain valid content.', { variant: 'error' })
|
||||
|
||||
@@ -112,9 +130,16 @@ export function Share(): ReactElement {
|
||||
navigate(ROUTES.ACCOUNT_FEEDS_UPDATE.replace(':hash', reference))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMountedRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
prepare().finally(() => {
|
||||
if (!isMountedRef.current) return
|
||||
setLoading(false)
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -124,19 +149,58 @@ export function Share(): ReactElement {
|
||||
if (!beeApi) {
|
||||
return
|
||||
}
|
||||
|
||||
putHistory(HISTORY_KEYS.DOWNLOAD_HISTORY, reference, determineHistoryName(reference, indexDocument))
|
||||
setDownloading(true)
|
||||
|
||||
if (Object.keys(swarmEntries).length === 1) {
|
||||
window.open(`${apiUrl}/bzz/${reference}/`, '_blank')
|
||||
const singleFileName = Object.keys(swarmEntries)[0]
|
||||
const singleFileHash = Object.values(swarmEntries)[0]
|
||||
|
||||
let fileData: Bytes
|
||||
try {
|
||||
fileData = await beeApi.downloadData(singleFileHash)
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to download file: ', err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const dataArray = fileData.toUint8Array()
|
||||
const arrayBuffer = new ArrayBuffer(dataArray.length)
|
||||
const view = new Uint8Array(arrayBuffer)
|
||||
view.set(dataArray)
|
||||
const blob = new Blob([arrayBuffer], { type: metadata?.type || 'application/octet-stream' })
|
||||
saveAs(blob, metadata?.name || singleFileName || reference)
|
||||
} else {
|
||||
const zip = new JSZip()
|
||||
for (const [path, hash] of Object.entries(swarmEntries)) {
|
||||
zip.file(path, (await beeApi.downloadData(hash)).toUint8Array())
|
||||
try {
|
||||
zip.file(path, (await beeApi.downloadData(hash)).toUint8Array())
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to download files: ', err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
const content = await zip.generateAsync({ type: 'blob' })
|
||||
|
||||
let content: Blob
|
||||
try {
|
||||
content = await zip.generateAsync({ type: 'blob' })
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to compress file: ', err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
saveAs(content, reference + '.zip')
|
||||
}
|
||||
|
||||
if (!isMountedRef.current) return
|
||||
|
||||
setDownloading(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { DocumentationText } from '../../components/DocumentationText'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { Context, UploadOrigin } from '../../providers/File'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { detectIndexHtml } from '../../utils/file'
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
|
||||
interface Props {
|
||||
uploadOrigin: UploadOrigin
|
||||
@@ -51,6 +53,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
|
||||
const { setFiles, setUploadOrigin } = useContext(Context)
|
||||
const { nodeInfo } = useContext(BeeContext)
|
||||
const classes = useStyles()
|
||||
const navigate = useNavigate()
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
@@ -121,35 +124,52 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
const isUploadEnabled = nodeInfo?.beeMode !== BeeModes.ULTRA_LIGHT
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.areaWrapper}>
|
||||
<DropzoneArea
|
||||
key={version}
|
||||
dropzoneClass={classes.dropzone}
|
||||
onChange={handleChange}
|
||||
filesLimit={1e9}
|
||||
maxFileSize={MAX_FILE_SIZE}
|
||||
showPreviews={false}
|
||||
/>
|
||||
<div className={classes.buttonWrapper}>
|
||||
<SwarmButton className={classes.button} onClick={onUploadFileClick} iconType={FilePlus}>
|
||||
Add File
|
||||
</SwarmButton>
|
||||
<SwarmButton className={classes.button} onClick={onUploadFolderClick} iconType={FolderPlus}>
|
||||
Add Folder
|
||||
</SwarmButton>
|
||||
<SwarmButton className={classes.button} onClick={onUploadWebsiteClick} iconType={PlusCircle}>
|
||||
Add Website
|
||||
</SwarmButton>
|
||||
{isUploadEnabled && (
|
||||
<div className={classes.areaWrapper}>
|
||||
<DropzoneArea
|
||||
key={version}
|
||||
dropzoneClass={classes.dropzone}
|
||||
onChange={handleChange}
|
||||
filesLimit={1e9}
|
||||
maxFileSize={MAX_FILE_SIZE}
|
||||
showPreviews={false}
|
||||
/>
|
||||
<div className={classes.buttonWrapper}>
|
||||
<SwarmButton className={classes.button} onClick={onUploadFileClick} iconType={FilePlus}>
|
||||
Add File
|
||||
</SwarmButton>
|
||||
<SwarmButton className={classes.button} onClick={onUploadFolderClick} iconType={FolderPlus}>
|
||||
Add Folder
|
||||
</SwarmButton>
|
||||
<SwarmButton className={classes.button} onClick={onUploadWebsiteClick} iconType={PlusCircle}>
|
||||
Add Website
|
||||
</SwarmButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showHelp && (
|
||||
)}
|
||||
{isUploadEnabled && showHelp && (
|
||||
<DocumentationText>
|
||||
You can click the buttons above or simply drag and drop to add a file or folder. To upload a website to Swarm,
|
||||
make sure that your folder contains an “index.html” file.
|
||||
</DocumentationText>
|
||||
)}
|
||||
{!isUploadEnabled && (
|
||||
<DocumentationText>
|
||||
Uploading files requires running a light node. Please{' '}
|
||||
<a
|
||||
href="https://docs.ethswarm.org/docs/desktop/configuration/#upgrading-from-an-ultra-light-to-a-light-node"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
upgrade
|
||||
</a>{' '}
|
||||
to continue.
|
||||
</DocumentationText>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user