feat: sync and update with all changes from fork (#720)

* feat: sync and update with all changes from fork
* refactor: extract clipboard copy logic into custom hook
* fix: correct spelling of DEFAULT_REFRESH_FREQUENCY_MS in Stamps and WalletBalance providers
* refactor(ui-tests): replace fixed sleeps with condition-based waits
* fix: handle null values for size and granteeCount in infoGroups
* fix(lint): add newline at end of file in useClipboardCopy hook
* fix(ui-tests): page.goto URL
* refactor: update import paths for useClipboardCopy

---------

Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com>
This commit is contained in:
Bálint Ujvári
2026-03-02 11:34:39 +01:00
committed by GitHub
parent b0f00a624a
commit 519c411db0
303 changed files with 16609 additions and 29415 deletions
@@ -1,15 +1,22 @@
import { ReactElement } from 'react'
import ImageIcon from 'remixicon-react/Image2LineIcon'
import FileIcon from 'remixicon-react/FileTextLineIcon'
import ImageIcon from 'remixicon-react/Image2LineIcon'
import { guessMime } from './view'
interface ContextMenuProps {
icon: string
name: string
metadata?: Record<string, string>
size?: string
color?: string
}
export function GetIconElement({ icon, size = '21px', color = '#ed8131' }: ContextMenuProps): ReactElement {
switch (icon) {
export function GetIconElement({ name, metadata, size = '21px', color = '#ed8131' }: ContextMenuProps): ReactElement {
const { mime } = guessMime(name, metadata)
const iconType = mime.split('/')[0]?.toLowerCase() || 'file'
switch (iconType) {
case 'image':
return <ImageIcon size={size} color={color} />
default:
+4 -3
View File
@@ -1,11 +1,13 @@
import { BatchId, Bee, BZZ, Duration, PostageBatch, RedundancyLevel, Size } from '@ethersphere/bee-js'
import {
FileManagerBase,
DriveInfo,
estimateDriveListMetadataSize,
estimateFileInfoMetadataSize,
FileInfo,
FileManagerBase,
} from '@solarpunkltd/file-manager-lib'
import React from 'react'
import { getHumanReadableFileSize } from '../../../utils/file'
import { ActionTag } from '../constants/transfers'
@@ -57,7 +59,7 @@ export const fmGetStorageCost = async (
}
return undefined
} catch (e) {
} catch {
return undefined
}
}
@@ -172,7 +174,6 @@ export const handleCreateDrive = async (options: CreateDriveOptions): Promise<vo
batchId = await beeApi.buyStorage(size, duration, { label }, undefined, encryption, redundancyLevel)
} else {
// TODO: redundant, fm checks for stamp validtiy
const isValid = await validateStampStillExists(beeApi, existingBatch.batchID)
if (!isValid) {
+11 -12
View File
@@ -1,7 +1,8 @@
import { PrivateKey } from '@ethersphere/bee-js'
import { Bytes, PrivateKey } from '@ethersphere/bee-js'
import { FileInfo, FileStatus } from '@solarpunkltd/file-manager-lib'
import { keccak256 } from '@ethersproject/keccak256'
import { toUtf8Bytes } from '@ethersproject/strings'
import React from 'react'
import { LocalStorageKeys } from '../../../utils/localStorage'
import { lifetimeAdjustments } from '../constants/stamps'
export function getDaysLeft(expiryDate: Date): number {
@@ -83,19 +84,17 @@ export function getFileId(fi: FileInfo): string {
return fi.topic.toString()
}
export const KEY_STORAGE = 'privateKey'
export function getSigner(input: string): PrivateKey {
const normalized = input.trim().toLowerCase()
const hash = keccak256(toUtf8Bytes(normalized))
const privateKeyHex = hash.slice(2)
const inputBytes = Bytes.fromUtf8(normalized)
const privateKeyHex = Bytes.keccak256(inputBytes).toHex()
return new PrivateKey(privateKeyHex)
}
export function getSignerPk(): PrivateKey | undefined {
try {
const fromLocalPk = localStorage.getItem(KEY_STORAGE)
const fromLocalPk = localStorage.getItem(LocalStorageKeys.fmPrivateKey)
if (!fromLocalPk) {
// eslint-disable-next-line no-console
@@ -107,24 +106,24 @@ export function getSignerPk(): PrivateKey | undefined {
return new PrivateKey(fromLocalPk)
} catch (err) {
// eslint-disable-next-line no-console
console.error(`Private key error in localStorage under key "${KEY_STORAGE}": `, err)
console.error(`Private key error in localStorage under key "${LocalStorageKeys.fmPrivateKey}": `, err)
return undefined
}
}
export function setSignerPk(pk: string): void {
localStorage.setItem(KEY_STORAGE, pk)
localStorage.setItem(LocalStorageKeys.fmPrivateKey, pk)
}
export function removeSignerPk(): void {
localStorage.removeItem(KEY_STORAGE)
localStorage.removeItem(LocalStorageKeys.fmPrivateKey)
}
export const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
export const safeSetState =
<T>(ref: React.MutableRefObject<boolean>, setter: React.Dispatch<React.SetStateAction<T>>) =>
<T>(ref: React.RefObject<boolean>, setter: React.Dispatch<React.SetStateAction<T>>) =>
(value: React.SetStateAction<T>) => {
if (ref.current) setter(value)
}
+9 -2
View File
@@ -1,8 +1,10 @@
import { FileInfo, FileManager } from '@solarpunkltd/file-manager-lib'
import { guessMime, VIEWERS } from './view'
import { AbortManager } from './abortManager'
import { DownloadProgress, DownloadState } from '../constants/transfers'
import { AbortManager } from './abortManager'
import { guessMime, VIEWERS } from './view'
const downloadAborts = new AbortManager()
enum Errors {
@@ -54,6 +56,7 @@ const processStream = async (
onDownloadProgress?.({ progress, isDownloading: false, state: DownloadState.Cancelled })
} else {
onDownloadProgress?.({ progress, isDownloading: false, state: DownloadState.Error })
// eslint-disable-next-line no-console
console.error('Failed to process stream: ', e)
}
@@ -70,6 +73,7 @@ const processStream = async (
}
} catch (e: unknown) {
/* no-op */
// eslint-disable-next-line no-console
console.error('filehandle close/abort error: ', e)
}
@@ -106,6 +110,7 @@ const streamToBlob = async (
onDownloadProgress?.({ progress, isDownloading: false, state: DownloadState.Cancelled })
} else {
onDownloadProgress?.({ progress, isDownloading: false, state: DownloadState.Error })
// eslint-disable-next-line no-console
console.error('Error during stream processing: ', error)
}
@@ -133,6 +138,7 @@ interface FileInfoWithHandle {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isPickerSupported = (): boolean => typeof (window as any).showSaveFilePicker === 'function'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isDirectoryPickerSupported = (): boolean => typeof (window as any).showDirectoryPicker === 'function'
@@ -384,6 +390,7 @@ export const startDownloadingQueue = async (
if (!isAbortError) {
tracker?.({ progress: 0, isDownloading: false, state: DownloadState.Error })
// eslint-disable-next-line no-console
console.error('download queue error: ', error)
} else {
@@ -1,9 +1,10 @@
import type { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
import type { FileManagerBase } from '@solarpunkltd/file-manager-lib'
import type { PostageBatch, RedundancyLevel } from '@ethersphere/bee-js'
import type { DriveInfo, FileInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
import { ActionTag } from '../constants/transfers'
import { verifyDriveSpace } from './bee'
import { capitalizeFirstLetter } from './common'
import { ActionTag } from '../constants/transfers'
export enum FileOperation {
Trash = 'trash',
+14 -9
View File
@@ -1,13 +1,14 @@
import type { ReactElement } from 'react'
import { FileStatus, FileInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
import { GetGranteesResult, PostageBatch } from '@ethersphere/bee-js'
import GeneralIcon from 'remixicon-react/FileTextLineIcon'
import { FileInfo, FileManagerBase, FileStatus } from '@solarpunkltd/file-manager-lib'
import type { ReactElement } from 'react'
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
import AccessIcon from 'remixicon-react/ShieldKeyholeLineIcon'
import GeneralIcon from 'remixicon-react/FileTextLineIcon'
import HardDriveIcon from 'remixicon-react/HardDrive2LineIcon'
import AccessIcon from 'remixicon-react/ShieldKeyholeLineIcon'
import { erasureCodeMarks, FEED_INDEX_ZERO } from '../constants/common'
import { indexStrToBigint, truncateNameMiddle } from './common'
import { FEED_INDEX_ZERO, erasureCodeMarks } from '../constants/common'
export type FileProperty = { key: string; label: string; value: string; raw?: string }
export type FilePropertyGroup = { title: string; icon?: ReactElement; properties: FileProperty[] }
@@ -98,7 +99,7 @@ function buildGeneralGroup(
icon: <GeneralIcon size="14px" color="rgb(237, 129, 49)" />,
properties: [
{ key: 'type', label: 'Type', value: mime ?? dash },
{ key: 'size', label: 'Size', value: size != null ? formatBytes(size) : dash },
{ key: 'size', label: 'Size', value: size !== undefined && size !== null ? formatBytes(size) : dash },
{ key: 'count', label: 'Items', value: fileCount ?? '1' },
{ key: 'path', label: 'Location', value: truncateNameMiddle(path || dash, 35, 10, 10) },
{
@@ -137,7 +138,11 @@ function buildAccessGroup(fi: FileInfo, granteeCount?: number): FilePropertyGrou
raw: fi.owner.toString(),
},
{ key: 'shared', label: 'Sharing', value: fi.shared ? 'Shared' : 'Private' },
{ key: 'grantees', label: 'Grantees', value: granteeCount != null ? `${granteeCount}` : dash },
{
key: 'grantees',
label: 'Grantees',
value: granteeCount !== undefined && granteeCount !== null ? `${granteeCount}` : dash,
},
{
key: 'actpub',
label: 'ACT Publisher',
@@ -167,7 +172,7 @@ function buildStorageGroup(fi: FileInfo, driveName: string, stamp?: PostageBatch
const redundancyLabel =
fi.redundancyLevel !== undefined
? erasureCodeMarks.find(mark => mark.value === fi.redundancyLevel)?.label ?? fi.redundancyLevel.toString()
? (erasureCodeMarks.find(mark => mark.value === fi.redundancyLevel)?.label ?? fi.redundancyLevel.toString())
: dash
return {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Point, Dir } from './common'
import { Dir, Point } from './common'
export function computeContextMenuPosition(args: {
clickPos: Point