Compare commits

...

14 Commits

Author SHA1 Message Date
bee-worker d9e7560117 chore: release 0.13.0 (#289) 2022-01-31 13:04:20 +01:00
Cafe137 3a30ee59d4 ci: add swarm-cli extra flags (#299)
* ci: add swarm-cli extra flags

* ci: rename SWARM_CLI_EXTRA_FLAGS to GATEWAY_AUTHORIZATION_HEADER

* ci: change bee-url
2022-01-28 14:02:15 +01:00
Cafe137 7880c802ae fix: do not print size and name when meta is unknown (#297)
* fix: do not print zero when size is unknown

* fix: do not print name if it is the same as the hash

* feat: shorten asset name
2022-01-27 16:22:27 +01:00
Vojtech Simetka f4013142af feat: add metadata and preview (#292)
* chore: upload flow uses metadata object and has preview

* chore: remove SwarmFile

* feat: upload metadata and file preview

* feat: add metadata and preview on download

* fix: package the meta and preview files

* fix: upload websites that are inside a folder (#296)

* fix: upload websites that are inside a folder

* docs: few comments to clarify what is going on

* refactor: decrease local variables and fix state order to detect websites properly

Co-authored-by: Cafe137 <aron@aronsoos.com>
2022-01-26 18:29:09 +01:00
Cafe137 57bff96c99 style: make select and text input style consistent (#295) 2022-01-25 18:03:40 +01:00
Cafe137 a406e0fc01 fix: clean up spinner and disabled state on download page (#294) 2022-01-25 18:02:57 +01:00
Cafe137 1310deb17a fix: disable feeds page when disconnected (#293) 2022-01-25 18:02:20 +01:00
Vojtech Simetka d8787476ac fix: correct folder name when uploading multiple files or mix of files & directories (#291) 2022-01-24 18:03:06 +01:00
Cafe137 bc82e67561 fix: get current price from chain state (#286)
* fix: get current price from chain state

* refactor: do not allow optional currentPrice
2022-01-20 15:49:41 +01:00
Cafe137 63e79ae2aa ci: enable beeload action (#290)
* ci: enable beeload action

* ci: remove testnet

* ci: add continue-on-error to testnet beeload-action
2022-01-20 15:49:30 +01:00
Cafe137 48ce9ba659 refactor: remove google fonts dependency (#285)
* refactor: remove google fonts dependency

* fix: change montserrat to work sans

* refactor: omit unicode ranges
2022-01-20 14:51:46 +01:00
Cafe137 9ee1c9107b feat: add hash based routing (#287) 2022-01-20 14:51:27 +01:00
Vojtech Simetka a90b4c439b chore(deps): update react router from v5 to v6 (#280)
* chore(deps): update react router from v5 to v6

* fix: correctly choose navigate target if there is no history
2022-01-17 14:47:26 +01:00
Vojtech Simetka 2187b9001c refactor: settings to use less useEffects (and therefore less re-renders) (#277) 2022-01-17 12:31:07 +01:00
42 changed files with 595 additions and 467 deletions
+14
View File
@@ -69,3 +69,17 @@ jobs:
- name: Build Component - name: Build Component
run: npm run build:component run: npm run build:component
- name: Create preview
uses: ethersphere/beeload-action@v1
with:
bee-url: https://unlimited.gateway.ethswarm.org
preview: 'true'
token: ${{ secrets.REPO_GHA_PAT }}
extra-params: '-H "${{ secrets.GATEWAY_AUTHORIZATION_HEADER }}"'
- name: Upload to testnet
continue-on-error: true
uses: ethersphere/beeload-action@v1
with:
bee-url: https://api.gateway.testnet.ethswarm.org
+17
View File
@@ -1,5 +1,22 @@
# Changelog # Changelog
## [0.13.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.12.0...v0.13.0) (2022-01-28)
### Features
* add hash based routing ([#287](https://www.github.com/ethersphere/bee-dashboard/issues/287)) ([9ee1c91](https://www.github.com/ethersphere/bee-dashboard/commit/9ee1c9107bb08d1838044f39e4d0dd5817c8f283))
* add metadata and preview ([#292](https://www.github.com/ethersphere/bee-dashboard/issues/292)) ([f401314](https://www.github.com/ethersphere/bee-dashboard/commit/f4013142afdb407e699eff9587921e23c971f1db))
### Bug Fixes
* clean up spinner and disabled state on download page ([#294](https://www.github.com/ethersphere/bee-dashboard/issues/294)) ([a406e0f](https://www.github.com/ethersphere/bee-dashboard/commit/a406e0fc014991fcbaca230f27f41cd071d8a863))
* correct folder name when uploading multiple files or mix of files & directories ([#291](https://www.github.com/ethersphere/bee-dashboard/issues/291)) ([d878747](https://www.github.com/ethersphere/bee-dashboard/commit/d8787476acf068be6609a77b1fadb2f61d0fd502))
* disable feeds page when disconnected ([#293](https://www.github.com/ethersphere/bee-dashboard/issues/293)) ([1310deb](https://www.github.com/ethersphere/bee-dashboard/commit/1310deb17aec91f368f99974aaa245abb0a3e201))
* do not print size and name when meta is unknown ([#297](https://www.github.com/ethersphere/bee-dashboard/issues/297)) ([7880c80](https://www.github.com/ethersphere/bee-dashboard/commit/7880c802aea6b0830ca52b47b88540b8df5888cc))
* get current price from chain state ([#286](https://www.github.com/ethersphere/bee-dashboard/issues/286)) ([bc82e67](https://www.github.com/ethersphere/bee-dashboard/commit/bc82e6756154b33d01796a6e66e51dcfa1495338))
## [0.12.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.11.2...v0.12.0) (2021-12-21) ## [0.12.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.11.2...v0.12.0) (2021-12-21)
+33 -160
View File
@@ -1,12 +1,12 @@
{ {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.12.0", "version": "0.13.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.12.0", "version": "0.13.0",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"@ethersphere/bee-js": "3.1.0", "@ethersphere/bee-js": "3.1.0",
@@ -31,8 +31,8 @@
"react-dom": ">= 17.0.2", "react-dom": ">= 17.0.2",
"react-feather": "2.0.9", "react-feather": "2.0.9",
"react-identicons": "1.2.5", "react-identicons": "1.2.5",
"react-router": "5.2.0", "react-router": "6.2.1",
"react-router-dom": "5.2.0", "react-router-dom": "6.2.1",
"react-syntax-highlighter": "15.4.4", "react-syntax-highlighter": "15.4.4",
"semver": "7.3.5", "semver": "7.3.5",
"serve-handler": "6.1.3" "serve-handler": "6.1.3"
@@ -11867,16 +11867,11 @@
} }
}, },
"node_modules/history": { "node_modules/history": {
"version": "4.10.1", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.7.6"
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
} }
}, },
"node_modules/hmac-drbg": { "node_modules/hmac-drbg": {
@@ -13220,11 +13215,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -16287,19 +16277,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/mini-css-extract-plugin": { "node_modules/mini-css-extract-plugin": {
"version": "0.11.3", "version": "0.11.3",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
@@ -17523,14 +17500,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true "dev": true
}, },
"node_modules/path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"dependencies": {
"isarray": "0.0.1"
}
},
"node_modules/path-type": { "node_modules/path-type": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -21899,47 +21868,29 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "5.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.1.2", "history": "^5.2.0"
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=15" "react": ">=16.8"
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "5.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.1.2", "history": "^5.2.0",
"history": "^4.9.0", "react-router": "6.2.1"
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=15" "react": ">=16.8",
"react-dom": ">=16.8"
} }
}, },
"node_modules/react-router/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz",
@@ -22636,11 +22587,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"node_modules/resolve-url": { "node_modules/resolve-url": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -25523,11 +25469,6 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true "dev": true
}, },
"node_modules/tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"node_modules/tiny-warning": { "node_modules/tiny-warning": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -26326,11 +26267,6 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -37639,16 +37575,11 @@
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
}, },
"history": { "history": {
"version": "4.10.1", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.7.6"
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
} }
}, },
"hmac-drbg": { "hmac-drbg": {
@@ -38664,11 +38595,6 @@
"is-docker": "^2.0.0" "is-docker": "^2.0.0"
} }
}, },
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"isexe": { "isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -41131,15 +41057,6 @@
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true "dev": true
}, },
"mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"requires": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
}
},
"mini-css-extract-plugin": { "mini-css-extract-plugin": {
"version": "0.11.3", "version": "0.11.3",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
@@ -42109,14 +42026,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true "dev": true
}, },
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
},
"path-type": { "path-type": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -45497,41 +45406,20 @@
"dev": true "dev": true
}, },
"react-router": { "react-router": {
"version": "5.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "history": "^5.2.0"
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
} }
}, },
"react-router-dom": { "react-router-dom": {
"version": "5.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "history": "^5.2.0",
"history": "^4.9.0", "react-router": "6.2.1"
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
} }
}, },
"react-scripts": { "react-scripts": {
@@ -46074,11 +45962,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true "dev": true
}, },
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url": { "resolve-url": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -48406,11 +48289,6 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true "dev": true
}, },
"tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"tiny-warning": { "tiny-warning": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -49034,11 +48912,6 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.12.0", "version": "0.13.0",
"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",
"keywords": [ "keywords": [
"bee", "bee",
@@ -48,8 +48,8 @@
"react-dom": ">= 17.0.2", "react-dom": ">= 17.0.2",
"react-feather": "2.0.9", "react-feather": "2.0.9",
"react-identicons": "1.2.5", "react-identicons": "1.2.5",
"react-router": "5.2.0", "react-router": "6.2.1",
"react-router-dom": "5.2.0", "react-router-dom": "6.2.1",
"react-syntax-highlighter": "15.4.4", "react-syntax-highlighter": "15.4.4",
"semver": "7.3.5", "semver": "7.3.5",
"serve-handler": "6.1.3" "serve-handler": "6.1.3"
+1 -7
View File
@@ -6,13 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<link rel="preconnect" href="https://fonts.gstatic.com"> <meta name="description" content="Bee Dashboard" />
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap" rel="stylesheet">
<meta
name="description"
content="Bee Dashboard"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
+37 -8
View File
@@ -1,34 +1,63 @@
@font-face { @font-face {
font-family: "IBMPlexMono500"; font-family: 'Work Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(assets/fonts/WorkSans/WorkSans-Light.ttf) format('truetype');
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(assets/fonts/WorkSans/WorkSans-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(assets/fonts/WorkSans/WorkSans-Medium.ttf) format('truetype');
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(assets/fonts/WorkSans/WorkSans-SemiBold.ttf) format('truetype');
}
@font-face {
font-family: 'IBMPlexMono500';
src: url(assets/fonts/IBMPlexMono500.ttf) format('truetype'); src: url(assets/fonts/IBMPlexMono500.ttf) format('truetype');
font-weight: 500; font-weight: 500;
} }
@font-face { @font-face {
font-family: "IBMPlexMono600"; font-family: 'IBMPlexMono600';
src: url(assets/fonts/IBMPlexMono600.ttf) format('truetype'); src: url(assets/fonts/IBMPlexMono600.ttf) format('truetype');
font-weight: 600; font-weight: 600;
} }
@font-face { @font-face {
font-family: "IBMPlexMonoregular"; font-family: 'IBMPlexMonoregular';
src: url(assets/fonts/IBMPlexMonoregular.ttf) format('truetype'); src: url(assets/fonts/IBMPlexMonoregular.ttf) format('truetype');
font-weight: 300; font-weight: 300;
} }
@font-face { @font-face {
font-family: "WorkSans-Italic-VariableFont_wght"; font-family: 'WorkSans-Italic-VariableFont_wght';
src: url(assets/fonts/WorkSans-Italic-VariableFont_wght.ttf) format('truetype'); src: url(assets/fonts/WorkSans-Italic-VariableFont_wght.ttf) format('truetype');
font-weight: 700; font-weight: 700;
} }
@font-face { @font-face {
font-family: "WorkSans-VariableFont_wght"; font-family: 'WorkSans-VariableFont_wght';
src: url(assets/fonts/WorkSans-VariableFont_wght.ttf) format('truetype'); src: url(assets/fonts/WorkSans-VariableFont_wght.ttf) format('truetype');
font-weight: 400; font-weight: 400;
} }
.App { .App {
font-family: "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif; font-family: 'Work Sans', 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
} }
a, button { a,
font-family: "IBMPlexMono500" !important; button {
font-family: 'IBMPlexMono500' !important;
color: #dd7700; color: #dd7700;
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import CssBaseline from '@material-ui/core/CssBaseline'
import { ThemeProvider } from '@material-ui/core/styles' import { ThemeProvider } from '@material-ui/core/styles'
import { SnackbarProvider } from 'notistack' import { SnackbarProvider } from 'notistack'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { BrowserRouter as Router } from 'react-router-dom' import { HashRouter as Router } from 'react-router-dom'
import './App.css' import './App.css'
import Dashboard from './layout/Dashboard' import Dashboard from './layout/Dashboard'
import { Provider as BeeProvider } from './providers/Bee' import { Provider as BeeProvider } from './providers/Bee'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -3,7 +3,7 @@ import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ArrowForward, OpenInNewSharp } from '@material-ui/icons' import { ArrowForward, OpenInNewSharp } from '@material-ui/icons'
import { ReactElement, useState } from 'react' import { ReactElement, useState } from 'react'
import CopyToClipboard from 'react-copy-to-clipboard' import CopyToClipboard from 'react-copy-to-clipboard'
import { useHistory } from 'react-router' import { useNavigate } from 'react-router'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -61,7 +61,7 @@ export default function ExpandableListItemLink({
}: Props): ReactElement | null { }: Props): ReactElement | null {
const classes = useStyles() const classes = useStyles()
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const history = useHistory() const navigate = useNavigate()
const tooltipClickHandler = () => setCopied(true) const tooltipClickHandler = () => setCopied(true)
const tooltipCloseHandler = () => setCopied(false) const tooltipCloseHandler = () => setCopied(false)
@@ -72,7 +72,7 @@ export default function ExpandableListItemLink({
if (navigationType === 'NEW_WINDOW') { if (navigationType === 'NEW_WINDOW') {
window.open(link || value) window.open(link || value)
} else { } else {
history.push(link || value) navigate(link || value)
} }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core' import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { ArrowBack } from '@material-ui/icons' import { ArrowBack } from '@material-ui/icons'
import { ReactElement } from 'react' import { ReactElement } from 'react'
import { useHistory } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
interface Props { interface Props {
children: string children: string
@@ -20,10 +20,10 @@ const useStyles = makeStyles(() =>
export function HistoryHeader({ children }: Props): ReactElement { export function HistoryHeader({ children }: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
const history = useHistory() const navigate = useNavigate()
function goBack() { function goBack() {
history.goBack() navigate(-1)
} }
return ( return (
+1 -1
View File
@@ -50,7 +50,7 @@ interface Props {
export default function SideBarItem({ iconStart, iconEnd, path, label }: Props): ReactElement { export default function SideBarItem({ iconStart, iconEnd, path, label }: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
const location = useLocation() const location = useLocation()
const isSelected = Boolean(matchPath(location.pathname, { path, exact: true })) const isSelected = Boolean(path && matchPath(location.pathname, path))
return ( return (
<StyledListItem button selected={isSelected} disableRipple> <StyledListItem button selected={isSelected} disableRipple>
+1 -1
View File
@@ -56,7 +56,7 @@ export default function SideBarItem({ path }: Props): ReactElement {
const { status, isLoading } = useContext(Context) const { status, isLoading } = useContext(Context)
const classes = useStyles() const classes = useStyles()
const location = useLocation() const location = useLocation()
const isSelected = Boolean(matchPath(location.pathname, { path, exact: true })) const isSelected = Boolean(path && matchPath(location.pathname, path))
return ( return (
<ListItem <ListItem
+7
View File
@@ -25,6 +25,11 @@ const useStyles = makeStyles((theme: Theme) =>
'& fieldset': { '& fieldset': {
border: 0, border: 0,
}, },
'& .MuiSelect-select': {
'&:focus': {
background: theme.palette.background.paper,
},
},
}, },
option: { option: {
height: '52px', height: '52px',
@@ -48,6 +53,7 @@ export function SwarmSelect({ defaultValue, formik, name, options, onChange, lab
defaultValue={defaultValue || ''} defaultValue={defaultValue || ''}
className={classes.select} className={classes.select}
placeholder={label} placeholder={label}
MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
> >
{options.map((x, i) => ( {options.map((x, i) => (
<MenuItem key={i} value={x.value} className={classes.option}> <MenuItem key={i} value={x.value} className={classes.option}>
@@ -71,6 +77,7 @@ export function SwarmSelect({ defaultValue, formik, name, options, onChange, lab
defaultValue={defaultValue || ''} defaultValue={defaultValue || ''}
onChange={onChange} onChange={onChange}
placeholder={label} placeholder={label}
MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
> >
{options.map((x, i) => ( {options.map((x, i) => (
<MenuItem key={i} value={x.value} className={classes.option}> <MenuItem key={i} value={x.value} className={classes.option}>
+13 -3
View File
@@ -16,10 +16,18 @@ const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
field: { field: {
background: theme.palette.background.paper, background: theme.palette.background.paper,
height: '52px',
'& fieldset': { '& fieldset': {
border: 0, border: 0,
}, },
'& .Mui-focused': {
background: theme.palette.background.paper,
},
'& .MuiInputBase-root': {
background: theme.palette.background.paper,
},
'& .MuiFilledInput-root': {
borderRadius: 0,
},
}, },
}), }),
) )
@@ -36,9 +44,10 @@ export function SwarmTextInput({ name, label, password, optional, formik, onChan
name={name} name={name}
label={label} label={label}
fullWidth fullWidth
variant="outlined" variant="filled"
className={classes.field} className={classes.field}
defaultValue="" defaultValue=""
InputProps={{ disableUnderline: true }}
/> />
) )
} }
@@ -49,10 +58,11 @@ export function SwarmTextInput({ name, label, password, optional, formik, onChan
required required
label={label} label={label}
fullWidth fullWidth
variant="outlined" variant="filled"
className={classes.field} className={classes.field}
defaultValue="" defaultValue=""
onChange={onChange} onChange={onChange}
InputProps={{ disableUnderline: true }}
/> />
) )
} }
+3
View File
@@ -0,0 +1,3 @@
export const META_FILE_NAME = '.swarmgatewaymeta.json'
export const PREVIEW_FILE_NAME = '.swarmgatewaypreview.jpeg'
export const PREVIEW_DIMENSIONS = { maxWidth: 250, maxHeight: 175 }
+4 -4
View File
@@ -3,7 +3,7 @@ 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'
import { Check, X } from 'react-feather' import { Check, X } from 'react-feather'
import { useHistory } from 'react-router' import { useNavigate } from 'react-router'
import { DocumentationText } from '../../components/DocumentationText' import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemActions from '../../components/ExpandableListItemActions' import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey' import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -34,7 +34,7 @@ export default function CreateNewFeed(): ReactElement {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const history = useHistory() const navigate = useNavigate()
async function onSubmit(values: FormValues) { async function onSubmit(values: FormValues) {
setLoading(true) setLoading(true)
@@ -65,12 +65,12 @@ export default function CreateNewFeed(): ReactElement {
const identity = await convertWalletToIdentity(wallet, values.type, values.identityName, values.password) const identity = await convertWalletToIdentity(wallet, values.type, values.identityName, values.password)
persistIdentity(identities, identity) persistIdentity(identities, identity)
setIdentities(identities) setIdentities(identities)
history.push(ROUTES.FEEDS) navigate(ROUTES.FEEDS)
setLoading(false) setLoading(false)
} }
function cancel() { function cancel() {
history.goBack() navigate(-1)
} }
return ( return (
+6 -10
View File
@@ -2,7 +2,7 @@ import * as swarmCid from '@ethersphere/swarm-cid'
import { Box } from '@material-ui/core' import { Box } from '@material-ui/core'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import { RouteComponentProps, useHistory } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { DocumentationText } from '../../components/DocumentationText' import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemActions from '../../components/ExpandableListItemActions' import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey' import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -15,20 +15,16 @@ import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
import { UploadArea } from '../files/UploadArea' import { UploadArea } from '../files/UploadArea'
interface MatchParams { export function FeedSubpage(): ReactElement {
uuid: string
}
export function FeedSubpage(props: RouteComponentProps<MatchParams>): ReactElement {
const { identities } = useContext(IdentityContext) const { identities } = useContext(IdentityContext)
const { uuid } = useParams()
const { beeApi } = useContext(SettingsContext) const { beeApi } = useContext(SettingsContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const history = useHistory() const navigate = useNavigate()
const [available, setAvailable] = useState(false) const [available, setAvailable] = useState(false)
const uuid = props.match.params.uuid
const identity = identities.find(x => x.uuid === uuid) const identity = identities.find(x => x.uuid === uuid)
useEffect(() => { useEffect(() => {
@@ -44,13 +40,13 @@ export function FeedSubpage(props: RouteComponentProps<MatchParams>): ReactEleme
}, [beeApi, uuid, identity]) }, [beeApi, uuid, identity])
if (!identity || !status.all) { if (!identity || !status.all) {
history.replace(ROUTES.FEEDS) navigate(ROUTES.FEEDS, { replace: true })
return <></> return <></>
} }
function onClose() { function onClose() {
history.push(ROUTES.FEEDS) navigate(ROUTES.FEEDS)
} }
return ( return (
+7 -10
View File
@@ -2,7 +2,7 @@ import { Box, Grid, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { Bookmark, X } from 'react-feather' import { Bookmark, X } from 'react-feather'
import { RouteComponentProps, useHistory } from 'react-router' import { useParams, useNavigate } from 'react-router'
import ExpandableListItemActions from '../../components/ExpandableListItemActions' import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
import { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
@@ -16,15 +16,12 @@ import { ROUTES } from '../../routes'
import { persistIdentity, updateFeed } from '../../utils/identity' import { persistIdentity, updateFeed } from '../../utils/identity'
import { FeedPasswordDialog } from './FeedPasswordDialog' import { FeedPasswordDialog } from './FeedPasswordDialog'
interface MatchParams { export default function UpdateFeed(): ReactElement {
hash: string
}
export default function UpdateFeed(props: RouteComponentProps<MatchParams>): ReactElement {
const { identities, setIdentities } = useContext(IdentityContext) const { identities, setIdentities } = useContext(IdentityContext)
const { beeApi, beeDebugApi } = useContext(SettingsContext) const { beeApi, beeDebugApi } = useContext(SettingsContext)
const { stamps, refresh } = useContext(StampContext) const { stamps, refresh } = useContext(StampContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const { hash } = useParams()
const [selectedStamp, setSelectedStamp] = useState<string | null>(null) const [selectedStamp, setSelectedStamp] = useState<string | null>(null)
const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(null) const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(null)
@@ -32,7 +29,7 @@ export default function UpdateFeed(props: RouteComponentProps<MatchParams>): Rea
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false) const [showPasswordPrompt, setShowPasswordPrompt] = useState(false)
const history = useHistory() const navigate = useNavigate()
useEffect(() => { useEffect(() => {
refresh() refresh()
@@ -50,7 +47,7 @@ export default function UpdateFeed(props: RouteComponentProps<MatchParams>): Rea
} }
function onCancel() { function onCancel() {
history.goBack() navigate(-1)
} }
function onBeginUpdatingFeed() { function onBeginUpdatingFeed() {
@@ -76,10 +73,10 @@ export default function UpdateFeed(props: RouteComponentProps<MatchParams>): Rea
} }
try { try {
await updateFeed(beeApi, identity, props.match.params.hash, selectedStamp, password as string) await updateFeed(beeApi, identity, hash!, selectedStamp, password as string) // eslint-disable-line
persistIdentity(identities, identity) persistIdentity(identities, identity)
setIdentities([...identities]) setIdentities([...identities])
history.push(ROUTES.FEEDS_PAGE.replace(':uuid', identity.uuid)) navigate(ROUTES.FEEDS_PAGE.replace(':uuid', identity.uuid))
} catch (error: unknown) { } catch (error: unknown) {
setLoading(false) setLoading(false)
+10 -9
View File
@@ -1,12 +1,13 @@
import { Box, Typography } from '@material-ui/core' import { Box, Typography } from '@material-ui/core'
import { ReactElement, useContext, useState } from 'react' import { ReactElement, useContext, useState } from 'react'
import { Download, Info, PlusSquare, Trash } from 'react-feather' import { Download, Info, PlusSquare, Trash } from 'react-feather'
import { useHistory } from 'react-router' import { useNavigate } from 'react-router'
import ExpandableList from '../../components/ExpandableList' 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 { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee' import { Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../providers/Feeds' import { Context as IdentityContext, Identity } from '../../providers/Feeds'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
@@ -20,7 +21,7 @@ export default function Feeds(): ReactElement {
const { identities, setIdentities } = useContext(IdentityContext) const { identities, setIdentities } = useContext(IdentityContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const history = useHistory() const navigate = useNavigate()
const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(null) const [selectedIdentity, setSelectedIdentity] = useState<Identity | null>(null)
const [showImport, setShowImport] = useState(false) const [showImport, setShowImport] = useState(false)
@@ -28,11 +29,11 @@ export default function Feeds(): ReactElement {
const [showDelete, setShowDelete] = useState(false) const [showDelete, setShowDelete] = useState(false)
function createNewFeed() { function createNewFeed() {
return history.push(ROUTES.FEEDS_NEW) return navigate(ROUTES.FEEDS_NEW)
} }
function viewFeed(uuid: string) { function viewFeed(uuid: string) {
history.push(ROUTES.FEEDS_PAGE.replace(':uuid', uuid)) navigate(ROUTES.FEEDS_PAGE.replace(':uuid', uuid))
} }
function onDialogClose() { function onDialogClose() {
@@ -59,6 +60,8 @@ export default function Feeds(): ReactElement {
setShowDelete(true) setShowDelete(true)
} }
if (!status.all) return <TroubleshootConnectionCard />
return ( return (
<div> <div>
{showImport && <ImportFeedDialog onClose={() => setShowImport(false)} />} {showImport && <ImportFeedDialog onClose={() => setShowImport(false)} />}
@@ -95,11 +98,9 @@ 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>
{status.all && ( <SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info}>
<SwarmButton onClick={() => viewFeed(x.uuid)} iconType={Info}> View Feed Page
View Feed Page </SwarmButton>
</SwarmButton>
)}
<SwarmButton onClick={() => onShowExport(x)} iconType={Download}> <SwarmButton onClick={() => onShowExport(x)} iconType={Download}>
Export... Export...
</SwarmButton> </SwarmButton>
+28 -69
View File
@@ -1,99 +1,58 @@
import { Box, Grid, Typography } from '@material-ui/core' import { Box, Grid, Typography } from '@material-ui/core'
import { Web } from '@material-ui/icons' import { Web } from '@material-ui/icons'
import { ReactElement, useEffect, useState } from 'react' import { ReactElement } from 'react'
import { File, Folder } from 'react-feather' import { File, Folder } from 'react-feather'
import { FitImage } from '../../components/FitImage' import { FitImage } from '../../components/FitImage'
import { detectIndexHtml, getAssetNameFromFiles, getHumanReadableFileSize } from '../../utils/file' import { shortenText } from '../../utils'
import { SwarmFile } from '../../utils/SwarmFile' import { getHumanReadableFileSize } from '../../utils/file'
import { shortenHash } from '../../utils/hash'
import { AssetIcon } from './AssetIcon' import { AssetIcon } from './AssetIcon'
interface Props { interface Props {
assetName?: string previewUri?: string
files: SwarmFile[] metadata?: Metadata
} }
// 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({ assetName, files }: Props): ReactElement { export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
const [previewComponent, setPreviewComponent] = useState<ReactElement | undefined>(undefined) let previewComponent = <File />
const [previewUri, setPreviewUri] = useState<string | undefined>(undefined) let type = metadata?.type
useEffect(() => { if (metadata?.isWebsite) {
if (files.length === 1) { previewComponent = <Web />
// single image type = 'Website'
if (files[0].type.startsWith('image/')) { } else if (metadata?.type === 'folder') {
files[0].arrayBuffer().then(value => { previewComponent = <Folder />
const blob = new Blob([value]) type = 'Folder'
setPreviewUri(URL.createObjectURL(blob))
})
// single non-image
} else {
setPreviewUri(undefined)
setPreviewComponent(<AssetIcon icon={<File />} />)
}
// collection
} else if (detectIndexHtml(files)) {
setPreviewUri(undefined)
setPreviewComponent(<AssetIcon icon={<Web />} />)
} else {
setPreviewUri(undefined)
setPreviewComponent(<AssetIcon icon={<Folder />} />)
}
}, [files])
const getPrimaryText = () => {
const name = getAssetNameFromFiles(files)
if (files.length === 1) {
return 'Filename: ' + (assetName || name)
}
return 'Folder name: ' + (assetName || name)
} }
const getKind = () => {
if (files.length === 1) {
return files[0].type
}
if (detectIndexHtml(files)) {
return 'Website'
}
return 'Folder'
}
const isFolder = () => ['Folder', 'Website'].includes(getKind())
const getSize = () => {
const bytes = files.reduce((total, item) => total + item.size, 0)
return getHumanReadableFileSize(bytes)
}
const size = getSize()
return ( return (
<Box mb={4}> <Box mb={4}>
<Box bgcolor="background.paper"> <Box bgcolor="background.paper">
<Grid container direction="row"> <Grid container direction="row">
{previewComponent ? ( {previewUri ? (
previewComponent
) : (
<FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} /> <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
) : (
<AssetIcon icon={previewComponent} />
)} )}
<Box p={2}> <Box p={2}>
<Typography>{getPrimaryText()}</Typography> {metadata?.hash && <Typography>Swarm Hash: {shortenHash(metadata.hash)}</Typography>}
<Typography>Kind: {getKind()}</Typography> {metadata?.name && metadata?.name !== metadata?.hash && (
{size !== '0 bytes' && <Typography>Size: {size}</Typography>} <Typography>
{metadata?.type === 'folder' ? 'Folder Name' : 'Filename'}: {shortenText(metadata?.name)}
</Typography>
)}
<Typography>Kind: {type}</Typography>
{metadata?.size ? <Typography>Size: {getHumanReadableFileSize(metadata.size)}</Typography> : null}
</Box> </Box>
</Grid> </Grid>
</Box> </Box>
{isFolder() && ( {metadata?.type === 'folder' && metadata.count && (
<Box mt={0.25} p={2} bgcolor="background.paper"> <Box mt={0.25} p={2} bgcolor="background.paper">
<Grid container justifyContent="space-between" alignItems="center" direction="row"> <Grid container justifyContent="space-between" alignItems="center" direction="row">
<Typography variant="subtitle2">Folder content</Typography> <Typography variant="subtitle2">Folder content</Typography>
<Typography variant="subtitle2">{files.length} items</Typography> <Typography variant="subtitle2">{metadata.count} items</Typography>
</Grid> </Grid>
</Box> </Box>
)} )}
+3 -5
View File
@@ -4,21 +4,19 @@ import { ReactElement } from 'react'
import { DocumentationText } from '../../components/DocumentationText' import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemKey from '../../components/ExpandableListItemKey' import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import ExpandableListItemLink from '../../components/ExpandableListItemLink' import ExpandableListItemLink from '../../components/ExpandableListItemLink'
import { detectIndexHtml } from '../../utils/file'
import { SwarmFile } from '../../utils/SwarmFile'
interface Props { interface Props {
files: SwarmFile[] isWebsite?: boolean
hash: string hash: string
} }
export function AssetSummary({ files, hash }: Props): ReactElement { export function AssetSummary({ isWebsite, hash }: Props): ReactElement {
return ( return (
<> <>
<Box mb={4}> <Box mb={4}>
<ExpandableListItemKey label="Swarm hash" value={hash} /> <ExpandableListItemKey label="Swarm hash" value={hash} />
<ExpandableListItemLink label="Share on Swarm Gateway" value={`https://gateway.ethswarm.org/access/${hash}`} /> <ExpandableListItemLink label="Share on Swarm Gateway" value={`https://gateway.ethswarm.org/access/${hash}`} />
{detectIndexHtml(files) && ( {isWebsite && (
<ExpandableListItemLink <ExpandableListItemLink
label="BZZ Link" label="BZZ Link"
value={`https://${swarmCid.encodeManifestReference(hash).toString()}.bzz.link`} value={`https://${swarmCid.encodeManifestReference(hash).toString()}.bzz.link`}
+3 -3
View File
@@ -2,7 +2,7 @@ import { Utils } from '@ethersphere/bee-js'
import { ManifestJs } from '@ethersphere/manifest-js' import { ManifestJs } from '@ethersphere/manifest-js'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react' import { ReactElement, useContext, useState } from 'react'
import { useHistory } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import ExpandableListItemInput from '../../components/ExpandableListItemInput' import ExpandableListItemInput from '../../components/ExpandableListItemInput'
import { History } from '../../components/History' import { History } from '../../components/History'
import { Context, defaultUploadOrigin } from '../../providers/File' import { Context, defaultUploadOrigin } from '../../providers/File'
@@ -20,7 +20,7 @@ export function Download(): ReactElement {
const { setUploadOrigin } = useContext(Context) const { setUploadOrigin } = useContext(Context)
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const history = useHistory() const navigate = useNavigate()
const validateChange = (value: string) => { const validateChange = (value: string) => {
if (Utils.isHexString(value, 64) || Utils.isHexString(value, 128) || !value.trim().length) { if (Utils.isHexString(value, 64) || Utils.isHexString(value, 128) || !value.trim().length) {
@@ -54,7 +54,7 @@ export function Download(): ReactElement {
const indexDocument = await manifestJs.getIndexDocumentPath(identifier) const indexDocument = await manifestJs.getIndexDocumentPath(identifier)
putHistory(HISTORY_KEYS.DOWNLOAD_HISTORY, identifier, determineHistoryName(identifier, indexDocument)) putHistory(HISTORY_KEYS.DOWNLOAD_HISTORY, identifier, determineHistoryName(identifier, indexDocument))
setUploadOrigin(defaultUploadOrigin) setUploadOrigin(defaultUploadOrigin)
history.push(ROUTES.HASH.replace(':hash', identifier)) navigate(ROUTES.HASH.replace(':hash', identifier))
} catch (error: unknown) { } catch (error: unknown) {
let message = typeof error === 'object' && error !== null && Reflect.get(error, 'message') let message = typeof error === 'object' && error !== null && Reflect.get(error, 'message')
+2 -2
View File
@@ -32,12 +32,12 @@ export function DownloadActionBar({
<SwarmButton onClick={onDownload} iconType={Download} disabled={loading} loading={loading}> <SwarmButton onClick={onDownload} iconType={Download} disabled={loading} loading={loading}>
Download Download
</SwarmButton> </SwarmButton>
<SwarmButton onClick={onCancel} iconType={X} disabled={loading} loading={loading} cancel> <SwarmButton onClick={onCancel} iconType={X} disabled={loading} cancel>
Close Close
</SwarmButton> </SwarmButton>
</ExpandableListItemActions> </ExpandableListItemActions>
<Box mb={1} mr={1}> <Box mb={1} mr={1}>
<SwarmButton onClick={onUpdateFeed} iconType={Bookmark}> <SwarmButton onClick={onUpdateFeed} iconType={Bookmark} disabled={loading}>
Update Feed Update Feed
</SwarmButton> </SwarmButton>
</Box> </Box>
+3 -3
View File
@@ -1,6 +1,6 @@
import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core' import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core'
import { ReactElement } from 'react' import { ReactElement } from 'react'
import { useHistory } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
interface Props { interface Props {
@@ -24,10 +24,10 @@ const useStyles = makeStyles((theme: Theme) =>
export function FileNavigation({ active }: Props): ReactElement { export function FileNavigation({ active }: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
const history = useHistory() const navigate = useNavigate()
function onChange(event: React.ChangeEvent<Record<string, never>>, newValue: number) { function onChange(event: React.ChangeEvent<Record<string, never>>, newValue: number) {
history.push(newValue === 1 ? ROUTES.DOWNLOAD : ROUTES.UPLOAD) navigate(newValue === 1 ? ROUTES.DOWNLOAD : ROUTES.UPLOAD)
} }
return ( return (
+45 -28
View File
@@ -4,40 +4,37 @@ import { saveAs } from 'file-saver'
import JSZip from 'jszip' import JSZip from 'jszip'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { RouteComponentProps, useHistory } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading' import { Loading } from '../../components/Loading'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import config from '../../config'
import { META_FILE_NAME, PREVIEW_FILE_NAME } 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 { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
import { convertBeeFileToBrowserFile, convertManifestToFiles } from '../../utils/file'
import { shortenHash } from '../../utils/hash'
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage' import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { SwarmFile } from '../../utils/SwarmFile'
import { AssetPreview } from './AssetPreview' import { AssetPreview } from './AssetPreview'
import { AssetSummary } from './AssetSummary' import { AssetSummary } from './AssetSummary'
import { DownloadActionBar } from './DownloadActionBar' import { DownloadActionBar } from './DownloadActionBar'
interface MatchParams { export function Share(): ReactElement {
hash: string
}
export function Share(props: RouteComponentProps<MatchParams>): ReactElement {
const { apiUrl, beeApi } = useContext(SettingsContext) const { apiUrl, beeApi } = useContext(SettingsContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const reference = props.match.params.hash const { hash } = useParams()
const reference = hash! // eslint-disable-line
const history = useHistory() const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [downloading, setDownloading] = useState(false) const [downloading, setDownloading] = useState(false)
const [files, setFiles] = useState<SwarmFile[]>([])
const [swarmEntries, setSwarmEntries] = useState<Record<string, string>>({}) const [swarmEntries, setSwarmEntries] = useState<Record<string, string>>({})
const [indexDocument, setIndexDocument] = useState<string | null>(null) const [indexDocument, setIndexDocument] = useState<string | null>(null)
const [notFound, setNotFound] = useState(false) const [notFound, setNotFound] = useState(false)
const [preview, setPreview] = useState<string | undefined>(undefined)
const [metadata, setMetadata] = useState<Metadata | undefined>()
async function prepare() { async function prepare() {
if (!beeApi || !status.all) { if (!beeApi || !status.all) {
@@ -54,16 +51,37 @@ export function Share(props: RouteComponentProps<MatchParams>): ReactElement {
return return
} }
const entries = await manifestJs.getHashes(reference) const entries = await manifestJs.getHashes(reference)
setSwarmEntries(entries)
const indexDocument = await manifestJs.getIndexDocumentPath(reference) const indexDocument = await manifestJs.getIndexDocumentPath(reference)
setIndexDocument(indexDocument) setIndexDocument(indexDocument)
if (Object.keys(entries).length === 1) { const previewFile = entries[PREVIEW_FILE_NAME]
const response = await beeApi.downloadFile(reference)
setFiles([new SwarmFile(convertBeeFileToBrowserFile(response) as File)]) delete entries[META_FILE_NAME]
} else { delete entries[PREVIEW_FILE_NAME]
setFiles(convertManifestToFiles(entries)) setSwarmEntries(entries)
const count = Object.keys(entries).length
let metadata: Metadata | undefined = {
hash,
size: 0,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
isWebsite: Boolean(indexDocument) && count > 1,
count,
} }
try {
const mtdt = await beeApi.downloadFile(reference, META_FILE_NAME)
const remoteMetadata = mtdt.data.text()
metadata = { ...metadata, ...(JSON.parse(remoteMetadata) as Metadata) }
} catch (e) {} // eslint-disable-line no-empty
if (previewFile) {
setPreview(`${config.BEE_API_HOST}/bzz/${reference}/${PREVIEW_FILE_NAME}`)
}
setMetadata(metadata)
} }
function onOpen() { function onOpen() {
@@ -71,16 +89,17 @@ export function Share(props: RouteComponentProps<MatchParams>): ReactElement {
} }
function onClose() { function onClose() {
// POP means there is no history - nowhere to go back yet if (navigate.length > 0) {
if (history.action === 'POP') { // There is at least one different route in browser history that we can return to
history.push(ROUTES.UPLOAD) navigate(-1)
} else { } else {
history.goBack() // This is the first page user opened, navigate to upload page instead of going back
navigate(ROUTES.UPLOAD)
} }
} }
function onUpdateFeed() { function onUpdateFeed() {
history.push(ROUTES.FEEDS_UPDATE.replace(':hash', reference)) navigate(ROUTES.FEEDS_UPDATE.replace(':hash', reference))
} }
useEffect(() => { useEffect(() => {
@@ -111,8 +130,6 @@ export function Share(props: RouteComponentProps<MatchParams>): ReactElement {
setDownloading(false) setDownloading(false)
} }
const assetName = shortenHash(reference)
if (!status.all) return <TroubleshootConnectionCard /> if (!status.all) return <TroubleshootConnectionCard />
if (loading) { if (loading) {
@@ -131,17 +148,17 @@ export function Share(props: RouteComponentProps<MatchParams>): ReactElement {
return ( return (
<> <>
<Box mb={4}> <Box mb={4}>
<AssetPreview files={files} assetName={assetName} /> <AssetPreview metadata={metadata} previewUri={preview} />
</Box> </Box>
<Box mb={4}> <Box mb={4}>
<AssetSummary files={files} hash={reference} /> <AssetSummary isWebsite={metadata?.isWebsite} hash={reference} />
</Box> </Box>
<DownloadActionBar <DownloadActionBar
onOpen={onOpen} onOpen={onOpen}
onCancel={onClose} onCancel={onClose}
onDownload={onDownload} onDownload={onDownload}
onUpdateFeed={onUpdateFeed} onUpdateFeed={onUpdateFeed}
hasIndexDocument={Boolean(indexDocument && files.length > 1)} hasIndexDocument={Boolean(metadata?.isWebsite)}
loading={downloading} loading={downloading}
/> />
</> </>
+59 -11
View File
@@ -1,7 +1,7 @@
import { Box } from '@material-ui/core' import { Box } from '@material-ui/core'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { DocumentationText } from '../../components/DocumentationText' import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator' import { ProgressIndicator } from '../../components/ProgressIndicator'
@@ -12,7 +12,7 @@ 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 { detectIndexHtml, getAssetNameFromFiles } from '../../utils/file' import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
import { persistIdentity, updateFeed } from '../../utils/identity' import { persistIdentity, updateFeed } from '../../utils/identity'
import { HISTORY_KEYS, putHistory } from '../../utils/local-storage' import { HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog' import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
@@ -21,6 +21,7 @@ import { PostageStampSelector } from '../stamps/PostageStampSelector'
import { AssetPreview } from './AssetPreview' import { AssetPreview } from './AssetPreview'
import { StampPreview } from './StampPreview' import { StampPreview } from './StampPreview'
import { UploadActionBar } from './UploadActionBar' import { UploadActionBar } from './UploadActionBar'
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
export function Upload(): ReactElement { export function Upload(): ReactElement {
const [step, setStep] = useState(0) const [step, setStep] = useState(0)
@@ -31,12 +32,12 @@ export function Upload(): ReactElement {
const { refresh } = useContext(StampsContext) const { refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext) const { beeApi } = useContext(SettingsContext)
const { files, setFiles, uploadOrigin } = useContext(FileContext) const { files, setFiles, uploadOrigin, metadata, previewUri, previewBlob } = useContext(FileContext)
const { identities, setIdentities } = useContext(IdentityContext) const { identities, setIdentities } = useContext(IdentityContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const history = useHistory() const navigate = useNavigate()
useEffect(() => { useEffect(() => {
refresh() refresh()
@@ -46,7 +47,7 @@ export function Upload(): ReactElement {
if (!files.length) { if (!files.length) {
setFiles([]) setFiles([])
history.replace(ROUTES.UPLOAD) navigate(ROUTES.UPLOAD, { replace: true })
return <></> return <></>
} }
@@ -66,26 +67,73 @@ export function Upload(): ReactElement {
} }
const uploadFiles = (password?: string) => { const uploadFiles = (password?: string) => {
if (!beeApi || !files.length || !stamp) { if (!beeApi || !files.length || !stamp || !metadata) {
return return
} }
const indexDocument = files.length === 1 ? files[0].name : detectIndexHtml(files) || undefined let fls = files.map(packageFile) // Apart from packaging, this is needed to not modify the original files array as it can trigger effects
let indexDocument: string | undefined = undefined // This means we assume it's folder
if (files.length === 1) indexDocument = files[0].name
else if (files.length > 1) {
const idx = detectIndexHtml(files)
// This is a website
if (idx) {
// The website is in some directory, remove it
if (idx.commonPrefix) {
const substrStart = idx.commonPrefix.length
indexDocument = idx.indexPath.substr(substrStart)
fls = fls.map(f => {
const path = (f.path as string).substr(substrStart)
return { ...f, path, webkitRelativePath: path, fullPath: path }
})
} else {
// The website is not packed in a directory
indexDocument = idx.indexPath
}
}
}
const lastModified = files[0].lastModified
// We want to store only some metadata
const mtd: SwarmMetadata = {
name: metadata.name,
size: metadata.size,
}
// Type of the file only makes sense for a single file
if (files.length === 1) mtd.type = metadata.type
const metafile = new File([JSON.stringify(mtd)], META_FILE_NAME, {
type: 'application/json',
lastModified,
})
fls.push(packageFile(metafile))
if (previewBlob) {
const previewFile = new File([previewBlob], PREVIEW_FILE_NAME, {
type: 'image/jpeg',
lastModified,
})
fls.push(packageFile(previewFile))
}
setUploading(true) setUploading(true)
beeApi beeApi
.uploadFiles(stamp.batchID, files as unknown as File[], { indexDocument }) .uploadFiles(stamp.batchID, fls, { indexDocument })
.then(hash => { .then(hash => {
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference, getAssetNameFromFiles(files)) putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference, getAssetNameFromFiles(files))
if (uploadOrigin.origin === 'UPLOAD') { if (uploadOrigin.origin === 'UPLOAD') {
history.replace(ROUTES.HASH.replace(':hash', hash.reference)) navigate(ROUTES.HASH.replace(':hash', hash.reference), { replace: true })
} else { } else {
updateFeed(beeApi, identity as Identity, hash.reference, stamp.batchID, password as string).then(() => { updateFeed(beeApi, identity as Identity, hash.reference, stamp.batchID, password as string).then(() => {
persistIdentity(identities, identity as Identity) persistIdentity(identities, identity as Identity)
setIdentities([...identities]) setIdentities([...identities])
history.replace(ROUTES.FEEDS_PAGE.replace(':uuid', uploadOrigin.uuid as string)) navigate(ROUTES.FEEDS_PAGE.replace(':uuid', uploadOrigin.uuid as string), { replace: true })
}) })
} }
}) })
@@ -121,7 +169,7 @@ export function Upload(): ReactElement {
<Box mb={4}> <Box mb={4}>
<ProgressIndicator steps={['Preview', 'Add postage stamp', 'Upload to node']} index={step} /> <ProgressIndicator steps={['Preview', 'Add postage stamp', 'Upload to node']} index={step} />
</Box> </Box>
{(step === 0 || step === 2) && <AssetPreview files={files} />} {(step === 0 || step === 2) && <AssetPreview metadata={metadata} previewUri={previewUri} />}
{step === 1 && ( {step === 1 && (
<> <>
<Box mb={2}> <Box mb={2}>
+6 -7
View File
@@ -3,13 +3,12 @@ import { DropzoneArea } from 'material-ui-dropzone'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react' import { ReactElement, useContext, useState } from 'react'
import { FilePlus, FolderPlus, PlusCircle } from 'react-feather' import { FilePlus, FolderPlus, PlusCircle } from 'react-feather'
import { useHistory } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { DocumentationText } from '../../components/DocumentationText' import { DocumentationText } from '../../components/DocumentationText'
import { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
import { Context, UploadOrigin } from '../../providers/File' import { Context, UploadOrigin } from '../../providers/File'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
import { detectIndexHtml } from '../../utils/file' import { detectIndexHtml } from '../../utils/file'
import { SwarmFile } from '../../utils/SwarmFile'
interface Props { interface Props {
uploadOrigin: UploadOrigin uploadOrigin: UploadOrigin
@@ -51,7 +50,7 @@ const useStyles = makeStyles((theme: Theme) =>
export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement { export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
const { setFiles, setUploadOrigin } = useContext(Context) const { setFiles, setUploadOrigin } = useContext(Context)
const classes = useStyles() const classes = useStyles()
const history = useHistory() const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
const [strictWebsiteMode, setStrictWebsiteMode] = useState(false) const [strictWebsiteMode, setStrictWebsiteMode] = useState(false)
const [version, setVersion] = useState(0) const [version, setVersion] = useState(0)
@@ -99,8 +98,8 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
const handleChange = (files?: File[]) => { const handleChange = (files?: File[]) => {
if (files) { if (files) {
const swarmFiles = files.map(x => new SwarmFile(x)) const FilePaths = files as FilePath[]
const indexDocument = files.length === 1 ? files[0].name : detectIndexHtml(swarmFiles) || undefined const indexDocument = files.length === 1 ? files[0].name : detectIndexHtml(FilePaths) || undefined
if (files.length && strictWebsiteMode && !indexDocument) { if (files.length && strictWebsiteMode && !indexDocument) {
enqueueSnackbar('To upload a website, there must be an index.html or index.htm in the root of the folder.', { enqueueSnackbar('To upload a website, there must be an index.html or index.htm in the root of the folder.', {
@@ -111,11 +110,11 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
return return
} }
setFiles(swarmFiles) setFiles(FilePaths)
if (files.length) { if (files.length) {
setUploadOrigin(uploadOrigin) setUploadOrigin(uploadOrigin)
history.push(ROUTES.UPLOAD_IN_PROGRESS) navigate(ROUTES.UPLOAD_IN_PROGRESS)
} }
} }
} }
+3 -3
View File
@@ -1,14 +1,14 @@
import { ReactElement } from 'react' import { ReactElement } from 'react'
import { useHistory } from 'react-router' import { useNavigate } from 'react-router'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
import { PostageStampCreation } from './PostageStampCreation' import { PostageStampCreation } from './PostageStampCreation'
export function CreatePostageStampPage(): ReactElement { export function CreatePostageStampPage(): ReactElement {
const history = useHistory() const navigate = useNavigate()
function onFinished() { function onFinished() {
history.push(ROUTES.STAMPS) navigate(ROUTES.STAMPS)
} }
return ( return (
+11 -4
View File
@@ -6,8 +6,9 @@ import React, { ReactElement, useContext } from 'react'
import { Check } from 'react-feather' import { Check } from 'react-feather'
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 } from '../../providers/Stamps' import { Context as StampsContext } from '../../providers/Stamps'
import { import {
calculateStampPrice, calculateStampPrice,
convertAmountToSeconds, convertAmountToSeconds,
@@ -34,8 +35,10 @@ interface Props {
} }
export function PostageStampCreation({ onFinished }: Props): ReactElement { export function PostageStampCreation({ onFinished }: Props): ReactElement {
const { refresh } = useContext(Context) const { chainState } = useContext(BeeContext)
const { refresh } = useContext(StampsContext)
const { beeDebugApi } = useContext(SettingsContext) const { beeDebugApi } = useContext(SettingsContext)
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
function getFileSize(depth: number): string { function getFileSize(depth: number): string {
@@ -55,10 +58,14 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
} }
function getPrice(depth: number, amount: number): string { function getPrice(depth: number, amount: number): string {
if (isNaN(amount) || amount <= 0 || isNaN(depth) || depth < 17 || depth > 255) { const hasInvalidInput = isNaN(amount) || amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
const isCurrentPriceAvailable = chainState && chainState.currentPrice
if (hasInvalidInput || !isCurrentPriceAvailable) {
return '-' return '-'
} }
const price = calculateStampPrice(depth, amount)
const price = calculateStampPrice(depth, amount, chainState.currentPrice)
return `${formatBzz(price)} BZZ` return `${formatBzz(price)} BZZ`
} }
+3 -3
View File
@@ -2,7 +2,7 @@ import { CircularProgress, Container } from '@material-ui/core'
import { createStyles, makeStyles } from '@material-ui/core/styles' import { createStyles, makeStyles } from '@material-ui/core/styles'
import { ReactElement, useContext, useEffect } from 'react' import { ReactElement, useContext, useEffect } from 'react'
import { PlusSquare } from 'react-feather' import { PlusSquare } from 'react-feather'
import { useHistory } from 'react-router' import { useNavigate } from 'react-router'
import { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee' import { Context as BeeContext } from '../../providers/Bee'
@@ -29,7 +29,7 @@ const useStyles = makeStyles(() =>
export default function Stamp(): ReactElement { export default function Stamp(): ReactElement {
const classes = useStyles() const classes = useStyles()
const history = useHistory() const navigate = useNavigate()
const { stamps, isLoading, error, start, stop } = useContext(StampsContext) const { stamps, isLoading, error, start, stop } = useContext(StampsContext)
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
@@ -44,7 +44,7 @@ export default function Stamp(): ReactElement {
if (!status.all) return <TroubleshootConnectionCard /> if (!status.all) return <TroubleshootConnectionCard />
function navigateToNewStamp() { function navigateToNewStamp() {
history.push(ROUTES.STAMPS_NEW) navigate(ROUTES.STAMPS_NEW)
} }
return ( return (
+13
View File
@@ -1,4 +1,5 @@
import type { import type {
ChainState,
ChequebookAddressResponse, ChequebookAddressResponse,
Health, Health,
LastChequesResponse, LastChequesResponse,
@@ -44,6 +45,7 @@ interface ContextInterface {
peerBalances: Balance[] | null peerBalances: Balance[] | null
peerCheques: LastChequesResponse | null peerCheques: LastChequesResponse | null
settlements: Settlements | null settlements: Settlements | null
chainState: ChainState | null
latestBeeRelease: LatestBeeRelease | null latestBeeRelease: LatestBeeRelease | null
isLoading: boolean isLoading: boolean
isRefreshing: boolean isRefreshing: boolean
@@ -82,6 +84,7 @@ const initialValues: ContextInterface = {
peerBalances: null, peerBalances: null,
peerCheques: null, peerCheques: null,
settlements: null, settlements: null,
chainState: null,
latestBeeRelease: null, latestBeeRelease: null,
isLoading: true, isLoading: true,
isRefreshing: false, isRefreshing: false,
@@ -144,6 +147,8 @@ export function Provider({ children }: Props): ReactElement {
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null) const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null) const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [settlements, setSettlements] = useState<Settlements | null>(null) const [settlements, setSettlements] = useState<Settlements | null>(null)
const [chainState, setChainState] = useState<ChainState | null>(null)
const { latestBeeRelease } = useLatestBeeRelease() const { latestBeeRelease } = useLatestBeeRelease()
const [error, setError] = useState<Error | null>(initialValues.error) const [error, setError] = useState<Error | null>(initialValues.error)
@@ -177,6 +182,7 @@ export function Provider({ children }: Props): ReactElement {
setPeerBalances(null) setPeerBalances(null)
setPeerCheques(null) setPeerCheques(null)
setSettlements(null) setSettlements(null)
setChainState(null)
refresh() refresh()
}, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps }, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps
@@ -277,6 +283,12 @@ export function Provider({ children }: Props): ReactElement {
.then(setPeerCheques) .then(setPeerCheques)
.catch(() => setPeerCheques(null)), .catch(() => setPeerCheques(null)),
// Chain state
beeDebugApi
.getChainState()
.then(setChainState)
.catch(() => setChainState(null)),
// Chequebook balance // Chequebook balance
chequeBalanceWrapper() chequeBalanceWrapper()
.then(setChequebookBalance) .then(setChequebookBalance)
@@ -354,6 +366,7 @@ export function Provider({ children }: Props): ReactElement {
peerBalances, peerBalances,
peerCheques, peerCheques,
settlements, settlements,
chainState,
latestBeeRelease, latestBeeRelease,
isLoading, isLoading,
isRefreshing, isRefreshing,
+41 -6
View File
@@ -1,17 +1,22 @@
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
import { createContext, ReactChild, ReactElement, useState } from 'react' import { createContext, ReactChild, ReactElement, useState, useEffect } from 'react'
import { SwarmFile } from '../utils/SwarmFile' import { getMetadata } from '../utils/file'
import { resize } from '../utils/image'
import { PREVIEW_DIMENSIONS } from '../constants'
export type UploadOrigin = { origin: 'UPLOAD' | 'FEED'; uuid?: string } export type UploadOrigin = { origin: 'UPLOAD' | 'FEED'; uuid?: string }
export const defaultUploadOrigin: UploadOrigin = { origin: 'UPLOAD' } export const defaultUploadOrigin: UploadOrigin = { origin: 'UPLOAD' }
interface ContextInterface { interface ContextInterface {
files: SwarmFile[] files: FilePath[]
setFiles: (files: SwarmFile[]) => void setFiles: (files: FilePath[]) => void
uploadOrigin: UploadOrigin uploadOrigin: UploadOrigin
setUploadOrigin: (uploadOrigin: UploadOrigin) => void setUploadOrigin: (uploadOrigin: UploadOrigin) => void
metadata?: Metadata
previewUri?: string
previewBlob?: Blob
} }
const initialValues: ContextInterface = { const initialValues: ContextInterface = {
@@ -29,8 +34,38 @@ interface Props {
} }
export function Provider({ children }: Props): ReactElement { export function Provider({ children }: Props): ReactElement {
const [files, setFiles] = useState<SwarmFile[]>(initialValues.files) const [files, setFiles] = useState<FilePath[]>(initialValues.files)
const [uploadOrigin, setUploadOrigin] = useState<UploadOrigin>(initialValues.uploadOrigin) const [uploadOrigin, setUploadOrigin] = useState<UploadOrigin>(initialValues.uploadOrigin)
const [metadata, setMetadata] = useState<Metadata | undefined>(undefined)
const [previewUri, setPreviewUri] = useState<string | undefined>(undefined)
const [previewBlob, setPreviewBlob] = useState<Blob | undefined>(undefined)
return <Context.Provider value={{ files, setFiles, uploadOrigin, setUploadOrigin }}>{children}</Context.Provider> useEffect(() => {
setMetadata(getMetadata(files))
if (previewUri) {
URL.revokeObjectURL(previewUri) // Clear the preview from memory
setPreviewUri(undefined)
setPreviewBlob(undefined)
}
if (files.length !== 1 || !files[0].type.startsWith('image')) return
resize(files[0], PREVIEW_DIMENSIONS.maxWidth, PREVIEW_DIMENSIONS.maxHeight).then(blob => {
setPreviewUri(URL.createObjectURL(blob)) // NOTE: Until it is cleared with URL.revokeObjectURL, the file stays allocated in memory
setPreviewBlob(blob)
})
return () => {
if (previewUri) {
URL.revokeObjectURL(previewUri)
}
}
}, [files]) // eslint-disable-line react-hooks/exhaustive-deps
return (
<Context.Provider value={{ files, setFiles, uploadOrigin, setUploadOrigin, metadata, previewUri, previewBlob }}>
{children}
</Context.Provider>
)
} }
+18 -15
View File
@@ -44,35 +44,38 @@ export function Provider({
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null) const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
const [lockedApiSettings] = useState<boolean>(Boolean(extLockedApiSettings)) const [lockedApiSettings] = useState<boolean>(Boolean(extLockedApiSettings))
const url = beeApiUrl || apiUrl
const debugUrl = beeDebugApiUrl || apiDebugUrl
useEffect(() => { useEffect(() => {
try { try {
setBeeApi(new Bee(apiUrl)) setBeeApi(new Bee(url))
sessionStorage.setItem('api_host', apiUrl) sessionStorage.setItem('api_host', url)
} catch (e) { } catch (e) {
setBeeApi(null) setBeeApi(null)
} }
}, [apiUrl]) }, [url])
useEffect(() => {
if (beeApiUrl) setApiUrl(beeApiUrl)
}, [beeApiUrl])
useEffect(() => {
if (beeDebugApiUrl) setDebugApiUrl(beeDebugApiUrl)
}, [beeDebugApiUrl])
useEffect(() => { useEffect(() => {
try { try {
setBeeDebugApi(new BeeDebug(apiDebugUrl)) setBeeDebugApi(new BeeDebug(debugUrl))
sessionStorage.setItem('debug_api_host', apiDebugUrl) sessionStorage.setItem('debug_api_host', debugUrl)
} catch (e) { } catch (e) {
setBeeDebugApi(null) setBeeDebugApi(null)
} }
}, [apiDebugUrl]) }, [debugUrl])
return ( return (
<Context.Provider <Context.Provider
value={{ apiUrl, apiDebugUrl, beeApi, beeDebugApi, setApiUrl, setDebugApiUrl, lockedApiSettings }} value={{
apiUrl: url,
apiDebugUrl: debugUrl,
beeApi,
beeDebugApi,
setApiUrl,
setDebugApiUrl,
lockedApiSettings,
}}
> >
{children} {children}
</Context.Provider> </Context.Provider>
+15
View File
@@ -21,3 +21,18 @@ interface StatusEthereumConnectionHook extends StatusHookCommon {
interface StatusTopologyHook extends StatusHookCommon { interface StatusTopologyHook extends StatusHookCommon {
topology: Topology | null topology: Topology | null
} }
interface SwarmMetadata {
size: number
name: string
type?: string
}
interface Metadata extends SwarmMetadata {
type: string
isWebsite: boolean
count?: number
hash?: string
}
type FilePath = File & { path?: string; fullPath?: string }
+17 -17
View File
@@ -1,5 +1,5 @@
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import { Route, Switch } from 'react-router-dom' import { Route, Routes } from 'react-router-dom'
import Accounting from './pages/accounting' import Accounting from './pages/accounting'
import Feeds from './pages/feeds' import Feeds from './pages/feeds'
import CreateNewFeed from './pages/feeds/CreateNewFeed' import CreateNewFeed from './pages/feeds/CreateNewFeed'
@@ -34,22 +34,22 @@ export enum ROUTES {
} }
const BaseRouter = (): ReactElement => ( const BaseRouter = (): ReactElement => (
<Switch> <Routes>
<Route exact path={ROUTES.UPLOAD_IN_PROGRESS} component={Upload} /> <Route path={ROUTES.UPLOAD_IN_PROGRESS} element={<Upload />} />
<Route exact path={ROUTES.UPLOAD} component={UploadLander} /> <Route path={ROUTES.UPLOAD} element={<UploadLander />} />
<Route exact path={ROUTES.DOWNLOAD} component={Download} /> <Route path={ROUTES.DOWNLOAD} element={<Download />} />
<Route exact path={ROUTES.HASH} component={Share} /> <Route path={ROUTES.HASH} element={<Share />} />
<Route exact path={ROUTES.ACCOUNTING} component={Accounting} /> <Route path={ROUTES.ACCOUNTING} element={<Accounting />} />
<Route exact path={ROUTES.SETTINGS} component={Settings} /> <Route path={ROUTES.SETTINGS} element={<Settings />} />
<Route exact path={ROUTES.STAMPS} component={Stamps} /> <Route path={ROUTES.STAMPS} element={<Stamps />} />
<Route exact path={ROUTES.STAMPS_NEW} component={CreatePostageStampPage} /> <Route path={ROUTES.STAMPS_NEW} element={<CreatePostageStampPage />} />
<Route exact path={ROUTES.STATUS} component={Status} /> <Route path={ROUTES.STATUS} element={<Status />} />
<Route exact path={ROUTES.FEEDS} component={Feeds} /> <Route path={ROUTES.FEEDS} element={<Feeds />} />
<Route exact path={ROUTES.FEEDS_NEW} component={CreateNewFeed} /> <Route path={ROUTES.FEEDS_NEW} element={<CreateNewFeed />} />
<Route exact path={ROUTES.FEEDS_UPDATE} component={UpdateFeed} /> <Route path={ROUTES.FEEDS_UPDATE} element={<UpdateFeed />} />
<Route exact path={ROUTES.FEEDS_PAGE} component={FeedSubpage} /> <Route path={ROUTES.FEEDS_PAGE} element={<FeedSubpage />} />
<Route path={ROUTES.INFO} component={Info} /> <Route path={ROUTES.INFO} element={<Info />} />
</Switch> </Routes>
) )
export default BaseRouter export default BaseRouter
-24
View File
@@ -1,24 +0,0 @@
export class SwarmFile {
public name: string
public path: string
public type: string
public size: number
public webkitRelativePath: string
public arrayBuffer: () => Promise<ArrayBuffer>
private data: Promise<ArrayBuffer>
constructor(file: File) {
const path = Reflect.get(file, 'path') || file.webkitRelativePath || file.name
this.path = path.startsWith('/') ? path.slice(1) : path
this.webkitRelativePath = this.path
this.name = file.name
this.type = file.type
this.size = file.size
this.data = file.arrayBuffer()
this.arrayBuffer = async () => {
const data = await this.data
return data
}
}
}
+58 -41
View File
@@ -1,28 +1,32 @@
import { FileData } from '@ethersphere/bee-js'
import { SwarmFile } from './SwarmFile'
const indexHtmls = ['index.html', 'index.htm'] const indexHtmls = ['index.html', 'index.htm']
export function detectIndexHtml(files: SwarmFile[]): string | false { interface DetectedIndex {
if (!files.length) { indexPath: string
commonPrefix?: string
}
export function detectIndexHtml(files: FilePath[]): DetectedIndex | false {
const paths = files.map(getPath)
if (!paths.length) {
return false return false
} }
const exactMatch = files.find(x => indexHtmls.includes(x.path)) const exactMatch = paths.find(x => indexHtmls.includes(x))
if (exactMatch) { if (exactMatch) {
return exactMatch.name return { indexPath: exactMatch }
} }
const prefix = files[0].path.split('/')[0] + '/' const prefix = paths[0].split('/')[0] + '/'
const allStartWithSamePrefix = files.every(x => x.path.startsWith(prefix)) const allStartWithSamePrefix = paths.every(x => x.startsWith(prefix))
if (allStartWithSamePrefix) { if (allStartWithSamePrefix) {
const match = files.find(x => indexHtmls.map(y => prefix + y).includes(x.path)) const match = paths.find(x => indexHtmls.map(y => prefix + y).includes(x))
if (match) { if (match) {
return match.name return { indexPath: match, commonPrefix: prefix }
} }
} }
@@ -53,37 +57,50 @@ export function getHumanReadableFileSize(bytes: number): string {
return bytes + ' bytes' return bytes + ' bytes'
} }
export function convertBeeFileToBrowserFile(file: FileData<ArrayBuffer>): Partial<File> { export function getAssetNameFromFiles(files: FilePath[]): string {
if (files.length === 1) return files[0].name
if (files.length > 0) {
const prefix = getPath(files[0]).split('/')[0]
// Only if all files have a common prefix we can use it as a folder name
if (files.every(f => getPath(f).split('/')[0] === prefix)) return prefix
}
return 'unknown'
}
export function getMetadata(files: FilePath[]): Metadata {
const size = files.reduce((total, item) => total + item.size, 0)
const isWebsite = Boolean(detectIndexHtml(files))
const name = getAssetNameFromFiles(files)
const type = files.length === 1 ? files[0].type : 'folder'
const count = files.length
return { size, name, type, isWebsite, count }
}
export function getPath(file: FilePath): string {
return (file.path || file.webkitRelativePath || file.name).replace(/^\//g, '') // remove the starting slash
}
/**
* Utility function that is needed to have correct directory structure as webkitRelativePath is read only
*/
export function packageFile(file: FilePath): FilePath {
const path = getPath(file)
return { return {
path: path,
fullPath: path,
webkitRelativePath: path,
lastModified: file.lastModified,
name: file.name, name: file.name,
size: file.data.byteLength, size: file.size,
type: file.contentType, type: file.type,
arrayBuffer: () => new Promise(resolve => resolve(file.data)), stream: file.stream,
slice: file.slice,
text: file.text,
arrayBuffer: async () => await file.arrayBuffer(), // This is needed for successful upload and can not simply be { arrayBuffer: file.arrayBuffer }
} }
} }
export function convertManifestToFiles(files: Record<string, string>): SwarmFile[] {
return Object.entries(files).map(
x =>
({
name: x[0],
path: x[0],
type: 'n/a',
size: 0,
webkitRelativePath: x[0],
arrayBuffer: () => new Promise(resolve => resolve(new ArrayBuffer(0))),
} as SwarmFile),
)
}
export function getAssetNameFromFiles(files: SwarmFile[]): string {
if (!files.length) {
return 'Unknown'
}
if (files.length === 1) {
return files[0].name
}
return files[0].path.split('/')[0]
}
+89
View File
@@ -0,0 +1,89 @@
interface Dimensions {
width: number
height: number
}
/**
* Get the dimensions of the image after resize
*
* @param imgWidth Current image width
* @param imgHeight Current image height
* @param maxWidth Desired max width
* @param maxHeight Desired max height
*
* @returns Downscaled dimensions of the image to fit in the bounding box
*/
export function getDimensions(imgWidth: number, imgHeight: number, maxWidth?: number, maxHeight?: number): Dimensions {
const ratioWidth = maxWidth ? imgWidth / maxWidth : 1
const ratioHeight = maxHeight ? imgHeight / maxHeight : 1
const ratio = Math.max(ratioWidth, ratioHeight)
// No need to resize
if (ratio <= 1) return { width: imgWidth, height: imgHeight }
return { width: imgWidth / ratio, height: imgHeight / ratio }
}
/**
* Resize image passed to fit in the bounding box defined with maxWidth and maxHeight.
* Note that one or both of the bounding box dimensions may be omitted
*
* @param file Image file to be resized
* @param maxWidth Maximal image width
* @param maxHeight Maximal image height
*
* @returns Promise that resolves into the resized image blob
*/
export function resize(file: File, maxWidth?: number, maxHeight?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
const allowedTypes = [
'image/bmp',
'image/gif',
'image/vnd.microsoft.icon',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
]
if (!file.size || !file.type || !allowedTypes.includes(file.type)) return reject('File not supported!')
try {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = event => {
const src = event?.target?.result
if (!src || typeof src !== 'string') throw new Error('Failed to load the image source')
const img = new Image()
img.src = src
img.onload = () => {
const dimensions = getDimensions(img.width, img.height, maxWidth, maxHeight)
const elem = document.createElement('canvas')
elem.width = dimensions.width
elem.height = dimensions.height
const ctx = elem.getContext('2d')
if (!ctx) throw new Error('Failed to create canvas context')
ctx.drawImage(img, 0, 0, elem.width, elem.height)
ctx.canvas.toBlob(
blob => {
if (!blob) throw new Error('Failed to extract the blob from canvas')
resolve(blob)
},
'image/jpeg',
1,
)
}
}
reader.onerror = error => reject(error)
} catch (error) {
reject(error)
}
})
}
+13 -2
View File
@@ -1,3 +1,4 @@
import { NumberString } from '@ethersphere/bee-js'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
/** /**
@@ -186,6 +187,16 @@ export function convertAmountToSeconds(amount: number): number {
return amount / 10 / 1 return amount / 10 / 1
} }
export function calculateStampPrice(depth: number, amount: number): number { export function calculateStampPrice(depth: number, amount: number, currentPrice: NumberString): number {
return (amount * 2 ** (depth - 16) * 2) / 1e16 const price = parseInt(currentPrice, 10)
return (amount * 2 ** (depth - 16) * price) / 1e16
}
export function shortenText(text: string, length = 20, separator = '[…]'): string {
if (text.length <= length * 2 + separator.length) {
return text
}
return `${text.slice(0, length)}${separator}${text.slice(-length)}`
} }