Compare commits

..

21 Commits

Author SHA1 Message Date
bee-worker 082a8f52ef chore(master): release 0.32.0 (#689) 2025-03-12 12:34:55 +01:00
Levente Kiss bcd3d50b42 feat: vod display (#686)
* feat: preview for html5 supported videos

* fix: handle out of limit tags

* feat: support preview on the donwload screen

* refactor: rework meta and preview handling to be more general

* fix: missing meta

* fix: do not allow maybe or probably types

* fix: make the media check more strict

---------

Co-authored-by: Levente Kiss <levente.kiss@solarpunk.bzz>
2025-02-12 11:35:46 +01:00
bee-worker f695ac3a1c chore(master): release 0.31.0 (#688) 2025-01-13 14:57:16 +01:00
Ferenc Sárai a6125b3d0b feat: remove experimental FDP menu item (#687)
Co-authored-by: Ferenc Sárai <ferenc.sarai@solarpunk.buzz>
2025-01-13 14:43:10 +01:00
bee-worker e01d9fe3d7 chore(master): release 0.30.0 (#675) 2024-11-25 12:35:42 +01:00
Cafe137 6294bb0a7b fix: allow changing api url (#676)
* fix: allow changing api url

* chore: bump ci

* fix: add missing hook dependency
2024-11-25 11:17:27 +01:00
Cafe137 fbb2ed8a57 feat: update map data (#684) 2024-11-25 10:47:30 +01:00
Cafe137 aef6c07371 chore: update bee-js (#683) 2024-11-25 09:51:14 +01:00
Cafe137 ed75198528 ci: remove update supported bee action (#682) 2024-11-25 09:27:24 +01:00
Cafe137 d0c94b7316 feat: add experimental fdp (#681)
* feat: add experimental fdp

* ci: update swarm-actions to v1

* fix: fix eslint violations

* refactor: decaf
2024-11-21 12:43:30 +01:00
Cafe137 63f338075b fix: explicitly define type 0 transaction (#674) 2024-10-03 15:27:17 +02:00
bee-worker 4cb0bcd3b9 chore(master): release 0.29.0 (#668) 2024-07-30 23:24:02 +02:00
Cafe137 01b1b39c42 feat: clarify labels and syncing (#670) 2024-07-17 19:30:21 +02:00
Cafe137 8558860f0a feat: polish app (#669) 2024-07-17 15:49:22 +02:00
tamas6 b4ebfc7c3f fix: clarify withdraw and deposit message (#654)
* fix: clear withdraw/deposit message

* fix: withdraw/deposit message shorten
2024-07-17 14:07:45 +02:00
bee-worker a47de8fcb5 chore(master): release 0.28.0 (#667) 2024-06-18 14:46:52 +02:00
Cafe137 e9ebe33d51 feat: upgrade bee-js to 7.0.3 (#666) 2024-06-18 14:46:04 +02:00
bee-worker 4c06ff5d8e chore(master): release 0.27.0 (#665) 2024-06-11 21:26:45 +02:00
rampall 999399fb08 feat: add redeem shortcut to sidebar 2024-06-11 21:22:11 +02:00
bee-worker a00ca77b3e chore(master): release 0.26.2 (#663) 2024-06-05 06:56:12 +02:00
Cafe137 cae90c1a82 fix: merge version and health check (#662) 2024-06-05 06:55:40 +02:00
67 changed files with 54788 additions and 8710 deletions
+1 -2
View File
@@ -9,7 +9,6 @@
"file-loader",
"ts-node",
"webpack-cli",
"assert",
"buffer",
"crypto*",
"stream*",
@@ -17,4 +16,4 @@
"open",
"base64-inline-loader"
]
}
}
+2 -9
View File
@@ -56,13 +56,6 @@ jobs:
- name: Types check
run: npm run check:types
- name: Update supported Bee action
uses: ethersphere/update-supported-bee-action@v1
if: github.ref == 'refs/heads/master'
with:
updateEngine: true
token: ${{ secrets.GHA_PAT_BASIC }}
- name: Build
run: npm run build
@@ -70,7 +63,7 @@ jobs:
run: npm run build:component
- name: Create preview
uses: ethersphere/swarm-actions/pr-preview@v0
uses: ethersphere/swarm-actions/pr-preview@v1
continue-on-error: true
with:
bee-url: https://unlimited.gateway.ethswarm.org
@@ -79,7 +72,7 @@ jobs:
headers: '${{ secrets.GATEWAY_AUTHORIZATION_HEADER }}'
- name: Upload to testnet
uses: ethersphere/swarm-actions/upload-dir@v0
uses: ethersphere/swarm-actions/upload-dir@v1
continue-on-error: true
with:
index-document: index.html
+62
View File
@@ -1,5 +1,67 @@
# Changelog
## [0.32.0](https://github.com/ethersphere/bee-dashboard/compare/v0.31.0...v0.32.0) (2025-02-12)
### Features
* vod display ([#686](https://github.com/ethersphere/bee-dashboard/issues/686)) ([bcd3d50](https://github.com/ethersphere/bee-dashboard/commit/bcd3d50b4209a4f66a259b8a3f6ea5ffd908471f))
## [0.31.0](https://github.com/ethersphere/bee-dashboard/compare/v0.30.0...v0.31.0) (2025-01-13)
### Features
* remove experimental FDP menu item ([#687](https://github.com/ethersphere/bee-dashboard/issues/687)) ([a6125b3](https://github.com/ethersphere/bee-dashboard/commit/a6125b3d0b0b680a9fa61a8edcd75b2ae6c153e0))
## [0.30.0](https://github.com/ethersphere/bee-dashboard/compare/v0.29.0...v0.30.0) (2024-11-25)
### Features
* add experimental fdp ([#681](https://github.com/ethersphere/bee-dashboard/issues/681)) ([d0c94b7](https://github.com/ethersphere/bee-dashboard/commit/d0c94b7316ea2b139bddc5481132ea7de7cb840d))
* update map data ([#684](https://github.com/ethersphere/bee-dashboard/issues/684)) ([fbb2ed8](https://github.com/ethersphere/bee-dashboard/commit/fbb2ed8a576f3519883e71382b7f4e8505fbe139))
### Bug Fixes
* allow changing api url ([#676](https://github.com/ethersphere/bee-dashboard/issues/676)) ([6294bb0](https://github.com/ethersphere/bee-dashboard/commit/6294bb0a7be6b9b82354c42da8c84e767fad899e))
* explicitly define type 0 transaction ([#674](https://github.com/ethersphere/bee-dashboard/issues/674)) ([63f3380](https://github.com/ethersphere/bee-dashboard/commit/63f338075b919cb70d79665c3d86537f2ac1d2e9))
## [0.29.0](https://github.com/ethersphere/bee-dashboard/compare/v0.28.0...v0.29.0) (2024-07-17)
### Features
* clarify labels and syncing ([#670](https://github.com/ethersphere/bee-dashboard/issues/670)) ([01b1b39](https://github.com/ethersphere/bee-dashboard/commit/01b1b39c42cc5b68a0132c3696c3c42a27ea2ee4))
* polish app ([#669](https://github.com/ethersphere/bee-dashboard/issues/669)) ([8558860](https://github.com/ethersphere/bee-dashboard/commit/8558860f0a3baa82c31c091a44c78bb8e97de70d))
### Bug Fixes
* clarify withdraw and deposit message ([#654](https://github.com/ethersphere/bee-dashboard/issues/654)) ([b4ebfc7](https://github.com/ethersphere/bee-dashboard/commit/b4ebfc7c3fd449807db47fa25763df464cc45618))
## [0.28.0](https://github.com/ethersphere/bee-dashboard/compare/v0.27.0...v0.28.0) (2024-06-18)
### Features
* upgrade bee-js to 7.0.3 ([#666](https://github.com/ethersphere/bee-dashboard/issues/666)) ([e9ebe33](https://github.com/ethersphere/bee-dashboard/commit/e9ebe33d51aa525921eacfad683577605e591531))
## [0.27.0](https://github.com/ethersphere/bee-dashboard/compare/v0.26.2...v0.27.0) (2024-06-11)
### Features
* add redeem shortcut to sidebar ([999399f](https://github.com/ethersphere/bee-dashboard/commit/999399fb08c1a47a671ba0ad50409624654a1082))
## [0.26.2](https://github.com/ethersphere/bee-dashboard/compare/v0.26.1...v0.26.2) (2024-06-05)
### Bug Fixes
* merge version and health check ([#662](https://github.com/ethersphere/bee-dashboard/issues/662)) ([cae90c1](https://github.com/ethersphere/bee-dashboard/commit/cae90c1a82e16ee8c7908c43e2fd17f7130eb89d))
## [0.26.1](https://github.com/ethersphere/bee-dashboard/compare/v0.26.0...v0.26.1) (2024-06-03)
+2 -6
View File
@@ -13,12 +13,8 @@
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and
working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
This project is intended to be used with \*\*Bee version
<!-- SUPPORTED_BEE_START -->1.12.0-88c1d236<!-- SUPPORTED_BEE_END -->**. Using it with older or newer Bee versions is
not recommended and may not work. Stay up to date by joining the [official Discord](https://discord.gg/GU22h2utj6) and
by keeping an eye on the [releases tab](https://github.com/ethersphere/bee-dashboard/releases).
Stay up to date by joining the [official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
[releases tab](https://github.com/ethersphere/bee-dashboard/releases).
![Status page](/ui_samples/info.png)
+1576 -1357
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@ethersphere/bee-dashboard",
"version": "0.26.1",
"version": "0.32.0",
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
"keywords": [
"bee",
@@ -26,30 +26,30 @@
"url": "https://github.com/ethersphere/bee-dashboard.git"
},
"dependencies": {
"@ethersphere/bee-js": "^7.0.0",
"@ethersphere/bee-js": "^8.3.1",
"@ethersphere/swarm-cid": "^0.1.0",
"@fairdatasociety/fdp-storage": "^0.19.0",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.57",
"assert": "^2.0.0",
"axios": "0.24.0",
"bignumber.js": "9.0.1",
"axios": "^0.28.1",
"bignumber.js": "^9.1.2",
"buffer": "^6.0.3",
"crypto": "npm:crypto-browserify",
"crypto-browserify": "^3.12.0",
"dotted-map": "^2.2.3",
"ethers": "^5.6.4",
"ethers": "^5.7.2",
"file-saver": "^2.0.5",
"formik": "2.2.9",
"formik-material-ui": "3.0.1",
"jszip": "^3.7.1",
"jszip": "^3.10.1",
"mantaray-js": "^1.0.3",
"material-ui-dropzone": "3.5.0",
"notistack": "1.0.10",
"notistack": "^3.0.1",
"opener": "1.5.2",
"qrcode.react": "1.0.1",
"react": ">= 17.0.2",
"react-copy-to-clipboard": "5.0.4",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": ">= 17.0.2",
"react-identicons": "1.2.5",
"react-router": "6.2.1",
@@ -77,15 +77,15 @@
"@types/jest": "27.0.2",
"@types/qrcode.react": "1.0.2",
"@types/react": "17.0.34",
"@types/react-copy-to-clipboard": "5.0.2",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "17.0.11",
"@types/react-router": "5.1.18",
"@types/react-router-dom": "5.3.2",
"@types/react-syntax-highlighter": "13.5.2",
"@typescript-eslint/eslint-plugin": "5.28.0",
"@typescript-eslint/parser": "5.28.0",
"babel-eslint": "10.1.0",
"babel-loader": "8.1.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-syntax-dynamic-import": "6.18.0",
"babel-plugin-tsconfig-paths": "1.0.2",
"base64-inline-loader": "^2.0.1",
@@ -113,7 +113,7 @@
"ts-node": "^10.8.1",
"typescript": "4.8.3",
"web-vitals": "2.1.2",
"webpack": "^5.73.0",
"webpack": "^5.93.0",
"webpack-cli": "^4.10.0"
},
"peerDependencies": {
+51739 -7115
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
import { ChainState } from '@ethersphere/bee-js'
import { useContext, useEffect, useState } from 'react'
import { Context } from '../providers/Settings'
import ExpandableListItem from './ExpandableListItem'
export function ChainSync() {
const { beeApi } = useContext(Context)
const [chainState, setChainState] = useState<ChainState | null>(null)
useEffect(() => {
const interval = setInterval(() => {
if (!beeApi) {
return
}
beeApi.getChainState().then(setChainState).catch(console.error) // eslint-disable-line
}, 3_000)
return () => clearInterval(interval)
})
return (
<ExpandableListItem label="Chain state" value={chainState ? `${chainState.block} / ${chainState.chainTip}` : '-'} />
)
}
+9 -7
View File
@@ -1,9 +1,11 @@
import { Utils } from '@ethersphere/bee-js'
import { Typography } from '@material-ui/core/'
import { ReactElement } from 'react'
import Identicon from 'react-identicons'
import ClipboardCopy from './ClipboardCopy'
import QRCodeModal from './QRCodeModal'
import { BLOCKCHAIN_EXPLORER_URL } from '../constants'
import ClipboardCopy from './ClipboardCopy'
import { Flex } from './Flex'
import QRCodeModal from './QRCodeModal'
interface Props {
address: string | undefined
@@ -16,10 +18,10 @@ export default function EthereumAddress(props: Props): ReactElement {
return (
<Typography component="div" variant="subtitle1">
{props.address ? (
<div style={{ display: 'flex' }}>
<Flex>
{props.hideBlockie ? null : (
<div style={{ paddingTop: '5px', marginRight: '10px' }}>
<Identicon size={20} string={props.address} />
<Identicon size={20} string={Utils.capitalizeAddressERC55(props.address)} />
</div>
)}
<div>
@@ -43,9 +45,9 @@ export default function EthereumAddress(props: Props): ReactElement {
{props.address}
</a>
</div>
<QRCodeModal value={props.address} label={'Ethereum Address'} />
<ClipboardCopy value={props.address} />
</div>
<QRCodeModal value={Utils.capitalizeAddressERC55(props.address)} label={'Ethereum Address'} />
<ClipboardCopy value={Utils.capitalizeAddressERC55(props.address)} />
</Flex>
) : (
'-'
)}
+5 -4
View File
@@ -1,7 +1,8 @@
import { ReactElement, ReactNode, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Collapse, ListItem, ListItemText, Typography } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ExpandLess, ExpandMore } from '@material-ui/icons'
import { ReactElement, ReactNode, useState } from 'react'
import { Flex } from './Flex'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -65,14 +66,14 @@ export default function ExpandableList({ children, label, level, defaultOpen, in
<div className={`${classes.root} ${rootLevelClass}`}>
<ListItem button onClick={handleClick} className={classes.header}>
<ListItemText primary={<Typography variant={typographyVariant}>{label}</Typography>} />
<div style={{ display: 'flex' }}>
<Flex>
{!open && (
<Typography variant="body2" className={classes.infoText}>
{info}
</Typography>
)}
{open ? <ExpandLess /> : <ExpandMore />}
</div>
</Flex>
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={contentLevelClass}>{children}</div>
+4 -8
View File
@@ -2,14 +2,14 @@ import { Box, Grid, IconButton, InputBase, ListItem, Typography } from '@materia
import Collapse from '@material-ui/core/Collapse'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ChangeEvent, ReactElement, useState } from 'react'
import type { RemixiconReactIconProps } from 'remixicon-react'
import Check from 'remixicon-react/CheckLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import Edit from 'remixicon-react/PencilLineIcon'
import Minus from 'remixicon-react/SubtractLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import ExpandableListItemActions from './ExpandableListItemActions'
import ExpandableListItemNote from './ExpandableListItemNote'
import { SwarmButton } from './SwarmButton'
import type { RemixiconReactIconProps } from 'remixicon-react'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -108,12 +108,8 @@ export default function ExpandableListItemKey({
<div>
{!open && value}
{!expandedOnly && !locked && (
<IconButton size="small" className={classes.copyValue}>
{open ? (
<Minus onClick={toggleOpen} strokeWidth={1} />
) : (
<Edit onClick={toggleOpen} strokeWidth={1} />
)}
<IconButton size="small" className={classes.copyValue} onClick={toggleOpen}>
{open ? <Minus strokeWidth={1} /> : <Edit strokeWidth={1} />}
</IconButton>
)}
</div>
+12 -14
View File
@@ -77,20 +77,18 @@ export default function ExpandableListItemKey({ label, value, expanded }: Props)
<Grid container direction="row" justifyContent="space-between" alignItems="center">
{label && <Typography variant="body1">{label}</Typography>}
<Typography variant="body2">
<div>
{!open && (
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{value ? spanText : ''}</span>
</CopyToClipboard>
</Tooltip>
</span>
)}
<IconButton size="small" className={classes.copyValue}>
{open ? <Minus onClick={toggleOpen} strokeWidth={1} /> : <Eye onClick={toggleOpen} strokeWidth={1} />}
</IconButton>
</div>
{!open && (
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{value ? spanText : ''}</span>
</CopyToClipboard>
</Tooltip>
</span>
)}
<IconButton size="small" className={classes.copyValue} onClick={toggleOpen}>
{open ? <Minus strokeWidth={1} /> : <Eye strokeWidth={1} />}
</IconButton>
</Typography>
</Grid>
<Collapse in={open} timeout="auto" unmountOnExit>
+14 -16
View File
@@ -82,22 +82,20 @@ export default function ExpandableListItemLink({
<Grid container direction="row" justifyContent="space-between" alignItems="center">
{label && <Typography variant="body1">{label}</Typography>}
<Typography variant="body2">
<div>
{allowClipboard && (
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{displayValue}</span>
</CopyToClipboard>
</Tooltip>
</span>
)}
{!allowClipboard && <span onClick={onNavigation}>{displayValue}</span>}
<IconButton size="small" className={classes.openLinkIcon}>
{navigationType === 'NEW_WINDOW' && <OpenInNewSharp onClick={onNavigation} strokeWidth={1} />}
{navigationType === 'HISTORY_PUSH' && <ArrowForward onClick={onNavigation} strokeWidth={1} />}
</IconButton>
</div>
{allowClipboard && (
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{displayValue}</span>
</CopyToClipboard>
</Tooltip>
</span>
)}
{!allowClipboard && <span onClick={onNavigation}>{displayValue}</span>}
<IconButton size="small" className={classes.openLinkIcon} onClick={onNavigation}>
{navigationType === 'NEW_WINDOW' && <OpenInNewSharp strokeWidth={1} />}
{navigationType === 'HISTORY_PUSH' && <ArrowForward strokeWidth={1} />}
</IconButton>
</Typography>
</Grid>
</Grid>
+29
View File
@@ -0,0 +1,29 @@
import { createStyles, makeStyles } from '@material-ui/core'
import { ReactElement } from 'react'
const useStyles = makeStyles(() =>
createStyles({
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
}),
)
interface VideoProps {
src: string | undefined
maxHeight?: string
maxWidth?: string
}
export function FitVideo(props: VideoProps): ReactElement {
const classes = useStyles()
const inlineStyles: Record<string, string> = {}
props.maxHeight && (inlineStyles.maxHeight = props.maxHeight)
props.maxWidth && (inlineStyles.maxWidth = props.maxWidth)
return <video className={classes.video} src={props.src} style={inlineStyles} controls />
}
+9
View File
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export function Flex({ children }: Props) {
return <div style={{ display: 'flex' }}>{children}</div>
}
+8 -2
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Divider, Drawer, Grid, List, Link as MUILink } from '@material-ui/core'
import { Box, Divider, Drawer, Grid, List, Link as MUILink, Typography } from '@material-ui/core'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { ReactElement, useContext } from 'react'
import { Link } from 'react-router-dom'
@@ -133,10 +133,16 @@ export default function SideBar(): ReactElement {
<SideBarItem
iconStart={<GithubIcon className={classes.icon} />}
iconEnd={<ExternalLinkIcon className={classes.icon} color="#595959" />}
label={<span>Github repository</span>}
label={<span>GitHub</span>}
/>
</MUILink>
</List>
<Divider className={classes.divider} />
<Box mt={4}>
<Link to={ROUTES.TOP_UP_GIFT_CODE}>
<Typography align="center">Redeem gift code</Typography>
</Link>
</Box>
</Grid>
<Grid>
<List>
+1 -2
View File
@@ -1,5 +1,4 @@
export const META_FILE_NAME = '.swarmgatewaymeta.json'
export const PREVIEW_FILE_NAME = '.swarmgatewaypreview.jpeg'
export const META_FILE_NAME = 'metadata'
export const PREVIEW_DIMENSIONS = { maxWidth: 250, maxHeight: 175 }
export const BZZ_LINK_DOMAIN = 'bzz.link'
export const BLOCKCHAIN_EXPLORER_URL = 'https://blockscout.com/xdai/mainnet'
+1 -1
View File
@@ -13,7 +13,7 @@ export default function DepositModal(): ReactElement {
<WithdrawDepositModal
successMessage="Successful deposit."
errorMessage="Error with depositing"
dialogMessage="Specify the amount of xBZZ you would like to deposit to your node."
dialogMessage="Amount of xBZZ to deposit to the checkbook, from your node."
label="Deposit"
icon={<Download size="1rem" />}
min={new BigNumber(0)}
+1 -1
View File
@@ -13,7 +13,7 @@ export default function WithdrawModal(): ReactElement {
<WithdrawDepositModal
successMessage="Successful withdrawal."
errorMessage="Error with withdrawing."
dialogMessage="Specify the amount of xBZZ you would like to withdraw from your node."
dialogMessage="Amount of xBZZ to withdraw from the checkbook to your node."
label="Withdraw"
icon={<Upload size="1rem" />}
min={new BigNumber(0)}
+3 -2
View File
@@ -4,6 +4,7 @@ import { useSnackbar } from 'notistack'
import React, { ReactElement, useContext, useEffect } from 'react'
import CloseIcon from 'remixicon-react/CloseCircleLineIcon'
import ErrorBoundary from '../components/ErrorBoundary'
import { Flex } from '../components/Flex'
import SideBar from '../components/SideBar'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../constants'
import { useBeeDesktop, useNewBeeDesktopVersion } from '../hooks/apiHooks'
@@ -81,13 +82,13 @@ const Dashboard = (props: Props): ReactElement => {
)
return (
<div style={{ display: 'flex' }}>
<Flex>
<SideBar />
<Container className={classes.content}>
{' '}
<ErrorBoundary errorReporting={props.errorReporting}>{content}</ErrorBoundary>
</Container>
</div>
</Flex>
)
}
+8 -7
View File
@@ -81,17 +81,18 @@ export class Token {
return asString.slice(0, indexOfSignificantDigit + digits)
}
minusBaseUnits(amount: string): Token {
minusBaseUnits(amount: string | BigNumber | bigint): Token {
const baseUnits = makeBigNumber(amount)
return new Token(
this.toBigNumber.minus(new BigNumber(amount).multipliedBy(new BigNumber(10).pow(this.decimals))),
this.toBigNumber.minus(baseUnits.multipliedBy(new BigNumber(10).pow(this.decimals))),
this.decimals,
)
}
plusBaseUnits(amount: string): Token {
return new Token(
this.toBigNumber.plus(new BigNumber(amount).multipliedBy(new BigNumber(10).pow(this.decimals))),
this.decimals,
)
plusBaseUnits(amount: string | BigNumber | bigint): Token {
const baseUnits = makeBigNumber(amount)
return new Token(this.toBigNumber.plus(baseUnits.multipliedBy(new BigNumber(10).pow(this.decimals))), this.decimals)
}
}
@@ -1,3 +1,4 @@
import { Utils } from '@ethersphere/bee-js'
import { Box } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
@@ -57,7 +58,10 @@ export function AccountChequebook(): ReactElement {
</ExpandableList>
)}
<ExpandableList label="Blockchain" defaultOpen>
<ExpandableListItemKey label="Ethereum address" value={nodeAddresses?.ethereum || ''} />
<ExpandableListItemKey
label="Ethereum address"
value={nodeAddresses?.ethereum ? Utils.capitalizeAddressERC55(nodeAddresses.ethereum) : ''}
/>
<ExpandableListItemKey
label="Chequebook contract address"
value={chequebookAddress?.chequebookAddress || ''}
+2 -1
View File
@@ -2,6 +2,7 @@ import { CircularProgress, Container, createStyles, makeStyles } from '@material
import { ReactElement, useContext, useEffect } from 'react'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import { ChainSync } from '../../../components/ChainSync'
import { Loading } from '../../../components/Loading'
import { SwarmButton } from '../../../components/SwarmButton'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
@@ -57,7 +58,7 @@ export function AccountStamps(): ReactElement {
{error && (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<Loading />
{error.message}
<ChainSync />
</Container>
)}
{!error && (
+8 -4
View File
@@ -1,4 +1,4 @@
import { BeeModes } from '@ethersphere/bee-js'
import { BeeModes, Utils } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
@@ -26,7 +26,7 @@ export function AccountWallet(): ReactElement {
const navigate = useNavigate()
function onCheckTransactions() {
window.open(`https://blockscout.com/xdai/mainnet/address/${nodeAddresses?.ethereum}/transactions`, '_blank')
window.open(`https://gnosisscan.io/address/${nodeAddresses?.ethereum}`, '_blank')
}
function onInvite() {
@@ -56,7 +56,11 @@ export function AccountWallet(): ReactElement {
{balance && nodeAddresses ? (
<>
<Box mb={0.25}>
<ExpandableListItemKey label="Node wallet address" value={nodeAddresses.ethereum} expanded />
<ExpandableListItemKey
label="Node wallet address"
value={Utils.capitalizeAddressERC55(nodeAddresses.ethereum)}
expanded
/>
</Box>
<Box mb={0.25}>
<ExpandableListItem label="xDAI balance" value={`${balance.dai.toSignificantDigits(4)} xDAI`} />
@@ -72,7 +76,7 @@ export function AccountWallet(): ReactElement {
)}
<ExpandableListItemActions>
<SwarmButton onClick={onCheckTransactions} iconType={Link}>
Check transactions on Blockscout
Check transactions
</SwarmButton>
{isDesktop && (
<SwarmButton onClick={onInvite} iconType={Gift}>
+106
View File
@@ -0,0 +1,106 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Checkbox, InputBase, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { useEffect, useState } from 'react'
import RegisterIcon from 'remixicon-react/AddBoxLineIcon'
import LoginIcon from 'remixicon-react/LoginBoxLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
onSuccessfulLogin: () => void
}
export function FdpLogin({ fdp, onSuccessfulLogin }: Props) {
const [username, setUsername] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [remember, setRemember] = useState<boolean>(false)
const [sepolia, setSepolia] = useState<string>('https://sepolia.drpc.org')
const { enqueueSnackbar } = useSnackbar()
const inputStyle = { background: 'white', padding: '2px 8px', width: '100%' }
useEffect(() => {
const storedSepolia = localStorage.getItem('sepolia')
if (storedSepolia) {
setSepolia(storedSepolia)
}
const fdpCredentials = localStorage.getItem('fdpCredentials')
if (fdpCredentials) {
const { username, password } = JSON.parse(fdpCredentials)
setUsername(username)
setPassword(password)
setRemember(true)
}
}, [])
async function onLogin() {
localStorage.setItem('sepolia', sepolia)
if (remember) {
localStorage.setItem('fdpCredentials', JSON.stringify({ username, password }))
} else {
localStorage.removeItem('fdpCredentials')
}
enqueueSnackbar('Logging in...', { variant: 'info' })
try {
await fdp.account.login(username, password)
enqueueSnackbar('Logged in successfully', { variant: 'success' })
onSuccessfulLogin()
} catch {
enqueueSnackbar('Login failed', { variant: 'error' })
} finally {
setUsername('')
setPassword('')
setRemember(false)
}
}
function onRegister() {
window.open('https://create.fairdatasociety.org/', '_blank')
}
return (
<div
style={{
maxWidth: '500px',
margin: 'auto',
}}
>
<Vertical gap={16} full>
<Vertical gap={8} left full>
<Typography variant="body2">Sepolia JSON RPC</Typography>
<InputBase value={sepolia} onChange={e => setSepolia(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Username</Typography>
<InputBase value={username} onChange={e => setUsername(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Password</Typography>
<InputBase value={password} onChange={e => setPassword(e.target.value)} style={inputStyle} type="password" />
</Vertical>
<Vertical gap={8} left full>
<Horizontal>
<Checkbox checked={remember} onChange={e => setRemember(e.target.checked)} />
<Typography variant="body2">Remember me</Typography>
</Horizontal>
</Vertical>
<Vertical left full>
<Horizontal gap={4}>
<SwarmButton iconType={LoginIcon} onClick={onLogin}>
Login
</SwarmButton>
<SwarmButton iconType={RegisterIcon} onClick={onRegister}>
Registration
</SwarmButton>
</Horizontal>
</Vertical>
</Vertical>
</div>
)
}
+98
View File
@@ -0,0 +1,98 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { useState } from 'react'
import { CafeReactFs } from '../../react-fs/CafeReactFs'
import { FsItem, FsItemType } from '../../react-fs/CafeReactType'
import { joinUrl } from '../../react-fs/Utility'
interface Props {
fdp: FdpStorage
name: string
}
export function FdpPod({ fdp, name }: Props) {
const [reloader, setReloader] = useState(0)
function reload() {
setReloader(reloader + 1)
}
return (
<CafeReactFs
rootAlias={`/${name}`}
backgroundColor="#ffffff"
reloader={reloader}
onDeleteFile={async (path: string) => {
await fdp.file.delete(name, path)
reload()
}}
onDeleteDirectory={async (path: string) => {
await fdp.directory.delete(name, path)
reload()
}}
onUpload={(path: string) => {
const input = document.createElement('input')
input.type = 'file'
input.multiple = true
input.click()
return new Promise<void>(resolve => {
input.onchange = async () => {
if (!input.files || !input.files.length) {
resolve()
return
}
for (const file of Array.from(input.files)) {
const data = await file.arrayBuffer()
await fdp.file.uploadData(name, joinUrl(path, file.name), new Uint8Array(data))
}
reload()
resolve()
}
})
}}
onCreateDirectory={async (path: string) => {
// eslint-disable-next-line no-alert
const newDirectoryName = prompt('Directory name')
if (!newDirectoryName) {
return
}
await fdp.directory.create(name, joinUrl(path, newDirectoryName))
reload()
}}
// eslint-disable-next-line require-await
onSync={async () => {
setReloader(reloader + 1)
}}
download={async (path: string) => {
const data = await fdp.file.downloadData(name, path)
const url = URL.createObjectURL(new Blob([data]))
const a = document.createElement('a')
a.href = url
a.download = path.split('/').pop() || 'Untitled'
a.click()
}}
list={async (path: string) => {
const fdpResponse = await fdp.directory.read(name, path)
const items: FsItem[] = []
for (const directory of fdpResponse.directories) {
items.push({
name: directory.name,
$type: FsItemType.DIRECTORY,
id: directory.name,
})
}
for (const file of fdpResponse.files) {
items.push({
name: file.name,
$type: FsItemType.FILE,
id: file.name,
})
}
return items
}}
/>
)
}
+30
View File
@@ -0,0 +1,30 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { FdpPod } from './FdpPod'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
pods: Pod[]
loadingPods: boolean
}
export function FdpPods({ fdp, pods, loadingPods }: Props) {
if (loadingPods) {
return (
<Vertical gap={32} full>
<CircularProgress />
<Typography>Loading your pods...</Typography>
</Vertical>
)
}
return (
<Vertical gap={16} full left>
{pods.map(pod => (
<FdpPod key={pod.index} fdp={fdp} name={pod.name} />
))}
</Vertical>
)
}
+22
View File
@@ -0,0 +1,22 @@
interface Props {
children: React.ReactNode
p?: string
gap?: number
between?: boolean
background?: string
}
export function Horizontal({ children, p = '0', gap = 8, between, background }: Props) {
const style = {
display: 'flex',
flexDirection: 'row' as 'row', //eslint-disable-line
alignItems: 'center',
justifyContent: between ? 'space-between' : 'flex-start',
gap: `${gap}px`,
padding: p,
background,
width: between ? '100%' : 'auto',
}
return <div style={style}>{children}</div>
}
+20
View File
@@ -0,0 +1,20 @@
interface Props {
children: React.ReactNode
p?: number
gap?: number
left?: boolean
full?: boolean
}
export function Vertical({ children, p = 0, gap = 0, left = false, full = false }: Props) {
const style = {
display: 'flex',
flexDirection: 'column' as 'column', //eslint-disable-line
alignItems: left ? 'flex-start' : 'center',
gap: `${gap}px`,
width: full ? '100%' : 'auto',
padding: `${p}px`,
}
return <div style={style}>{children}</div>
}
+163
View File
@@ -0,0 +1,163 @@
import { Bee } from '@ethersphere/bee-js'
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useEffect, useState } from 'react'
import ImportIcon from 'remixicon-react/AddBoxLineIcon'
import PlusCircle from 'remixicon-react/AddCircleLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { joinUrl } from '../../react-fs/Utility'
import { ManifestJs } from '../../utils/manifest'
import { FdpLogin } from './FdpLogin'
import { FdpPods } from './FdpPods'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
async function makeFdp(): Promise<FdpStorage | null> {
const bee = new Bee('http://localhost:1633')
const sepolia = localStorage.getItem('sepolia') ?? 'https://sepolia.drpc.org'
const postageBatches = await bee.getAllPostageBatch()
const usableBatches = postageBatches.filter(batch => batch.usable)
const highestCapacityBatch = usableBatches.length ? usableBatches.reduce((a, b) => (a.depth > b.depth ? a : b)) : null
if (!highestCapacityBatch) {
return null
}
return new FdpStorage('http://localhost:1633', highestCapacityBatch.batchID, {
ensOptions: {
rpcUrl: sepolia,
contractAddresses: {
ensRegistry: '0x42a96D45d787685ac4b36292d218B106Fb39be7F',
fdsRegistrar: '0xFBF00389140C00384d88d458239833E3231a7414',
nameResolver: '0xE20ECe6Ea93c4edE41e4d3B973f6679F1E89986A',
publicResolver: '0xC904989B579c2B216A75723688C784038AA99B56',
reverseResolver: '0xbDC8D98d3cbFd68EA9c165E1f15Df6e77A2ae0C5',
},
gasEstimation: 1,
performChecks: true,
},
providerOptions: {
url: sepolia,
},
ensDomain: 'fds',
})
}
export default function FDP(): ReactElement {
const [fdp, setFdp] = useState<FdpStorage | null>(null)
const [pods, setPods] = useState<Pod[]>([])
const [loggedIn, setLoggedIn] = useState<boolean>(false)
const [loadingPods, setLoadingPods] = useState<boolean>(false)
const [creatingPod, setCreatingPod] = useState<boolean>(false)
const { enqueueSnackbar } = useSnackbar()
useEffect(() => {
makeFdp().then(fdp => {
if (!fdp) {
enqueueSnackbar('FDP could not be initialized. Do you have a postage batch?', { variant: 'error' })
}
setFdp(fdp)
})
}, [enqueueSnackbar])
useEffect(() => {
if (fdp && loggedIn) {
setLoadingPods(true)
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setLoadingPods(false)
})
}
}, [fdp, loggedIn])
function onSuccessfulLogin() {
setLoggedIn(true)
}
function onCreatePod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
if (name) {
setCreatingPod(true)
fdp.personalStorage.create(name).then(() => {
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setCreatingPod(false)
})
})
}
}
async function onImportPod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
// eslint-disable-next-line no-alert
const importHash = prompt('Enter the Swarm reference')
if (!name || !importHash) {
return
}
setCreatingPod(true)
const bee = new Bee('http://localhost:1633')
const manifestJs = new ManifestJs(bee)
const entries = await manifestJs.getHashes(importHash)
await fdp.personalStorage.create(name)
for (const [path, hash] of Object.entries(entries)) {
await fdp.file.uploadData(name, joinUrl('/', path), await bee.downloadData(hash))
}
const pods = await fdp.personalStorage.list()
setPods(pods.pods)
setCreatingPod(false)
}
if (!fdp) {
return <CircularProgress />
}
return (
<Vertical gap={32} full left>
<Horizontal between>
<Typography variant="h1">Files</Typography>
{loggedIn && (
<Horizontal gap={4}>
<SwarmButton onClick={onCreatePod} iconType={PlusCircle}>
Create
</SwarmButton>
<SwarmButton onClick={onImportPod} iconType={ImportIcon}>
Import
</SwarmButton>
</Horizontal>
)}
</Horizontal>
{!loggedIn && <FdpLogin fdp={fdp} onSuccessfulLogin={onSuccessfulLogin} />}
{loggedIn && <FdpPods fdp={fdp} pods={pods} loadingPods={loadingPods || creatingPod} />}
{loggedIn && !loadingPods && !creatingPod && pods.length === 0 && (
<Typography>
<strong>You do not have any pods yet.</strong> Get started by clicking the Create or Import button on the top
right.
</Typography>
)}
</Vertical>
)
}
+32 -15
View File
@@ -1,6 +1,6 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { Web } from '@material-ui/icons'
import { ReactElement } from 'react'
import { ReactElement, useMemo } from 'react'
import File from 'remixicon-react/FileLineIcon'
import Folder from 'remixicon-react/FolderLineIcon'
import { FitImage } from '../../components/FitImage'
@@ -8,35 +8,52 @@ import { shortenText } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
import { shortenHash } from '../../utils/hash'
import { AssetIcon } from './AssetIcon'
import { FitVideo } from '../../components/FitVideo'
interface Props {
previewUri?: string
metadata?: Metadata
}
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
/* eslint-disable react/display-name */
const getPreviewComponent = (previewUri?: string, metadata?: Metadata) => {
if (metadata?.isVideo) {
return () => <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
}
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
let previewComponent = <File />
let type = metadata?.type
if (metadata?.isImage) {
return () => <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
}
if (metadata?.isWebsite) {
previewComponent = <Web />
type = 'Website'
} else if (metadata?.type === 'folder') {
previewComponent = <Folder />
type = 'Folder'
return () => <AssetIcon icon={<Web />} />
}
if (metadata?.type === 'folder') {
return () => <AssetIcon icon={<Folder />} />
}
return () => <AssetIcon icon={<File />} />
}
const getType = (metadata?: Metadata) => {
if (metadata?.isWebsite) return 'Website'
if (metadata?.type === 'folder') return 'Folder'
return metadata?.type
}
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
const PreviewAssetComponent = useMemo(() => getPreviewComponent(previewUri, metadata), [metadata, previewUri])
const type = useMemo(() => getType(metadata), [metadata])
return (
<Box mb={4}>
<Box bgcolor="background.paper">
<Grid container direction="row">
{previewUri ? (
<FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
) : (
<AssetIcon icon={previewComponent} />
)}
<PreviewAssetComponent />
<Box p={2}>
{metadata?.hash && <Typography>Swarm Hash: {shortenHash(metadata.hash)}</Typography>}
{metadata?.name && metadata?.name !== metadata?.hash && (
+14 -7
View File
@@ -3,6 +3,7 @@ import { Box } from '@material-ui/core'
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import { LinearProgressWithLabel } from '../../components/ProgressBar'
import { Tag } from '@ethersphere/bee-js'
interface Props {
reference: string
@@ -16,12 +17,20 @@ export function AssetSyncing({ reference }: Props): ReactElement {
const [syncProgress, setSyncProgress] = useState<number>(0)
const syncCheck = async () => {
if (!beeApi) {
return
}
if (!beeApi) return
const tags = await beeApi.getAllTags()
const tag = tags.find(t => t.address === reference)
let allTags: Tag[] = []
let offset = 0
const limit = 1000
let tagsBatch
do {
tagsBatch = await beeApi.getAllTags({ limit, offset })
allTags = allTags.concat(tagsBatch)
offset += limit
} while (tagsBatch.length === limit) // Continue if the batch is full, stop if fewer than the limit
const tag = allTags.find(t => t.address === reference)
if (tag) {
const progress = ((tag.seen + tag.synced) / tag.split) * 100
@@ -51,8 +60,6 @@ export function AssetSyncing({ reference }: Props): ReactElement {
There are instances when it seems that the content isn't synchronized, despite being already available.
To ensure it's not due to invalid synchronization data,
verify availability from at least 70% using one of the stewardship endpoints.
TODO: is 70 a good number?
*/
if (beeApi && !isRetrieveChecking && syncProgress > 10 && syncProgress < 100) {
// It's a long running task make sure only one run occurs at a time.
+1 -1
View File
@@ -84,7 +84,7 @@ export function Download(): ReactElement {
<>
{nodeInfo?.beeMode !== BeeModes.ULTRA_LIGHT && <FileNavigation active="DOWNLOAD" />}
<ExpandableListItemInput
label="Swarm Hash"
label="Swarm Hash or ENS"
onConfirm={value => onSwarmIdentifier(value)}
onChange={validateChange}
helperText={referenceError}
+24 -27
View File
@@ -7,7 +7,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
@@ -50,38 +50,35 @@ export function Share(): ReactElement {
return
}
const entries = await manifestJs.getHashes(reference)
const entries = await manifestJs.getHashes(reference, { exclude: [META_FILE_NAME] })
setSwarmEntries(entries)
const indexDocument = await manifestJs.getIndexDocumentPath(reference)
setIndexDocument(indexDocument)
const previewFile = entries[PREVIEW_FILE_NAME]
delete entries[META_FILE_NAME]
delete entries[PREVIEW_FILE_NAME]
setSwarmEntries(entries)
const count = Object.keys(entries).length
let metadata: Metadata | undefined = {
hash,
size: 0,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
isWebsite: Boolean(indexDocument) && count > 1,
count,
}
try {
const mtdt = await beeApi.downloadFile(reference, META_FILE_NAME)
const remoteMetadata = mtdt.data.text()
metadata = { ...metadata, ...(JSON.parse(remoteMetadata) as Metadata) }
} catch (e) {} // eslint-disable-line no-empty
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
const formattedMetadata = JSON.parse(remoteMetadata.data.text()) as Metadata
if (previewFile) {
setPreview(`${apiUrl}/bzz/${reference}/${PREVIEW_FILE_NAME}`)
if (formattedMetadata.isVideo || formattedMetadata.isImage) {
setPreview(`${apiUrl}/bzz/${reference}`)
}
setMetadata({ ...formattedMetadata, hash })
} catch (e) {
// if metadata is not available or invalid go with the default one
const count = Object.keys(entries).length
setMetadata({
hash,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
count,
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogg|mp3|ogg|wav)$/i.test(indexDocument)),
isImage: Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument)),
// naive assumption based on indexDocument, we don't want to donwload the whole manifest
})
}
setMetadata(metadata)
}
function onOpen() {
+4 -20
View File
@@ -6,7 +6,7 @@ import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { Context as FileContext } from '../../providers/File'
@@ -33,7 +33,7 @@ export function Upload(): ReactElement {
const { stamps, refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
const { files, setFiles, uploadOrigin, metadata, previewUri, previewBlob } = useContext(FileContext)
const { files, setFiles, uploadOrigin, metadata, previewUri } = useContext(FileContext)
const { identities, setIdentities } = useContext(IdentityContext)
const { status } = useContext(BeeContext)
@@ -98,31 +98,15 @@ export function Upload(): ReactElement {
}
}
}
const lastModified = files[0].lastModified
// We want to store only some metadata
const mtd: SwarmMetadata = {
name: metadata.name,
size: metadata.size,
}
// Type of the file only makes sense for a single file
if (files.length === 1) mtd.type = metadata.type
const metafile = new File([JSON.stringify(mtd)], META_FILE_NAME, {
const metafile = new File([JSON.stringify(metadata)], META_FILE_NAME, {
type: 'application/json',
lastModified,
})
fls.push(packageFile(metafile))
if (previewBlob) {
const previewFile = new File([previewBlob], PREVIEW_FILE_NAME, {
type: 'image/jpeg',
lastModified,
})
fls.push(packageFile(previewFile))
}
setUploading(true)
await waitUntilStampUsable(stamp.batchID, beeApi)
+3 -3
View File
@@ -17,12 +17,12 @@ export function ChequebookInfoCard() {
<Card
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
children: 'Manage chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
title={`${chequebookBalance?.availableBalance.toSignificantDigits(4)} xBZZ`}
subtitle="Current chequebook balance."
subtitle="Network transfer balance."
status="ok"
/>
)
@@ -32,7 +32,7 @@ export function ChequebookInfoCard() {
<Card
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
children: 'Manage chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
+13 -3
View File
@@ -1,5 +1,6 @@
import { Button } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import { ChainSync } from '../../components/ChainSync'
import ExpandableListItem from '../../components/ExpandableListItem'
import Map from '../../components/Map'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../constants'
@@ -19,11 +20,17 @@ export default function Status(): ReactElement {
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'stretch',
alignContent: 'stretch',
gap: '8px',
}}
>
<NodeInfoCard />
<div style={{ width: '8px' }}></div>
<WalletInfoCard />
<div style={{ width: '8px' }}></div>
<ChequebookInfoCard />
</div>
<div style={{ height: '16px' }} />
@@ -31,6 +38,9 @@ export default function Status(): ReactElement {
<div style={{ height: '2px' }} />
<ExpandableListItem label="Connected peers" value={topology?.connected ?? '-'} />
<ExpandableListItem label="Population" value={topology?.population ?? '-'} />
<ExpandableListItem label="Depth" value={topology?.depth ?? '-'} />
<ChainSync />
<div style={{ height: '16px' }} />
{isDesktop && (
<ExpandableListItem
+25 -20
View File
@@ -1,35 +1,37 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { ReactElement, useContext, useEffect } from 'react'
import { useNavigate } from 'react-router'
import { ChainSync } from '../../components/ChainSync'
import { Waiting } from '../../components/Waiting'
import { Context } from '../../providers/Bee'
import { Context } from '../../providers/Settings'
import { ROUTES } from '../../routes'
const STARTED_UPGRADE_AT = 'started-upgrade-at'
export default function LightModeRestart(): ReactElement {
const [startedAt] = useState(Number.parseInt(localStorage.getItem(STARTED_UPGRADE_AT) ?? Date.now().toFixed()))
const { apiHealth, nodeInfo } = useContext(Context)
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
const { beeApi } = useContext(Context)
useEffect(() => {
localStorage.setItem(STARTED_UPGRADE_AT, startedAt.toFixed())
}, [startedAt])
useEffect(() => {
if (Date.now() - startedAt < 45_000) {
if (!beeApi) {
return
}
if (apiHealth && nodeInfo?.beeMode === BeeModes.LIGHT) {
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
localStorage.removeItem(STARTED_UPGRADE_AT)
navigate(ROUTES.INFO)
}
}, [startedAt, navigate, nodeInfo, apiHealth, enqueueSnackbar])
const interval = setInterval(() => {
beeApi
.getNodeInfo()
.then(nodeInfo => {
if (nodeInfo.beeMode === BeeModes.LIGHT) {
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
navigate(ROUTES.INFO)
}
})
.catch(console.error) // eslint-disable-line
}, 3_000)
return () => clearInterval(interval)
}, [beeApi, enqueueSnackbar, navigate])
return (
<Grid container direction="column" justifyContent="center" alignItems="center">
@@ -41,9 +43,12 @@ export default function LightModeRestart(): ReactElement {
<strong>Upgrading Bee</strong>
</Typography>
</Box>
<Typography>
You will be redirected automatically once your node is up and running. This may take up to 10 minutes.
</Typography>
<Box mb={1}>
<Typography>
You will be redirected automatically once your node is up and running. This may take up to 10 minutes.
</Typography>
</Box>
<ChainSync />
</Grid>
)
}
@@ -72,7 +72,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
return `${secondsToTimeString(
convertAmountToSeconds(amount, pricePerBlock),
)} (with price of ${pricePerBlock.toFixed(0)} per block)`
)} (with price of ${pricePerBlock.toFixed(0)} PLUR per block)`
}
function getPrice(depth: number, amount: bigint): string {
@@ -67,7 +67,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
return `${secondsToTimeString(
convertAmountToSeconds(parseInt(amount, 10), pricePerBlock),
)} (with price of ${pricePerBlock.toFixed(0)} per block)`
)} (with price of ${pricePerBlock.toFixed(0)} PLUR per block)`
}
function getPrice(depth: number, amount: bigint): string {
@@ -196,7 +196,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
</Box>
<Box display="flex" justifyContent={'right'} mt={0.5}>
<Typography style={{ fontSize: '10px', color: 'rgba(0, 0, 0, 0.26)' }}>
Current price of 24000 per block
Current price of 24000 PLUR per block
</Typography>
</Box>
</Box>
+6 -4
View File
@@ -265,13 +265,15 @@ export function Provider({ children }: Props): ReactElement {
}
const promises = [
// API health
beeApi
.getHealth({ timeout: TIMEOUT })
.then(response => setBeeVersion(response.version))
.catch(() => setBeeVersion(null)),
// API health
beeApi.isConnected({ timeout: TIMEOUT }).catch(() => setApiHealth(false)),
.then(() => setApiHealth(true))
.catch(() => {
setBeeVersion(null)
setApiHealth(false)
}),
// Node Addresses
beeApi
+16 -6
View File
@@ -41,7 +41,8 @@ export function Provider({ children }: Props): ReactElement {
const [previewBlob, setPreviewBlob] = useState<Blob | undefined>(undefined)
useEffect(() => {
setMetadata(getMetadata(files))
const metadata = getMetadata(files)
setMetadata(metadata)
if (previewUri) {
URL.revokeObjectURL(previewUri) // Clear the preview from memory
@@ -49,12 +50,21 @@ export function Provider({ children }: Props): ReactElement {
setPreviewBlob(undefined)
}
if (files.length !== 1 || !files[0].type.startsWith('image')) return
if (files.length !== 1) return
resize(files[0], PREVIEW_DIMENSIONS.maxWidth, PREVIEW_DIMENSIONS.maxHeight).then(blob => {
setPreviewUri(URL.createObjectURL(blob)) // NOTE: Until it is cleared with URL.revokeObjectURL, the file stays allocated in memory
setPreviewBlob(blob)
})
if (metadata.isVideo) {
const videoFile = files[0]
const videoBlob = new Blob([videoFile], { type: videoFile.type })
setPreviewUri(URL.createObjectURL(videoBlob))
setPreviewBlob(videoBlob)
}
if (metadata.isImage) {
resize(files[0], PREVIEW_DIMENSIONS.maxWidth, PREVIEW_DIMENSIONS.maxHeight).then(blob => {
setPreviewUri(URL.createObjectURL(blob)) // NOTE: Until it is cleared with URL.revokeObjectURL, the file stays allocated in memory
setPreviewBlob(blob)
})
}
return () => {
if (previewUri) {
+6 -7
View File
@@ -65,17 +65,15 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
const propsProviderUrl =
localStorage.getItem(LocalStorageKeys.providerUrl) || propsSettings.defaultRpcUrl || DEFAULT_RPC_URL
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
const [apiUrl, setApiUrl] = useState<string>(
sessionStorage.getItem('api_host') ?? propsSettings.beeApiUrl ?? initialValues.apiUrl,
)
const [beeApi, setBeeApi] = useState<Bee | null>(null)
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
const [rpcProviderUrl, setRpcProviderUrl] = useState(propsProviderUrl)
const [rpcProvider, setRpcProvider] = useState(new providers.JsonRpcProvider(propsProviderUrl))
const { config, isLoading, error } = useGetBeeConfig(desktopUrl)
const url = makeHttpUrl(
config?.['api-addr'] ?? sessionStorage.getItem('api_host') ?? propsSettings.beeApiUrl ?? apiUrl,
)
useEffect(() => {
const urlSearchParams = new URLSearchParams(window.location.search)
const newApiKey = urlSearchParams.get('v')
@@ -88,18 +86,19 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
}, [])
useEffect(() => {
const url = makeHttpUrl(config?.['api-addr'] ?? apiUrl)
try {
setBeeApi(new Bee(url))
sessionStorage.setItem('api_host', url)
} catch (e) {
setBeeApi(null)
}
}, [url])
}, [config, apiUrl])
return (
<Context.Provider
value={{
apiUrl: url,
apiUrl,
beeApi,
setApiUrl,
lockedApiSettings: Boolean(propsSettings.lockedApiSettings),
+1
View File
@@ -70,6 +70,7 @@ export function Provider({ children }: Props): ReactElement {
setStamps(stamps.filter(x => x.exists).map(enrichStamp))
setLastUpdate(Date.now())
setError(null)
} catch (e) {
setError(e as Error)
} finally {
+4 -2
View File
@@ -6,14 +6,16 @@ interface LatestBeeRelease {
}
interface SwarmMetadata {
size: number
size?: number
name: string
type?: string
}
interface Metadata extends SwarmMetadata {
type: string
isWebsite: boolean
isWebsite?: boolean
isVideo?: boolean
isImage?: boolean
count?: number
hash?: string
}
+118
View File
@@ -0,0 +1,118 @@
import { useEffect, useState } from 'react'
import { CafeReactFsCreate } from './CafeReactFsCreate'
import { CafeReactFsItem } from './CafeReactFsItem'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsPath } from './CafeReactFsPath'
import { CafeReactFsSync } from './CafeReactFsSync'
import { CafeReactFsUpload } from './CafeReactFsUpload'
import { FsItem } from './CafeReactType'
const DEFAULT_BACKGROUND_COLOR = '#f0f0f0'
interface Props {
download: (path: string) => Promise<void>
list: (path: string) => Promise<FsItem[]>
onUpload: (path: string) => Promise<void>
onCreateDirectory: (path: string) => Promise<void>
onDeleteFile: (path: string) => Promise<void>
onDeleteDirectory: (path: string) => Promise<void>
onSync: () => Promise<void>
reloader: number
backgroundColor?: string
rootAlias?: string
}
export function CafeReactFs({
download,
list,
onUpload,
onCreateDirectory,
onDeleteFile,
onDeleteDirectory,
onSync,
reloader,
backgroundColor,
rootAlias,
}: Props) {
const [path, setPath] = useState('/')
const [items, setItems] = useState<FsItem[]>([])
const [loading, setLoading] = useState(false)
function setItemsSorted(items: FsItem[]) {
// directories first, all alphabetically
const sortedItems = items.slice().sort((a, b) => {
if (a.$type === b.$type) {
return a.name.localeCompare(b.name)
}
return a.$type === 'directory' ? -1 : 1
})
setItems(sortedItems)
}
useEffect(() => {
setLoading(true)
list(path)
.then(setItemsSorted)
.finally(() => setLoading(false))
}, [reloader, list, path])
const pathParts = ['/', ...path.split('/').filter(x => x)]
function jumpToDirectory(fullPath: string) {
setPath(fullPath)
setLoading(true)
list(fullPath)
.then(setItemsSorted)
.finally(() => setLoading(false))
}
function enterDirectory(name: string) {
const newPath = path.endsWith('/') ? `${path}${name}` : `${path}/${name}`
setPath(newPath)
setLoading(true)
list(newPath)
.then(setItemsSorted)
.finally(() => setLoading(false))
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<CafeReactFsPath
pathParts={pathParts}
jumpToDirectory={jumpToDirectory}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
rootAlias={rootAlias}
/>
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: '4px' }}>
{loading && <CafeReactFsLoading backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR} />}
{!loading &&
items.map(item => (
<CafeReactFsItem
key={item.id}
path={path}
item={item}
enterDirectory={enterDirectory}
onDeleteFile={onDeleteFile}
onDeleteDirectory={onDeleteDirectory}
download={download}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
))}
{!loading && (
<>
<CafeReactFsUpload
onUpload={() => onUpload(path)}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
<CafeReactFsCreate
onCreateDirectory={() => onCreateDirectory(path)}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
<CafeReactFsSync backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR} onSync={onSync} />
</>
)}
</div>
</div>
)
}
+54
View File
@@ -0,0 +1,54 @@
import { useState } from 'react'
import { CafeReactFsLoading } from './CafeReactFsLoading'
interface Props {
backgroundColor: string
onCreateDirectory: () => Promise<void>
}
export function CafeReactFsCreate({ backgroundColor, onCreateDirectory }: Props) {
const [loading, setLoading] = useState(false)
function proxyUpload() {
setLoading(true)
onCreateDirectory().finally(() => setLoading(false))
}
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={proxyUpload}
>
<img
alt="Create"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20106%20131%20l%20100%200%20l%2025%2025%20l%20175%200%20l%200%20200%20l%20-300%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20206%20256%20l%20100%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20256%20206%20l%200%20100%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
New Folder
</p>
</div>
)
}
+25
View File
@@ -0,0 +1,25 @@
interface Props {
onDelete: () => Promise<void>
}
export function CafeReactFsDelete({ onDelete }: Props) {
return (
<img
alt="Delete"
style={{
position: 'absolute',
top: '2px',
right: '2px',
width: '20px',
height: '20px',
cursor: 'pointer',
zIndex: 1,
}}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22128%22%20height%3D%22128%22%20viewBox%3D%220%200%20128%20128%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2264%22%20cy%3D%2264%22%20r%3D%2264%22%20fill%3D%22%231F2D3D%22%20stroke%3D%22none%22%20stroke-width%3D%220%22%20%20%2F%3E%3Cpath%20d%3D%22M%2032%2064%20l%2064%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
onClick={event => {
onDelete()
event.stopPropagation()
}}
/>
)
}
+51
View File
@@ -0,0 +1,51 @@
import { useState } from 'react'
import { CafeReactFsDelete } from './CafeReactFsDelete'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsName } from './CafeReactFsName'
import { VirtualDirectory } from './CafeReactType'
interface Props {
directory: VirtualDirectory
enterDirectory: (name: string) => void
deleteDirectory: (name: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsDirectory({ directory, enterDirectory, deleteDirectory, backgroundColor }: Props) {
const [hovered, setHovered] = useState(false)
const [loading, setLoading] = useState(false)
function proxyDelete() {
setLoading(true)
return deleteDirectory(directory.name).finally(() => setLoading(false))
}
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={() => enterDirectory(directory.name)}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{hovered && <CafeReactFsDelete onDelete={proxyDelete} />}
<img
alt="Directory"
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20106%20131%20l%20100%200%20l%2025%2025%20l%20175%200%20l%200%20200%20l%20-300%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
/>
<CafeReactFsName name={directory.name} />
</div>
)
}
+53
View File
@@ -0,0 +1,53 @@
import { useState } from 'react'
import { CafeReactFsDelete } from './CafeReactFsDelete'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsName } from './CafeReactFsName'
import { VirtualFile } from './CafeReactType'
import { joinUrl } from './Utility'
interface Props {
path: string
file: VirtualFile
download: (path: string) => Promise<void>
deleteFile: (path: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsFile({ path, file, download, deleteFile, backgroundColor }: Props) {
const [hovered, setHovered] = useState(false)
const [loading, setLoading] = useState(false)
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
function proxyDelete() {
setLoading(true)
return deleteFile(joinUrl(path, file.name)).finally(() => setLoading(false))
}
return (
<div
onClick={() => download(joinUrl(path, file.name))}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
>
{hovered && <CafeReactFsDelete onDelete={proxyDelete} />}
<img
alt="File"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20131%20l%20150%200%20l%2050%2050%20l%200%20200%20l%20-200%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20306%20131%20l%200%2050%20l%2050%200%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<CafeReactFsName name={file.name} />
</div>
)
}
+48
View File
@@ -0,0 +1,48 @@
import { CafeReactFsDirectory } from './CafeReactFsDirectory'
import { CafeReactFsFile } from './CafeReactFsFile'
import { FsItem, isVirtualDirectory, isVirtualFile } from './CafeReactType'
interface Props {
path: string
item: FsItem
download: (path: string) => Promise<void>
enterDirectory: (name: string) => void
onDeleteFile: (path: string) => Promise<void>
onDeleteDirectory: (path: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsItem({
path,
item,
download,
enterDirectory,
onDeleteFile,
onDeleteDirectory,
backgroundColor,
}: Props) {
if (isVirtualFile(item)) {
return (
<CafeReactFsFile
path={path}
file={item}
download={download}
deleteFile={onDeleteFile}
backgroundColor={backgroundColor}
/>
)
}
if (isVirtualDirectory(item)) {
return (
<CafeReactFsDirectory
directory={item}
enterDirectory={enterDirectory}
deleteDirectory={onDeleteDirectory}
backgroundColor={backgroundColor}
/>
)
}
return null
}
+52
View File
@@ -0,0 +1,52 @@
import { CSSProperties } from 'react'
interface Props {
backgroundColor: string
}
export function CafeReactFsLoading({ backgroundColor }: Props) {
const spinnerStyle = {
width: '80px',
height: '80px',
borderRadius: '2px',
position: 'relative',
background: backgroundColor,
} as CSSProperties
const bounceStyle = {
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: '#333',
top: '24px',
left: '24px',
opacity: 0.6,
position: 'absolute',
animation: 'bounce 2.0s infinite ease-in-out',
} as CSSProperties
const bounceStyle2 = {
...bounceStyle,
animationDelay: '-1.0s',
}
const keyframes = `
@keyframes bounce {
0%, 100% {
transform: scale(0.0);
} 50% {
transform: scale(1.0);
}
}
`
return (
<>
<style>{keyframes}</style>
<div style={spinnerStyle}>
<div style={bounceStyle}></div>
<div style={bounceStyle2}></div>
</div>
</>
)
}
+25
View File
@@ -0,0 +1,25 @@
interface Props {
name: string
}
export function CafeReactFsName({ name }: Props) {
const shortName = name.length > 10 ? name.slice(0, 10) + '...' : name
return (
<p
title={name}
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
{shortName}
</p>
)
}
+44
View File
@@ -0,0 +1,44 @@
import { joinUrl } from './Utility'
interface Props {
pathParts: string[]
jumpToDirectory: (fullPath: string) => void
backgroundColor: string
rootAlias?: string
}
export function CafeReactFsPath({ pathParts, jumpToDirectory, backgroundColor, rootAlias }: Props) {
const absolutePaths: string[] = []
for (const pathPart of pathParts) {
if (absolutePaths.length === 0) {
absolutePaths.push(pathPart)
} else {
absolutePaths.push(joinUrl(absolutePaths[absolutePaths.length - 1], pathPart))
}
}
return (
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: '4px' }}>
{pathParts.map((part, index) => (
<button
key={index}
style={{
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
outline: 'none',
border: 'none',
fontSize: '20px',
padding: '4px 16px',
}}
onClick={() => {
jumpToDirectory(absolutePaths[index])
}}
>
{rootAlias && part === '/' ? rootAlias : part}
</button>
))}
</div>
)
}
+40
View File
@@ -0,0 +1,40 @@
interface Props {
backgroundColor: string
onSync: () => Promise<void>
}
export function CafeReactFsSync({ backgroundColor, onSync }: Props) {
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={onSync}
>
<img
alt="Sync"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20156%20l%20250%200%20l%200%20150%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20356%20256%20l%2050%2050%20l%2050%20-50%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20356%20356%20l%20-250%200%20l%200%20-150%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20156%20256%20l%20-50%20-50%20l%20-50%2050%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
Sync
</p>
</div>
)
}
+54
View File
@@ -0,0 +1,54 @@
import { useState } from 'react'
import { CafeReactFsLoading } from './CafeReactFsLoading'
interface Props {
onUpload: () => Promise<void>
backgroundColor: string
}
export function CafeReactFsUpload({ onUpload, backgroundColor }: Props) {
const [uploading, setUploading] = useState(false)
function proxyUpload() {
setUploading(true)
onUpload().finally(() => setUploading(false))
}
if (uploading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={proxyUpload}
>
<img
alt="Upload"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20131%20l%20150%200%20l%2050%2050%20l%200%20200%20l%20-200%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20306%20131%20l%200%2050%20l%2050%200%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20206%20256%20l%20100%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20256%20206%20l%200%20100%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
Upload
</p>
</div>
)
}
+26
View File
@@ -0,0 +1,26 @@
export enum FsItemType {
FILE = 'file',
DIRECTORY = 'directory',
}
export interface VirtualFile {
id: string | number
name: string
$type: FsItemType.FILE
}
export interface VirtualDirectory {
id: string | number
name: string
$type: FsItemType.DIRECTORY
}
export type FsItem = VirtualFile | VirtualDirectory
export function isVirtualFile(item: FsItem): item is VirtualFile {
return item.$type === FsItemType.FILE
}
export function isVirtualDirectory(item: FsItem): item is VirtualDirectory {
return item.$type === FsItemType.DIRECTORY
}
+6
View File
@@ -0,0 +1,6 @@
export function joinUrl(...parts: unknown[]): string {
return parts
.filter(x => x)
.join('/')
.replace(/(?<!:)\/+/g, '/')
}
+4 -1
View File
@@ -5,6 +5,7 @@ import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
import { AccountStaking } from './pages/account/staking/AccountStaking'
import { AccountStamps } from './pages/account/stamps/AccountStamps'
import { AccountWallet } from './pages/account/wallet/AccountWallet'
import FDP from './pages/fdp'
import CreateNewFeed from './pages/feeds/CreateNewFeed'
import { FeedSubpage } from './pages/feeds/FeedSubpage'
import UpdateFeed from './pages/feeds/UpdateFeed'
@@ -17,6 +18,7 @@ import Info from './pages/info'
import LightModeRestart from './pages/restart/LightModeRestart'
import Settings from './pages/settings'
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage'
import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage'
import Status from './pages/status'
import TopUp from './pages/top-up'
import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex'
@@ -25,7 +27,6 @@ import { GiftCardFund } from './pages/top-up/GiftCardFund'
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
import { Swap } from './pages/top-up/Swap'
import { Context as SettingsContext } from './providers/Settings'
import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage'
export enum ROUTES {
INFO = '/',
@@ -55,6 +56,7 @@ export enum ROUTES {
ACCOUNT_FEEDS_VIEW = '/account/feeds/:uuid',
ACCOUNT_INVITATIONS = '/account/invitations',
ACCOUNT_STAKING = '/account/staking',
FDP = '/fdp',
}
export const ACCOUNT_TABS = [
@@ -95,6 +97,7 @@ const BaseRouter = (): ReactElement => {
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
<Route path={ROUTES.ACCOUNT_STAKING} element={<AccountStaking />} />
<Route path={ROUTES.FDP} element={<FDP />} />
{isDesktop && <Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />}
</Routes>
)
+7 -2
View File
@@ -1,3 +1,6 @@
import { isSupportedImageType } from './image'
import { isSupportedVideoType } from './video'
const indexHtmls = ['index.html', 'index.htm']
interface DetectedIndex {
@@ -83,12 +86,14 @@ export function getAssetNameFromFiles(files: FilePath[]): string {
export function getMetadata(files: FilePath[]): Metadata {
const size = files.reduce((total, item) => total + item.size, 0)
const isWebsite = Boolean(detectIndexHtml(files))
const name = getAssetNameFromFiles(files)
const type = files.length === 1 ? files[0].type : 'folder'
const count = files.length
const isWebsite = Boolean(detectIndexHtml(files))
const isVideo = isSupportedVideoType(type)
const isImage = isSupportedImageType(type)
return { size, name, type, isWebsite, count }
return { size, name, type, isWebsite, count, isVideo, isImage }
}
export function getPath(file: FilePath): string {
+23 -10
View File
@@ -25,6 +25,28 @@ export function getDimensions(imgWidth: number, imgHeight: number, maxWidth?: nu
return { width: imgWidth / ratio, height: imgHeight / ratio }
}
function getAllowedTypes(): string[] {
return [
'image/bmp',
'image/gif',
'image/vnd.microsoft.icon',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
]
}
/**
* Check if the image type is supported
*
* @param type Image MIME type
*
* @returns True if the type is supported, false otherwise
*/
export const isSupportedImageType = (type: string): boolean => getAllowedTypes().includes(type)
/**
* Resize image passed to fit in the bounding box defined with maxWidth and maxHeight.
* Note that one or both of the bounding box dimensions may be omitted
@@ -37,16 +59,7 @@ export function getDimensions(imgWidth: number, imgHeight: number, maxWidth?: nu
*/
export function resize(file: File, maxWidth?: number, maxHeight?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
const allowedTypes = [
'image/bmp',
'image/gif',
'image/vnd.microsoft.icon',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
]
const allowedTypes = getAllowedTypes()
if (!file.size || !file.type || !allowedTypes.includes(file.type)) return reject('File not supported!')
+1 -2
View File
@@ -31,8 +31,7 @@ export function makeBigNumber(value: BigNumber | bigint | number | string): BigN
if (typeof value === 'bigint') return new BigNumber(value.toString())
// FIXME: bee-js still returns some values as numbers and even outside of SAFE INTEGER bounds
if (typeof value === 'number' /* && Number.isSafeInteger(value)*/) return new BigNumber(value)
if (typeof value === 'number') return new BigNumber(value)
throw new TypeError(`Not a BigNumber or BigNumber convertible value. Type: ${typeof value} value: ${value}`)
}
+8 -2
View File
@@ -50,14 +50,20 @@ export class ManifestJs {
/**
* Retrieves all paths with the associated hashes from a Swarm manifest
*/
public async getHashes(hash: string): Promise<Record<string, string>> {
public async getHashes(hash: string, options?: { exclude: string[] }): Promise<Record<string, string>> {
const data = await this.bee.downloadData(hash)
const node = new MantarayNode()
node.deserialize(data)
await loadAllNodes(this.load.bind(this), node)
const result = {}
const result: Record<string, string> = {}
this.extractHashes(result, node)
if (options?.exclude) {
for (const path of options.exclude) {
delete result[path]
}
}
return result
}
+1 -1
View File
@@ -19,7 +19,7 @@ export async function sendRequest(
const authorization = localStorage.getItem('apiKey')
if (!authorization) {
throw Error('API key not found in local storage')
throw Error('API key not found in local storage. Please reopen via Swarm Desktop > Open Web UI.')
}
const headers = {
authorization,
+7 -1
View File
@@ -66,7 +66,13 @@ export async function sendNativeTransaction(
): Promise<TransferResponse> {
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
const gasPrice = externalGasPrice ?? (await signer.getGasPrice())
const transaction = await signer.sendTransaction({ to, value, gasPrice })
const transaction = await signer.sendTransaction({
to,
value: BN.from(value),
gasPrice,
gasLimit: BN.from(21000),
type: 0,
})
const receipt = await transaction.wait(1)
return { transaction, receipt }
+8
View File
@@ -0,0 +1,8 @@
export function isSupportedVideoType(type: string) {
const video = document.createElement('video')
const result = video.canPlayType(type)
const isDefinitelySupported = result && result !== 'maybe'
return Boolean(isDefinitelySupported)
}
+3 -1
View File
@@ -18,7 +18,9 @@ async function getData(url) {
function processData(data) {
const db = new Map()
data.nodes.forEach(node => {
db.set(node.overlay, { lat: node.location.latitude, lng: node.location.longitude })
if (node.location) {
db.set(node.overlay, { lat: node.location.latitude, lng: node.location.longitude })
}
})
return Object.fromEntries([...db.entries()].sort())