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:
Bálint Ujvári
2026-01-26 12:57:14 +01:00
committed by GitHub
parent ecadafd21d
commit 0d5138f5bc
78 changed files with 3961 additions and 1194 deletions
+71 -7
View File
@@ -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)
}