Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3137fbef1 | |||
| e6f882d7e1 | |||
| eda0529dfd | |||
| cb5adfe031 | |||
| 4848b5be97 | |||
| becb87377e | |||
| f149f86155 | |||
| 37ab8fedaa |
@@ -1,3 +1,6 @@
|
|||||||
PORT=3002
|
PORT=3002
|
||||||
|
VITE_BEE_DESKTOP_URL=http://localhost:3054
|
||||||
VITE_FORMBRICKS_ENV_ID=
|
VITE_FORMBRICKS_ENV_ID=
|
||||||
VITE_FORMBRICKS_APP_URL=
|
VITE_FORMBRICKS_APP_URL=
|
||||||
|
VITE_DEFAULT_RPC_URL=
|
||||||
|
VITE_BEE_DESKTOP_ENABLED=
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
name: Publish on npm
|
name: Publish on npm
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
@@ -35,4 +36,4 @@ jobs:
|
|||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- run: pnpm publish --provenance --access public
|
- run: pnpm publish --provenance --access public --no-git-checks
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
/**.log
|
||||||
|
|
||||||
|
|
||||||
settings.json
|
settings.json
|
||||||
|
|||||||
+73
-28
@@ -1,42 +1,87 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.34.0](https://github.com/ethersphere/bee-dashboard/compare/v0.33.5...v0.34.0) (2026-03-20)
|
## [0.35.1](https://github.com/ethersphere/bee-dashboard/compare/v0.35.0...v0.35.1) (2026-04-10)
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add image opening functionality to DownloadActionBar ([#725](https://github.com/ethersphere/bee-dashboard/issues/725)) ([3ff645c](https://github.com/ethersphere/bee-dashboard/commit/3ff645cab1b4e9fba0c42ed99e7c3fac7b0ed0ca))
|
|
||||||
* spdv-995 ([#225](https://github.com/ethersphere/bee-dashboard/issues/225)) ([fa8a26e](https://github.com/ethersphere/bee-dashboard/commit/fa8a26e80d83d5c794a9d572cf716f0cb484e642))
|
|
||||||
* sync and update with all changes from fork ([#720](https://github.com/ethersphere/bee-dashboard/issues/720)) ([519c411](https://github.com/ethersphere/bee-dashboard/commit/519c411db0b37f3d4004366ca267013e2268f8eb))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* add error handling and ui notifications for download failures ([#217](https://github.com/ethersphere/bee-dashboard/issues/217)) ([b58f01c](https://github.com/ethersphere/bee-dashboard/commit/b58f01cc2b5535ba0ad347d03ae4102e18f637f0))
|
- [`cb6854e`](https://github.com/ethersphere/bee-dashboard/commit/cb6854eb68ffe3064a39a171bc1e23f628ee93bb) fix: swap error caused by invalid id and batchcount
|
||||||
|
- [`bb93d5c`](https://github.com/ethersphere/bee-dashboard/commit/bb93d5c26fa5414c6423b87a3992e0f2e410e515) fix: enhance creation messages for admin drive and user drives [(#238)](https://github.com/ethersphere/bee-dashboard/issues/238)
|
||||||
|
- [`c08bf8a`](https://github.com/ethersphere/bee-dashboard/commit/d65da143d2200db653fe7a80a7891dacf4c2937e) fix: identity and wallet creation [(#240)](https://github.com/ethersphere/bee-dashboard/pull/240)
|
||||||
|
- [`d65da14`](https://github.com/ethersphere/bee-dashboard/commit/d65da143d2200db653fe7a80a7891dacf4c2937e) fix: ui display changes [(#239)](https://github.com/ethersphere/bee-dashboard/issues/239)
|
||||||
|
- [`c890f7c`](https://github.com/ethersphere/bee-dashboard/commit/c890f7c1e8e4d21f8d252b3e1a9c783982459adf) fix: stamp buy and dilute [(#242)](https://github.com/ethersphere/bee-dashboard/issues/242)
|
||||||
|
- [`b33b663`](https://github.com/ethersphere/bee-dashboard/commit/b33b6630c2b5830b0fdbfbcf14cadc3fa1225190) fix: standard mode postage stamp purchase [(#243)](https://github.com/ethersphere/bee-dashboard/issues/243)
|
||||||
|
- [`f943f7a`](https://github.com/ethersphere/bee-dashboard/commit/f943f7ad666de15ef780cb5adf736b533902eef7) fix: add syncing message for bee node and update page state [(#244)](https://github.com/ethersphere/bee-dashboard/pull/244)
|
||||||
|
- [`056188a`](https://github.com/ethersphere/bee-dashboard/commit/056188abedf3a8ac828b8eb10a71a3b823cd5e6e) fix: duplicated ttl (time to live) information [(#245)](https://github.com/ethersphere/bee-dashboard/issues/245)
|
||||||
|
- [`8b36556`](https://github.com/ethersphere/bee-dashboard/commit/8b36556502d316ac5bd7dba49ce34b594857d449) fix: misleading "bee node is syncing" message for ultra-light nodes in file manager [(#246)](https://github.com/ethersphere/bee-dashboard/pull/246)
|
||||||
|
- [`9732170`](https://github.com/ethersphere/bee-dashboard/commit/97321706c33fb02abe7e067e6d865a046051d68b) fix: validate stamp before every upgrade click [(#247)](https://github.com/ethersphere/bee-dashboard/issues/247)
|
||||||
|
- [`f52ed4a`](https://github.com/ethersphere/bee-dashboard/commit/f52ed4abb2bb5274b33430c1e8efadae6b3fa795) fix: use tochecksum() and toplurbigint for ethers v6 compatibility [(#248)](https://github.com/ethersphere/bee-dashboard/pull/248)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.35.0](https://github.com/ethersphere/bee-dashboard/compare/v0.34.0...v0.35.0) (2026-04-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add image opening functionality to DownloadActionBar ([3ff645c](https://github.com/ethersphere/bee-dashboard/commit/3ff645cab1b4e9fba0c42ed99e7c3fac7b0ed0ca))
|
||||||
|
* the dashboard sidebar should be collapsible ([fa8a26e](https://github.com/ethersphere/bee-dashboard/commit/fa8a26e80d83d5c794a9d572cf716f0cb484e642))
|
||||||
|
* sync and update with all changes from fork ([519c411](https://github.com/ethersphere/bee-dashboard/commit/519c411db0b37f3d4004366ca267013e2268f8eb))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* swap error caused by invalid id and batchcount
|
||||||
|
* enhance creation messages for admin drive and user drives
|
||||||
|
* identity and wallet creation
|
||||||
|
* asset preview types
|
||||||
|
* fm search unicode text
|
||||||
|
* feed identity and stamp usage
|
||||||
|
* ui display changes
|
||||||
|
* stamp buy and dilute
|
||||||
|
* vite polyfill warning for stream
|
||||||
|
* standard mode postage stamp purchase reserves incorrect size and duration
|
||||||
|
* add syncing message for Bee node and update page state handling
|
||||||
|
* stamp depth and amount validation
|
||||||
|
* add --no-git-checks to pnpm publish command ([37ab8fe](https://github.com/ethersphere/bee-dashboard/commit/37ab8fedaa9fa2f941b84746ec83fe87cf61b014))
|
||||||
|
* add workflow_dispatch trigger to npm publish workflow ([becb873](https://github.com/ethersphere/bee-dashboard/commit/becb87377e506aca0c429439d70e43151025d755))
|
||||||
|
* remove cross-env from prepublishOnly script ([4848b5b](https://github.com/ethersphere/bee-dashboard/commit/4848b5be97a827abeb0ca8156ef53cab0d2f315e))
|
||||||
|
|
||||||
|
## [0.34.0](https://github.com/ethersphere/bee-dashboard/compare/v0.33.5...v0.34.0) (2026-03-20)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add image opening functionality to DownloadActionBar ([3ff645c](https://github.com/ethersphere/bee-dashboard/commit/3ff645cab1b4e9fba0c42ed99e7c3fac7b0ed0ca))
|
||||||
|
* the dashboard sidebar should be collapsible ([fa8a26e](https://github.com/ethersphere/bee-dashboard/commit/fa8a26e80d83d5c794a9d572cf716f0cb484e642))
|
||||||
|
* sync and update with all changes from fork ([519c411](https://github.com/ethersphere/bee-dashboard/commit/519c411db0b37f3d4004366ca267013e2268f8eb))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add error handling and ui notifications for download failures ([b58f01c](https://github.com/ethersphere/bee-dashboard/commit/b58f01cc2b5535ba0ad347d03ae4102e18f637f0))
|
||||||
* buy stamp values display ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
* buy stamp values display ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
||||||
* buy stamp values display ([#226](https://github.com/ethersphere/bee-dashboard/issues/226)) ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
* cannot forget expired drives ([7e05a56](https://github.com/ethersphere/bee-dashboard/commit/7e05a56073a2be306a1394bf5b2e798a1a457c74))
|
||||||
* cannot forget expired drives ([#214](https://github.com/ethersphere/bee-dashboard/issues/214)) ([7e05a56](https://github.com/ethersphere/bee-dashboard/commit/7e05a56073a2be306a1394bf5b2e798a1a457c74))
|
* Correct misleading update warning message for desktop version ([bc2c0ad](https://github.com/ethersphere/bee-dashboard/commit/bc2c0addbb685454fbae61d317f2e920d507c07c))
|
||||||
* correct misleading update warning message for desktop version ([#218](https://github.com/ethersphere/bee-dashboard/issues/218)) ([bc2c0ad](https://github.com/ethersphere/bee-dashboard/commit/bc2c0addbb685454fbae61d317f2e920d507c07c))
|
|
||||||
* dockerfile build and use compose ([76bf221](https://github.com/ethersphere/bee-dashboard/commit/76bf2211ca75f44255e5696d58072e639a9cd2b0))
|
* dockerfile build and use compose ([76bf221](https://github.com/ethersphere/bee-dashboard/commit/76bf2211ca75f44255e5696d58072e639a9cd2b0))
|
||||||
* download and upload files ([#223](https://github.com/ethersphere/bee-dashboard/issues/223)) ([e8e01c0](https://github.com/ethersphere/bee-dashboard/commit/e8e01c054a867447e07a71960d45f9cb36926e61))
|
* download and upload files ([e8e01c0](https://github.com/ethersphere/bee-dashboard/commit/e8e01c054a867447e07a71960d45f9cb36926e61))
|
||||||
* filemanager state handling ([#232](https://github.com/ethersphere/bee-dashboard/issues/232)) ([855a017](https://github.com/ethersphere/bee-dashboard/commit/855a0170334fb36d8ff119ce96f8c9bdb4294d58))
|
* filemanager state handling ([855a017](https://github.com/ethersphere/bee-dashboard/commit/855a0170334fb36d8ff119ce96f8c9bdb4294d58))
|
||||||
* reflect file rename immediately ([#227](https://github.com/ethersphere/bee-dashboard/issues/227)) ([a56a5c3](https://github.com/ethersphere/bee-dashboard/commit/a56a5c3ed8554cf81afc7c6401ca2dc65a8e898d))
|
* reflect file rename immediately ([a56a5c3](https://github.com/ethersphere/bee-dashboard/commit/a56a5c3ed8554cf81afc7c6401ca2dc65a8e898d))
|
||||||
* **rpc:** ensure 0x prefix for recipient in sendBzzTransaction and add tests ([#722](https://github.com/ethersphere/bee-dashboard/issues/722)) ([fff94d9](https://github.com/ethersphere/bee-dashboard/commit/fff94d907157d0cd4f6e490c450cdc58cefeec31))
|
* rpc: ensure 0x prefix for recipient in sendBzzTransaction and add tests ([fff94d9](https://github.com/ethersphere/bee-dashboard/commit/fff94d907157d0cd4f6e490c450cdc58cefeec31))
|
||||||
* **sidebar:** use swarm-desktop GitHub link when running in desktop mode ([#724](https://github.com/ethersphere/bee-dashboard/issues/724)) ([7382da8](https://github.com/ethersphere/bee-dashboard/commit/7382da8595e86230c03dab93aec726cd02cc91ec))
|
* sidebar: use swarm-desktop GitHub link when running in desktop mode ([7382da8](https://github.com/ethersphere/bee-dashboard/commit/7382da8595e86230c03dab93aec726cd02cc91ec))
|
||||||
* spdv-1007 cost values correcting ([#234](https://github.com/ethersphere/bee-dashboard/issues/234)) ([a7e4205](https://github.com/ethersphere/bee-dashboard/commit/a7e42053ae99990c5e9e782e8fe14326f155730c))
|
* cost values correcting [a7e4205](https://github.com/ethersphere/bee-dashboard/commit/a7e42053ae99990c5e9e782e8fe14326f155730c))
|
||||||
* spdv-914 - Modals are partially cut off in File Manager on Windows (Chrome) ([#219](https://github.com/ethersphere/bee-dashboard/issues/219)) ([220618f](https://github.com/ethersphere/bee-dashboard/commit/220618f19bc95b92a0521dc62eb8f5414dfdcbba))
|
* modals are partially cut off in File Manager on Windows (Chrome) ([220618f](https://github.com/ethersphere/bee-dashboard/commit/220618f19bc95b92a0521dc62eb8f5414dfdcbba))
|
||||||
* spdv-934 ([#231](https://github.com/ethersphere/bee-dashboard/issues/231)) ([55e7879](https://github.com/ethersphere/bee-dashboard/commit/55e78798492e715ef9aa92262f5aa874aec240b0))
|
* misleading update warning (Desktop vs Dashboard) ([55e7879](https://github.com/ethersphere/bee-dashboard/commit/55e78798492e715ef9aa92262f5aa874aec240b0))
|
||||||
* spdv-963 - Expired admin drive upgrading handling ([#230](https://github.com/ethersphere/bee-dashboard/issues/230)) ([e1fdd52](https://github.com/ethersphere/bee-dashboard/commit/e1fdd52676653c65beba06283f07e470d7ee4668))
|
* expired admin drive upgrading handling ([e1fdd52](https://github.com/ethersphere/bee-dashboard/commit/e1fdd52676653c65beba06283f07e470d7ee4668))
|
||||||
* spdv-971 ([#211](https://github.com/ethersphere/bee-dashboard/issues/211)) ([e00918b](https://github.com/ethersphere/bee-dashboard/commit/e00918b192f9a916d72df7ed6790eddf908397d3))
|
* in case of a synced bee node the Purchase stamp and create drive button is inactive ([e00918b](https://github.com/ethersphere/bee-dashboard/commit/e00918b192f9a916d72df7ed6790eddf908397d3))
|
||||||
* spdv-973 ([3c4d618](https://github.com/ethersphere/bee-dashboard/commit/3c4d618cc85b2836a2f794b7da79f4da12205869))
|
* newly created drive sometimes appears under “Expired Drives” in Chrome until page refresh and sometimes you have to delete cache in order to appear. ([3c4d618](https://github.com/ethersphere/bee-dashboard/commit/3c4d618cc85b2836a2f794b7da79f4da12205869))
|
||||||
* spdv-986 [commit: 519c411d] - Admin drive stucks in "in progress" state and the already existing admin drives are not listed on the initial modal ([#215](https://github.com/ethersphere/bee-dashboard/issues/215)) ([ad8c8f1](https://github.com/ethersphere/bee-dashboard/commit/ad8c8f18eb5dabbf64e8059f0fcfe98a68b97eea))
|
* admin drive stucks in "in progress" state and the already existing admin drives are not listed on the initial modal ([ad8c8f1](https://github.com/ethersphere/bee-dashboard/commit/ad8c8f18eb5dabbf64e8059f0fcfe98a68b97eea))
|
||||||
* update documentation message for file access clarification ([#221](https://github.com/ethersphere/bee-dashboard/issues/221)) ([ae63615](https://github.com/ethersphere/bee-dashboard/commit/ae63615c7ca26896c1114c16dc6a921aa267bc0d))
|
* update documentation message for file access clarification ([ae63615](https://github.com/ethersphere/bee-dashboard/commit/ae63615c7ca26896c1114c16dc6a921aa267bc0d))
|
||||||
* upload history text align ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
* upload history text align ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
||||||
* upload q and drive size error ([#220](https://github.com/ethersphere/bee-dashboard/issues/220)) ([8992c18](https://github.com/ethersphere/bee-dashboard/commit/8992c189fd0300b0ee28d44a109c47b633174c06))
|
* upload and drive size error ([8992c18](https://github.com/ethersphere/bee-dashboard/commit/8992c189fd0300b0ee28d44a109c47b633174c06))
|
||||||
* use upload and download abort signals ([#212](https://github.com/ethersphere/bee-dashboard/issues/212)) ([308ec3d](https://github.com/ethersphere/bee-dashboard/commit/308ec3dcc0ff3806777b89dc99fef669c993bcf7))
|
* use upload and download abort signals ([308ec3d](https://github.com/ethersphere/bee-dashboard/commit/308ec3dcc0ff3806777b89dc99fef669c993bcf7))
|
||||||
* use uploadFile for single files to support long filenames and reafactor for linter ([#228](https://github.com/ethersphere/bee-dashboard/issues/228)) ([db52e44](https://github.com/ethersphere/bee-dashboard/commit/db52e4471a9519a0aca8a6fd2702a242e03d2a06))
|
* use uploadFile for single files to support long filenames and reafactor for linter ([db52e44](https://github.com/ethersphere/bee-dashboard/commit/db52e4471a9519a0aca8a6fd2702a242e03d2a06))
|
||||||
* withdraw and deposit buttons style ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
* withdraw and deposit buttons style ([3031d54](https://github.com/ethersphere/bee-dashboard/commit/3031d54272d51da020d3a3cd7154b969c60e8669))
|
||||||
|
|
||||||
|
|
||||||
## [0.33.5](https://github.com/ethersphere/bee-dashboard/compare/v0.33.4...v0.33.5) (2026-02-17)
|
## [0.33.5](https://github.com/ethersphere/bee-dashboard/compare/v0.33.4...v0.33.5) (2026-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.34.0",
|
"version": "0.35.1",
|
||||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "cross-env NODE_ENV=production pnpm run prepare",
|
"prepublishOnly": "NODE_ENV=production pnpm run prepare",
|
||||||
"prepare": "pnpm run build && pnpm run build:component",
|
"prepare": "pnpm run build && pnpm run build:component",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"desktop": "node ./desktop.mjs",
|
"desktop": "node ./desktop.mjs",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function ExpandableListItem({ label, value, tooltip }: Props): Re
|
|||||||
)}
|
)}
|
||||||
{value && (
|
{value && (
|
||||||
<Box flex={1} textAlign="right">
|
<Box flex={1} textAlign="right">
|
||||||
<Typography variant="body2">
|
<Typography variant="body2" component="div">
|
||||||
{value}
|
{value}
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
<Tooltip title={tooltip} placement="top" arrow>
|
<Tooltip title={tooltip} placement="top" arrow>
|
||||||
|
|||||||
@@ -16,11 +16,17 @@ const useStyles = makeStyles()(theme => ({
|
|||||||
header: {
|
header: {
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
marginBottom: theme.spacing(0.25),
|
marginBottom: theme.spacing(0.25),
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`,
|
borderLeft: `${theme.spacing(0.25)} solid rgba(0,0,0,0)`,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
headerOpen: {
|
headerOpen: {
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`,
|
borderLeft: `${theme.spacing(0.25)} solid ${theme.palette.primary.main}`,
|
||||||
},
|
},
|
||||||
copyValue: {
|
copyValue: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -95,35 +101,35 @@ export default function ExpandableListItemInput({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
<>
|
||||||
<Box display="flex" flexDirection="column" width="100%">
|
<ListItemButton className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
||||||
<Box display="flex" flexDirection="row" alignItems="center" width="100%">
|
<Box display="flex" flexDirection="column" width="100%">
|
||||||
{label && (
|
<Box display="flex" flexDirection="row" alignItems="center" width="100%">
|
||||||
<Box flex={1} minWidth={0}>
|
{label && (
|
||||||
<Typography variant="body1" className={classes.unselectableLabel} component="span">
|
<Box flex={1} minWidth={0}>
|
||||||
{label}
|
<Typography variant="body1" className={classes.unselectableLabel} component="span">
|
||||||
</Typography>
|
{label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box flex={3} display="flex" alignItems="center" justifyContent="flex-end" minWidth={0} gap={1}>
|
||||||
|
{!open && value && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
component="span"
|
||||||
|
sx={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{!expandedOnly && !locked && (
|
||||||
|
<IconButton size="small" className={classes.copyValue} onClick={toggleOpen}>
|
||||||
|
{open ? <Minus strokeWidth={1} /> : <Edit strokeWidth={1} />}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
<Box flex={3} display="flex" alignItems="center" justifyContent="flex-end" minWidth={0} gap={1}>
|
|
||||||
{!open && value && (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
component="span"
|
|
||||||
sx={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{!expandedOnly && !locked && (
|
|
||||||
<IconButton size="small" className={classes.copyValue} onClick={toggleOpen}>
|
|
||||||
{open ? <Minus strokeWidth={1} /> : <Edit strokeWidth={1} />}
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
|
||||||
<Box display="flex" flexDirection="column" width="100%">
|
|
||||||
<Box display="flex" alignItems="center" width="100%" minWidth={0}>
|
<Box display="flex" alignItems="center" width="100%" minWidth={0}>
|
||||||
<InputBase
|
<InputBase
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
@@ -146,36 +152,38 @@ export default function ExpandableListItemInput({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{helperText && <ExpandableListItemNote>{helperText}</ExpandableListItemNote>}
|
{helperText && <ExpandableListItemNote>{helperText}</ExpandableListItemNote>}
|
||||||
<Box mt={2}>
|
</Collapse>
|
||||||
<ExpandableListItemActions>
|
</Box>
|
||||||
<SwarmButton
|
</ListItemButton>
|
||||||
disabled={
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
loading ||
|
<Box mt={2}>
|
||||||
inputValue === value ||
|
<ExpandableListItemActions>
|
||||||
Boolean(confirmLabelDisabled) ||
|
<SwarmButton
|
||||||
(inputValue === '' && value === undefined)
|
disabled={
|
||||||
}
|
loading ||
|
||||||
loading={loading}
|
inputValue === value ||
|
||||||
iconType={confirmIcon ?? Check}
|
Boolean(confirmLabelDisabled) ||
|
||||||
onClick={() => {
|
(inputValue === '' && value === undefined)
|
||||||
onConfirm?.(inputValue.trim())
|
}
|
||||||
}}
|
loading={loading}
|
||||||
>
|
iconType={confirmIcon ?? Check}
|
||||||
{confirmLabel || 'Save'}
|
onClick={() => {
|
||||||
</SwarmButton>
|
onConfirm?.(inputValue.trim())
|
||||||
<SwarmButton
|
}}
|
||||||
disabled={loading || inputValue === value || inputValue === ''}
|
>
|
||||||
iconType={X}
|
{confirmLabel || 'Save'}
|
||||||
onClick={() => setInputValue(value || '')}
|
</SwarmButton>
|
||||||
cancel
|
<SwarmButton
|
||||||
>
|
disabled={loading || inputValue === value || inputValue === ''}
|
||||||
Cancel
|
iconType={X}
|
||||||
</SwarmButton>
|
onClick={() => setInputValue(value || '')}
|
||||||
</ExpandableListItemActions>
|
cancel
|
||||||
</Box>
|
>
|
||||||
</Box>
|
Cancel
|
||||||
</Collapse>
|
</SwarmButton>
|
||||||
</Box>
|
</ExpandableListItemActions>
|
||||||
</ListItemButton>
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,17 @@ const useStyles = makeStyles()(theme => ({
|
|||||||
header: {
|
header: {
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
marginBottom: theme.spacing(0.25),
|
marginBottom: theme.spacing(0.25),
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`,
|
borderLeft: `${theme.spacing(0.25)} solid rgba(0,0,0,0)`,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
headerOpen: {
|
headerOpen: {
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`,
|
borderLeft: `${theme.spacing(0.25)} solid ${theme.palette.primary.main}`,
|
||||||
},
|
},
|
||||||
copyValue: {
|
copyValue: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -69,7 +75,7 @@ export default function ExpandableListItemKey({ label, value, expanded }: Props)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
<ListItemButton className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
||||||
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
|
<Grid container direction="column" justifyContent="space-between" alignItems="stretch" style={{ width: '100%' }}>
|
||||||
<Grid container direction="row" justifyContent="space-between" alignItems="center">
|
<Grid container direction="row" justifyContent="space-between" alignItems="center">
|
||||||
{label && (
|
{label && (
|
||||||
<Typography variant="body1" component="span">
|
<Typography variant="body1" component="span">
|
||||||
|
|||||||
@@ -10,11 +10,17 @@ const useStyles = makeStyles()(theme => ({
|
|||||||
header: {
|
header: {
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
marginBottom: theme.spacing(0.25),
|
marginBottom: theme.spacing(0.25),
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`,
|
borderLeft: `${theme.spacing(0.25)} solid rgba(0,0,0,0)`,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
headerOpen: {
|
headerOpen: {
|
||||||
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`,
|
borderLeft: `${theme.spacing(0.25)} solid ${theme.palette.primary.main}`,
|
||||||
},
|
},
|
||||||
openLinkIcon: {
|
openLinkIcon: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export default function SideBar(): ReactElement {
|
|||||||
label: 'File Manager',
|
label: 'File Manager',
|
||||||
path: ROUTES.FILEMANAGER,
|
path: ROUTES.FILEMANAGER,
|
||||||
icon: FileManagerIcon,
|
icon: FileManagerIcon,
|
||||||
pathMatcherSubstring: '/filemanager/',
|
pathMatcherSubstring: '/filemanager',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Account',
|
label: 'Account',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BatchId, Bee } from '@ethersphere/bee-js'
|
import { Bee, PostageBatch } from '@ethersphere/bee-js'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import Dialog from '@mui/material/Dialog'
|
import Dialog from '@mui/material/Dialog'
|
||||||
@@ -8,19 +8,48 @@ import DialogTitle from '@mui/material/DialogTitle'
|
|||||||
import Input from '@mui/material/Input'
|
import Input from '@mui/material/Input'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
import React, { ReactElement, ReactNode, useState } from 'react'
|
import React, { ReactElement, ReactNode, useState } from 'react'
|
||||||
|
import { makeStyles } from 'tss-react/mui'
|
||||||
|
|
||||||
interface Props {
|
import { CheckState } from '../providers/Bee'
|
||||||
type: 'Topup' | 'Dilute'
|
|
||||||
icon: ReactNode
|
const useStyles = makeStyles()(theme => ({
|
||||||
bee: Bee
|
buttonSelected: {
|
||||||
stamp: BatchId
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
'&:hover': {
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
'@media (hover: none)': {
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttonUnselected: {
|
||||||
|
color: '#dd7700',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export enum StampExtensionType {
|
||||||
|
Topup = 'Topup',
|
||||||
|
Dilute = 'Dilute',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StampExtensionModal({ type, icon, bee, stamp }: Props): ReactElement {
|
interface Props {
|
||||||
const [open, setOpen] = useState(false)
|
type: StampExtensionType
|
||||||
const [amount, setAmount] = useState('')
|
icon: ReactNode
|
||||||
|
bee: Bee
|
||||||
|
stamp: PostageBatch
|
||||||
|
status: CheckState
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StampExtensionModal({ type, icon, bee, stamp, status }: Props): ReactElement {
|
||||||
|
const { classes } = useStyles()
|
||||||
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
|
const [amount, setAmount] = useState<string>('')
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const label = `${type} ${stamp.toHex().substring(0, 8)}`
|
const label = `${type} ${stamp.batchID.toHex().substring(0, 8)}`
|
||||||
|
|
||||||
const handleClickOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleClickOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -32,23 +61,65 @@ export default function StampExtensionModal({ type, icon, bee, stamp }: Props):
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAction = async () => {
|
const handleAction = async () => {
|
||||||
if (type === 'Topup') {
|
if (status !== CheckState.OK) {
|
||||||
|
enqueueSnackbar(`Node connection status is not ${CheckState.OK}: ${status}`, { variant: 'error' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === StampExtensionType.Topup) {
|
||||||
|
const isAmountInvalid = BigInt(amount) <= BigInt(0)
|
||||||
|
|
||||||
|
if (isAmountInvalid) {
|
||||||
|
enqueueSnackbar(`Invalid amount: ${amount}, it must be greate than 0`, { variant: 'error' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await bee.topUpBatch(stamp, amount)
|
await bee.topUpBatch(stamp.batchID, amount)
|
||||||
enqueueSnackbar(`Successfully topped up stamp, your changes will appear soon`, { variant: 'success' })
|
enqueueSnackbar(`Successfully topped up stamp, your changes will appear soon`, { variant: 'success' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackbar(`Failed to topup stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to topup stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'Dilute') {
|
if (type === StampExtensionType.Dilute) {
|
||||||
|
const newDepth = parseInt(amount, 10)
|
||||||
|
const ttlDays = stamp.duration.toDays()
|
||||||
|
const currentDepth = stamp.depth
|
||||||
|
const maxHalvings = Math.floor(Math.log2(ttlDays)) + currentDepth
|
||||||
|
const isDepthInvalid = newDepth > maxHalvings || newDepth <= currentDepth
|
||||||
|
|
||||||
|
if (isDepthInvalid) {
|
||||||
|
enqueueSnackbar(`Invalid depth: ${newDepth} (${currentDepth} < new depth < ${maxHalvings})`, {
|
||||||
|
variant: 'error',
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttlDays <= 2) {
|
||||||
|
enqueueSnackbar(`TTL: ${ttlDays} <= 2 days, cannot dilute stamp (min. TTL is 1 day)`, {
|
||||||
|
variant: 'warning',
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await bee.diluteBatch(stamp, parseInt(amount, 10))
|
await bee.diluteBatch(stamp.batchID, newDepth)
|
||||||
enqueueSnackbar(`Successfully diluted stamp, your changes will appear soon`, { variant: 'success' })
|
enqueueSnackbar(`Successfully diluted stamp, your changes will appear soon`, { variant: 'success' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackbar(`Failed to dilute stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to dilute stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar(`Failed to extend stamp, unknown operation: ${type}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
@@ -57,7 +128,7 @@ export default function StampExtensionModal({ type, icon, bee, stamp }: Props):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
<Button variant="contained" onClick={handleClickOpen} startIcon={icon}>
|
<Button className={classes.buttonSelected} variant="contained" onClick={handleClickOpen} startIcon={icon}>
|
||||||
{type}
|
{type}
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
@@ -68,7 +139,7 @@ export default function StampExtensionModal({ type, icon, bee, stamp }: Props):
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
id="name"
|
id="name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={type === 'Topup' ? 'Amount to add' : 'New depth to dilute'}
|
placeholder={type === StampExtensionType.Topup ? 'Amount to add' : 'New depth to dilute'}
|
||||||
fullWidth
|
fullWidth
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -78,7 +149,14 @@ export default function StampExtensionModal({ type, icon, bee, stamp }: Props):
|
|||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={amount === ''} onClick={handleAction} color="primary">
|
<Button
|
||||||
|
disabled={amount === ''}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleAction()
|
||||||
|
handleClose()
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
{type}
|
{type}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export function SwarmSelect({
|
|||||||
value={value}
|
value={value}
|
||||||
className={classes.select}
|
className={classes.select}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
|
onChange={onChange}
|
||||||
renderValue={(value: unknown) => (value ? renderValue(value) : placeholder)}
|
renderValue={(value: unknown) => (value ? renderValue(value) : placeholder)}
|
||||||
MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
|
MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function SwarmTextInput({
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
className={classes.field}
|
className={classes.field}
|
||||||
defaultValue={defaultValue || ''}
|
defaultValue={defaultValue || ''}
|
||||||
InputProps={{ disableUnderline: true }}
|
slotProps={{ input: { disableUnderline: true } }}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -73,7 +73,7 @@ export function SwarmTextInput({
|
|||||||
className={classes.field}
|
className={classes.field}
|
||||||
defaultValue={defaultValue || ''}
|
defaultValue={defaultValue || ''}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
InputProps={{ disableUnderline: true }}
|
slotProps={{ input: { disableUnderline: true } }}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ const useStyles = makeStyles()(theme => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
buttonSelected: {
|
buttonSelected: {
|
||||||
color: 'white',
|
color: theme.palette.secondary.main,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: 'white',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: theme.palette.secondary.main,
|
color: 'white',
|
||||||
backgroundColor: 'white',
|
backgroundColor: theme.palette.primary.main,
|
||||||
'@media (hover: none)': {
|
'@media (hover: none)': {
|
||||||
color: 'white',
|
color: theme.palette.secondary.main,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: 'white',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ export const BEE_DESKTOP_LATEST_RELEASE_PAGE_API =
|
|||||||
'https://api.github.com/repos/ethersphere/bee-desktop/releases/latest'
|
'https://api.github.com/repos/ethersphere/bee-desktop/releases/latest'
|
||||||
export const DEFAULT_BEE_API_HOST = 'http://localhost:1633'
|
export const DEFAULT_BEE_API_HOST = 'http://localhost:1633'
|
||||||
export const DEFAULT_RPC_URL = 'https://xdai.fairdatasociety.org'
|
export const DEFAULT_RPC_URL = 'https://xdai.fairdatasociety.org'
|
||||||
|
export const MIN_STAMP_DEPTH = 17
|
||||||
|
export const MAX_STAMP_DEPTH = 255
|
||||||
|
|||||||
@@ -179,7 +179,13 @@ export function AdminStatusBar({
|
|||||||
const isBusy = loading || isUpgrading || isCreationInProgress
|
const isBusy = loading || isUpgrading || isCreationInProgress
|
||||||
const blurCls = isBusy ? ' is-loading' : ''
|
const blurCls = isBusy ? ' is-loading' : ''
|
||||||
const statusVerb = isCreationInProgress ? 'Creating' : 'Loading'
|
const statusVerb = isCreationInProgress ? 'Creating' : 'Loading'
|
||||||
const statusText = statusVerb + ' admin drive, please do not reload'
|
const statusText = (
|
||||||
|
<>
|
||||||
|
{statusVerb} admin drive — please do not reload the page.
|
||||||
|
<br />
|
||||||
|
This may take a few minutes.
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
const renderModalsAndOverlays = () => {
|
const renderModalsAndOverlays = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface ConfirmModalProps {
|
|||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
showFooter?: boolean
|
showFooter?: boolean
|
||||||
isProgress?: boolean
|
isProgress?: boolean
|
||||||
spinnerMessage?: string
|
spinnerMessage?: React.ReactNode
|
||||||
showMinimize?: boolean
|
showMinimize?: boolean
|
||||||
onMinimize?: () => void
|
onMinimize?: () => void
|
||||||
background?: boolean
|
background?: boolean
|
||||||
|
|||||||
+18
-2
@@ -1,9 +1,10 @@
|
|||||||
import { PostageBatch } from '@ethersphere/bee-js'
|
import { Bee, PostageBatch } from '@ethersphere/bee-js'
|
||||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||||
import { ReactElement, useEffect, useMemo, useState } from 'react'
|
import { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||||
|
|
||||||
|
import { validateStampStillExists } from '../../utils/bee'
|
||||||
import { getDaysLeft } from '../../utils/common'
|
import { getDaysLeft } from '../../utils/common'
|
||||||
import { Button } from '../Button/Button'
|
import { Button } from '../Button/Button'
|
||||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||||
@@ -16,19 +17,23 @@ import '../../styles/global.scss'
|
|||||||
const EXPIRING_ITEMS_PAGE_SIZE = 3
|
const EXPIRING_ITEMS_PAGE_SIZE = 3
|
||||||
|
|
||||||
interface ExpiringNotificationModalProps {
|
interface ExpiringNotificationModalProps {
|
||||||
|
bee: Bee
|
||||||
stamps: PostageBatch[]
|
stamps: PostageBatch[]
|
||||||
drives: DriveInfo[]
|
drives: DriveInfo[]
|
||||||
files: FileInfo[]
|
files: FileInfo[]
|
||||||
onCancelClick: () => void
|
onCancelClick: () => void
|
||||||
setErrorMessage?: (error: string) => void
|
setErrorMessage?: (error: string) => void
|
||||||
|
setShowError: (show: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExpiringNotificationModal({
|
export function ExpiringNotificationModal({
|
||||||
|
bee,
|
||||||
stamps,
|
stamps,
|
||||||
drives,
|
drives,
|
||||||
files,
|
files,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
setErrorMessage,
|
setErrorMessage,
|
||||||
|
setShowError,
|
||||||
}: ExpiringNotificationModalProps): ReactElement {
|
}: ExpiringNotificationModalProps): ReactElement {
|
||||||
const [showUpgradeDriveModal, setShowUpgradeDriveModal] = useState(false)
|
const [showUpgradeDriveModal, setShowUpgradeDriveModal] = useState(false)
|
||||||
const [actualStamp, setActualStamp] = useState<PostageBatch | undefined>(undefined)
|
const [actualStamp, setActualStamp] = useState<PostageBatch | undefined>(undefined)
|
||||||
@@ -75,7 +80,18 @@ export function ExpiringNotificationModal({
|
|||||||
files={files}
|
files={files}
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
index={index}
|
index={index}
|
||||||
onUpgradeClick={(stamp, drive) => {
|
onUpgradeClick={async (stamp, drive) => {
|
||||||
|
const isStampValid = await validateStampStillExists(bee, stamp.batchID)
|
||||||
|
|
||||||
|
if (!isStampValid) {
|
||||||
|
setErrorMessage?.(
|
||||||
|
`Drive ${drive.name} has expired. Please clear the browser cache and reload the page.`,
|
||||||
|
)
|
||||||
|
setShowError(true)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setActualStamp(stamp)
|
setActualStamp(stamp)
|
||||||
setActualDrive(drive)
|
setActualDrive(drive)
|
||||||
setShowUpgradeDriveModal(true)
|
setShowUpgradeDriveModal(true)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
||||||
import { useView } from '../../../../pages/filemanager/ViewContext'
|
import { useView } from '../../../../pages/filemanager/ViewContext'
|
||||||
@@ -87,7 +88,9 @@ function ErrorModalBlock({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ErrorModal label={label} onClick={onOk} />
|
const modalRoot = document.querySelector('.fm-main') || document.body
|
||||||
|
|
||||||
|
return createPortal(<ErrorModal label={label} onClick={onOk} />, modalRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => {
|
const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => {
|
||||||
@@ -431,8 +434,10 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
|||||||
if (rafIdRef.current) {
|
if (rafIdRef.current) {
|
||||||
cancelAnimationFrame(rafIdRef.current)
|
cancelAnimationFrame(rafIdRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setShowError(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [setShowError])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let title = currentDrive?.name || ''
|
let title = currentDrive?.name || ''
|
||||||
|
|||||||
+1
-2
@@ -3,8 +3,7 @@ import DownIcon from 'remixicon-react/ArrowDownSLineIcon'
|
|||||||
|
|
||||||
import { BulkActionsResult } from '../../../hooks/useBulkActions'
|
import { BulkActionsResult } from '../../../hooks/useBulkActions'
|
||||||
import { SortDir, SortKey } from '../../../hooks/useSorting'
|
import { SortDir, SortKey } from '../../../hooks/useSorting'
|
||||||
|
import { capitalizeFirstLetter } from '../../../utils/common'
|
||||||
import { capitalizeFirstLetter } from '@/modules/filemanager/utils/common'
|
|
||||||
|
|
||||||
interface FileBrowserHeaderProps {
|
interface FileBrowserHeaderProps {
|
||||||
isSearchMode: boolean
|
isSearchMode: boolean
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
|||||||
const [stampsToExpire, setStampsToExpire] = useState<PostageBatch[]>([])
|
const [stampsToExpire, setStampsToExpire] = useState<PostageBatch[]>([])
|
||||||
const [drivesToExpire, setDrivesToExpire] = useState<DriveInfo[]>([])
|
const [drivesToExpire, setDrivesToExpire] = useState<DriveInfo[]>([])
|
||||||
const { beeApi } = useContext(SettingsContext)
|
const { beeApi } = useContext(SettingsContext)
|
||||||
const { drives, files, adminDrive } = useContext(FMContext)
|
const { drives, files, adminDrive, setShowError } = useContext(FMContext)
|
||||||
|
|
||||||
const showExpiration = stampsToExpire.length > 0
|
const showExpiration = stampsToExpire.length > 0
|
||||||
|
|
||||||
@@ -109,8 +109,9 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
|||||||
<div className="fm-notification-bar fm-red-font" onClick={() => setShowExpiringModal(true)}>
|
<div className="fm-notification-bar fm-red-font" onClick={() => setShowExpiringModal(true)}>
|
||||||
{stampsToExpire.length} drive{stampsToExpire.length > 1 ? 's' : ''} expiring soon <UpIcon size="16px" />
|
{stampsToExpire.length} drive{stampsToExpire.length > 1 ? 's' : ''} expiring soon <UpIcon size="16px" />
|
||||||
</div>
|
</div>
|
||||||
{showExpiringModal && (
|
{showExpiringModal && beeApi && (
|
||||||
<ExpiringNotificationModal
|
<ExpiringNotificationModal
|
||||||
|
bee={beeApi}
|
||||||
stamps={stampsToExpire}
|
stamps={stampsToExpire}
|
||||||
drives={drivesToExpire}
|
drives={drivesToExpire}
|
||||||
files={files}
|
files={files}
|
||||||
@@ -118,6 +119,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
|||||||
setShowExpiringModal(false)
|
setShowExpiringModal(false)
|
||||||
}}
|
}}
|
||||||
setErrorMessage={setErrorMessage}
|
setErrorMessage={setErrorMessage}
|
||||||
|
setShowError={setShowError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ReactElement, useState } from 'react'
|
|||||||
import CheckDoubleLineIcon from 'remixicon-react/CheckDoubleLineIcon'
|
import CheckDoubleLineIcon from 'remixicon-react/CheckDoubleLineIcon'
|
||||||
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
|
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
|
||||||
|
|
||||||
|
import { uuidV4 } from '../../../../utils'
|
||||||
import { TOOLTIPS } from '../../constants/tooltips'
|
import { TOOLTIPS } from '../../constants/tooltips'
|
||||||
import { getSigner, setSignerPk } from '../../utils/common'
|
import { getSigner, setSignerPk } from '../../utils/common'
|
||||||
import { Button } from '../Button/Button'
|
import { Button } from '../Button/Button'
|
||||||
@@ -10,8 +11,6 @@ import { Tooltip } from '../Tooltip/Tooltip'
|
|||||||
|
|
||||||
import './PrivateKeyModal.scss'
|
import './PrivateKeyModal.scss'
|
||||||
|
|
||||||
import { uuidV4 } from '@/utils'
|
|
||||||
|
|
||||||
type Props = { onSaved: () => void }
|
type Props = { onSaved: () => void }
|
||||||
|
|
||||||
const generateNewPrivateKey = (): string => {
|
const generateNewPrivateKey = (): string => {
|
||||||
|
|||||||
@@ -305,7 +305,11 @@ export function Sidebar({ setErrorMessage, loading }: SidebarProps): ReactElemen
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isDriveCreationInProgress && (
|
{isDriveCreationInProgress && (
|
||||||
<div className="fm-sidebar-drive-creation">Creating drive, please do not reload</div>
|
<div className="fm-sidebar-drive-creation">
|
||||||
|
Creating drive — please do not reload the page.
|
||||||
|
<br />
|
||||||
|
This may take a few minutes.
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface UseFileFilteringReturn {
|
|||||||
export function useFileFiltering(props: UseFileFilteringProps): UseFileFilteringReturn {
|
export function useFileFiltering(props: UseFileFilteringProps): UseFileFilteringReturn {
|
||||||
const { files, currentDrive, view, isSearchMode, query, scope, includeActive, includeTrashed } = props
|
const { files, currentDrive, view, isSearchMode, query, scope, includeActive, includeTrashed } = props
|
||||||
|
|
||||||
const q = query.trim().toLowerCase()
|
const q = query.trim().toLowerCase().normalize('NFC')
|
||||||
|
|
||||||
const statusIncluded = useCallback(
|
const statusIncluded = useCallback(
|
||||||
(fi: FileInfo): boolean => {
|
(fi: FileInfo): boolean => {
|
||||||
@@ -44,9 +44,11 @@ export function useFileFiltering(props: UseFileFilteringProps): UseFileFiltering
|
|||||||
const matchesQuery = useCallback(
|
const matchesQuery = useCallback(
|
||||||
(fi: FileInfo): boolean => {
|
(fi: FileInfo): boolean => {
|
||||||
if (!q) return true
|
if (!q) return true
|
||||||
const name = fi.name.toLowerCase()
|
const name = fi.name.toLowerCase().normalize('NFC')
|
||||||
const mime = (fi.customMetadata?.mime || '').toLowerCase()
|
const mime = (fi.customMetadata?.mime || '').toLowerCase().normalize('NFC')
|
||||||
const topic = String(fi.topic ?? '').toLowerCase()
|
const topic = String(fi.topic ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFC')
|
||||||
|
|
||||||
return name.includes(q) || mime.includes(q) || topic.includes(q)
|
return name.includes(q) || mime.includes(q) || topic.includes(q)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ReactElement } from 'react'
|
|||||||
import FileIcon from 'remixicon-react/FileTextLineIcon'
|
import FileIcon from 'remixicon-react/FileTextLineIcon'
|
||||||
import ImageIcon from 'remixicon-react/Image2LineIcon'
|
import ImageIcon from 'remixicon-react/Image2LineIcon'
|
||||||
|
|
||||||
import { guessMime } from './view'
|
import { guessMime } from '../../../utils/file'
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { FileInfo, FileManager } from '@solarpunkltd/file-manager-lib'
|
import { FileInfo, FileManager } from '@solarpunkltd/file-manager-lib'
|
||||||
|
|
||||||
|
import { guessMime, VIEWERS } from '../../../utils/file'
|
||||||
import { DownloadProgress, DownloadState } from '../constants/transfers'
|
import { DownloadProgress, DownloadState } from '../constants/transfers'
|
||||||
|
|
||||||
import { AbortManager } from './abortManager'
|
import { AbortManager } from './abortManager'
|
||||||
import { isDirectoryPickerSupported, isPickerSupported } from './fileOperations'
|
import { isDirectoryPickerSupported, isPickerSupported } from './fileOperations'
|
||||||
import { guessMime, VIEWERS } from './view'
|
|
||||||
|
|
||||||
const DefaultDownloadFolder = 'downloads'
|
const DefaultDownloadFolder = 'downloads'
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
const EXT_TO_MIME: Record<string, string> = {
|
|
||||||
mp4: 'video/mp4',
|
|
||||||
webm: 'video/webm',
|
|
||||||
ogv: 'video/ogg',
|
|
||||||
mp3: 'audio/mpeg',
|
|
||||||
m4a: 'audio/mp4',
|
|
||||||
aac: 'audio/aac',
|
|
||||||
wav: 'audio/wav',
|
|
||||||
ogg: 'audio/ogg',
|
|
||||||
png: 'image/png',
|
|
||||||
jpg: 'image/jpeg',
|
|
||||||
jpeg: 'image/jpeg',
|
|
||||||
gif: 'image/gif',
|
|
||||||
webp: 'image/webp',
|
|
||||||
avif: 'image/avif',
|
|
||||||
svg: 'image/svg+xml',
|
|
||||||
pdf: 'application/pdf',
|
|
||||||
txt: 'text/plain',
|
|
||||||
md: 'text/markdown',
|
|
||||||
json: 'application/json',
|
|
||||||
csv: 'text/csv',
|
|
||||||
html: 'text/html',
|
|
||||||
htm: 'text/html',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getExtensionFromName(name: string): string {
|
|
||||||
const ext = name.split('.').pop()?.toLowerCase() || ''
|
|
||||||
const hasExtension = name.includes('.') && ext && ext !== name
|
|
||||||
|
|
||||||
return hasExtension ? ext : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
export function guessMime(name: string, mtdt?: Record<string, string> | undefined): { mime: string; ext: string } {
|
|
||||||
const md = mtdt?.mimeType || mtdt?.mime || mtdt?.['content-type']
|
|
||||||
const ext = getExtensionFromName(name)
|
|
||||||
|
|
||||||
if (md) return { mime: md, ext }
|
|
||||||
|
|
||||||
const mime = EXT_TO_MIME[ext] || 'application/octet-stream'
|
|
||||||
|
|
||||||
return { mime, ext }
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Viewer = {
|
|
||||||
name: string
|
|
||||||
test: (mime: string) => boolean
|
|
||||||
render: (win: Window, url: string, mime: string, name: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const VIDEO_HTML = (u: string, title: string) =>
|
|
||||||
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body style="margin:0;background:#000">
|
|
||||||
<video controls autoplay style="width:100%;height:100%" src="${u}"></video>
|
|
||||||
</body></html>`
|
|
||||||
|
|
||||||
const AUDIO_HTML = (u: string, title: string) =>
|
|
||||||
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body>
|
|
||||||
<audio controls autoplay style="width:100%" src="${u}"></audio>
|
|
||||||
</body></html>`
|
|
||||||
|
|
||||||
const IMAGE_HTML = (u: string, title: string) =>
|
|
||||||
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body style="margin:0;background:#111;display:grid;place-items:center;min-height:100vh">
|
|
||||||
<img style="max-width:100%;max-height:100vh" src="${u}" />
|
|
||||||
</body></html>`
|
|
||||||
|
|
||||||
export const VIEWERS: Viewer[] = [
|
|
||||||
{
|
|
||||||
name: 'video',
|
|
||||||
test: m => m.startsWith('video/'),
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.write(VIDEO_HTML(url, name))
|
|
||||||
w.document.title = name
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'audio',
|
|
||||||
test: m => m.startsWith('audio/'),
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.write(AUDIO_HTML(url, name))
|
|
||||||
w.document.title = name
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'image',
|
|
||||||
test: m => m.startsWith('image/'),
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.write(IMAGE_HTML(url, name))
|
|
||||||
w.document.title = name
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pdf',
|
|
||||||
test: m => m === 'application/pdf',
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.title = name
|
|
||||||
w.location.href = url
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'html',
|
|
||||||
test: m => m === 'text/html',
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.title = name
|
|
||||||
w.location.href = url
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'text-like',
|
|
||||||
test: m => m.startsWith('text/') || m === 'application/json' || m === 'text/markdown',
|
|
||||||
render: (w, url, mime, name) => {
|
|
||||||
w.document.title = name
|
|
||||||
w.location.href = url
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -104,7 +104,7 @@ export function AccountFeeds(): ReactElement {
|
|||||||
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
|
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
|
||||||
<Box mt={0.75}>
|
<Box mt={0.75}>
|
||||||
<ExpandableListItemActions>
|
<ExpandableListItemActions>
|
||||||
<SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info}>
|
<SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info} disabled={Boolean(!x.feedHash)}>
|
||||||
View Feed Page
|
View Feed Page
|
||||||
</SwarmButton>
|
</SwarmButton>
|
||||||
<SwarmButton onClick={() => onShowExport(x)} iconType={Download}>
|
<SwarmButton onClick={() => onShowExport(x)} iconType={Download}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NULL_TOPIC } from '@ethersphere/bee-js'
|
import { NULL_TOPIC, PostageBatch } from '@ethersphere/bee-js'
|
||||||
import { Box, Grid, Typography } from '@mui/material'
|
import { Box, Grid, Typography } from '@mui/material'
|
||||||
|
import { Wallet } from 'ethers'
|
||||||
import { Form, Formik } from 'formik'
|
import { Form, Formik } from 'formik'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
import { ReactElement, useContext, useState } from 'react'
|
import { ReactElement, useContext, useState } from 'react'
|
||||||
@@ -12,7 +13,7 @@ import ExpandableListItemActions from '../../components/ExpandableListItemAction
|
|||||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmSelect } from '../../components/SwarmSelect'
|
import { SelectEvent, SwarmSelect } from '../../components/SwarmSelect'
|
||||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||||
import { Context as FeedsContext, IdentityType } from '../../providers/Feeds'
|
import { Context as FeedsContext, IdentityType } from '../../providers/Feeds'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
@@ -34,7 +35,8 @@ const initialValues: FormValues = {
|
|||||||
export default function CreateNewFeed(): ReactElement {
|
export default function CreateNewFeed(): ReactElement {
|
||||||
const { beeApi } = useContext(SettingsContext)
|
const { beeApi } = useContext(SettingsContext)
|
||||||
const { identities, setIdentities } = useContext(FeedsContext)
|
const { identities, setIdentities } = useContext(FeedsContext)
|
||||||
const [loading, setLoading] = useState(false)
|
const [identityType, setIdentityType] = useState<IdentityType>(IdentityType.PrivateKey)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -48,11 +50,24 @@ export default function CreateNewFeed(): ReactElement {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const wallet = generateWallet()
|
|
||||||
const stamps = await beeApi.getPostageBatches()
|
let stamps: PostageBatch[] = []
|
||||||
|
let wallet: Wallet
|
||||||
|
|
||||||
|
try {
|
||||||
|
wallet = generateWallet()
|
||||||
|
stamps = (await beeApi.getPostageBatches()).filter(s => s.usable)
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err)
|
||||||
|
enqueueSnackbar(<span>Error during wallet generation or postage stamp retrieval!</span>, { variant: 'error' })
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!stamps || !stamps.length) {
|
if (!stamps || !stamps.length) {
|
||||||
enqueueSnackbar(<span>No stamp available</span>, { variant: 'error' })
|
enqueueSnackbar(<span>No usable stamp available</span>, { variant: 'error' })
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -65,17 +80,29 @@ export default function CreateNewFeed(): ReactElement {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const identity = await convertWalletToIdentity(wallet, values.type, values.identityName, values.password)
|
try {
|
||||||
persistIdentity(identities, identity)
|
const identity = await convertWalletToIdentity(wallet, values.type, values.identityName, values.password)
|
||||||
setIdentities(identities)
|
persistIdentity(identities, identity)
|
||||||
navigate(ROUTES.ACCOUNT_FEEDS)
|
setIdentities(identities)
|
||||||
setLoading(false)
|
navigate(ROUTES.ACCOUNT_FEEDS)
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err)
|
||||||
|
enqueueSnackbar(<span>Error identity creation!</span>, { variant: 'error' })
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
navigate(-1)
|
navigate(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onIdentityTypeChange(event: SelectEvent) {
|
||||||
|
const type = event.target.value as IdentityType
|
||||||
|
setIdentityType(type)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<HistoryHeader>Create new feed</HistoryHeader>
|
<HistoryHeader>Create new feed</HistoryHeader>
|
||||||
@@ -102,10 +129,13 @@ export default function CreateNewFeed(): ReactElement {
|
|||||||
<SwarmSelect
|
<SwarmSelect
|
||||||
formik
|
formik
|
||||||
name="type"
|
name="type"
|
||||||
|
label={'type'}
|
||||||
|
value={identityType}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Keypair Only', value: IdentityType.PrivateKey },
|
{ label: 'Keypair Only', value: IdentityType.PrivateKey },
|
||||||
{ label: 'Password Protected', value: IdentityType.V3 },
|
{ label: 'Password Protected', value: IdentityType.V3 },
|
||||||
]}
|
]}
|
||||||
|
onChange={onIdentityTypeChange}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{values.type === IdentityType.V3 && <SwarmTextInput name="password" label="Password" password formik />}
|
{values.type === IdentityType.V3 && <SwarmTextInput name="password" label="Password" password formik />}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export default function UpdateFeed(): ReactElement {
|
|||||||
const { status } = useContext(BeeContext)
|
const { status } = useContext(BeeContext)
|
||||||
const { hash } = useParams()
|
const { hash } = useParams()
|
||||||
|
|
||||||
const [selectedStamp, setSelectedStamp] = useState<string | null>(null)
|
const [selectedStamp, setSelectedStamp] = useState<string | null>(stamps ? stamps[0]?.batchID.toHex() : null)
|
||||||
const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(null)
|
const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(identities[0] ?? null)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false)
|
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false)
|
||||||
@@ -119,19 +119,28 @@ export default function UpdateFeed(): ReactElement {
|
|||||||
<HistoryHeader>Update feed</HistoryHeader>
|
<HistoryHeader>Update feed</HistoryHeader>
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<SwarmSelect
|
{identities && identities.length ? (
|
||||||
options={identities.map(x => ({ value: x.uuid, label: `${x.name} Website` }))}
|
<SwarmSelect
|
||||||
onChange={onFeedChange}
|
value={selectedIdentity?.uuid ?? ''}
|
||||||
label="Feed"
|
options={identities.map(x => ({ value: x.uuid, label: `${x.name} Website` }))}
|
||||||
/>
|
onChange={onFeedChange}
|
||||||
|
label="Feed"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography>You need to create an identiy first to be able to update its feed.</Typography>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box mb={4}>
|
<Box mb={4}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
{stamps ? (
|
{stamps && stamps.length ? (
|
||||||
<SwarmSelect
|
<SwarmSelect
|
||||||
options={stamps.map(x => ({ value: x.batchID.toHex(), label: x.batchID.toHex().slice(0, 8) }))}
|
value={selectedStamp ?? ''}
|
||||||
|
options={stamps.map(x => ({
|
||||||
|
value: x.batchID.toHex(),
|
||||||
|
label: x.label ? x.batchID.toHex().slice(0, 8) + ` (${x.label})` : x.batchID.toHex().slice(0, 8),
|
||||||
|
}))}
|
||||||
onChange={onStampChange}
|
onChange={onStampChange}
|
||||||
label="Stamp"
|
label="Stamp"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function Feeds(): ReactElement {
|
|||||||
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
|
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
|
||||||
<Box mt={0.75}>
|
<Box mt={0.75}>
|
||||||
<ExpandableListItemActions>
|
<ExpandableListItemActions>
|
||||||
<SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info}>
|
<SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info} disabled={Boolean(!x.feedHash)}>
|
||||||
View Feed Page
|
View Feed Page
|
||||||
</SwarmButton>
|
</SwarmButton>
|
||||||
<SwarmButton onClick={() => onShowExport(x)} iconType={Download}>
|
<SwarmButton onClick={() => onShowExport(x)} iconType={Download}>
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
|
import { BeeModes } from '@ethersphere/bee-js'
|
||||||
import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
|
import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
|
||||||
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { AdminStatusBar } from '../../modules/filemanager/components/AdminStatusBar/AdminStatusBar'
|
||||||
|
import { Button } from '../../modules/filemanager/components/Button/Button'
|
||||||
|
import { ConfirmModal } from '../../modules/filemanager/components/ConfirmModal/ConfirmModal'
|
||||||
|
import { ErrorModal } from '../../modules/filemanager/components/ErrorModal/ErrorModal'
|
||||||
|
import { FileBrowser } from '../../modules/filemanager/components/FileBrowser/FileBrowser'
|
||||||
|
import { FormbricksIntegration } from '../../modules/filemanager/components/FormbricksIntegration/FormbricksIntegration'
|
||||||
|
import { Header } from '../../modules/filemanager/components/Header/Header'
|
||||||
|
import { InitialModal } from '../../modules/filemanager/components/InitialModal/InitialModal'
|
||||||
|
import { PrivateKeyModal } from '../../modules/filemanager/components/PrivateKeyModal/PrivateKeyModal'
|
||||||
|
import { Sidebar } from '../../modules/filemanager/components/Sidebar/Sidebar'
|
||||||
|
import { getSignerPk, removeSignerPk } from '../../modules/filemanager/utils/common'
|
||||||
|
import { CheckState, Context as BeeContext } from '../../providers/Bee'
|
||||||
|
import { Context as FMContext } from '../../providers/FileManager'
|
||||||
|
import { BrowserPlatform, cacheClearUrls, detectBrowser } from '../../providers/Platform'
|
||||||
|
|
||||||
import { SearchProvider } from './SearchContext'
|
import { SearchProvider } from './SearchContext'
|
||||||
import { ViewProvider } from './ViewContext'
|
import { ViewProvider } from './ViewContext'
|
||||||
|
|
||||||
import './FileManager.scss'
|
import './FileManager.scss'
|
||||||
|
|
||||||
import { AdminStatusBar } from '@/modules/filemanager/components/AdminStatusBar/AdminStatusBar'
|
|
||||||
import { Button } from '@/modules/filemanager/components/Button/Button'
|
|
||||||
import { ConfirmModal } from '@/modules/filemanager/components/ConfirmModal/ConfirmModal'
|
|
||||||
import { ErrorModal } from '@/modules/filemanager/components/ErrorModal/ErrorModal'
|
|
||||||
import { FileBrowser } from '@/modules/filemanager/components/FileBrowser/FileBrowser'
|
|
||||||
import { FormbricksIntegration } from '@/modules/filemanager/components/FormbricksIntegration/FormbricksIntegration'
|
|
||||||
import { Header } from '@/modules/filemanager/components/Header/Header'
|
|
||||||
import { InitialModal } from '@/modules/filemanager/components/InitialModal/InitialModal'
|
|
||||||
import { PrivateKeyModal } from '@/modules/filemanager/components/PrivateKeyModal/PrivateKeyModal'
|
|
||||||
import { Sidebar } from '@/modules/filemanager/components/Sidebar/Sidebar'
|
|
||||||
import { getSignerPk, removeSignerPk } from '@/modules/filemanager/utils/common'
|
|
||||||
import { CheckState, Context as BeeContext } from '@/providers/Bee'
|
|
||||||
import { Context as FMContext } from '@/providers/FileManager'
|
|
||||||
import { BrowserPlatform, cacheClearUrls, detectBrowser } from '@/providers/Platform'
|
|
||||||
|
|
||||||
function PrivateKeyModalBlock({ onSaved }: { onSaved: () => void }) {
|
function PrivateKeyModalBlock({ onSaved }: { onSaved: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="fm-main">
|
<div className="fm-main">
|
||||||
@@ -44,6 +45,18 @@ function InitializationErrorBlock({ onOk }: { onOk: () => void }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function UltraLightNodeErrorBlock() {
|
||||||
|
return (
|
||||||
|
<div className="fm-main">
|
||||||
|
<div className="fm-loading">
|
||||||
|
<div className="fm-loading-title">
|
||||||
|
File Manager is not available with an Ultra-light node. Please upgrade to a Light node to continue.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) {
|
function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="fm-main">
|
<div className="fm-main">
|
||||||
@@ -95,6 +108,22 @@ function LoadingBlock() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ChainSyncingBlock() {
|
||||||
|
return (
|
||||||
|
<div className="fm-main">
|
||||||
|
<div className="fm-loading" aria-live="polite">
|
||||||
|
<div className="fm-spinner" aria-hidden="true" />
|
||||||
|
<div className="fm-loading-title">Bee node is syncing…</div>
|
||||||
|
<div className="fm-loading-subtitle">
|
||||||
|
Your Bee node is still syncing the postage batch state from the chain.
|
||||||
|
<br />
|
||||||
|
File Manager will be available once the sync is complete.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ErrorModalBlock({ onClick, label }: { onClick: () => void; label: string }) {
|
function ErrorModalBlock({ onClick, label }: { onClick: () => void; label: string }) {
|
||||||
return <ErrorModal label={label} onClick={onClick} />
|
return <ErrorModal label={label} onClick={onClick} />
|
||||||
}
|
}
|
||||||
@@ -153,10 +182,12 @@ function FileManagerMainContent(props: {
|
|||||||
|
|
||||||
enum PageState {
|
enum PageState {
|
||||||
Connecting = 'connecting', // still warming up — show nothing / loader
|
Connecting = 'connecting', // still warming up — show nothing / loader
|
||||||
|
UltraLightNode = 'ultra-light-node', // ultra-light node — file manager not available
|
||||||
NoPrivateKey = 'no-pk', // private key not set
|
NoPrivateKey = 'no-pk', // private key not set
|
||||||
Loading = 'loading', // bee ready, pk present, FM init in progress
|
Loading = 'loading', // bee ready, pk present, FM init in progress
|
||||||
Reset = 'reset', // STATE_INVALID emitted and user has not yet acknowledged
|
Reset = 'reset', // STATE_INVALID emitted and user has not yet acknowledged
|
||||||
InitError = 'init-error', // FM init completed with an error (non-reset case)
|
InitError = 'init-error', // FM init completed with an error (non-reset case)
|
||||||
|
ChainSyncing = 'chain-syncing', // bee node is still syncing postage batch state from chain
|
||||||
Initial = 'initial', // FM ready but no admin stamp/drive → show InitialModal
|
Initial = 'initial', // FM ready but no admin stamp/drive → show InitialModal
|
||||||
AdminError = 'admin-error', // drive creation failed
|
AdminError = 'admin-error', // drive creation failed
|
||||||
Ready = 'ready', // fully operational
|
Ready = 'ready', // fully operational
|
||||||
@@ -172,7 +203,7 @@ export function FileManagerPage(): ReactElement {
|
|||||||
const [connectionErrorDismissed, setConnectionErrorDismissed] = useState<boolean>(false)
|
const [connectionErrorDismissed, setConnectionErrorDismissed] = useState<boolean>(false)
|
||||||
const [cacheHelpUrl, setCacheHelpUrl] = useState<string>(cacheClearUrls[BrowserPlatform.Chrome])
|
const [cacheHelpUrl, setCacheHelpUrl] = useState<string>(cacheClearUrls[BrowserPlatform.Chrome])
|
||||||
|
|
||||||
const { status } = useContext(BeeContext)
|
const { status, chainState, nodeInfo } = useContext(BeeContext)
|
||||||
const { fm, initDone, shallReset, adminDrive, initializationError, notifyPkSaved } = useContext(FMContext)
|
const { fm, initDone, shallReset, adminDrive, initializationError, notifyPkSaved } = useContext(FMContext)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -207,8 +238,12 @@ export function FileManagerPage(): ReactElement {
|
|||||||
}, [isConnectionError])
|
}, [isConnectionError])
|
||||||
|
|
||||||
const pageState = useMemo((): PageState => {
|
const pageState = useMemo((): PageState => {
|
||||||
|
const isChainSyncing = chainState === null
|
||||||
|
|
||||||
if (!isBeeReady && !initDone) return PageState.Connecting
|
if (!isBeeReady && !initDone) return PageState.Connecting
|
||||||
|
|
||||||
|
if (nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT) return PageState.UltraLightNode
|
||||||
|
|
||||||
if (!hasPk) return PageState.NoPrivateKey
|
if (!hasPk) return PageState.NoPrivateKey
|
||||||
|
|
||||||
if (!initDone) return PageState.Loading
|
if (!initDone) return PageState.Loading
|
||||||
@@ -217,12 +252,15 @@ export function FileManagerPage(): ReactElement {
|
|||||||
|
|
||||||
if (initializationError && !shallReset) return PageState.InitError
|
if (initializationError && !shallReset) return PageState.InitError
|
||||||
|
|
||||||
if (showAdminErrorModal) return PageState.AdminError
|
|
||||||
|
|
||||||
const hasAdminStamp = Boolean(fm?.adminStamp)
|
const hasAdminStamp = Boolean(fm?.adminStamp)
|
||||||
const hasAdminDrive = Boolean(adminDrive)
|
const hasAdminDrive = Boolean(adminDrive)
|
||||||
|
const setupIncomplete = !hasAdminStamp && !hasAdminDrive
|
||||||
|
|
||||||
if (!hasAdminStamp && !hasAdminDrive && !isCreationInProgress) return PageState.Initial
|
if (setupIncomplete && isChainSyncing) return PageState.ChainSyncing
|
||||||
|
|
||||||
|
if (showAdminErrorModal) return PageState.AdminError
|
||||||
|
|
||||||
|
if (setupIncomplete && !isCreationInProgress) return PageState.Initial
|
||||||
|
|
||||||
return PageState.Ready
|
return PageState.Ready
|
||||||
}, [
|
}, [
|
||||||
@@ -236,6 +274,8 @@ export function FileManagerPage(): ReactElement {
|
|||||||
fm,
|
fm,
|
||||||
adminDrive,
|
adminDrive,
|
||||||
isCreationInProgress,
|
isCreationInProgress,
|
||||||
|
chainState,
|
||||||
|
nodeInfo?.beeMode,
|
||||||
])
|
])
|
||||||
|
|
||||||
const handlePrivateKeySaved = useCallback(() => {
|
const handlePrivateKeySaved = useCallback(() => {
|
||||||
@@ -251,10 +291,18 @@ export function FileManagerPage(): ReactElement {
|
|||||||
const loading = !fm?.adminStamp || !adminDrive
|
const loading = !fm?.adminStamp || !adminDrive
|
||||||
const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !loading)
|
const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !loading)
|
||||||
|
|
||||||
|
if (pageState === PageState.UltraLightNode) {
|
||||||
|
return <UltraLightNodeErrorBlock />
|
||||||
|
}
|
||||||
|
|
||||||
if (pageState === PageState.Connecting || pageState === PageState.Loading) {
|
if (pageState === PageState.Connecting || pageState === PageState.Loading) {
|
||||||
return <LoadingBlock />
|
return <LoadingBlock />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageState === PageState.ChainSyncing) {
|
||||||
|
return <ChainSyncingBlock />
|
||||||
|
}
|
||||||
|
|
||||||
if (pageState === PageState.NoPrivateKey) {
|
if (pageState === PageState.NoPrivateKey) {
|
||||||
return <PrivateKeyModalBlock onSaved={handlePrivateKeySaved} />
|
return <PrivateKeyModalBlock onSaved={handlePrivateKeySaved} />
|
||||||
}
|
}
|
||||||
@@ -289,12 +337,15 @@ export function FileManagerPage(): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pageState === PageState.AdminError) {
|
if (pageState === PageState.AdminError) {
|
||||||
|
const adminErrorLabel =
|
||||||
|
chainState === null
|
||||||
|
? 'Your Bee node is still syncing the postage batch state from the chain. Please wait for the sync to complete and try again.'
|
||||||
|
: errorMessage ||
|
||||||
|
'Error creating Admin Drive. Please try again. Possible causes include insufficient xDAI balance or a lost connection to the RPC.'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorModalBlock
|
<ErrorModalBlock
|
||||||
label={
|
label={adminErrorLabel}
|
||||||
errorMessage ||
|
|
||||||
'Error creating Admin Drive. Please try again. Possible causes include insufficient xDAI balance or a lost connection to the RPC.'
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAdminShowErrorModal(false)
|
setAdminShowErrorModal(false)
|
||||||
setErrorMessage('')
|
setErrorMessage('')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { FitAudio } from '../../components/FitAudio'
|
|||||||
import { FitImage } from '../../components/FitImage'
|
import { FitImage } from '../../components/FitImage'
|
||||||
import { FitVideo } from '../../components/FitVideo'
|
import { FitVideo } from '../../components/FitVideo'
|
||||||
import { shortenText } from '../../utils'
|
import { shortenText } from '../../utils'
|
||||||
import { getHumanReadableFileSize } from '../../utils/file'
|
import { getHumanReadableFileSize, guessMime } from '../../utils/file'
|
||||||
import { shortenHash } from '../../utils/hash'
|
import { shortenHash } from '../../utils/hash'
|
||||||
|
|
||||||
import { AssetIcon } from './AssetIcon'
|
import { AssetIcon } from './AssetIcon'
|
||||||
@@ -18,16 +18,20 @@ interface Props {
|
|||||||
metadata?: Metadata
|
metadata?: Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPreviewElement = (previewUri?: string, metadata?: Metadata) => {
|
const getPreviewElement = (previewUri?: string, metadata?: Metadata, type?: string) => {
|
||||||
if (metadata?.isVideo) {
|
const isVideoType = Boolean(type && /.*\.(mp4|webm|ogv)$/i.test(type))
|
||||||
|
const isAudioType = Boolean(type && /.*\.(mp3|ogg|oga|wav|webm|m4a|aac|flac)$/i.test(type))
|
||||||
|
const isImageType = Boolean(type && /.*\.(jpg|jpeg|png|gif|webp|svg|ico)$/i.test(type))
|
||||||
|
|
||||||
|
if (metadata?.isVideo || isVideoType) {
|
||||||
return <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
|
return <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata?.isAudio) {
|
if (metadata?.isAudio || isAudioType) {
|
||||||
return <FitAudio src={previewUri} maxWidth="250px" />
|
return <FitAudio src={previewUri} maxWidth="250px" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata?.isImage) {
|
if (metadata?.isImage || isImageType) {
|
||||||
return <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
|
return <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,18 +46,26 @@ const getPreviewElement = (previewUri?: string, metadata?: Metadata) => {
|
|||||||
return <AssetIcon icon={<File />} />
|
return <AssetIcon icon={<File />} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const getType = (metadata?: Metadata) => {
|
export const getType = (metadata?: Metadata): string => {
|
||||||
if (metadata?.isWebsite) return 'Website'
|
if (metadata?.isWebsite) return 'Website'
|
||||||
|
|
||||||
if (metadata?.type === 'folder') return 'Folder'
|
if (metadata?.type === 'folder') return 'Folder'
|
||||||
|
|
||||||
return metadata?.type
|
let metadataType = metadata?.type || 'unknown'
|
||||||
|
let typeFromExtension: string | undefined
|
||||||
|
|
||||||
|
if (metadataType === 'unknown' && metadata?.name) {
|
||||||
|
const { mime } = guessMime(metadata.name)
|
||||||
|
typeFromExtension = mime === 'application/octet-stream' ? 'file' : mime
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeFromExtension || metadataType
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
|
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
|
||||||
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
|
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
|
||||||
const previewElement = useMemo(() => getPreviewElement(previewUri, metadata), [metadata, previewUri])
|
|
||||||
const type = useMemo(() => getType(metadata), [metadata])
|
const type = useMemo(() => getType(metadata), [metadata])
|
||||||
|
const previewElement = useMemo(() => getPreviewElement(previewUri, metadata, type), [metadata, type, previewUri])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mb={4}>
|
<Box mb={4}>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ROUTES } from '../../routes'
|
|||||||
import { determineHistoryName, LocalStorageKeys, putHistory } from '../../utils/localStorage'
|
import { determineHistoryName, LocalStorageKeys, putHistory } from '../../utils/localStorage'
|
||||||
import { loadManifest } from '../../utils/manifest'
|
import { loadManifest } from '../../utils/manifest'
|
||||||
|
|
||||||
import { AssetPreview } from './AssetPreview'
|
import { AssetPreview, getType } from './AssetPreview'
|
||||||
import { AssetSummary } from './AssetSummary'
|
import { AssetSummary } from './AssetSummary'
|
||||||
import { AssetSyncing } from './AssetSyncing'
|
import { AssetSyncing } from './AssetSyncing'
|
||||||
import { DownloadActionBar } from './DownloadActionBar'
|
import { DownloadActionBar } from './DownloadActionBar'
|
||||||
@@ -46,7 +46,7 @@ export function Share(): ReactElement {
|
|||||||
const count = Object.keys(entries).length
|
const count = Object.keys(entries).length
|
||||||
const isVideo = Boolean(indexDocument && /.*\.(mp4|webm|ogv)$/i.test(indexDocument))
|
const isVideo = Boolean(indexDocument && /.*\.(mp4|webm|ogv)$/i.test(indexDocument))
|
||||||
const isAudio = Boolean(indexDocument && /.*\.(mp3|ogg|oga|wav|webm|m4a|aac|flac)$/i.test(indexDocument))
|
const isAudio = Boolean(indexDocument && /.*\.(mp3|ogg|oga|wav|webm|m4a|aac|flac)$/i.test(indexDocument))
|
||||||
const isImage = Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument))
|
const isImage = Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg|ico)$/i.test(indexDocument))
|
||||||
|
|
||||||
if (isImage || isVideo || isAudio) {
|
if (isImage || isVideo || isAudio) {
|
||||||
setPreview(`${apiUrl}/bzz/${hash}`)
|
setPreview(`${apiUrl}/bzz/${hash}`)
|
||||||
@@ -54,7 +54,7 @@ export function Share(): ReactElement {
|
|||||||
|
|
||||||
setMetadata({
|
setMetadata({
|
||||||
hash,
|
hash,
|
||||||
type: count > 1 ? 'folder' : 'unknown',
|
type: count > 1 ? 'folder' : getType(),
|
||||||
name: indexDocument || hash || '',
|
name: indexDocument || hash || '',
|
||||||
count,
|
count,
|
||||||
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
|
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import { Context as FileContext } from '../../providers/File'
|
|||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { Context as StampsContext, EnrichedPostageBatch } from '../../providers/Stamps'
|
import { Context as StampsContext, EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { waitUntilStampUsable } from '../../utils'
|
|
||||||
import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
|
import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
|
||||||
import { persistIdentity, updateFeed } from '../../utils/identity'
|
import { persistIdentity, updateFeed } from '../../utils/identity'
|
||||||
import { LocalStorageKeys, putHistory } from '../../utils/localStorage'
|
import { LocalStorageKeys, putHistory } from '../../utils/localStorage'
|
||||||
|
import { waitUntilStampUsable } from '../../utils/stamp'
|
||||||
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
|
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
|
||||||
import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation'
|
import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation'
|
||||||
import { PostageStampSelector } from '../stamps/PostageStampSelector'
|
import { PostageStampSelector } from '../stamps/PostageStampSelector'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BeeModes } from '@ethersphere/bee-js'
|
||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
import Upload from 'remixicon-react/UploadLineIcon'
|
import Upload from 'remixicon-react/UploadLineIcon'
|
||||||
@@ -19,7 +20,7 @@ export function WalletInfoCard() {
|
|||||||
)} xBZZ | ${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`
|
)} xBZZ | ${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeInfo?.beeMode && ['light', 'full', 'dev'].includes(nodeInfo.beeMode)) {
|
if (nodeInfo?.beeMode && [BeeModes.LIGHT, BeeModes.FULL, BeeModes.DEV].includes(nodeInfo.beeMode)) {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
|
import { PostageBatchOptions, RedundancyLevel, Utils } from '@ethersphere/bee-js'
|
||||||
import { Box, Grid, IconButton, Typography } from '@mui/material'
|
import { Box, Grid, IconButton, Typography } from '@mui/material'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
@@ -11,12 +11,14 @@ import { makeStyles } from 'tss-react/mui'
|
|||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmSelect } from '../../components/SwarmSelect'
|
import { SwarmSelect } from '../../components/SwarmSelect'
|
||||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||||
|
import { MAX_STAMP_DEPTH, MIN_STAMP_DEPTH } from '../../constants'
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { Context as StampsContext } from '../../providers/Stamps'
|
import { Context as StampsContext } from '../../providers/Stamps'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { secondsToTimeString } from '../../utils'
|
import { secondsToTimeString } from '../../utils'
|
||||||
import { getHumanReadableFileSize } from '../../utils/file'
|
import { getHumanReadableFileSize } from '../../utils/file'
|
||||||
|
import { validateDepthInput } from '../../utils/stamp'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onFinished: () => void
|
onFinished: () => void
|
||||||
@@ -80,7 +82,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPrice(depth: number, amount: bigint): string {
|
function getPrice(depth: number, amount: bigint): string {
|
||||||
const hasInvalidInput = amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
|
const hasInvalidInput = amount <= 0 || isNaN(depth) || depth < MIN_STAMP_DEPTH || depth > MAX_STAMP_DEPTH
|
||||||
|
|
||||||
if (hasInvalidInput) {
|
if (hasInvalidInput) {
|
||||||
return '-'
|
return '-'
|
||||||
@@ -147,38 +149,15 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
|
|||||||
setAmountInput(validAmountInput)
|
setAmountInput(validAmountInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateDepthInput(depthInput: string) {
|
|
||||||
let validDepthInput = '0'
|
|
||||||
|
|
||||||
if (!depthInput) {
|
|
||||||
setDepthError('Required field')
|
|
||||||
} else {
|
|
||||||
const depth = new BigNumber(depthInput)
|
|
||||||
|
|
||||||
if (!depth.isInteger()) {
|
|
||||||
setDepthError('Depth must be an integer')
|
|
||||||
} else if (depth.isLessThan(17)) {
|
|
||||||
setDepthError('Minimal depth is 17')
|
|
||||||
} else if (depth.isGreaterThan(255)) {
|
|
||||||
setDepthError('Depth has to be at most 255')
|
|
||||||
} else {
|
|
||||||
setDepthError('')
|
|
||||||
validDepthInput = depthInput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDepthInput(validDepthInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStampVolumesInfo() {
|
function renderStampVolumesInfo() {
|
||||||
const depth = parseInt(depthInput, 10)
|
const depth = parseInt(depthInput, 10)
|
||||||
|
|
||||||
if (depthError || isNaN(depth) || depth < 17 || depth > 255) {
|
if (depthError || isNaN(depth) || depth < MIN_STAMP_DEPTH || depth > MAX_STAMP_DEPTH) {
|
||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const theoreticalMaximumVolume = getHumanReadableFileSize(Utils.getStampTheoreticalBytes(depth))
|
const theoreticalMaximumVolume = getHumanReadableFileSize(Utils.getStampTheoreticalBytes(depth))
|
||||||
const effectiveVolume = getHumanReadableFileSize(Utils.getStampEffectiveBytes(depth))
|
const effectiveVolume = getHumanReadableFileSize(Utils.getStampEffectiveBytes(depth, false, RedundancyLevel.OFF))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="center" className={classes.stampVolumeWrapper}>
|
<Grid container alignItems="center" className={classes.stampVolumeWrapper}>
|
||||||
@@ -217,7 +196,11 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
<SwarmTextInput name="depth" label="Depth" onChange={event => validateDepthInput(event.target.value)} />
|
<SwarmTextInput
|
||||||
|
name="depth"
|
||||||
|
label="Depth"
|
||||||
|
onChange={event => validateDepthInput(event.target.value, setDepthError, setDepthInput)}
|
||||||
|
/>
|
||||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||||
<Grid container justifyContent="space-between" alignItems="center">
|
<Grid container justifyContent="space-between" alignItems="center">
|
||||||
<Typography>Corresponding file size</Typography>
|
<Typography>Corresponding file size</Typography>
|
||||||
@@ -242,7 +225,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
|
|||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
<SwarmSelect
|
<SwarmSelect
|
||||||
label="Immutable"
|
label="Immutable"
|
||||||
value="No"
|
value={immutable ? 'Yes' : 'No'}
|
||||||
onChange={event => setImmutable(event.target.value === 'Yes')}
|
onChange={event => setImmutable(event.target.value === 'Yes')}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'Yes', label: 'Yes' },
|
{ value: 'Yes', label: 'Yes' },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Duration, PostageBatchOptions, Size, Utils } from '@ethersphere/bee-js'
|
import { Duration, RedundancyLevel, Size, Utils } from '@ethersphere/bee-js'
|
||||||
import { Box, Button, Grid, Slider, Typography } from '@mui/material'
|
import { Box, Button, Grid, Slider, Typography } from '@mui/material'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
import { ReactElement, useContext, useState } from 'react'
|
import { ReactElement, useContext, useState } from 'react'
|
||||||
@@ -8,10 +8,12 @@ import { makeStyles } from 'tss-react/mui'
|
|||||||
|
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { Context as StampsContext } from '../../providers/Stamps'
|
import { Context as StampsContext } from '../../providers/Stamps'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { secondsToTimeString } from '../../utils'
|
import { secondsToTimeString } from '../../utils'
|
||||||
|
import { validateDepthInput } from '../../utils/stamp'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onFinished: () => void
|
onFinished: () => void
|
||||||
@@ -48,12 +50,17 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
const { classes } = useStyles()
|
const { classes } = useStyles()
|
||||||
const { refresh } = useContext(StampsContext)
|
const { refresh } = useContext(StampsContext)
|
||||||
const { beeApi } = useContext(SettingsContext)
|
const { beeApi } = useContext(SettingsContext)
|
||||||
|
const { chainState } = useContext(BeeContext)
|
||||||
const [depthInput, setDepthInput] = useState<number>(Utils.getDepthForSize(Size.fromGigabytes(4)))
|
const [depthInput, setDepthInput] = useState<number>(Utils.getDepthForSize(Size.fromGigabytes(4)))
|
||||||
const [amountInput, setAmountInput] = useState<bigint>(Utils.getAmountForDuration(Duration.fromDays(30), 26500, 5))
|
const [amountInput, setAmountInput] = useState<bigint>(Utils.getAmountForDuration(Duration.fromDays(30), 26500, 5))
|
||||||
const [labelInput, setLabelInput] = useState('')
|
const [labelInput, setLabelInput] = useState('')
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
const [buttonValue, setButtonValue] = useState(4)
|
const [buttonValue, setButtonValue] = useState(4)
|
||||||
|
const [depthError, setDepthError] = useState<string>('')
|
||||||
|
const [sliderValue, setSliderValue] = useState(30)
|
||||||
|
|
||||||
|
const pricePerBlockDefault = 24000
|
||||||
|
const currentPrice = chainState?.currentPrice ?? pricePerBlockDefault
|
||||||
|
|
||||||
const getBatchValue = (value: number) => {
|
const getBatchValue = (value: number) => {
|
||||||
return (
|
return (
|
||||||
@@ -74,18 +81,18 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
if (typeof newValue !== 'number') {
|
if (typeof newValue !== 'number') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const amountValue = Utils.getAmountForDuration(Duration.fromDays(newValue), 26500, 5)
|
const amountValue = Utils.getAmountForDuration(Duration.fromDays(newValue), currentPrice, 5)
|
||||||
|
|
||||||
setAmountInput(amountValue)
|
setAmountInput(amountValue)
|
||||||
|
setSliderValue(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
function getTtl(amount: bigint): string {
|
function getTtl(amount: bigint): string {
|
||||||
const pricePerBlock = 24000
|
|
||||||
|
|
||||||
return `${secondsToTimeString(
|
return `${secondsToTimeString(
|
||||||
Utils.getStampDuration(amount, pricePerBlock, 5).toSeconds(),
|
Utils.getStampDuration(amount, currentPrice, 5).toSeconds(),
|
||||||
)} (with price of ${pricePerBlock} PLUR per block)`
|
)} (with price of ${currentPrice} PLUR per block)`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrice(depth: number, amount: bigint): string {
|
function getPrice(depth: number, amount: bigint): string {
|
||||||
@@ -106,15 +113,15 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
const amount = BigInt(amountInput)
|
|
||||||
const depth = depthInput
|
|
||||||
const options: PostageBatchOptions = {
|
|
||||||
waitForUsable: false,
|
|
||||||
label: labelInput || undefined,
|
|
||||||
immutableFlag: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
await beeApi.createPostageBatch(amount.toString(), depth, options)
|
await beeApi.buyStorage(
|
||||||
|
Size.fromGigabytes(buttonValue),
|
||||||
|
Duration.fromDays(sliderValue),
|
||||||
|
{ label: labelInput, immutableFlag: true },
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
RedundancyLevel.OFF,
|
||||||
|
)
|
||||||
await refresh()
|
await refresh()
|
||||||
onFinished()
|
onFinished()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -127,8 +134,8 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
|
|
||||||
function handleBatchSize(gigabytes: number) {
|
function handleBatchSize(gigabytes: number) {
|
||||||
setButtonValue(gigabytes)
|
setButtonValue(gigabytes)
|
||||||
const capacity = Utils.getDepthForSize(Size.fromGigabytes(gigabytes))
|
const capacity = Utils.getDepthForSize(Size.fromGigabytes(gigabytes), false, RedundancyLevel.OFF)
|
||||||
setDepthInput(capacity)
|
validateDepthInput(String(capacity), setDepthError, (v: string) => setDepthInput(Number(v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -162,6 +169,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
{getBatchValue(32)}
|
{getBatchValue(32)}
|
||||||
{getBatchValue(256)}
|
{getBatchValue(256)}
|
||||||
</Box>
|
</Box>
|
||||||
|
{depthError && <Typography>{depthError}</Typography>}
|
||||||
</Box>
|
</Box>
|
||||||
<Box mb={1}>
|
<Box mb={1}>
|
||||||
<Typography variant="h2">Data persistence</Typography>
|
<Typography variant="h2">Data persistence</Typography>
|
||||||
@@ -187,7 +195,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" justifyContent={'right'} mt={0.5}>
|
<Box display="flex" justifyContent={'right'} mt={0.5}>
|
||||||
<Typography style={{ fontSize: '10px', color: 'rgba(0, 0, 0, 0.26)' }}>
|
<Typography style={{ fontSize: '10px', color: 'rgba(0, 0, 0, 0.26)' }}>
|
||||||
Current price of 24000 PLUR per block
|
Current price of {currentPrice} PLUR per block
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -200,7 +208,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
|||||||
<Grid container justifyContent="space-between" alignItems="center">
|
<Grid container justifyContent="space-between" alignItems="center">
|
||||||
<Grid>
|
<Grid>
|
||||||
<SwarmButton
|
<SwarmButton
|
||||||
disabled={submitting || !depthInput || !amountInput}
|
disabled={submitting || !depthInput || Boolean(depthError) || !amountInput}
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
iconType={Check}
|
iconType={Check}
|
||||||
loading={submitting}
|
loading={submitting}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import ExpandableList from '../../components/ExpandableList'
|
|||||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||||
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
||||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||||
import StampExtensionModal from '../../components/StampExtensionModal'
|
import StampExtensionModal, { StampExtensionType } from '../../components/StampExtensionModal'
|
||||||
import { Context } from '../../providers/Settings'
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
import { secondsToTimeString } from '../../utils'
|
import { secondsToTimeString } from '../../utils'
|
||||||
import { getHumanReadableFileSize } from '../../utils/file'
|
import { getHumanReadableFileSize } from '../../utils/file'
|
||||||
@@ -20,7 +21,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StampsTable({ postageStamps }: Props): ReactElement | null {
|
function StampsTable({ postageStamps }: Props): ReactElement | null {
|
||||||
const { beeApi } = useContext(Context)
|
const { beeApi } = useContext(SettingsContext)
|
||||||
|
const { status } = useContext(BeeContext)
|
||||||
|
|
||||||
if (!postageStamps || !beeApi) {
|
if (!postageStamps || !beeApi) {
|
||||||
return null
|
return null
|
||||||
@@ -37,8 +39,8 @@ function StampsTable({ postageStamps }: Props): ReactElement | null {
|
|||||||
<ExpandableListItem label="Depth" value={String(stamp.depth)} />
|
<ExpandableListItem label="Depth" value={String(stamp.depth)} />
|
||||||
<ExpandableListItem
|
<ExpandableListItem
|
||||||
label="Capacity"
|
label="Capacity"
|
||||||
value={`${getHumanReadableFileSize(2 ** stamp.depth * 4096 * stamp.usage)} / ${getHumanReadableFileSize(
|
value={`${getHumanReadableFileSize(stamp.size.toBytes() - stamp.remainingSize.toBytes())} / ${getHumanReadableFileSize(
|
||||||
2 ** stamp.depth * 4096,
|
stamp.size.toBytes(),
|
||||||
)}`}
|
)}`}
|
||||||
/>
|
/>
|
||||||
<ExpandableListItem label="Amount" value={parseInt(stamp.amount, 10).toLocaleString()} />
|
<ExpandableListItem label="Amount" value={parseInt(stamp.amount, 10).toLocaleString()} />
|
||||||
@@ -49,16 +51,18 @@ function StampsTable({ postageStamps }: Props): ReactElement | null {
|
|||||||
<ExpandableListItem label="Purchase Block Number" value={stamp.blockNumber} />
|
<ExpandableListItem label="Purchase Block Number" value={stamp.blockNumber} />
|
||||||
<ExpandableListItemActions>
|
<ExpandableListItemActions>
|
||||||
<StampExtensionModal
|
<StampExtensionModal
|
||||||
type="Topup"
|
type={StampExtensionType.Topup}
|
||||||
icon={<TimerFlashFill size="1rem" />}
|
icon={<TimerFlashFill size="1rem" />}
|
||||||
bee={beeApi}
|
bee={beeApi}
|
||||||
stamp={stamp.batchID}
|
stamp={stamp}
|
||||||
|
status={status.all}
|
||||||
/>
|
/>
|
||||||
<StampExtensionModal
|
<StampExtensionModal
|
||||||
type="Dilute"
|
type={StampExtensionType.Dilute}
|
||||||
icon={<TimerFlashLine size="1rem" />}
|
icon={<TimerFlashLine size="1rem" />}
|
||||||
bee={beeApi}
|
bee={beeApi}
|
||||||
stamp={stamp.batchID}
|
stamp={stamp}
|
||||||
|
status={status.all}
|
||||||
/>
|
/>
|
||||||
</ExpandableListItemActions>
|
</ExpandableListItemActions>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { createContext, ReactElement, ReactNode, useEffect, useState } from 'react'
|
import { createContext, ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { PREVIEW_DIMENSIONS } from '../constants'
|
import { PREVIEW_DIMENSIONS } from '../constants'
|
||||||
|
import { FileOrigin } from '../pages/files/FileNavigation'
|
||||||
import { getMetadata } from '../utils/file'
|
import { getMetadata } from '../utils/file'
|
||||||
import { resize } from '../utils/image'
|
import { resize } from '../utils/image'
|
||||||
|
|
||||||
import { FileOrigin } from '@/pages/files/FileNavigation'
|
|
||||||
|
|
||||||
export type UploadOrigin = { origin: FileOrigin.Upload | FileOrigin.Feed; uuid?: string }
|
export type UploadOrigin = { origin: FileOrigin.Upload | FileOrigin.Feed; uuid?: string }
|
||||||
|
|
||||||
export const defaultUploadOrigin: UploadOrigin = { origin: FileOrigin.Upload }
|
export const defaultUploadOrigin: UploadOrigin = { origin: FileOrigin.Upload }
|
||||||
|
|||||||
+25
-3
@@ -1,5 +1,5 @@
|
|||||||
import { EthAddress } from '@ethersphere/bee-js'
|
import { EthAddress } from '@ethersphere/bee-js'
|
||||||
import { getAddress, JsonRpcProvider, Networkish } from 'ethers'
|
import { getAddress, JsonRpcPayload, JsonRpcProvider, JsonRpcResult, Networkish } from 'ethers'
|
||||||
|
|
||||||
export const GNOIS_NETWORK_ID = 100
|
export const GNOIS_NETWORK_ID = 100
|
||||||
export const GnosisNetwork: Networkish = { chainId: GNOIS_NETWORK_ID, name: 'gnosis', ensAddress: undefined }
|
export const GnosisNetwork: Networkish = { chainId: GNOIS_NETWORK_ID, name: 'gnosis', ensAddress: undefined }
|
||||||
@@ -39,6 +39,28 @@ export function ethAddressString(address: EthAddress | string): string {
|
|||||||
return typeof address === 'string' ? getAddress(address) : getAddress(address.toHex())
|
return typeof address === 'string' ? getAddress(address) : getAddress(address.toHex())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newGnosisProvider(url: string): JsonRpcProvider {
|
/**
|
||||||
return new JsonRpcProvider(url, GnosisNetwork, { staticNetwork: true })
|
* Some RPC endpoints always return id:1 in their JSON-RPC responses regardless of the id in the request.
|
||||||
|
* Ethers v6 validates that response ids match request ids, so we patch them.
|
||||||
|
*/
|
||||||
|
class FixedIdJsonRpcProvider extends JsonRpcProvider {
|
||||||
|
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult>> {
|
||||||
|
const results = await super._send(payload)
|
||||||
|
const payloads = Array.isArray(payload) ? payload : [payload]
|
||||||
|
|
||||||
|
return results.map((result, i) => ({ ...result, id: payloads[i]?.id ?? result.id }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newGnosisProvider(url: string): JsonRpcProvider {
|
||||||
|
return new FixedIdJsonRpcProvider(url, GnosisNetwork, { staticNetwork: true, batchMaxCount: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for RPC validation only — no staticNetwork so getNetwork() actually
|
||||||
|
* calls eth_chainId, but still uses FixedIdJsonRpcProvider to handle endpoints
|
||||||
|
* that return a fixed/wrong id in their responses.
|
||||||
|
*/
|
||||||
|
export function newGnosisProviderForValidation(url: string): JsonRpcProvider {
|
||||||
|
return new FixedIdJsonRpcProvider(url, undefined, { batchMaxCount: 1 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,3 +137,119 @@ export function packageFile(file: FilePath, pathOverwrite?: string): FilePath {
|
|||||||
bytes: file.bytes,
|
bytes: file.bytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getExtensionFromName(name: string): string {
|
||||||
|
const ext = name.split('.').pop()?.toLowerCase() || ''
|
||||||
|
const hasExtension = name.includes('.') && ext && ext !== name
|
||||||
|
|
||||||
|
return hasExtension ? ext : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXT_TO_MIME: Record<string, string> = {
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
webm: 'video/webm',
|
||||||
|
ogv: 'video/ogg',
|
||||||
|
mp3: 'audio/mpeg',
|
||||||
|
m4a: 'audio/mp4',
|
||||||
|
aac: 'audio/aac',
|
||||||
|
wav: 'audio/wav',
|
||||||
|
ogg: 'audio/ogg',
|
||||||
|
png: 'image/png',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
gif: 'image/gif',
|
||||||
|
webp: 'image/webp',
|
||||||
|
avif: 'image/avif',
|
||||||
|
svg: 'image/svg+xml',
|
||||||
|
pdf: 'application/pdf',
|
||||||
|
txt: 'text/plain',
|
||||||
|
md: 'text/markdown',
|
||||||
|
json: 'application/json',
|
||||||
|
csv: 'text/csv',
|
||||||
|
html: 'text/html',
|
||||||
|
htm: 'text/html',
|
||||||
|
ico: 'image/vnd.microsoft.icon',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function guessMime(name: string, mtdt?: Record<string, string> | undefined): { mime: string; ext: string } {
|
||||||
|
const md = mtdt?.mimeType || mtdt?.mime || mtdt?.['content-type']
|
||||||
|
const ext = getExtensionFromName(name)
|
||||||
|
|
||||||
|
if (md) return { mime: md, ext }
|
||||||
|
|
||||||
|
const mime = EXT_TO_MIME[ext] || 'application/octet-stream'
|
||||||
|
|
||||||
|
return { mime, ext }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Viewer = {
|
||||||
|
name: string
|
||||||
|
test: (mime: string) => boolean
|
||||||
|
render: (win: Window, url: string, mime: string, name: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const VIDEO_HTML = (u: string, title: string) =>
|
||||||
|
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body style="margin:0;background:#000">
|
||||||
|
<video controls autoplay style="width:100%;height:100%" src="${u}"></video>
|
||||||
|
</body></html>`
|
||||||
|
|
||||||
|
const AUDIO_HTML = (u: string, title: string) =>
|
||||||
|
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body>
|
||||||
|
<audio controls autoplay style="width:100%" src="${u}"></audio>
|
||||||
|
</body></html>`
|
||||||
|
|
||||||
|
const IMAGE_HTML = (u: string, title: string) =>
|
||||||
|
`<html><head><meta charset="utf-8"/><title>${title}</title></head><body style="margin:0;background:#111;display:grid;place-items:center;min-height:100vh">
|
||||||
|
<img style="max-width:100%;max-height:100vh" src="${u}" />
|
||||||
|
</body></html>`
|
||||||
|
|
||||||
|
export const VIEWERS: Viewer[] = [
|
||||||
|
{
|
||||||
|
name: 'video',
|
||||||
|
test: m => m.startsWith('video/'),
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.write(VIDEO_HTML(url, name))
|
||||||
|
w.document.title = name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'audio',
|
||||||
|
test: m => m.startsWith('audio/'),
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.write(AUDIO_HTML(url, name))
|
||||||
|
w.document.title = name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
test: m => m.startsWith('image/'),
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.write(IMAGE_HTML(url, name))
|
||||||
|
w.document.title = name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pdf',
|
||||||
|
test: m => m === 'application/pdf',
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.title = name
|
||||||
|
w.location.href = url
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'html',
|
||||||
|
test: m => m === 'text/html',
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.title = name
|
||||||
|
w.location.href = url
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text-like',
|
||||||
|
test: m => m.startsWith('text/') || m === 'application/json' || m === 'text/markdown',
|
||||||
|
render: (w, url, mime, name) => {
|
||||||
|
w.document.title = name
|
||||||
|
w.location.href = url
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { BatchId, Bee, NULL_TOPIC, PrivateKey, Reference } from '@ethersphere/bee-js'
|
import { BatchId, Bee, Bytes, NULL_TOPIC, PrivateKey, Reference } from '@ethersphere/bee-js'
|
||||||
import { randomBytes, Wallet } from 'ethers'
|
import { randomBytes, Wallet } from 'ethers'
|
||||||
|
|
||||||
import { Identity, IdentityType } from '../providers/Feeds'
|
import { Identity, IdentityType } from '../providers/Feeds'
|
||||||
|
|
||||||
import { LocalStorageKeys } from './localStorage'
|
import { LocalStorageKeys } from './localStorage'
|
||||||
import { uuidV4, waitUntilStampUsable } from '.'
|
import { waitUntilStampUsable } from './stamp'
|
||||||
|
import { uuidV4 } from '.'
|
||||||
|
|
||||||
export function generateWallet(): Wallet {
|
export function generateWallet(): Wallet {
|
||||||
const privateKey = randomBytes(PrivateKey.LENGTH).toString()
|
const privateKey = new Bytes(randomBytes(PrivateKey.LENGTH)).toString()
|
||||||
|
|
||||||
return new Wallet(privateKey)
|
return new Wallet(privateKey)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-34
@@ -1,4 +1,4 @@
|
|||||||
import { BatchId, Bee, PostageBatch, Reference } from '@ethersphere/bee-js'
|
import { Reference } from '@ethersphere/bee-js'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
|
|
||||||
import { BZZ_LINK_DOMAIN } from '../constants'
|
import { BZZ_LINK_DOMAIN } from '../constants'
|
||||||
@@ -207,36 +207,3 @@ export function shortenText(text: string, length = 20, separator = '[…]'): str
|
|||||||
|
|
||||||
return `${text.slice(0, length)}${separator}${text.slice(-length)}`
|
return `${text.slice(0, length)}${separator}${text.slice(-length)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_POLLING_FREQUENCY = 1_000
|
|
||||||
const DEFAULT_STAMP_USABLE_TIMEOUT = 5 * 60_000
|
|
||||||
|
|
||||||
interface Options {
|
|
||||||
pollingFrequency?: number
|
|
||||||
timeout?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function waitUntilStampUsable(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
|
|
||||||
return waitForStamp(batchId, bee, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function waitForStamp(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
|
|
||||||
const timeout = options?.timeout || DEFAULT_STAMP_USABLE_TIMEOUT
|
|
||||||
const pollingFrequency = options?.pollingFrequency || DEFAULT_POLLING_FREQUENCY
|
|
||||||
|
|
||||||
for (let i = 0; i < timeout; i += pollingFrequency) {
|
|
||||||
try {
|
|
||||||
const stamp = await bee.getPostageBatch(batchId)
|
|
||||||
|
|
||||||
if (stamp.usable) {
|
|
||||||
return stamp
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleepMs(pollingFrequency)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Wait until stamp usable timeout has been reached')
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-4
@@ -3,10 +3,10 @@ import { debounce } from '@mui/material'
|
|||||||
import { Contract, JsonRpcProvider, TransactionReceipt, TransactionResponse, Wallet } from 'ethers'
|
import { Contract, JsonRpcProvider, TransactionReceipt, TransactionResponse, Wallet } from 'ethers'
|
||||||
|
|
||||||
import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzzAbi'
|
import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzzAbi'
|
||||||
import { ethAddressString, newGnosisProvider } from './chain'
|
import { ethAddressString, newGnosisProvider, newGnosisProviderForValidation } from './chain'
|
||||||
|
|
||||||
async function getNetworkChainId(url: string): Promise<bigint> {
|
async function getNetworkChainId(url: string): Promise<bigint> {
|
||||||
const provider = newGnosisProvider(url)
|
const provider = newGnosisProviderForValidation(url)
|
||||||
const network = await provider.getNetwork()
|
const network = await provider.getNetwork()
|
||||||
|
|
||||||
return network.chainId
|
return network.chainId
|
||||||
@@ -84,7 +84,7 @@ export async function sendNativeTransaction(
|
|||||||
const feedData = await signer.provider.getFeeData()
|
const feedData = await signer.provider.getFeeData()
|
||||||
const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0')
|
const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0')
|
||||||
const transaction = await signer.sendTransaction({
|
const transaction = await signer.sendTransaction({
|
||||||
to: to.toHex(),
|
to: to.toChecksum(),
|
||||||
value: BigInt(value.toWeiString()),
|
value: BigInt(value.toWeiString()),
|
||||||
gasPrice: BigInt(gasPrice.toWeiString()),
|
gasPrice: BigInt(gasPrice.toWeiString()),
|
||||||
gasLimit: BigInt(21000),
|
gasLimit: BigInt(21000),
|
||||||
@@ -117,7 +117,7 @@ export async function sendBzzTransaction(
|
|||||||
const feeData = await signer.provider.getFeeData()
|
const feeData = await signer.provider.getFeeData()
|
||||||
const gasPrice = feeData.gasPrice || BigInt(0)
|
const gasPrice = feeData.gasPrice || BigInt(0)
|
||||||
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
|
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
|
||||||
const transaction = await bzz.transfer(to.toChecksum(), value, { gasPrice })
|
const transaction = await bzz.transfer(to.toChecksum(), value.toPLURBigInt(), { gasPrice })
|
||||||
const receipt = await transaction.wait(1)
|
const receipt = await transaction.wait(1)
|
||||||
|
|
||||||
if (!receipt) {
|
if (!receipt) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { BatchId, Bee, PostageBatch } from '@ethersphere/bee-js'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
|
import { MAX_STAMP_DEPTH, MIN_STAMP_DEPTH } from '../constants'
|
||||||
|
|
||||||
|
import { sleepMs } from '.'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function validateDepthInput(depthInput: string, onError: (v: any) => void, onSuccess: (v: any) => void) {
|
||||||
|
let validDepthInput = '0'
|
||||||
|
|
||||||
|
if (!depthInput) {
|
||||||
|
onError('Required field')
|
||||||
|
} else {
|
||||||
|
const depth = new BigNumber(depthInput)
|
||||||
|
|
||||||
|
if (!depth.isInteger()) {
|
||||||
|
onError('Depth must be an integer')
|
||||||
|
} else if (depth.isLessThan(MIN_STAMP_DEPTH)) {
|
||||||
|
onError(`Minimal depth is ${MIN_STAMP_DEPTH}`)
|
||||||
|
} else if (depth.isGreaterThan(MAX_STAMP_DEPTH)) {
|
||||||
|
onError(`Depth has to be at most ${MAX_STAMP_DEPTH}`)
|
||||||
|
} else {
|
||||||
|
onError('')
|
||||||
|
validDepthInput = depthInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(validDepthInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_POLLING_FREQUENCY = 1_000
|
||||||
|
const DEFAULT_STAMP_USABLE_TIMEOUT = 5 * 60_000
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
pollingFrequency?: number
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function waitUntilStampUsable(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
|
||||||
|
return waitForStamp(batchId, bee, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForStamp(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
|
||||||
|
const timeout = options?.timeout || DEFAULT_STAMP_USABLE_TIMEOUT
|
||||||
|
const pollingFrequency = options?.pollingFrequency || DEFAULT_POLLING_FREQUENCY
|
||||||
|
|
||||||
|
for (let i = 0; i < timeout; i += pollingFrequency) {
|
||||||
|
try {
|
||||||
|
const stamp = await bee.getPostageBatch(batchId)
|
||||||
|
|
||||||
|
if (stamp.usable) {
|
||||||
|
return stamp
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleepMs(pollingFrequency)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Wait until stamp usable timeout has been reached')
|
||||||
|
}
|
||||||
+52
-4
@@ -1,6 +1,6 @@
|
|||||||
import { BZZ } from '@ethersphere/bee-js'
|
import { BZZ, DAI } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
import { sendBzzTransaction } from '../../src/utils/rpc'
|
import { sendBzzTransaction, sendNativeTransaction } from '../../src/utils/rpc'
|
||||||
|
|
||||||
interface MockProvider {
|
interface MockProvider {
|
||||||
getFeeData: jest.Mock
|
getFeeData: jest.Mock
|
||||||
@@ -11,12 +11,14 @@ const mockWait = jest.fn()
|
|||||||
const mockTransfer = jest.fn()
|
const mockTransfer = jest.fn()
|
||||||
const mockGetFeeData = jest.fn()
|
const mockGetFeeData = jest.fn()
|
||||||
const mockGetNetwork = jest.fn()
|
const mockGetNetwork = jest.fn()
|
||||||
|
const mockSendTransaction = jest.fn()
|
||||||
const mockProvider: MockProvider = {
|
const mockProvider: MockProvider = {
|
||||||
getFeeData: mockGetFeeData,
|
getFeeData: mockGetFeeData,
|
||||||
getNetwork: mockGetNetwork,
|
getNetwork: mockGetNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = BZZ.fromDecimalString('1')
|
const bzzValue = BZZ.fromDecimalString('1')
|
||||||
|
const daiValue = DAI.fromDecimalString('1')
|
||||||
const privateKey = 'FFFF000000000000000000000000000000000000000000000000000000000000'
|
const privateKey = 'FFFF000000000000000000000000000000000000000000000000000000000000'
|
||||||
const jsonRpcProvider = 'http://mock-json-rpc-provider'
|
const jsonRpcProvider = 'http://mock-json-rpc-provider'
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ jest.mock('ethers', () => {
|
|||||||
|
|
||||||
class Wallet {
|
class Wallet {
|
||||||
provider: MockProvider
|
provider: MockProvider
|
||||||
|
sendTransaction = mockSendTransaction
|
||||||
|
|
||||||
constructor(_privateKey: string, provider: MockProvider) {
|
constructor(_privateKey: string, provider: MockProvider) {
|
||||||
this.provider = provider
|
this.provider = provider
|
||||||
@@ -64,8 +67,53 @@ describe('sendBzzTransaction', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it.each(addresses)('sendBzzTransaction to address: %s', async (address: string) => {
|
it.each(addresses)('sendBzzTransaction to address: %s', async (address: string) => {
|
||||||
await sendBzzTransaction(privateKey, address, value, jsonRpcProvider)
|
await sendBzzTransaction(privateKey, address, bzzValue, jsonRpcProvider)
|
||||||
const to = mockTransfer.mock.calls[0][0] as string
|
const to = mockTransfer.mock.calls[0][0] as string
|
||||||
expect(to.startsWith('0x')).toBe(true)
|
expect(to.startsWith('0x')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('passes BZZ value as bigint (not BZZ object)', async () => {
|
||||||
|
await sendBzzTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', bzzValue, jsonRpcProvider)
|
||||||
|
const transferredValue = mockTransfer.mock.calls[0][1]
|
||||||
|
expect(typeof transferredValue).toBe('bigint')
|
||||||
|
expect(transferredValue).toBe(bzzValue.toPLURBigInt())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendNativeTransaction', () => {
|
||||||
|
const addresses = ['52908400098527886e0f7030069857d2e4169ee7', '0x52908400098527886e0f7030069857d2e4169ee7']
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
mockWait.mockResolvedValue({ status: 1 })
|
||||||
|
mockSendTransaction.mockResolvedValue({ wait: mockWait })
|
||||||
|
mockGetFeeData.mockResolvedValue({ gasPrice: BigInt(1) })
|
||||||
|
mockGetNetwork.mockResolvedValue({ chainId: BigInt(100) })
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each(addresses)('sendNativeTransaction to address: %s passes 0x-prefixed address', async (address: string) => {
|
||||||
|
await sendNativeTransaction(privateKey, address, daiValue, jsonRpcProvider)
|
||||||
|
const tx = mockSendTransaction.mock.calls[0][0] as { to: string }
|
||||||
|
expect(tx.to.startsWith('0x')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('passes DAI value as bigint', async () => {
|
||||||
|
await sendNativeTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', daiValue, jsonRpcProvider)
|
||||||
|
const tx = mockSendTransaction.mock.calls[0][0] as { value: bigint }
|
||||||
|
expect(typeof tx.value).toBe('bigint')
|
||||||
|
expect(tx.value).toBe(BigInt(daiValue.toWeiString()))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses externalGasPrice when provided', async () => {
|
||||||
|
const externalGasPrice = DAI.fromWei('9999')
|
||||||
|
await sendNativeTransaction(
|
||||||
|
privateKey,
|
||||||
|
'0x52908400098527886e0f7030069857d2e4169ee7',
|
||||||
|
daiValue,
|
||||||
|
jsonRpcProvider,
|
||||||
|
externalGasPrice,
|
||||||
|
)
|
||||||
|
const tx = mockSendTransaction.mock.calls[0][0] as { gasPrice: bigint }
|
||||||
|
expect(tx.gasPrice).toBe(BigInt(externalGasPrice.toWeiString()))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
nodePolyfills({
|
nodePolyfills({
|
||||||
include: ['util', 'buffer'],
|
include: ['util', 'buffer', 'stream'],
|
||||||
globals: {
|
globals: {
|
||||||
Buffer: true,
|
Buffer: true,
|
||||||
global: true,
|
global: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user