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
@@ -1,20 +1,13 @@
import { ReactElement, useCallback, useContext, useEffect, useRef, useState } from 'react'
import './UpgradeDriveModal.scss'
import '../../styles/global.scss'
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
import { Button } from '../Button/Button'
import { Warning } from '@material-ui/icons'
import { createPortal } from 'react-dom'
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
import DatabaseIcon from 'remixicon-react/Database2LineIcon'
import WalletIcon from 'remixicon-react/Wallet3LineIcon'
import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon'
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
import { desiredLifetimeOptions } from '../../constants/stamps'
import { Context as BeeContext } from '../../../../providers/Bee'
import { fromBytesConversion, getExpiryDateByLifetime } from '../../utils/common'
import { Context as SettingsContext } from '../../../../providers/Settings'
import { Context as FMContext } from '../../../../providers/FileManager'
import {
BatchId,
BeeRequestOptions,
@@ -27,8 +20,17 @@ import {
Utils,
} from '@ethersphere/bee-js'
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
import { Button } from '../Button/Button'
import { desiredLifetimeOptions } from '../../constants/stamps'
import { Context as BeeContext } from '../../../../providers/Bee'
import { fromBytesConversion, getExpiryDateByLifetime, truncateNameMiddle } from '../../utils/common'
import { Context as SettingsContext } from '../../../../providers/Settings'
import { Context as FMContext } from '../../../../providers/FileManager'
import { getHumanReadableFileSize } from '../../../../utils/file'
import { Warning } from '@material-ui/icons'
import { useStampPolling } from '../../hooks/useStampPolling'
import { FILE_MANAGER_EVENTS, POLLING_TIMEOUT_MS } from '../../constants/common'
interface UpgradeDriveModalProps {
stamp: PostageBatch
@@ -50,10 +52,10 @@ export function UpgradeDriveModal({
}: UpgradeDriveModalProps): ReactElement {
const { nodeAddresses, walletBalance } = useContext(BeeContext)
const { beeApi } = useContext(SettingsContext)
const { setShowError } = useContext(FMContext)
const { refreshStamp, setShowError } = useContext(FMContext)
const [isBalanceSufficient, setIsBalanceSufficient] = useState(true)
const [capacity, setCapacity] = useState(Size.fromBytes(0))
const [capacity, setCapacity] = useState(stamp.size)
const [capacityExtensionCost, setCapacityExtensionCost] = useState('')
const [capacityIndex, setCapacityIndex] = useState(0)
const [durationExtensionCost, setDurationExtensionCost] = useState('')
@@ -66,14 +68,37 @@ export function UpgradeDriveModal({
const modalRoot = document.querySelector('.fm-main') || document.body
const isMountedRef = useRef(true)
useEffect(() => {
return () => {
isMountedRef.current = false
}
}, [])
const { startPolling } = useStampPolling({
refreshStamp,
onStampUpdated: (updatedStamp: PostageBatch) => {
window.dispatchEvent(
new CustomEvent(FILE_MANAGER_EVENTS.DRIVE_UPGRADE_END, {
detail: {
driveId: drive.id.toString(),
success: true,
updatedStamp,
},
}),
)
},
onTimeout: (finalStamp: PostageBatch | null) => {
window.dispatchEvent(
new CustomEvent(FILE_MANAGER_EVENTS.DRIVE_UPGRADE_TIMEOUT, {
detail: {
driveId: drive.id.toString(),
finalStamp: finalStamp || null,
},
}),
)
},
onPollingStateChange: () => {
// no-op
},
timeout: POLLING_TIMEOUT_MS,
})
const handleCapacityChange = (value: number, index: number) => {
setCapacity(Size.fromBytes(value === -1 ? 0 : value))
setCapacity(value === -1 ? stamp.size : Size.fromBytes(value))
setCapacityIndex(index)
}
@@ -88,8 +113,6 @@ export function UpgradeDriveModal({
isCapacityExtensionSet: boolean,
isDurationExtensionSet: boolean,
) => {
setIsBalanceSufficient(true)
let cost: BZZ | undefined
try {
@@ -107,6 +130,8 @@ export function UpgradeDriveModal({
if ((walletBalance && cost && cost.gte(walletBalance.bzzBalance)) || !walletBalance) {
setIsBalanceSufficient(false)
} else {
setIsBalanceSufficient(true)
}
const bothExtensions = isCapacityExtensionSet && isDurationExtensionSet
@@ -130,7 +155,7 @@ export function UpgradeDriveModal({
setExtensionCost(noExtensions ? '0' : costText)
},
[beeApi, walletBalance, setErrorMessage, setShowError],
[beeApi, walletBalance, isMountedRef, setErrorMessage, setShowError],
)
useEffect(() => {
@@ -158,13 +183,14 @@ export function UpgradeDriveModal({
useEffect(() => {
const fetchExtensionCost = () => {
const isCapacitySet = capacityIndex > 0
const isDurationSet = true
const duration = Duration.fromEndDate(validityEndDate)
const isDurationSet = lifetimeIndex >= 0
const extendDuration =
lifetimeIndex >= 0 ? Duration.fromEndDate(validityEndDate, stamp.duration.toEndDate()) : Duration.ZERO
handleCostCalculation(
stamp.batchID,
capacity,
duration,
extendDuration,
undefined,
false,
defaultErasureCodeLevel,
@@ -174,114 +200,126 @@ export function UpgradeDriveModal({
}
fetchExtensionCost()
}, [capacity, validityEndDate, capacityIndex, handleCostCalculation, lifetimeIndex, stamp.batchID])
}, [capacity, validityEndDate, capacityIndex, handleCostCalculation, lifetimeIndex, stamp.batchID, stamp.duration])
useEffect(() => {
setValidityEndDate(getExpiryDateByLifetime(lifetimeIndex, stamp.duration.toEndDate()))
}, [lifetimeIndex, stamp.duration])
useEffect(() => {
return () => {
isMountedRef.current = false
}
}, [])
const batchIdStr = stamp.batchID.toString()
const shortBatchId = batchIdStr.length > 12 ? `${batchIdStr.slice(0, 4)}...${batchIdStr.slice(-4)}` : batchIdStr
return createPortal(
<div className={`fm-modal-container${containerColor === 'none' ? ' fm-modal-container-no-bg' : ''}`}>
<div
className={`fm-modal-container fm-upgrade-drive-modal-container${
containerColor === 'none' ? ' fm-modal-container-no-bg' : ''
}`}
>
<div className="fm-modal-window fm-upgrade-drive-modal">
<div className="fm-modal-window-header">
<DriveIcon size="18px" /> Upgrade {drive.name || stamp.label || shortBatchId}
<DriveIcon size="18px" /> Upgrade {truncateNameMiddle(drive.name || stamp.label || shortBatchId, 35)}
</div>
<div>Choose extension period and additional storage for your drive.</div>
<div className="fm-modal-window-body">
<div className="fm-upgrade-drive-modal-wallet">
<div className="fm-upgrade-drive-modal-wallet-header fm-emphasized-text">
<WalletIcon size="14px" color="rgb(237, 129, 49)" /> Wallet information
</div>
{walletBalance && nodeAddresses ? (
<div className="fm-upgrade-drive-modal-wallet-info-container">
<div className="fm-upgrade-drive-modal-wallet-info">
<div>Balance</div>
<div>{`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`}</div>
</div>
<div className="fm-upgrade-drive-modal-wallet-info">
<div>Wallet address:</div>
<div className="fm-value-snippet">{`${walletBalance.walletAddress.slice(
0,
4,
)}...${walletBalance.walletAddress.slice(-4)}`}</div>
</div>
<div className="fm-modal-window-scrollable">
<div>Choose extension period and additional storage for your drive.</div>
<div className="fm-modal-window-body">
<div className="fm-upgrade-drive-modal-wallet">
<div className="fm-upgrade-drive-modal-wallet-header fm-emphasized-text">
<WalletIcon size="14px" color="rgb(237, 129, 49)" /> Wallet information
</div>
{walletBalance && nodeAddresses ? (
<div className="fm-upgrade-drive-modal-wallet-info-container">
<div className="fm-upgrade-drive-modal-wallet-info">
<div>Balance</div>
<div>{`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`}</div>
</div>
<div className="fm-upgrade-drive-modal-wallet-info">
<div>Wallet address:</div>
<div className="fm-value-snippet">{`${walletBalance.walletAddress.slice(
0,
4,
)}...${walletBalance.walletAddress.slice(-4)}`}</div>
</div>
</div>
) : (
<div>Wallet information is not available</div>
)}
<div className="fm-upgrade-drive-modal-info fm-swarm-orange-font">
<a
className="fm-upgrade-drive-modal-info-link fm-pointer"
href="https://www.ethswarm.org/get-bzz#how-to-get-bzz"
target="_blank"
rel="noopener noreferrer"
>
<ExternalLinkIcon size="14px" />
Need help topping up?
</a>
</div>
) : (
<div>Wallet information is not available</div>
)}
<div className="fm-upgrade-drive-modal-info fm-swarm-orange-font">
<a
className="fm-upgrade-drive-modal-info-link fm-pointer"
href="https://www.ethswarm.org/get-bzz#how-to-get-bzz"
target="_blank"
rel="noopener noreferrer"
>
<ExternalLinkIcon size="14px" />
Need help topping up?
</a>
</div>
</div>
</div>
<div className="fm-modal-window-body">
<div className="fm-upgrade-drive-modal-input-row">
<div className="fm-modal-window-input-container">
<CustomDropdown
id="drive-type"
label="Additional storage"
icon={<DatabaseIcon size="14px" color="rgb(237, 129, 49)" />}
options={sizeMarks}
value={capacityIndex === 0 ? -1 : capacity.toBytes()}
onChange={handleCapacityChange}
/>
</div>
<div className="fm-modal-window-input-container">
<CustomDropdown
id="drive-type"
label="Duration"
icon={<CalendarIcon size="14px" color="rgb(237, 129, 49)" />}
options={desiredLifetimeOptions}
value={lifetimeIndex}
onChange={(value, index) => {
setLifetimeIndex(value)
}}
/>
</div>
</div>
<div className="fm-modal-white-section">
<div className="fm-emphasized-text">Summary</div>
<div>
Drive: {drive.name} {drive.isAdmin && <Warning style={{ fontSize: '16px' }} />}
</div>
<div>
BatchId: {stamp.label} ({shortBatchId})
</div>
<div>Expiry: {stamp.duration.toEndDate().toLocaleDateString()}</div>
<div>
Additional storage:{' '}
{(() => {
if (capacityIndex === 0) return '0 GB'
return `${
fromBytesConversion(Math.max(capacity.toBytes() - stamp.size.toBytes(), 0), 'GB').toFixed(3) + ' GB'
} ${durationExtensionCost === '' ? '' : '(' + extensionCost + ' xBZZ)'}`
})()}
</div>
<div>
Extension period:{' '}
{`${desiredLifetimeOptions[lifetimeIndex]?.label} ${
capacityExtensionCost === '' ? '' : '(' + extensionCost + ' xBZZ)'
}`}
<div className="fm-modal-window-body">
<div className="fm-upgrade-drive-modal-input-row">
<div className="fm-modal-window-input-container">
<CustomDropdown
id="drive-type"
label="Additional storage"
icon={<DatabaseIcon size="14px" color="rgb(237, 129, 49)" />}
options={sizeMarks}
value={capacityIndex === 0 ? -1 : capacity.toBytes()}
onChange={handleCapacityChange}
/>
</div>
<div className="fm-modal-window-input-container">
<CustomDropdown
id="drive-type"
label="Duration"
icon={<CalendarIcon size="14px" color="rgb(237, 129, 49)" />}
options={desiredLifetimeOptions}
value={lifetimeIndex}
onChange={(value, index) => {
setLifetimeIndex(value)
}}
/>
</div>
</div>
<div className="fm-upgrade-drive-modal-info fm-emphasized-text">
Total:{' '}
<span className="fm-swarm-orange-font">
{extensionCost} xBZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
</span>
<div className="fm-modal-white-section">
<div className="fm-emphasized-text">Summary</div>
<div>
Drive: {truncateNameMiddle(drive.name)} {drive.isAdmin && <Warning style={{ fontSize: '16px' }} />}
</div>
<div>
BatchId: {truncateNameMiddle(stamp.label, 25)} ({shortBatchId})
</div>
<div>Expiry: {stamp.duration.toEndDate().toLocaleDateString()}</div>
<div>
Additional storage:{' '}
{(() => {
if (capacityIndex === 0) return '0 GB'
return `${
fromBytesConversion(Math.max(capacity.toBytes() - stamp.size.toBytes(), 0), 'GB').toFixed(3) + ' GB'
} ${durationExtensionCost === '' ? '' : '(' + extensionCost + ' xBZZ)'}`
})()}
</div>
<div>
Extension period:{' '}
{`${desiredLifetimeOptions[lifetimeIndex]?.label} ${
capacityExtensionCost === '' ? '' : '(' + extensionCost + ' xBZZ)'
}`}
</div>
<div className="fm-upgrade-drive-modal-info fm-emphasized-text">
Total:{' '}
<span className="fm-swarm-orange-font">
{extensionCost} xBZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
</span>
</div>
</div>
</div>
</div>
@@ -296,7 +334,7 @@ export function UpgradeDriveModal({
try {
setIsSubmitting(true)
window.dispatchEvent(
new CustomEvent('fm:drive-upgrade-start', {
new CustomEvent(FILE_MANAGER_EVENTS.DRIVE_UPGRADE_START, {
detail: { driveId: drive.id.toString() },
}),
)
@@ -306,24 +344,19 @@ export function UpgradeDriveModal({
await beeApi.extendStorage(
stamp.batchID,
capacity,
durationExtensionCost === '0'
? Duration.ZERO
: Duration.fromEndDate(validityEndDate, stamp.duration.toEndDate()),
lifetimeIndex >= 0
? Duration.fromEndDate(validityEndDate, stamp.duration.toEndDate())
: Duration.ZERO,
undefined,
false,
defaultErasureCodeLevel,
)
// TODO: replace eventlisteners with a better maintainable solution
window.dispatchEvent(
new CustomEvent('fm:drive-upgrade-end', {
detail: { driveId: drive.id.toString(), success: true },
}),
)
startPolling(stamp, capacityIndex > 0)
} catch (e) {
const msg = e instanceof Error ? e.message : 'Upgrade failed'
window.dispatchEvent(
new CustomEvent('fm:drive-upgrade-end', {
new CustomEvent(FILE_MANAGER_EVENTS.DRIVE_UPGRADE_END, {
detail: {
driveId: drive.id.toString(),
success: false,