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:
@@ -1,18 +1,20 @@
|
||||
import { ReactElement, useState, useMemo, useEffect, useContext, useCallback } from 'react'
|
||||
import './AdminStatusBar.scss'
|
||||
import { ProgressBar } from '../ProgressBar/ProgressBar'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo, estimateDriveListMetadataSize } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal'
|
||||
import { calculateStampCapacityMetrics } from '../../utils/bee'
|
||||
import { getHumanReadableFileSize } from '../../../../utils/file'
|
||||
import { FILE_MANAGER_EVENTS, POLLING_TIMEOUT_MS } from '../../constants/common'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { useStampPolling } from '../../hooks/useStampPolling'
|
||||
import { calculateStampCapacityMetrics } from '../../utils/bee'
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal'
|
||||
import { ProgressBar } from '../ProgressBar/ProgressBar'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||
import { UpgradeTimeoutModal } from '../UpgradeTimeoutModal/UpgradeTimeoutModal'
|
||||
import { FILE_MANAGER_EVENTS, POLLING_TIMEOUT_MS } from '../../constants/common'
|
||||
import { useStampPolling } from '../../hooks/useStampPolling'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
|
||||
import './AdminStatusBar.scss'
|
||||
|
||||
interface AdminStatusBarProps {
|
||||
adminStamp: PostageBatch | null
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import './Button.scss'
|
||||
|
||||
interface ButtonProps {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ReactElement } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import '../../styles/global.scss'
|
||||
import './ConfirmModal.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
interface ConfirmModalProps {
|
||||
title?: React.ReactNode
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { BeeModes, BZZ, DAI, Duration, RedundancyLevel, Size, Utils } from '@ethersphere/bee-js'
|
||||
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { BeeModes, BZZ, DAI, Duration, RedundancyLevel, Size, Utils } from '@ethersphere/bee-js'
|
||||
import './CreateDriveModal.scss'
|
||||
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
|
||||
import { Button } from '../Button/Button'
|
||||
import { fmFetchCost, handleCreateDrive } from '../../utils/bee'
|
||||
import { getExpiryDateByLifetime } from '../../utils/common'
|
||||
import { Context as BeeContext } from '../../../../providers/Bee'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { getHumanReadableFileSize } from '../../../../utils/file'
|
||||
import { erasureCodeMarks } from '../../constants/common'
|
||||
import { desiredLifetimeOptions } from '../../constants/stamps'
|
||||
import { Context as BeeContext } from '../../../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { FMSlider } from '../Slider/Slider'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { getHumanReadableFileSize } from '../../../../utils/file'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { fmFetchCost, handleCreateDrive } from '../../utils/bee'
|
||||
import { getExpiryDateByLifetime } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
|
||||
import { FMSlider } from '../Slider/Slider'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import './CreateDriveModal.scss'
|
||||
|
||||
const minMarkValue = Math.min(...erasureCodeMarks.map(mark => mark.value))
|
||||
const maxMarkValue = Math.max(...erasureCodeMarks.map(mark => mark.value))
|
||||
@@ -26,7 +27,7 @@ interface CreateDriveModalProps {
|
||||
onCreationStarted: (driveName: string) => void
|
||||
onCreationError: (name: string) => void
|
||||
}
|
||||
// TODO: select existing batch id or create a new one - just like in InitialModal
|
||||
|
||||
export function CreateDriveModal({
|
||||
onCancelClick,
|
||||
onDriveCreated,
|
||||
@@ -72,7 +73,7 @@ export function CreateDriveModal({
|
||||
}
|
||||
}, [duplicate, nameExists])
|
||||
|
||||
const handleCapacityChange = (value: number, index: number) => {
|
||||
const handleCapacityChange = (_: number, index: number) => {
|
||||
setCapacityIndex(index)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import './CustomDropdown.scss'
|
||||
import { useRef, useState } from 'react'
|
||||
import ArrowDropdown from 'remixicon-react/ArrowDropDownLineIcon'
|
||||
|
||||
import { useClickOutside } from '../../hooks/useClickOutside'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import './CustomDropdown.scss'
|
||||
|
||||
interface Option {
|
||||
value: number
|
||||
label: string
|
||||
@@ -31,7 +33,7 @@ export function CustomDropdown({
|
||||
infoText,
|
||||
}: CustomDropdownProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useClickOutside(ref, () => setOpen(false), open)
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import FormControl from '@mui/material/FormControl'
|
||||
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||
import Radio from '@mui/material/Radio'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import './DeleteFileModal.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import { createPortal } from 'react-dom'
|
||||
import TrashIcon from 'remixicon-react/DeleteBin6LineIcon'
|
||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||
import TrashIcon from 'remixicon-react/DeleteBin6LineIcon'
|
||||
|
||||
import Radio from '@material-ui/core/Radio'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { FileAction } from '../../constants/transfers'
|
||||
import { Button } from '../Button/Button'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import { FileAction } from '../../constants/transfers'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import './DeleteFileModal.scss'
|
||||
|
||||
interface DeleteFileModalProps {
|
||||
name?: string
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import '../../styles/global.scss'
|
||||
import './DestroyDriveModal.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
|
||||
interface DestroyDriveModalProps {
|
||||
drive: DriveInfo
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ReactElement } from 'react'
|
||||
import './ErrorModal.scss'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import './ErrorModal.scss'
|
||||
|
||||
interface ErrorModalProps {
|
||||
label: string
|
||||
onClick: () => void
|
||||
|
||||
+18
-15
@@ -1,20 +1,20 @@
|
||||
import { ReactElement, useState, useMemo, useEffect } from 'react'
|
||||
import { Warning } from '@material-ui/icons'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Warning } from '@mui/icons-material'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
|
||||
|
||||
import { calculateStampCapacityMetrics } from '../../utils/bee'
|
||||
import { getDaysLeft } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||
|
||||
import './ExpiringNotificationModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
import { createPortal } from 'react-dom'
|
||||
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||
import { getDaysLeft } from '../../utils/common'
|
||||
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { calculateStampCapacityMetrics } from '../../utils/bee'
|
||||
|
||||
const EXPIRING_ITEMS_PAGE_SIZE = 3
|
||||
|
||||
interface ExpiringNotificationModalProps {
|
||||
@@ -52,7 +52,10 @@ export function ExpiringNotificationModal({
|
||||
const paginatedStamps = sortedStamps.slice(startIndex, startIndex + EXPIRING_ITEMS_PAGE_SIZE)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(0)
|
||||
if (currentPage !== 0) {
|
||||
setCurrentPage(0)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [stamps])
|
||||
|
||||
if (stamps.length === 0) return <></>
|
||||
|
||||
@@ -1,52 +1,69 @@
|
||||
import { ReactElement, useEffect, useLayoutEffect, useRef, useState, useContext, useMemo, useCallback } from 'react'
|
||||
import './FileBrowser.scss'
|
||||
import { FileBrowserHeader } from './FileBrowserHeader/FileBrowserHeader'
|
||||
import { FileBrowserContent } from './FileBrowserContent/FileBrowserContent'
|
||||
import { useContextMenu } from '../../hooks/useContextMenu'
|
||||
import { NotificationBar } from '../NotificationBar/NotificationBar'
|
||||
import { FileAction, FileTransferType, TransferStatus, ViewType } from '../../constants/transfers'
|
||||
import { FileProgressNotification } from '../FileProgressNotification/FileProgressNotification'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import React, {
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
||||
import { useView } from '../../../../pages/filemanager/ViewContext'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { useTransfers } from '../../hooks/useTransfers'
|
||||
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
||||
import { useFileFiltering } from '../../hooks/useFileFiltering'
|
||||
import { useDragAndDrop } from '../../hooks/useDragAndDrop'
|
||||
import { useBulkActions } from '../../hooks/useBulkActions'
|
||||
import { SortKey, SortDir, useSorting } from '../../hooks/useSorting'
|
||||
|
||||
import { Point, Dir, safeSetState, getFileId } from '../../utils/common'
|
||||
import { computeContextMenuPosition } from '../../utils/ui'
|
||||
import { FileBrowserTopBar } from './FileBrowserTopBar/FileBrowserTopBar'
|
||||
import { handleDestroyAndForgetDrive } from '../../utils/bee'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { ErrorModal } from '../ErrorModal/ErrorModal'
|
||||
import { FileBrowserModals } from './FileBrowserModals'
|
||||
import { FileBrowserContextMenu } from './FileBrowserMenu/FileBrowserContextMenu'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { FileAction, FileTransferType, TransferStatus, ViewType } from '../../constants/transfers'
|
||||
import { useBulkActions } from '../../hooks/useBulkActions'
|
||||
import { useContextMenu } from '../../hooks/useContextMenu'
|
||||
import { useDragAndDrop } from '../../hooks/useDragAndDrop'
|
||||
import { useFileFiltering } from '../../hooks/useFileFiltering'
|
||||
import { SortDir, SortKey, useSorting } from '../../hooks/useSorting'
|
||||
import { useTransfers } from '../../hooks/useTransfers'
|
||||
import { handleDestroyAndForgetDrive } from '../../utils/bee'
|
||||
import { Dir, getFileId, Point, safeSetState } from '../../utils/common'
|
||||
import { computeContextMenuPosition } from '../../utils/ui'
|
||||
import { ProgressDestroyModal } from '../DestroyDriveModal/DestroyDriveModal'
|
||||
import { ErrorModal } from '../ErrorModal/ErrorModal'
|
||||
import { FileProgressNotification } from '../FileProgressNotification/FileProgressNotification'
|
||||
import { NotificationBar } from '../NotificationBar/NotificationBar'
|
||||
|
||||
const renderDestroySpinner = (
|
||||
isDestroying: boolean,
|
||||
isProgressModalOpen: boolean,
|
||||
currentDrive: DriveInfo | undefined,
|
||||
setter: () => void,
|
||||
) => {
|
||||
import { FileBrowserContent } from './FileBrowserContent/FileBrowserContent'
|
||||
import { FileBrowserHeader } from './FileBrowserHeader/FileBrowserHeader'
|
||||
import { FileBrowserContextMenu } from './FileBrowserMenu/FileBrowserContextMenu'
|
||||
import { FileBrowserTopBar } from './FileBrowserTopBar/FileBrowserTopBar'
|
||||
import { FileBrowserModals } from './FileBrowserModals'
|
||||
|
||||
import './FileBrowser.scss'
|
||||
|
||||
function DestroyProgressModal({
|
||||
isDestroying,
|
||||
isProgressModalOpen,
|
||||
currentDrive,
|
||||
onMinimize,
|
||||
}: {
|
||||
isDestroying: boolean
|
||||
isProgressModalOpen: boolean
|
||||
currentDrive?: DriveInfo
|
||||
onMinimize: () => void
|
||||
}) {
|
||||
if (isProgressModalOpen && isDestroying && currentDrive) {
|
||||
return <ProgressDestroyModal drive={currentDrive} onMinimize={setter} />
|
||||
return <ProgressDestroyModal drive={currentDrive} onMinimize={onMinimize} />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const showDestroyModal = (isDestroying: boolean, setter: () => void) => {
|
||||
function DestroyingOverlay({ isDestroying, onClick }: { isDestroying: boolean; onClick: () => void }) {
|
||||
if (!isDestroying) return null
|
||||
|
||||
return (
|
||||
<div className="fm-refresh-overlay" aria-busy="true" aria-live="polite">
|
||||
<div
|
||||
className="fm-refresh-content"
|
||||
onClick={setter}
|
||||
onClick={onClick}
|
||||
style={{ cursor: 'pointer' }}
|
||||
title="Click to show progress modal"
|
||||
>
|
||||
@@ -57,6 +74,22 @@ const showDestroyModal = (isDestroying: boolean, setter: () => void) => {
|
||||
)
|
||||
}
|
||||
|
||||
function ErrorModalBlock({
|
||||
showError,
|
||||
label,
|
||||
onOk,
|
||||
}: {
|
||||
showError: boolean
|
||||
label: string
|
||||
onOk: () => void
|
||||
}): ReactElement | null {
|
||||
if (!showError) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <ErrorModal label={label} onClick={onOk} />
|
||||
}
|
||||
|
||||
const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => {
|
||||
const out: File[] = []
|
||||
const items = e.clipboardData?.items ?? []
|
||||
@@ -78,6 +111,67 @@ interface FileBrowserProps {
|
||||
setErrorMessage?: (error: string) => void
|
||||
}
|
||||
|
||||
type FileBrowserContextMenuBlockProps = {
|
||||
showContext: boolean
|
||||
contextRef: React.RefObject<HTMLDivElement | null>
|
||||
safePos: { x: number; y: number }
|
||||
dropDir: Dir
|
||||
drives: DriveInfo[]
|
||||
view: ViewType
|
||||
bulk: ReturnType<typeof useBulkActions>
|
||||
adminStamp: PostageBatch | undefined
|
||||
doRefresh: () => void
|
||||
onContextUploadFile: () => void
|
||||
setConfirmBulkRestore: (b: boolean) => void
|
||||
setShowBulkDeleteModal: (b: boolean) => void
|
||||
setShowDestroyDriveModal: (b: boolean) => void
|
||||
}
|
||||
|
||||
function FileBrowserContextMenuBlock({
|
||||
showContext,
|
||||
contextRef,
|
||||
safePos,
|
||||
dropDir,
|
||||
drives,
|
||||
view,
|
||||
bulk,
|
||||
adminStamp,
|
||||
doRefresh,
|
||||
onContextUploadFile,
|
||||
setConfirmBulkRestore,
|
||||
setShowBulkDeleteModal,
|
||||
setShowDestroyDriveModal,
|
||||
}: FileBrowserContextMenuBlockProps): ReactElement | null {
|
||||
if (!showContext) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={contextRef}
|
||||
className="fm-file-browser-context-menu fm-context-menu"
|
||||
style={{ top: safePos.y, left: safePos.x }}
|
||||
data-drop={dropDir}
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<FileBrowserContextMenu
|
||||
drives={drives}
|
||||
view={view}
|
||||
selectedFilesCount={bulk.selectedFiles.length}
|
||||
onRefresh={doRefresh}
|
||||
enableRefresh={Boolean(adminStamp)}
|
||||
onUploadFile={onContextUploadFile}
|
||||
onBulkDownload={() => bulk.bulkDownload(bulk.selectedFiles)}
|
||||
onBulkRestore={() => setConfirmBulkRestore(true)}
|
||||
onBulkDelete={() => setShowBulkDeleteModal(true)}
|
||||
onBulkDestroy={() => setShowDestroyDriveModal(true)}
|
||||
onBulkForget={() => bulk.bulkForget(bulk.selectedFiles)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps): ReactElement {
|
||||
const { showContext, pos, contextRef, handleContextMenu, handleCloseContext } = useContextMenu<HTMLDivElement>()
|
||||
const { view, setActualItemView } = useView()
|
||||
@@ -120,11 +214,14 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
const q = query.trim().toLowerCase()
|
||||
const isSearchMode = q.length > 0
|
||||
|
||||
const getDriveName = (fi: FileInfo): string => {
|
||||
const match = drives.find(d => d.id.toString() === fi.driveId.toString())
|
||||
const getDriveName = useCallback(
|
||||
(driveId: string): string => {
|
||||
const match = drives.find(d => d.id.toString() === driveId)
|
||||
|
||||
return match?.name ?? ''
|
||||
}
|
||||
return match?.name ?? ''
|
||||
},
|
||||
[drives],
|
||||
)
|
||||
|
||||
const openTopbarMenu = (anchorEl: HTMLElement) => {
|
||||
const r = anchorEl.getBoundingClientRect()
|
||||
@@ -133,9 +230,7 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
const minY = (bodyRect?.top ?? 0) + 8
|
||||
const clickY = Math.max(Math.round(r.bottom + 6), minY)
|
||||
const fakeEvt = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
preventDefault: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
stopPropagation: () => {},
|
||||
clientX: clickX,
|
||||
clientY: clickY,
|
||||
@@ -379,7 +474,6 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
const showDragOverlay = isDragging && Boolean(currentDrive)
|
||||
const fileCountText = bulk.selectedFiles.length === 1 ? 'file' : 'files'
|
||||
|
||||
// Memoize onBulk object to prevent FileBrowserContent rerenders
|
||||
const onBulk = useMemo(
|
||||
() => ({
|
||||
download: () => bulk.bulkDownload(bulk.selectedFiles),
|
||||
@@ -445,42 +539,32 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
onBulk={onBulk}
|
||||
setErrorMessage={setErrorMessage}
|
||||
/>
|
||||
{showError && (
|
||||
<ErrorModal
|
||||
label={errorMessage || 'An error occurred'}
|
||||
onClick={() => {
|
||||
setShowError(false)
|
||||
setErrorMessage?.('')
|
||||
<ErrorModalBlock
|
||||
showError={Boolean(showError)}
|
||||
label={errorMessage || 'An error occurred'}
|
||||
onOk={() => {
|
||||
setShowError(false)
|
||||
setErrorMessage?.('')
|
||||
|
||||
return
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
return
|
||||
}}
|
||||
/>
|
||||
|
||||
{showContext && (
|
||||
<div
|
||||
ref={contextRef}
|
||||
className="fm-file-browser-context-menu fm-context-menu"
|
||||
style={{ top: safePos.y, left: safePos.x }}
|
||||
data-drop={dropDir}
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<FileBrowserContextMenu
|
||||
drives={drives}
|
||||
view={view}
|
||||
selectedFilesCount={bulk.selectedFiles.length}
|
||||
onRefresh={doRefresh}
|
||||
enableRefresh={Boolean(fm?.adminStamp)}
|
||||
onUploadFile={onContextUploadFile}
|
||||
onBulkDownload={() => bulk.bulkDownload(bulk.selectedFiles)}
|
||||
onBulkRestore={() => setConfirmBulkRestore(true)}
|
||||
onBulkDelete={() => setShowBulkDeleteModal(true)}
|
||||
onBulkDestroy={() => setShowDestroyDriveModal(true)}
|
||||
onBulkForget={() => bulk.bulkForget(bulk.selectedFiles)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FileBrowserContextMenuBlock
|
||||
showContext={showContext}
|
||||
contextRef={contextRef}
|
||||
safePos={safePos}
|
||||
dropDir={dropDir}
|
||||
drives={drives}
|
||||
view={view}
|
||||
bulk={bulk}
|
||||
adminStamp={fm?.adminStamp}
|
||||
doRefresh={doRefresh}
|
||||
onContextUploadFile={onContextUploadFile}
|
||||
setConfirmBulkRestore={setConfirmBulkRestore}
|
||||
setShowBulkDeleteModal={setShowBulkDeleteModal}
|
||||
setShowDestroyDriveModal={setShowDestroyDriveModal}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showDragOverlay && (
|
||||
@@ -537,9 +621,13 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showDestroyModal(isDestroying, () => setIsProgressModalOpen(true))}
|
||||
|
||||
{renderDestroySpinner(isDestroying, isProgressModalOpen, currentDrive, () => setIsProgressModalOpen(false))}
|
||||
<DestroyingOverlay isDestroying={isDestroying} onClick={() => setIsProgressModalOpen(true)} />
|
||||
<DestroyProgressModal
|
||||
isDestroying={isDestroying}
|
||||
isProgressModalOpen={isProgressModalOpen}
|
||||
currentDrive={currentDrive}
|
||||
onMinimize={() => setIsProgressModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="fm-file-browser-footer">
|
||||
@@ -556,7 +644,7 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
type={FileTransferType.Download}
|
||||
open={isDownloading}
|
||||
items={downloadItems}
|
||||
onRowClose={name => cancelOrDismissDownload(name)}
|
||||
onRowClose={(name: string) => cancelOrDismissDownload(name)}
|
||||
onCloseAll={() => dismissAllDownloads()}
|
||||
/>
|
||||
<NotificationBar setErrorMessage={setErrorMessage} />
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
import { ReactElement, useCallback, memo } from 'react'
|
||||
import { FileItem } from '../FileItem/FileItem'
|
||||
import { FileInfo, DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { memo, ReactElement, useCallback } from 'react'
|
||||
|
||||
import { DownloadProgress, TrackDownloadProps, ViewType } from '../../../constants/transfers'
|
||||
import { getFileId } from '../../../utils/common'
|
||||
import { FileItem } from '../FileItem/FileItem'
|
||||
|
||||
interface FileBrowserContentProps {
|
||||
listToRender: FileInfo[]
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
import { ReactElement } from 'react'
|
||||
import DownIcon from 'remixicon-react/ArrowDownSLineIcon'
|
||||
|
||||
import { useBulkActions } from '../../../hooks/useBulkActions'
|
||||
import { SortDir, SortKey } from '../../../hooks/useSorting'
|
||||
import { capitalizeFirstLetter } from '../../../../../../src/modules/filemanager/utils/common'
|
||||
|
||||
import { capitalizeFirstLetter } from '@/modules/filemanager/utils/common'
|
||||
|
||||
interface FileBrowserHeaderProps {
|
||||
isSearchMode: boolean
|
||||
|
||||
+6
-4
@@ -1,10 +1,12 @@
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { ReactElement } from 'react'
|
||||
import '../FileBrowser.scss'
|
||||
import { ViewType } from '../../../constants/transfers'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import { ViewType } from '../../../constants/transfers'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
|
||||
import '../FileBrowser.scss'
|
||||
|
||||
interface FileBrowserContextMenuProps {
|
||||
drives: DriveInfo[]
|
||||
view: ViewType
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement } from 'react'
|
||||
import type { FileInfo, DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { FileAction } from '../../constants/transfers'
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { DeleteFileModal } from '../DeleteFileModal/DeleteFileModal'
|
||||
import { DestroyDriveModal } from '../DestroyDriveModal/DestroyDriveModal'
|
||||
import { FileAction } from '../../constants/transfers'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
interface FileBrowserModalsProps {
|
||||
showDeleteModal: boolean
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
import { ReactElement } from 'react'
|
||||
import './FileBrowserTopBar.scss'
|
||||
|
||||
import { useView } from '../../../../../pages/filemanager/ViewContext'
|
||||
import { ViewType } from '../../../constants/transfers'
|
||||
|
||||
import './FileBrowserTopBar.scss'
|
||||
|
||||
type Props = {
|
||||
onOpenMenu?: (anchorEl: HTMLElement) => void
|
||||
canOpen?: boolean
|
||||
|
||||
@@ -1,33 +1,62 @@
|
||||
import { ReactElement, useContext, useLayoutEffect, useMemo, useState, useRef, useEffect, useCallback } from 'react'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import React, {
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import './FileItem.scss'
|
||||
import { GetIconElement } from '../../../utils/GetIconElement'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { Context as SettingsContext } from '../../../../../providers/Settings'
|
||||
import { DownloadProgress, TrackDownloadProps, ViewType } from '../../../constants/transfers'
|
||||
import { GetInfoModal } from '../../GetInfoModal/GetInfoModal'
|
||||
import { VersionHistoryModal } from '../../VersionHistoryModal/VersionHistoryModal'
|
||||
import { DeleteFileModal } from '../../DeleteFileModal/DeleteFileModal'
|
||||
import { RenameFileModal } from '../../RenameFileModal/RenameFileModal'
|
||||
import { buildGetInfoGroups } from '../../../utils/infoGroups'
|
||||
import type { FilePropertyGroup } from '../../../utils/infoGroups'
|
||||
import { useView } from '../../../../../pages/filemanager/ViewContext'
|
||||
import { Context as FMContext } from '../../../../../providers/FileManager'
|
||||
import { DestroyDriveModal } from '../../DestroyDriveModal/DestroyDriveModal'
|
||||
import { ConfirmModal } from '../../ConfirmModal/ConfirmModal'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
import { Dir, formatBytes, isTrashed, safeSetState, truncateNameMiddle } from '../../../utils/common'
|
||||
import { FileAction } from '../../../constants/transfers'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
import { startDownloadingQueue, createDownloadAbort } from '../../../utils/download'
|
||||
import { computeContextMenuPosition } from '../../../utils/ui'
|
||||
import { getUsableStamps, handleDestroyAndForgetDrive, verifyDriveSpace } from '../../../utils/bee'
|
||||
import { guessMime } from '../../../utils/view'
|
||||
import { performFileOperation, FileOperation } from '../../../utils/fileOperations'
|
||||
import { Context as SettingsContext } from '../../../../../providers/Settings'
|
||||
import { uuidV4 } from '../../../../../utils'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
import { DownloadProgress, FileAction, TrackDownloadProps, ViewType } from '../../../constants/transfers'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { getUsableStamps, handleDestroyAndForgetDrive, verifyDriveSpace } from '../../../utils/bee'
|
||||
import { Dir, formatBytes, isTrashed, safeSetState, truncateNameMiddle } from '../../../utils/common'
|
||||
import { createDownloadAbort, startDownloadingQueue } from '../../../utils/download'
|
||||
import { FileOperation, performFileOperation } from '../../../utils/fileOperations'
|
||||
import { GetIconElement } from '../../../utils/GetIconElement'
|
||||
import type { FilePropertyGroup } from '../../../utils/infoGroups'
|
||||
import { buildGetInfoGroups } from '../../../utils/infoGroups'
|
||||
import { computeContextMenuPosition } from '../../../utils/ui'
|
||||
import { ConfirmModal } from '../../ConfirmModal/ConfirmModal'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { DeleteFileModal } from '../../DeleteFileModal/DeleteFileModal'
|
||||
import { DestroyDriveModal } from '../../DestroyDriveModal/DestroyDriveModal'
|
||||
import { GetInfoModal } from '../../GetInfoModal/GetInfoModal'
|
||||
import { RenameFileModal } from '../../RenameFileModal/RenameFileModal'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
import { VersionHistoryModal } from '../../VersionHistoryModal/VersionHistoryModal'
|
||||
|
||||
import './FileItem.scss'
|
||||
|
||||
const MenuItem = ({
|
||||
disabled,
|
||||
danger,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
danger?: boolean
|
||||
onClick?: () => void
|
||||
children: React.ReactNode
|
||||
}) => (
|
||||
<div
|
||||
className={`fm-context-item${danger ? ' red' : ''}`}
|
||||
aria-disabled={disabled ? 'true' : 'false'}
|
||||
style={disabled ? { opacity: 0.5, pointerEvents: 'none' } : undefined}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
interface FileItemProps {
|
||||
fileInfo: FileInfo
|
||||
@@ -131,12 +160,6 @@ export function FileItem({
|
||||
return out
|
||||
}, [files, currentDrive, fileInfo.topic])
|
||||
|
||||
const handleItemContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.shiftKey) return
|
||||
handleContextMenu(e)
|
||||
}
|
||||
|
||||
// TODO: handleOpen shall only be available for images, videos etc... -> do not download 10GB into memory
|
||||
const handleDownload = useCallback(
|
||||
async (isNewWindow?: boolean) => {
|
||||
if (!fm || !beeApi) return
|
||||
@@ -241,7 +264,7 @@ export function FileItem({
|
||||
)
|
||||
|
||||
refreshStamp(driveStamp.batchID.toString())
|
||||
} catch (e: unknown) {
|
||||
} catch {
|
||||
setErrorMessage?.(`Error renaming file ${latestFileInfo.name}`)
|
||||
setShowError(true)
|
||||
}
|
||||
@@ -250,41 +273,16 @@ export function FileItem({
|
||||
[fm, driveStamp, currentDrive, latestFileInfo, takenNames, refreshStamp, setErrorMessage, setShowError],
|
||||
)
|
||||
|
||||
const MenuItem = ({
|
||||
disabled,
|
||||
danger,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
danger?: boolean
|
||||
onClick?: () => void
|
||||
children: React.ReactNode
|
||||
}) => (
|
||||
<div
|
||||
className={`fm-context-item${danger ? ' red' : ''}`}
|
||||
aria-disabled={disabled ? 'true' : 'false'}
|
||||
style={disabled ? { opacity: 0.5, pointerEvents: 'none' } : undefined}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const isBulk = (bulkSelectedCount ?? 0) > 1
|
||||
|
||||
const renderContextMenuItems = useCallback(() => {
|
||||
const isBulk = (bulkSelectedCount ?? 0) > 1
|
||||
|
||||
const viewItem = (
|
||||
<MenuItem disabled={isBulk} onClick={() => handleDownload(true)}>
|
||||
View / Open
|
||||
</MenuItem>
|
||||
)
|
||||
|
||||
const downloadItem = isBulk ? (
|
||||
<MenuItem onClick={onBulk.download}>Download</MenuItem>
|
||||
) : (
|
||||
<MenuItem onClick={() => handleDownload(false)}>Download</MenuItem>
|
||||
)
|
||||
const downloadItem = <MenuItem onClick={isBulk ? onBulk.download : () => handleDownload(false)}>Download</MenuItem>
|
||||
|
||||
const getInfoItem = (
|
||||
<MenuItem
|
||||
@@ -405,15 +403,15 @@ export function FileItem({
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
isBulk,
|
||||
view,
|
||||
currentDrive,
|
||||
drives,
|
||||
bulkSelectedCount,
|
||||
onBulk,
|
||||
fileInfo.driveId,
|
||||
handleDownload,
|
||||
handleCloseContext,
|
||||
openGetInfo,
|
||||
onBulk,
|
||||
currentDrive,
|
||||
drives,
|
||||
fileInfo.driveId,
|
||||
setErrorMessage,
|
||||
setShowError,
|
||||
])
|
||||
@@ -463,11 +461,15 @@ export function FileItem({
|
||||
return <div className="fm-file-item-content">Error</div>
|
||||
}
|
||||
|
||||
const { mime } = guessMime(fileInfo.name, fileInfo.customMetadata)
|
||||
const mimeType = mime.split('/')[0]?.toLowerCase() || 'file'
|
||||
|
||||
return (
|
||||
<div className="fm-file-item-content" onContextMenu={handleItemContextMenu} onClick={handleCloseContext}>
|
||||
<div
|
||||
className="fm-file-item-content"
|
||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.shiftKey) return
|
||||
handleContextMenu(e)
|
||||
}}
|
||||
onClick={handleCloseContext}
|
||||
>
|
||||
<div className="fm-file-item-content-item fm-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -478,7 +480,7 @@ export function FileItem({
|
||||
</div>
|
||||
|
||||
<div className="fm-file-item-content-item fm-name" onDoubleClick={() => handleDownload(true)}>
|
||||
<GetIconElement icon={mimeType} />
|
||||
<GetIconElement name={fileInfo.name} metadata={fileInfo.customMetadata} />
|
||||
{truncateNameMiddle(fileInfo.name)}
|
||||
</div>
|
||||
|
||||
|
||||
+8
-4
@@ -1,9 +1,11 @@
|
||||
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import './FileProgressNotification.scss'
|
||||
import UpIcon from 'remixicon-react/ArrowUpSLineIcon'
|
||||
import DownIcon from 'remixicon-react/ArrowDownSLineIcon'
|
||||
import UpIcon from 'remixicon-react/ArrowUpSLineIcon'
|
||||
|
||||
import { FileTransferType, ProgressItem, TransferStatus } from '../../constants/transfers'
|
||||
import { FileProgressWindow } from '../FileProgressWindow/FileProgressWindow'
|
||||
import { FileTransferType, TransferStatus, ProgressItem } from '../../constants/transfers'
|
||||
|
||||
import './FileProgressNotification.scss'
|
||||
|
||||
interface FileProgressNotificationProps {
|
||||
label?: string
|
||||
@@ -14,6 +16,8 @@ interface FileProgressNotificationProps {
|
||||
onCloseAll?: () => void
|
||||
}
|
||||
|
||||
const HIDER_TIMEOUT_MS = 3000
|
||||
|
||||
export function FileProgressNotification({
|
||||
label,
|
||||
type,
|
||||
@@ -49,7 +53,7 @@ export function FileProgressNotification({
|
||||
autoHideTimer.current = window.setTimeout(() => {
|
||||
setShowFileProgressWindow(false)
|
||||
autoHideTimer.current = null
|
||||
}, 3000) as unknown as number
|
||||
}, HIDER_TIMEOUT_MS) as unknown as number
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ReactElement, useLayoutEffect, useRef } from 'react'
|
||||
import CloseIcon from 'remixicon-react/CloseLineIcon'
|
||||
import ArrowDownIcon from 'remixicon-react/ArrowDownSLineIcon'
|
||||
import './FileProgressWindow.scss'
|
||||
import CloseIcon from 'remixicon-react/CloseLineIcon'
|
||||
|
||||
import { FileTransferType, ProgressItem, TransferBarColor, TransferStatus } from '../../constants/transfers'
|
||||
import { capitalizeFirstLetter, truncateNameMiddle } from '../../utils/common'
|
||||
import { GetIconElement } from '../../utils/GetIconElement'
|
||||
import { ProgressBar } from '../ProgressBar/ProgressBar'
|
||||
import { FileTransferType, TransferBarColor, TransferStatus, ProgressItem } from '../../constants/transfers'
|
||||
import { capitalizeFirstLetter, truncateNameMiddle } from '../../utils/common'
|
||||
import { guessMime } from '../../utils/view'
|
||||
|
||||
import './FileProgressWindow.scss'
|
||||
|
||||
interface FileProgressWindowProps {
|
||||
items?: ProgressItem[]
|
||||
@@ -138,9 +139,6 @@ export function FileProgressWindow({
|
||||
|
||||
const centerDisplay = getCenterText() || '\u00A0'
|
||||
|
||||
const { mime } = guessMime(item.name)
|
||||
const mimeType = mime.split('/')[0].toLowerCase() || 'file'
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fm-file-progress-window-file-item"
|
||||
@@ -148,7 +146,7 @@ export function FileProgressWindow({
|
||||
ref={idx === 0 ? firstRowRef : undefined}
|
||||
>
|
||||
<div className="fm-file-progress-window-file-type-icon">
|
||||
<GetIconElement size="14" icon={mimeType} color="black" />
|
||||
<GetIconElement size="14" name={item.name} color="black" />
|
||||
</div>
|
||||
|
||||
<div className="fm-file-progress-window-file-datas">
|
||||
|
||||
+17
-15
@@ -1,10 +1,12 @@
|
||||
import { useEffect, useRef, useCallback } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import formbricks from '@formbricks/js'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { LocalStorageKeys } from '../../../../utils/localStorage'
|
||||
|
||||
const FM_CLICK_STORAGE_KEY = 'fm_click_count_v1'
|
||||
const FM_SURVEY_TRIGGERED_KEY = 'fm_survey_triggered_v1'
|
||||
const FM_CLICK_THRESHOLD = 25
|
||||
const FM_FORMBRICKS_TRACK_CODE = 'file_manager_engagement_25_clicks'
|
||||
const FORMBRICKS_INIT_TIMEOUT_MS = 1000
|
||||
|
||||
interface FormbricksIntegrationProps {
|
||||
isActive: boolean
|
||||
@@ -16,14 +18,14 @@ export function FormbricksIntegration({ isActive }: FormbricksIntegrationProps)
|
||||
const formbricksReadyRef = useRef(false)
|
||||
const pendingEventRef = useRef(false)
|
||||
|
||||
const environmentId = process.env.REACT_APP_FORMBRICKS_ENV_ID
|
||||
const appUrl = process.env.REACT_APP_FORMBRICKS_APP_URL
|
||||
const environmentId = import.meta.env.VITE_FORMBRICKS_ENV_ID
|
||||
const appUrl = import.meta.env.VITE_FORMBRICKS_APP_URL
|
||||
|
||||
const flushPendingEvent = useCallback(() => {
|
||||
if (pendingEventRef.current && localStorage.getItem(FM_SURVEY_TRIGGERED_KEY) !== 'true') {
|
||||
if (pendingEventRef.current && localStorage.getItem(LocalStorageKeys.fmSurveyTriggered) !== 'true') {
|
||||
try {
|
||||
formbricks.track('file_manager_engagement_25_clicks')
|
||||
localStorage.setItem(FM_SURVEY_TRIGGERED_KEY, 'true')
|
||||
formbricks.track(FM_FORMBRICKS_TRACK_CODE)
|
||||
localStorage.setItem(LocalStorageKeys.fmSurveyTriggered, 'true')
|
||||
pendingEventRef.current = false
|
||||
} catch {
|
||||
// no-op
|
||||
@@ -45,7 +47,7 @@ export function FormbricksIntegration({ isActive }: FormbricksIntegrationProps)
|
||||
appUrl,
|
||||
})
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await new Promise(resolve => setTimeout(resolve, FORMBRICKS_INIT_TIMEOUT_MS))
|
||||
|
||||
if (!cancelled) {
|
||||
formbricksReadyRef.current = true
|
||||
@@ -79,11 +81,11 @@ export function FormbricksIntegration({ isActive }: FormbricksIntegrationProps)
|
||||
if (!isActive) return
|
||||
|
||||
const handleClick = async () => {
|
||||
if (localStorage.getItem(FM_SURVEY_TRIGGERED_KEY) === 'true') return
|
||||
if (localStorage.getItem(LocalStorageKeys.fmSurveyTriggered) === 'true') return
|
||||
|
||||
let count = 0
|
||||
try {
|
||||
const stored = localStorage.getItem(FM_CLICK_STORAGE_KEY)
|
||||
const stored = localStorage.getItem(LocalStorageKeys.fmClickStorage)
|
||||
|
||||
if (stored) count = parseInt(stored, 10) || 0
|
||||
} catch {
|
||||
@@ -92,7 +94,7 @@ export function FormbricksIntegration({ isActive }: FormbricksIntegrationProps)
|
||||
|
||||
count += 1
|
||||
try {
|
||||
localStorage.setItem(FM_CLICK_STORAGE_KEY, String(count))
|
||||
localStorage.setItem(LocalStorageKeys.fmClickStorage, String(count))
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
@@ -111,8 +113,8 @@ export function FormbricksIntegration({ isActive }: FormbricksIntegrationProps)
|
||||
}
|
||||
|
||||
try {
|
||||
await formbricks.track('file_manager_engagement_25_clicks')
|
||||
localStorage.setItem(FM_SURVEY_TRIGGERED_KEY, 'true')
|
||||
await formbricks.track(FM_FORMBRICKS_TRACK_CODE)
|
||||
localStorage.setItem(LocalStorageKeys.fmSurveyTriggered, 'true')
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ReactElement, useState, useEffect } from 'react'
|
||||
import './GetInfoModal.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import { ReactElement, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import InfoIcon from 'remixicon-react/InformationLineIcon'
|
||||
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
|
||||
import InfoIcon from 'remixicon-react/InformationLineIcon'
|
||||
|
||||
import type { FileProperty, FilePropertyGroup } from '../../utils/infoGroups'
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import './GetInfoModal.scss'
|
||||
|
||||
interface GetInfoModalProps {
|
||||
name: string
|
||||
@@ -13,31 +14,35 @@ interface GetInfoModalProps {
|
||||
onCancelClick: () => void
|
||||
}
|
||||
|
||||
const COPY_TIMEOUT_MS = 2000
|
||||
|
||||
export function GetInfoModal({ name, onCancelClick, properties }: GetInfoModalProps): ReactElement {
|
||||
const modalRoot = document.querySelector('.fm-main') || document.body
|
||||
const [copiedKey, setCopiedKey] = useState<string | null>(null)
|
||||
const timeoutRef = useState<{ [key: string]: NodeJS.Timeout }>({})[0]
|
||||
|
||||
const timeoutRef = useRef<Record<string, NodeJS.Timeout>>({})
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
Object.values(timeoutRef).forEach(timeout => clearTimeout(timeout))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
Object.values(timeoutRef.current).forEach(clearTimeout)
|
||||
}
|
||||
}, [timeoutRef])
|
||||
}, [])
|
||||
|
||||
const handleCopy = async (prop: FileProperty) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(prop.raw ?? prop.value)
|
||||
|
||||
if (timeoutRef[prop.key]) {
|
||||
clearTimeout(timeoutRef[prop.key])
|
||||
if (timeoutRef.current[prop.key]) {
|
||||
clearTimeout(timeoutRef.current[prop.key])
|
||||
}
|
||||
|
||||
setCopiedKey(prop.key)
|
||||
|
||||
timeoutRef[prop.key] = setTimeout(() => {
|
||||
timeoutRef.current[prop.key] = setTimeout(() => {
|
||||
setCopiedKey(prev => (prev === prop.key ? null : prev))
|
||||
delete timeoutRef[prop.key]
|
||||
}, 2000)
|
||||
delete timeoutRef.current[prop.key]
|
||||
}, COPY_TIMEOUT_MS)
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use 'sass:color';
|
||||
|
||||
$bg-900: #212121;
|
||||
$bg-800: #262626;
|
||||
$bg-700: #3e3e3e;
|
||||
@@ -13,7 +15,7 @@ $accent: #ed8131;
|
||||
height: 60px;
|
||||
padding: 10px 16px;
|
||||
background: $bg-900;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.04);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.fm-header-left {
|
||||
@@ -22,51 +24,73 @@ $accent: #ed8131;
|
||||
gap: 12px;
|
||||
}
|
||||
.fm-header-logo {
|
||||
width: 40px; height: 40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
background: $accent;
|
||||
color: $text-100;
|
||||
display: grid; place-items: center;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
svg { width: 18px; height: 18px; }
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
.fm-header-title {
|
||||
color: $text-100;
|
||||
font-weight: 600;
|
||||
letter-spacing: .2px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.fm-header-search {
|
||||
flex: 1 1 auto;
|
||||
max-width: 900px;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: $bg-700;
|
||||
border: 1px solid $border-400;
|
||||
color: $text-300;
|
||||
height: 36px; padding: 0 10px;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
&:focus-within {
|
||||
border-color: $accent;
|
||||
box-shadow: 0 0 0 2px rgba(237,129,49,0.25);
|
||||
box-shadow: 0 0 0 2px rgba(237, 129, 49, 0.25);
|
||||
}
|
||||
|
||||
.fm-header-search-icon { flex: 0 0 auto; }
|
||||
.fm-header-search-icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1 1 auto;
|
||||
background: transparent; border: none; outline: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
color: $text-100; font-size: 14px;
|
||||
color: $text-100;
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder { color: $text-300; }
|
||||
&::placeholder {
|
||||
color: $text-300;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-header-search-clear {
|
||||
appearance: none; border: none; background: transparent;
|
||||
color: $text-300; font-size: 18px; line-height: 1;
|
||||
padding: 0 2px; cursor: pointer;
|
||||
&:hover { color: $text-100; }
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: $text-300;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
padding: 0 2px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $text-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +100,9 @@ $accent: #ed8131;
|
||||
}
|
||||
|
||||
.fm-filter-btn {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $border-400;
|
||||
@@ -85,59 +111,83 @@ $accent: #ed8131;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { background: mix($bg-800, #fff, 92%); }
|
||||
&:focus-visible { outline: 2px solid rgba(237,129,49,0.4); outline-offset: 2px; }
|
||||
&:hover {
|
||||
background: color.mix($bg-800, #fff, 92%);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(237, 129, 49, 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
&[aria-expanded='true'] {
|
||||
border-color: $accent;
|
||||
box-shadow: 0 0 0 2px rgba(237,129,49,0.25);
|
||||
box-shadow: 0 0 0 2px rgba(237, 129, 49, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.fm-filter-menu {
|
||||
position: absolute;
|
||||
right: 0; top: calc(100% + 6px);
|
||||
right: 0;
|
||||
top: calc(100% + 6px);
|
||||
min-width: 260px;
|
||||
background: $bg-800;
|
||||
border: 1px solid $border-400;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 24px rgba(0,0,0,0.25);
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.25);
|
||||
padding: 10px;
|
||||
z-index: 2000;
|
||||
color: $text-100;
|
||||
}
|
||||
|
||||
.fm-filter-group + .fm-filter-group { margin-top: 10px; }
|
||||
.fm-filter-group + .fm-filter-group {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.fm-filter-group-title {
|
||||
font-size: 12px;
|
||||
color: $text-300;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.fm-filter-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 6px 4px; border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 4px;
|
||||
border-radius: 6px;
|
||||
cursor: default;
|
||||
color: $text-100;
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
width: 14px; height: 14px; margin: 0;
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
accent-color: $accent;
|
||||
}
|
||||
|
||||
&:hover { background: rgba(255,255,255,0.05); }
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.fm-filter-sep {
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.fm-header-filters { display: none; }
|
||||
.fm-header-filters-label { display: none; }
|
||||
.fm-header-chip-group { display: none; }
|
||||
.fm-chip { display: none; }
|
||||
.fm-header-filters {
|
||||
display: none;
|
||||
}
|
||||
.fm-header-filters-label {
|
||||
display: none;
|
||||
}
|
||||
.fm-header-chip-group {
|
||||
display: none;
|
||||
}
|
||||
.fm-chip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ReactElement, useMemo, useState, useEffect, useRef, useContext } from 'react'
|
||||
import SearchIcon from 'remixicon-react/SearchLineIcon'
|
||||
import { ReactElement, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import FileIcon from 'remixicon-react/File2LineIcon'
|
||||
import FilterIcon from 'remixicon-react/FilterLineIcon'
|
||||
import './Header.scss'
|
||||
import SearchIcon from 'remixicon-react/SearchLineIcon'
|
||||
|
||||
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
|
||||
import './Header.scss'
|
||||
|
||||
// Defaults used to determine “active filters”
|
||||
const DEFAULT_FILTERS = {
|
||||
scope: 'selected' as 'selected' | 'all',
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { BeeModes, BZZ, DAI, Duration, PostageBatch, RedundancyLevel, Size, Utils } from '@ethersphere/bee-js'
|
||||
import { ADMIN_STAMP_LABEL } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { BeeModes, BZZ, DAI, Duration, PostageBatch, RedundancyLevel, Size, Utils } from '@ethersphere/bee-js'
|
||||
import './InitialModal.scss'
|
||||
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
|
||||
import { Button } from '../Button/Button'
|
||||
import { calculateStampCapacityMetrics, fmFetchCost, getUsableStamps, handleCreateDrive } from '../../utils/bee'
|
||||
import { getExpiryDateByLifetime, safeSetState } from '../../utils/common'
|
||||
import { Context as BeeContext } from '../../../../providers/Bee'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { erasureCodeMarks } from '../../constants/common'
|
||||
import { desiredLifetimeOptions } from '../../constants/stamps'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { Context as BeeContext } from '../../../../providers/Bee'
|
||||
|
||||
import { FMSlider } from '../Slider/Slider'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { ADMIN_STAMP_LABEL } from '@solarpunkltd/file-manager-lib'
|
||||
import { ProgressBar } from '../ProgressBar/ProgressBar'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { calculateStampCapacityMetrics, fmFetchCost, getUsableStamps, handleCreateDrive } from '../../utils/bee'
|
||||
import { getExpiryDateByLifetime, safeSetState } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
|
||||
import { ProgressBar } from '../ProgressBar/ProgressBar'
|
||||
import { FMSlider } from '../Slider/Slider'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import './InitialModal.scss'
|
||||
|
||||
interface InitialModalProps {
|
||||
resetState: boolean
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import './NotificationBar.scss'
|
||||
import UpIcon from 'remixicon-react/ArrowUpSLineIcon'
|
||||
import { ExpiringNotificationModal } from '../ExpiringNotificationModal/ExpiringNotificationModal'
|
||||
import { getUsableStamps } from '../../utils/bee'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import UpIcon from 'remixicon-react/ArrowUpSLineIcon'
|
||||
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { FILE_MANAGER_EVENTS } from '../../constants/common'
|
||||
import { getUsableStamps } from '../../utils/bee'
|
||||
import { ExpiringNotificationModal } from '../ExpiringNotificationModal/ExpiringNotificationModal'
|
||||
|
||||
import './NotificationBar.scss'
|
||||
|
||||
const NUMBER_OF_DAYS_WARNING = 7
|
||||
const DAYS_TO_MILLISECONDS_MULTIPLIER = 24 * 60 * 60 * 1000
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import { useState, ReactElement, useEffect } from 'react'
|
||||
import './PrivateKeyModal.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import { setSignerPk, getSigner } from '../../utils/common'
|
||||
import { uuidV4 } from '../../../../utils'
|
||||
import { PrivateKey } from '@ethersphere/bee-js'
|
||||
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import CheckDoubleLineIcon from 'remixicon-react/CheckDoubleLineIcon'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
|
||||
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
import { getSigner, setSignerPk } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import './PrivateKeyModal.scss'
|
||||
|
||||
import { uuidV4 } from '@/utils'
|
||||
|
||||
type Props = { onSaved: () => void }
|
||||
|
||||
const generateNewPrivateKey = (): string => {
|
||||
const id = uuidV4()
|
||||
const signer = getSigner(id)
|
||||
|
||||
return signer.toHex()
|
||||
}
|
||||
|
||||
export function PrivateKeyModal({ onSaved }: Props): ReactElement {
|
||||
const [value, setValue] = useState('')
|
||||
const [value, setValue] = useState(generateNewPrivateKey())
|
||||
const [confirmValue, setConfirmValue] = useState('')
|
||||
const [showError, setShowError] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
handleGenerateNew()
|
||||
}, [])
|
||||
|
||||
const handleCopyPrivateKey = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
@@ -32,11 +38,7 @@ export function PrivateKeyModal({ onSaved }: Props): ReactElement {
|
||||
}
|
||||
|
||||
const handleGenerateNew = () => {
|
||||
const id = uuidV4()
|
||||
const signer = getSigner(id)
|
||||
const privKey = signer.toHex()
|
||||
|
||||
setValue(privKey)
|
||||
setValue(generateNewPrivateKey())
|
||||
setConfirmValue('')
|
||||
setCopied(false)
|
||||
setShowError(false)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import EditIcon from 'remixicon-react/EditLineIcon'
|
||||
|
||||
import { safeSetState } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import '../../styles/global.scss'
|
||||
import './RenameFileModal.scss'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
import EditIcon from 'remixicon-react/EditLineIcon'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { safeSetState } from '../../utils/common'
|
||||
|
||||
const maxFileNameLength = 60
|
||||
|
||||
interface RenameFileModalProps {
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
import { ReactElement, useState, useContext, useEffect, useMemo, useCallback, useRef, memo } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Drive from 'remixicon-react/HardDrive2LineIcon'
|
||||
import DriveFill from 'remixicon-react/HardDrive2FillIcon'
|
||||
import MoreFill from 'remixicon-react/MoreFillIcon'
|
||||
import './DriveItem.scss'
|
||||
import { ProgressBar } from '../../ProgressBar/ProgressBar'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { Button } from '../../Button/Button'
|
||||
import { DestroyDriveModal, ProgressDestroyModal } from '../../DestroyDriveModal/DestroyDriveModal'
|
||||
import { UpgradeDriveModal } from '../../UpgradeDriveModal/UpgradeDriveModal'
|
||||
import { UpgradeTimeoutModal } from '../../UpgradeTimeoutModal/UpgradeTimeoutModal'
|
||||
import { ViewType } from '../../../constants/transfers'
|
||||
import { useView } from '../../../../../pages/filemanager/ViewContext'
|
||||
import { Context as FMContext } from '../../../../../providers/FileManager'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { calculateStampCapacityMetrics, handleDestroyAndForgetDrive } from '../../../utils/bee'
|
||||
import React, { memo, ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import DriveFill from 'remixicon-react/HardDrive2FillIcon'
|
||||
import Drive from 'remixicon-react/HardDrive2LineIcon'
|
||||
import MoreFill from 'remixicon-react/MoreFillIcon'
|
||||
|
||||
import { useView } from '../../../../../pages/filemanager/ViewContext'
|
||||
import { Context as FMContext } from '../../../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../../../providers/Settings'
|
||||
import { truncateNameMiddle } from '../../../utils/common'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
import { FILE_MANAGER_EVENTS, UPLOAD_POLLING_TIMEOUT_MS } from '../../../constants/common'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
import { ViewType } from '../../../constants/transfers'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { useStampPolling } from '../../../hooks/useStampPolling'
|
||||
import { calculateStampCapacityMetrics, handleDestroyAndForgetDrive } from '../../../utils/bee'
|
||||
import { truncateNameMiddle } from '../../../utils/common'
|
||||
import { Button } from '../../Button/Button'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { DestroyDriveModal, ProgressDestroyModal } from '../../DestroyDriveModal/DestroyDriveModal'
|
||||
import { ProgressBar } from '../../ProgressBar/ProgressBar'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
import { UpgradeDriveModal } from '../../UpgradeDriveModal/UpgradeDriveModal'
|
||||
import { UpgradeTimeoutModal } from '../../UpgradeTimeoutModal/UpgradeTimeoutModal'
|
||||
|
||||
import './DriveItem.scss'
|
||||
|
||||
function useDriveEventListeners(
|
||||
driveId: string,
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { ReactElement, useState, useContext } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Drive from 'remixicon-react/HardDrive2LineIcon'
|
||||
import DriveFill from 'remixicon-react/HardDrive2FillIcon'
|
||||
import MoreFill from 'remixicon-react/MoreFillIcon'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import React, { ReactElement, useContext, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import DriveFill from 'remixicon-react/HardDrive2FillIcon'
|
||||
import Drive from 'remixicon-react/HardDrive2LineIcon'
|
||||
import MoreFill from 'remixicon-react/MoreFillIcon'
|
||||
|
||||
import { Context as FMContext } from '../../../../../providers/FileManager'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { handleDestroyAndForgetDrive } from '../../../utils/bee'
|
||||
import { ConfirmModal } from '../../ConfirmModal/ConfirmModal'
|
||||
import './DriveItem.scss'
|
||||
import { truncateNameMiddle } from '../../../utils/common'
|
||||
import { ConfirmModal } from '../../ConfirmModal/ConfirmModal'
|
||||
import { ContextMenu } from '../../ContextMenu/ContextMenu'
|
||||
|
||||
import './DriveItem.scss'
|
||||
|
||||
interface Props {
|
||||
drive: DriveInfo
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import './Sidebar.scss'
|
||||
import Add from 'remixicon-react/AddLineIcon'
|
||||
import Folder from 'remixicon-react/Folder3LineIcon'
|
||||
import FolderFill from 'remixicon-react/Folder3FillIcon'
|
||||
import ArrowRight from 'remixicon-react/ArrowRightSLineIcon'
|
||||
import ArrowDown from 'remixicon-react/ArrowDownSLineIcon'
|
||||
import Delete from 'remixicon-react/DeleteBin6LineIcon'
|
||||
import DeleteFill from 'remixicon-react/DeleteBin6FillIcon'
|
||||
import History from 'remixicon-react/HistoryLineIcon'
|
||||
import HistoryFill from 'remixicon-react/HistoryFillIcon'
|
||||
import { DriveItem } from './DriveItem/DriveItem'
|
||||
import { ExpiredDriveItem } from './DriveItem/ExpiredDriveItem'
|
||||
import { CreateDriveModal } from '../CreateDriveModal/CreateDriveModal'
|
||||
import { ViewType } from '../../constants/transfers'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import Add from 'remixicon-react/AddLineIcon'
|
||||
import ArrowDown from 'remixicon-react/ArrowDownSLineIcon'
|
||||
import ArrowRight from 'remixicon-react/ArrowRightSLineIcon'
|
||||
import DeleteFill from 'remixicon-react/DeleteBin6FillIcon'
|
||||
import Delete from 'remixicon-react/DeleteBin6LineIcon'
|
||||
import FolderFill from 'remixicon-react/Folder3FillIcon'
|
||||
import Folder from 'remixicon-react/Folder3LineIcon'
|
||||
import HistoryFill from 'remixicon-react/HistoryFillIcon'
|
||||
import History from 'remixicon-react/HistoryLineIcon'
|
||||
|
||||
import { useView } from '../../../../pages/filemanager/ViewContext'
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { getUsableStamps } from '../../utils/bee'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { truncateNameMiddle } from '../../utils/common'
|
||||
import { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { FILE_MANAGER_EVENTS } from '../../constants/common'
|
||||
import { ViewType } from '../../constants/transfers'
|
||||
import { getUsableStamps } from '../../utils/bee'
|
||||
import { truncateNameMiddle } from '../../utils/common'
|
||||
import { CreateDriveModal } from '../CreateDriveModal/CreateDriveModal'
|
||||
|
||||
import { DriveItem } from './DriveItem/DriveItem'
|
||||
import { ExpiredDriveItem } from './DriveItem/ExpiredDriveItem'
|
||||
|
||||
import './Sidebar.scss'
|
||||
|
||||
interface SidebarProps {
|
||||
loading: boolean
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import Slider from '@mui/material/Slider'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import './Slider.scss'
|
||||
import Slider from '@material-ui/core/Slider'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from 'tss-react/mui'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
import './Slider.scss'
|
||||
|
||||
const useStyles = makeStyles()({
|
||||
root: {
|
||||
width: '98%',
|
||||
marginLeft: '-3px',
|
||||
@@ -50,7 +51,7 @@ export function FMSlider({
|
||||
step,
|
||||
}: FMSliderProps): ReactElement {
|
||||
const [value, setValue] = useState(defaultValue || 0)
|
||||
const classes = useStyles()
|
||||
const { classes } = useStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactElement, useState, useRef, useCallback } from 'react'
|
||||
import React, { ReactElement, useCallback, useRef, useState } from 'react'
|
||||
import InfoIcon from 'remixicon-react/InformationLineIcon'
|
||||
|
||||
import './Tooltip.scss'
|
||||
|
||||
interface TooltipProps {
|
||||
@@ -82,7 +83,7 @@ export function Tooltip({
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
// Safe: label is always from static TOOLTIPS constant or hardcoded strings, never user input
|
||||
dangerouslySetInnerHTML={{ __html: label }}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { ReactElement, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
import './UpgradeDriveModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
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 {
|
||||
BatchId,
|
||||
BeeRequestOptions,
|
||||
@@ -19,18 +9,29 @@ import {
|
||||
Size,
|
||||
Utils,
|
||||
} from '@ethersphere/bee-js'
|
||||
import { Warning } from '@mui/icons-material'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import DatabaseIcon from 'remixicon-react/Database2LineIcon'
|
||||
import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon'
|
||||
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
|
||||
import WalletIcon from 'remixicon-react/Wallet3LineIcon'
|
||||
|
||||
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 { Context as SettingsContext } from '../../../../providers/Settings'
|
||||
import { getHumanReadableFileSize } from '../../../../utils/file'
|
||||
import { useStampPolling } from '../../hooks/useStampPolling'
|
||||
import { FILE_MANAGER_EVENTS, POLLING_TIMEOUT_MS } from '../../constants/common'
|
||||
import { desiredLifetimeOptions } from '../../constants/stamps'
|
||||
import { useStampPolling } from '../../hooks/useStampPolling'
|
||||
import { fromBytesConversion, getExpiryDateByLifetime, truncateNameMiddle } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { CustomDropdown } from '../CustomDropdown/CustomDropdown'
|
||||
|
||||
import './UpgradeDriveModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
|
||||
interface UpgradeDriveModalProps {
|
||||
stamp: PostageBatch
|
||||
@@ -117,7 +118,7 @@ export function UpgradeDriveModal({
|
||||
|
||||
try {
|
||||
cost = await beeApi?.getExtensionCost(batchId, capacity, duration, options, encryption, erasureCodeLevel)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
setErrorMessage?.('Failed to calculate extension cost')
|
||||
setShowError(true)
|
||||
|
||||
@@ -281,7 +282,7 @@ export function UpgradeDriveModal({
|
||||
icon={<CalendarIcon size="14px" color="rgb(237, 129, 49)" />}
|
||||
options={desiredLifetimeOptions}
|
||||
value={lifetimeIndex}
|
||||
onChange={(value, index) => {
|
||||
onChange={(value, _) => {
|
||||
setLifetimeIndex(value)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactElement } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import '../../styles/global.scss'
|
||||
import './UpgradeTimeoutModal.scss'
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ReactElement, useMemo, useState } from 'react'
|
||||
import WarningIcon from 'remixicon-react/ErrorWarningLineIcon'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
|
||||
import './UploadConflictModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
import { Button } from '../Button/Button'
|
||||
import WarningIcon from 'remixicon-react/ErrorWarningLineIcon'
|
||||
|
||||
interface Props {
|
||||
filename: string
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { ReactElement, useEffect, useMemo, useState, useCallback, useContext } from 'react'
|
||||
import './VersionHistoryModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
|
||||
import { Button } from '../Button/Button'
|
||||
import { FeedIndex } from '@ethersphere/bee-js'
|
||||
import { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import HistoryIcon from 'remixicon-react/HistoryLineIcon'
|
||||
|
||||
import { Context as FMContext } from '../../../../providers/FileManager'
|
||||
import { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { FeedIndex } from '@ethersphere/bee-js'
|
||||
import { ConflictAction, useUploadConflictDialog } from '../../hooks/useUploadConflictDialog'
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
import { TOOLTIPS } from '../../constants/tooltips'
|
||||
|
||||
import { verifyDriveSpace } from '../../utils/bee'
|
||||
import { indexStrToBigint, truncateNameMiddle } from '../../utils/common'
|
||||
import { VersionsList } from './VersionList/VersionList'
|
||||
import { ActionTag, DownloadProgress, TrackDownloadProps } from '../../constants/transfers'
|
||||
import { useTransfers } from '../../hooks/useTransfers'
|
||||
import { ConflictAction, useUploadConflictDialog } from '../../hooks/useUploadConflictDialog'
|
||||
import { verifyDriveSpace } from '../../utils/bee'
|
||||
import { indexStrToBigint, truncateNameMiddle } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal'
|
||||
import { Tooltip } from '../Tooltip/Tooltip'
|
||||
|
||||
import { VersionsList } from './VersionList/VersionList'
|
||||
|
||||
import './VersionHistoryModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
|
||||
const VERSION_HISTORY_PAGE_SIZE = 5
|
||||
|
||||
|
||||
+13
-14
@@ -1,24 +1,23 @@
|
||||
import './VersionList.scss'
|
||||
import '../../../styles/global.scss'
|
||||
import { memo, useState, useCallback, useContext } from 'react'
|
||||
|
||||
import { Button } from '../../Button/Button'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import UserIcon from 'remixicon-react/UserLineIcon'
|
||||
import DownloadIcon from 'remixicon-react/Download2LineIcon'
|
||||
|
||||
import { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { memo, useCallback, useContext, useState } from 'react'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import DownloadIcon from 'remixicon-react/Download2LineIcon'
|
||||
import UserIcon from 'remixicon-react/UserLineIcon'
|
||||
|
||||
import { Context as FMContext } from '../../../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../../../providers/Settings'
|
||||
import { uuidV4 } from '../../../../../utils'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
import { ActionTag, DownloadProgress, TrackDownloadProps } from '../../../constants/transfers'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { capitalizeFirstLetter, formatBytes, indexStrToBigint, truncateNameMiddle } from '../../../utils/common'
|
||||
import { startDownloadingQueue } from '../../../utils/download'
|
||||
import { ActionTag, DownloadProgress, TrackDownloadProps } from '../../../constants/transfers'
|
||||
import { Context as SettingsContext } from '../../../../../providers/Settings'
|
||||
import { useContextMenu } from '../../../hooks/useContextMenu'
|
||||
import { uuidV4 } from '../../../../../utils'
|
||||
import { Button } from '../../Button/Button'
|
||||
import { ConfirmModal } from '../../ConfirmModal/ConfirmModal'
|
||||
import { Tooltip } from '../../Tooltip/Tooltip'
|
||||
import { TOOLTIPS } from '../../../constants/tooltips'
|
||||
|
||||
import './VersionList.scss'
|
||||
import '../../../styles/global.scss'
|
||||
|
||||
interface VersionListProps {
|
||||
versions: FileInfo[]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FeedIndex, RedundancyLevel } from '@ethersphere/bee-js'
|
||||
|
||||
import { capitalizeFirstLetter } from '../utils/common'
|
||||
|
||||
export const FEED_INDEX_ZERO = FeedIndex.fromBigInt(BigInt(0))
|
||||
@@ -17,7 +18,7 @@ export const FILE_MANAGER_EVENTS = {
|
||||
DRIVE_UPGRADE_TIMEOUT: 'fm:drive-upgrade-timeout',
|
||||
} as const
|
||||
|
||||
export type FileManagerEventName = typeof FILE_MANAGER_EVENTS[keyof typeof FILE_MANAGER_EVENTS]
|
||||
export type FileManagerEventName = (typeof FILE_MANAGER_EVENTS)[keyof typeof FILE_MANAGER_EVENTS]
|
||||
|
||||
export const POLLING_TIMEOUT_MS = 90000
|
||||
export const UPLOAD_POLLING_TIMEOUT_MS = 10000
|
||||
|
||||
@@ -55,8 +55,6 @@ This action will do two things:
|
||||
It will create the Admin Drive.`,
|
||||
ADMIN_STATUS_WARNING:
|
||||
'The File Manager works only while your storage remains valid. If it expires, all catalogue metadata is permanently lost.',
|
||||
|
||||
// Drive Creation
|
||||
DRIVE_NAME: `${getTitleWithStyle('About Drive Name')}
|
||||
Set a human-readable label for this drive (e.g. Personal files). This name is stored as metadata.`,
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useCallback, useMemo, useRef, useState, useContext, useEffect } from 'react'
|
||||
import type { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import type { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { Context as FMContext } from '../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
import { startDownloadingQueue } from '../utils/download'
|
||||
import { formatBytes, getFileId, safeSetState } from '../utils/common'
|
||||
import { uuidV4 } from '../../../utils'
|
||||
import { DownloadProgress, TrackDownloadProps } from '../constants/transfers'
|
||||
import { getUsableStamps } from '../utils/bee'
|
||||
import { performBulkFileOperation, FileOperation } from '../utils/fileOperations'
|
||||
import { uuidV4 } from '../../../utils'
|
||||
import { formatBytes, getFileId, safeSetState } from '../utils/common'
|
||||
import { startDownloadingQueue } from '../utils/download'
|
||||
import { FileOperation, performBulkFileOperation } from '../utils/fileOperations'
|
||||
|
||||
interface BulkOptions {
|
||||
listToRender: FileInfo[]
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useEffect } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export function useClickOutside<T extends Element>(ref: React.RefObject<T>, onClickOutside: () => void, active = true) {
|
||||
export function useClickOutside<T extends HTMLDivElement>(
|
||||
ref: React.RefObject<T | null>,
|
||||
onClickOutside: () => void,
|
||||
active = true,
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
|
||||
function handleDocumentClick(e: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
if (ref?.current && !ref.current.contains(e.target as Node)) {
|
||||
onClickOutside()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { useClickOutside } from './useClickOutside'
|
||||
import React, { useRef, useState } from 'react'
|
||||
|
||||
import { Point } from '../utils/common'
|
||||
|
||||
import { useClickOutside } from './useClickOutside'
|
||||
|
||||
export function useContextMenu<T extends Element = HTMLDivElement>() {
|
||||
const [showContext, setShowContext] = useState(false)
|
||||
const [pos, setPos] = useState<Point>({ x: 0, y: 0 })
|
||||
const contextRef = useRef<T | null>(null)
|
||||
const contextRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
function handleContextMenu(e: React.MouseEvent<T> | MouseEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
|
||||
interface UseDragAndDropProps {
|
||||
onFilesDropped: (files: FileList) => void
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useCallback } from 'react'
|
||||
import { FileInfo, DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import { ViewType } from '../constants/transfers'
|
||||
import { indexStrToBigint, isTrashed } from '../utils/common'
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import type { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { LocalStorageKeys } from '../../../utils/localStorage'
|
||||
|
||||
export enum SortKey {
|
||||
Name = 'name',
|
||||
@@ -19,10 +21,9 @@ type Options = {
|
||||
persist?: boolean
|
||||
defaultState?: SortState
|
||||
storageKey?: string
|
||||
getDriveName?: (fi: FileInfo) => string
|
||||
getDriveName?: (driveId: string) => string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'fm.sort.v1'
|
||||
const DEFAULT_STATE: SortState = { key: SortKey.Timestamp, dir: SortDir.Desc }
|
||||
|
||||
const coerceNumber = (v: unknown): number => {
|
||||
@@ -61,7 +62,7 @@ export function useSorting(
|
||||
toggle: (key: SortKey) => void
|
||||
reset: () => void
|
||||
} {
|
||||
const { persist = true, defaultState = DEFAULT_STATE, storageKey = STORAGE_KEY, getDriveName } = opts
|
||||
const { persist = true, defaultState = DEFAULT_STATE, storageKey = LocalStorageKeys.fmSortKey, getDriveName } = opts
|
||||
|
||||
const [sort, setSort] = useState<SortState>(() => {
|
||||
if (!persist) return defaultState
|
||||
@@ -127,8 +128,8 @@ export function useSorting(
|
||||
}
|
||||
|
||||
if (sort.key === SortKey.Drive) {
|
||||
const ad = (getDriveName?.(a) ?? '').toLocaleLowerCase()
|
||||
const bd = (getDriveName?.(b) ?? '').toLocaleLowerCase()
|
||||
const ad = (getDriveName?.(a.driveId.toString()) ?? '').toLocaleLowerCase()
|
||||
const bd = (getDriveName?.(b.driveId.toString()) ?? '').toLocaleLowerCase()
|
||||
|
||||
if (ad < bd) return -1 * mul
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useRef, useCallback } from 'react'
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { useCallback, useRef } from 'react'
|
||||
|
||||
import { POLLING_INTERVAL_MS } from '../constants/common'
|
||||
|
||||
interface UseStampPollingOptions {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useState, useContext, useRef, useEffect } from 'react'
|
||||
import type { FileInfo, FileInfoOptions, UploadProgress } from '@solarpunkltd/file-manager-lib'
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Context as FMContext } from '../../../providers/FileManager'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
import type { FileInfo, FileInfoOptions, UploadProgress } from '@solarpunkltd/file-manager-lib'
|
||||
import { ConflictAction, useUploadConflictDialog } from './useUploadConflictDialog'
|
||||
import { formatBytes, safeSetState, truncateNameMiddle } from '../utils/common'
|
||||
import { uuidV4 } from '../../../utils'
|
||||
import { FILE_MANAGER_EVENTS } from '../constants/common'
|
||||
import {
|
||||
DownloadProgress,
|
||||
DownloadState,
|
||||
@@ -11,12 +12,12 @@ import {
|
||||
TrackDownloadProps,
|
||||
TransferStatus,
|
||||
} from '../constants/transfers'
|
||||
import { validateStampStillExists, verifyDriveSpace } from '../utils/bee'
|
||||
import { isTrashed } from '../utils/common'
|
||||
import { abortDownload } from '../utils/download'
|
||||
import { AbortManager } from '../utils/abortManager'
|
||||
import { uuidV4 } from '../../../utils'
|
||||
import { FILE_MANAGER_EVENTS } from '../constants/common'
|
||||
import { validateStampStillExists, verifyDriveSpace } from '../utils/bee'
|
||||
import { formatBytes, isTrashed, safeSetState, truncateNameMiddle } from '../utils/common'
|
||||
import { abortDownload } from '../utils/download'
|
||||
|
||||
import { ConflictAction, useUploadConflictDialog } from './useUploadConflictDialog'
|
||||
|
||||
const SAMPLE_WINDOW_MS = 500
|
||||
const ETA_SMOOTHING = 0.3
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactPortal, useCallback, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { UploadConflictModal } from '../components/UploadConflictModal/UploadConflictModal'
|
||||
|
||||
export enum ConflictAction {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import { Point, Dir } from './common'
|
||||
import { Dir, Point } from './common'
|
||||
|
||||
export function computeContextMenuPosition(args: {
|
||||
clickPos: Point
|
||||
|
||||
Reference in New Issue
Block a user