Compare commits

...

11 Commits

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

* fix: handle out of limit tags

* feat: support preview on the donwload screen

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

* fix: missing meta

* fix: do not allow maybe or probably types

* fix: make the media check more strict

---------

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

* chore: bump ci

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

* ci: update swarm-actions to v1

* fix: fix eslint violations

* refactor: decaf
2024-11-21 12:43:30 +01:00
Cafe137 63f338075b fix: explicitly define type 0 transaction (#674) 2024-10-03 15:27:17 +02:00
40 changed files with 53268 additions and 7252 deletions
+2 -9
View File
@@ -56,13 +56,6 @@ jobs:
- name: Types check
run: npm run check:types
- name: Update supported Bee action
uses: ethersphere/update-supported-bee-action@v1
if: github.ref == 'refs/heads/master'
with:
updateEngine: true
token: ${{ secrets.GHA_PAT_BASIC }}
- name: Build
run: npm run build
@@ -70,7 +63,7 @@ jobs:
run: npm run build:component
- name: Create preview
uses: ethersphere/swarm-actions/pr-preview@v0
uses: ethersphere/swarm-actions/pr-preview@v1
continue-on-error: true
with:
bee-url: https://unlimited.gateway.ethswarm.org
@@ -79,7 +72,7 @@ jobs:
headers: '${{ secrets.GATEWAY_AUTHORIZATION_HEADER }}'
- name: Upload to testnet
uses: ethersphere/swarm-actions/upload-dir@v0
uses: ethersphere/swarm-actions/upload-dir@v1
continue-on-error: true
with:
index-document: index.html
+28
View File
@@ -1,5 +1,33 @@
# Changelog
## [0.32.0](https://github.com/ethersphere/bee-dashboard/compare/v0.31.0...v0.32.0) (2025-02-12)
### Features
* vod display ([#686](https://github.com/ethersphere/bee-dashboard/issues/686)) ([bcd3d50](https://github.com/ethersphere/bee-dashboard/commit/bcd3d50b4209a4f66a259b8a3f6ea5ffd908471f))
## [0.31.0](https://github.com/ethersphere/bee-dashboard/compare/v0.30.0...v0.31.0) (2025-01-13)
### Features
* remove experimental FDP menu item ([#687](https://github.com/ethersphere/bee-dashboard/issues/687)) ([a6125b3](https://github.com/ethersphere/bee-dashboard/commit/a6125b3d0b0b680a9fa61a8edcd75b2ae6c153e0))
## [0.30.0](https://github.com/ethersphere/bee-dashboard/compare/v0.29.0...v0.30.0) (2024-11-25)
### Features
* add experimental fdp ([#681](https://github.com/ethersphere/bee-dashboard/issues/681)) ([d0c94b7](https://github.com/ethersphere/bee-dashboard/commit/d0c94b7316ea2b139bddc5481132ea7de7cb840d))
* update map data ([#684](https://github.com/ethersphere/bee-dashboard/issues/684)) ([fbb2ed8](https://github.com/ethersphere/bee-dashboard/commit/fbb2ed8a576f3519883e71382b7f4e8505fbe139))
### Bug Fixes
* allow changing api url ([#676](https://github.com/ethersphere/bee-dashboard/issues/676)) ([6294bb0](https://github.com/ethersphere/bee-dashboard/commit/6294bb0a7be6b9b82354c42da8c84e767fad899e))
* explicitly define type 0 transaction ([#674](https://github.com/ethersphere/bee-dashboard/issues/674)) ([63f3380](https://github.com/ethersphere/bee-dashboard/commit/63f338075b919cb70d79665c3d86537f2ac1d2e9))
## [0.29.0](https://github.com/ethersphere/bee-dashboard/compare/v0.28.0...v0.29.0) (2024-07-17)
+271 -23
View File
@@ -1,16 +1,17 @@
{
"name": "@ethersphere/bee-dashboard",
"version": "0.29.0",
"version": "0.32.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ethersphere/bee-dashboard",
"version": "0.29.0",
"version": "0.32.0",
"license": "BSD-3-Clause",
"dependencies": {
"@ethersphere/bee-js": "^7.1.0",
"@ethersphere/bee-js": "^8.3.1",
"@ethersphere/swarm-cid": "^0.1.0",
"@fairdatasociety/fdp-storage": "^0.19.0",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.57",
@@ -2437,18 +2438,22 @@
}
},
"node_modules/@ethersphere/bee-js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-7.1.0.tgz",
"integrity": "sha512-81PWyNKlO6EhOsfiKmDlHVlD8jhMVNTEfU4+5TC7jGc/vMN8resSu5BIG4gV3GHFzSiwK11sZffWC+6d2gMmPw==",
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-8.3.1.tgz",
"integrity": "sha512-HIyEzYQmDynFoD0+yXMtNkSBTwdRIKZZs1cZ6H85BAI1eKEXt2dGy+t/IzsUAa5oLq1K7uyoaawaaLZMJqlfrQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@ethersphere/swarm-cid": "^0.1.0",
"axios": "^0.28.1",
"cafe-utility": "^21.3.1",
"cafe-utility": "^23.10.0",
"elliptic": "^6.5.4",
"isomorphic-ws": "^4.0.1",
"js-sha3": "^0.8.0",
"semver": "^7.3.5",
"ws": "^8.7.0"
},
"engines": {
"bee": "2.2.0-06a0aca7",
"beeApiVersion": "7.1.0"
}
},
"node_modules/@ethersphere/bee-js/node_modules/ws": {
@@ -3164,6 +3169,106 @@
"@ethersproject/strings": "^5.7.0"
}
},
"node_modules/@fairdatasociety/fdp-contracts-js": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/@fairdatasociety/fdp-contracts-js/-/fdp-contracts-js-3.12.0.tgz",
"integrity": "sha512-pfmRucv40GMGAMfXB8hFDRvdxkY5nX172dQFnWh4vGCS2iRKbz6p78cqnF8Xyu9lYSjtSVEWAnXOk9Yug6X5OQ==",
"license": "MIT",
"peerDependencies": {
"ethers": ">=5.6.4"
}
},
"node_modules/@fairdatasociety/fdp-storage": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@fairdatasociety/fdp-storage/-/fdp-storage-0.19.0.tgz",
"integrity": "sha512-tN1mosanu4nAxLx+uxuRSdbSV+/HSgAIsakR1jsgJ2Lneycc4i9Yd7but2LFyOQ5okNh6TM5P6S4hlPUpWk/jQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@ethersphere/bee-js": "^6.2.0",
"@fairdatasociety/fdp-contracts-js": "^3.11.0",
"crypto-js": "^4.2.0",
"elliptic": "^6.5.4",
"ethers": "^5.5.2",
"js-sha3": "^0.9.2",
"pako": "^2.1.0"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=9.0.0"
}
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/@ethersphere/bee-js": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.9.1.tgz",
"integrity": "sha512-aZaoD4Q9BH2jOPU049vIsN3N/RID/CTrXdpMb593PREU9S9PksZkxVPS95sgLWnUsOK1IzW9a8WtkOIjcRZswA==",
"license": "BSD-3-Clause",
"dependencies": {
"@ethersphere/swarm-cid": "^0.1.0",
"@types/readable-stream": "^2.3.13",
"axios": "^0.28.0",
"cafe-utility": "^15.0.2",
"elliptic": "^6.5.4",
"fetch-blob": "2.1.2",
"isomorphic-ws": "^4.0.1",
"js-sha3": "^0.8.0",
"semver": "^7.3.5",
"tar-js": "^0.3.0",
"web-streams-polyfill": "^4.0.0-beta.3",
"ws": "^8.7.0"
},
"engines": {
"bee": "1.18.2-759f56f",
"beeApiVersion": "4.0.0",
"beeDebugApiVersion": "4.0.0",
"node": ">=14.0.0",
"npm": ">=6.0.0"
}
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/@ethersphere/bee-js/node_modules/js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
"license": "MIT"
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/cafe-utility": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-15.0.2.tgz",
"integrity": "sha512-TNKSfA/q/XRd86NwYtF5QImQB8U5n/hKZuWblYFgYW4aveHcSg2RGOfR3+xquXRXF7BCeNoAXe2/snWFKviPzw==",
"license": "MIT"
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/js-sha3": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz",
"integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==",
"license": "MIT"
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/@fairdatasociety/fdp-storage/node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -4910,8 +5015,7 @@
"node_modules/@types/node": {
"version": "16.11.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -5028,6 +5132,16 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"node_modules/@types/readable-stream": {
"version": "2.3.15",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz",
"integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"safe-buffer": "~5.1.1"
}
},
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -6927,9 +7041,10 @@
}
},
"node_modules/cafe-utility": {
"version": "21.5.0",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-21.5.0.tgz",
"integrity": "sha512-5u+9cf7fvcH3j2Q3jrd7nA3bUITUBj8b9Arg4eA6almqeA5+dwQA6NKba4GnW6zS9uL1iVCEQqM3z3tQVs2Xjw=="
"version": "23.12.0",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-23.12.0.tgz",
"integrity": "sha512-B8MHryv6dDTw8GRfJxHLy4zzhewEEYulPAXiSRqkNCeqXFoQAk8THhlU00Yk7dvc8bppnHoS7FaQ468dfGfe6A==",
"license": "MIT"
},
"node_modules/call-bind": {
"version": "1.0.2",
@@ -7758,6 +7873,12 @@
"node": "*"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -10179,6 +10300,20 @@
"pend": "~1.2.0"
}
},
"node_modules/fetch-blob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
"integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==",
"license": "MIT",
"engines": {
"node": "^10.17.0 || >=12.3.0"
},
"peerDependenciesMeta": {
"domexception": {
"optional": true
}
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -18754,6 +18889,14 @@
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-js": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/tar-js/-/tar-js-0.3.0.tgz",
"integrity": "sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA==",
"engines": {
"node": "*"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@@ -19603,6 +19746,15 @@
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz",
"integrity": "sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/web-vitals": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.2.tgz",
@@ -22224,13 +22376,12 @@
}
},
"@ethersphere/bee-js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-7.1.0.tgz",
"integrity": "sha512-81PWyNKlO6EhOsfiKmDlHVlD8jhMVNTEfU4+5TC7jGc/vMN8resSu5BIG4gV3GHFzSiwK11sZffWC+6d2gMmPw==",
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-8.3.1.tgz",
"integrity": "sha512-HIyEzYQmDynFoD0+yXMtNkSBTwdRIKZZs1cZ6H85BAI1eKEXt2dGy+t/IzsUAa5oLq1K7uyoaawaaLZMJqlfrQ==",
"requires": {
"@ethersphere/swarm-cid": "^0.1.0",
"axios": "^0.28.1",
"cafe-utility": "^21.3.1",
"cafe-utility": "^23.10.0",
"elliptic": "^6.5.4",
"isomorphic-ws": "^4.0.1",
"js-sha3": "^0.8.0",
@@ -22639,6 +22790,75 @@
"@ethersproject/strings": "^5.7.0"
}
},
"@fairdatasociety/fdp-contracts-js": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/@fairdatasociety/fdp-contracts-js/-/fdp-contracts-js-3.12.0.tgz",
"integrity": "sha512-pfmRucv40GMGAMfXB8hFDRvdxkY5nX172dQFnWh4vGCS2iRKbz6p78cqnF8Xyu9lYSjtSVEWAnXOk9Yug6X5OQ==",
"requires": {}
},
"@fairdatasociety/fdp-storage": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@fairdatasociety/fdp-storage/-/fdp-storage-0.19.0.tgz",
"integrity": "sha512-tN1mosanu4nAxLx+uxuRSdbSV+/HSgAIsakR1jsgJ2Lneycc4i9Yd7but2LFyOQ5okNh6TM5P6S4hlPUpWk/jQ==",
"requires": {
"@ethersphere/bee-js": "^6.2.0",
"@fairdatasociety/fdp-contracts-js": "^3.11.0",
"crypto-js": "^4.2.0",
"elliptic": "^6.5.4",
"ethers": "^5.5.2",
"js-sha3": "^0.9.2",
"pako": "^2.1.0"
},
"dependencies": {
"@ethersphere/bee-js": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.9.1.tgz",
"integrity": "sha512-aZaoD4Q9BH2jOPU049vIsN3N/RID/CTrXdpMb593PREU9S9PksZkxVPS95sgLWnUsOK1IzW9a8WtkOIjcRZswA==",
"requires": {
"@ethersphere/swarm-cid": "^0.1.0",
"@types/readable-stream": "^2.3.13",
"axios": "^0.28.0",
"cafe-utility": "^15.0.2",
"elliptic": "^6.5.4",
"fetch-blob": "2.1.2",
"isomorphic-ws": "^4.0.1",
"js-sha3": "^0.8.0",
"semver": "^7.3.5",
"tar-js": "^0.3.0",
"web-streams-polyfill": "^4.0.0-beta.3",
"ws": "^8.7.0"
},
"dependencies": {
"js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
}
}
},
"cafe-utility": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-15.0.2.tgz",
"integrity": "sha512-TNKSfA/q/XRd86NwYtF5QImQB8U5n/hKZuWblYFgYW4aveHcSg2RGOfR3+xquXRXF7BCeNoAXe2/snWFKviPzw=="
},
"js-sha3": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz",
"integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg=="
},
"pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
},
"ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"requires": {}
}
}
},
"@humanwhocodes/config-array": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -23913,8 +24133,7 @@
"@types/node": {
"version": "16.11.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -24033,6 +24252,15 @@
"@types/react": "*"
}
},
"@types/readable-stream": {
"version": "2.3.15",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz",
"integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==",
"requires": {
"@types/node": "*",
"safe-buffer": "~5.1.1"
}
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -25504,9 +25732,9 @@
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="
},
"cafe-utility": {
"version": "21.5.0",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-21.5.0.tgz",
"integrity": "sha512-5u+9cf7fvcH3j2Q3jrd7nA3bUITUBj8b9Arg4eA6almqeA5+dwQA6NKba4GnW6zS9uL1iVCEQqM3z3tQVs2Xjw=="
"version": "23.12.0",
"resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-23.12.0.tgz",
"integrity": "sha512-B8MHryv6dDTw8GRfJxHLy4zzhewEEYulPAXiSRqkNCeqXFoQAk8THhlU00Yk7dvc8bppnHoS7FaQ468dfGfe6A=="
},
"call-bind": {
"version": "1.0.2",
@@ -26165,6 +26393,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -27989,6 +28222,11 @@
"pend": "~1.2.0"
}
},
"fetch-blob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
"integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow=="
},
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -34331,6 +34569,11 @@
"tar-stream": "^2.1.4"
}
},
"tar-js": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/tar-js/-/tar-js-0.3.0.tgz",
"integrity": "sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA=="
},
"tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@@ -34953,6 +35196,11 @@
"minimalistic-assert": "^1.0.0"
}
},
"web-streams-polyfill": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz",
"integrity": "sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw=="
},
"web-vitals": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.2.tgz",
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ethersphere/bee-dashboard",
"version": "0.29.0",
"version": "0.32.0",
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
"keywords": [
"bee",
@@ -26,8 +26,9 @@
"url": "https://github.com/ethersphere/bee-dashboard.git"
},
"dependencies": {
"@ethersphere/bee-js": "^7.1.0",
"@ethersphere/bee-js": "^8.3.1",
"@ethersphere/swarm-cid": "^0.1.0",
"@fairdatasociety/fdp-storage": "^0.19.0",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.57",
+51739 -7115
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
import { createStyles, makeStyles } from '@material-ui/core'
import { ReactElement } from 'react'
const useStyles = makeStyles(() =>
createStyles({
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
}),
)
interface VideoProps {
src: string | undefined
maxHeight?: string
maxWidth?: string
}
export function FitVideo(props: VideoProps): ReactElement {
const classes = useStyles()
const inlineStyles: Record<string, string> = {}
props.maxHeight && (inlineStyles.maxHeight = props.maxHeight)
props.maxWidth && (inlineStyles.maxWidth = props.maxWidth)
return <video className={classes.video} src={props.src} style={inlineStyles} controls />
}
+1 -2
View File
@@ -1,5 +1,4 @@
export const META_FILE_NAME = '.swarmgatewaymeta.json'
export const PREVIEW_FILE_NAME = '.swarmgatewaypreview.jpeg'
export const META_FILE_NAME = 'metadata'
export const PREVIEW_DIMENSIONS = { maxWidth: 250, maxHeight: 175 }
export const BZZ_LINK_DOMAIN = 'bzz.link'
export const BLOCKCHAIN_EXPLORER_URL = 'https://blockscout.com/xdai/mainnet'
+106
View File
@@ -0,0 +1,106 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Checkbox, InputBase, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { useEffect, useState } from 'react'
import RegisterIcon from 'remixicon-react/AddBoxLineIcon'
import LoginIcon from 'remixicon-react/LoginBoxLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
onSuccessfulLogin: () => void
}
export function FdpLogin({ fdp, onSuccessfulLogin }: Props) {
const [username, setUsername] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [remember, setRemember] = useState<boolean>(false)
const [sepolia, setSepolia] = useState<string>('https://sepolia.drpc.org')
const { enqueueSnackbar } = useSnackbar()
const inputStyle = { background: 'white', padding: '2px 8px', width: '100%' }
useEffect(() => {
const storedSepolia = localStorage.getItem('sepolia')
if (storedSepolia) {
setSepolia(storedSepolia)
}
const fdpCredentials = localStorage.getItem('fdpCredentials')
if (fdpCredentials) {
const { username, password } = JSON.parse(fdpCredentials)
setUsername(username)
setPassword(password)
setRemember(true)
}
}, [])
async function onLogin() {
localStorage.setItem('sepolia', sepolia)
if (remember) {
localStorage.setItem('fdpCredentials', JSON.stringify({ username, password }))
} else {
localStorage.removeItem('fdpCredentials')
}
enqueueSnackbar('Logging in...', { variant: 'info' })
try {
await fdp.account.login(username, password)
enqueueSnackbar('Logged in successfully', { variant: 'success' })
onSuccessfulLogin()
} catch {
enqueueSnackbar('Login failed', { variant: 'error' })
} finally {
setUsername('')
setPassword('')
setRemember(false)
}
}
function onRegister() {
window.open('https://create.fairdatasociety.org/', '_blank')
}
return (
<div
style={{
maxWidth: '500px',
margin: 'auto',
}}
>
<Vertical gap={16} full>
<Vertical gap={8} left full>
<Typography variant="body2">Sepolia JSON RPC</Typography>
<InputBase value={sepolia} onChange={e => setSepolia(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Username</Typography>
<InputBase value={username} onChange={e => setUsername(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Password</Typography>
<InputBase value={password} onChange={e => setPassword(e.target.value)} style={inputStyle} type="password" />
</Vertical>
<Vertical gap={8} left full>
<Horizontal>
<Checkbox checked={remember} onChange={e => setRemember(e.target.checked)} />
<Typography variant="body2">Remember me</Typography>
</Horizontal>
</Vertical>
<Vertical left full>
<Horizontal gap={4}>
<SwarmButton iconType={LoginIcon} onClick={onLogin}>
Login
</SwarmButton>
<SwarmButton iconType={RegisterIcon} onClick={onRegister}>
Registration
</SwarmButton>
</Horizontal>
</Vertical>
</Vertical>
</div>
)
}
+98
View File
@@ -0,0 +1,98 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { useState } from 'react'
import { CafeReactFs } from '../../react-fs/CafeReactFs'
import { FsItem, FsItemType } from '../../react-fs/CafeReactType'
import { joinUrl } from '../../react-fs/Utility'
interface Props {
fdp: FdpStorage
name: string
}
export function FdpPod({ fdp, name }: Props) {
const [reloader, setReloader] = useState(0)
function reload() {
setReloader(reloader + 1)
}
return (
<CafeReactFs
rootAlias={`/${name}`}
backgroundColor="#ffffff"
reloader={reloader}
onDeleteFile={async (path: string) => {
await fdp.file.delete(name, path)
reload()
}}
onDeleteDirectory={async (path: string) => {
await fdp.directory.delete(name, path)
reload()
}}
onUpload={(path: string) => {
const input = document.createElement('input')
input.type = 'file'
input.multiple = true
input.click()
return new Promise<void>(resolve => {
input.onchange = async () => {
if (!input.files || !input.files.length) {
resolve()
return
}
for (const file of Array.from(input.files)) {
const data = await file.arrayBuffer()
await fdp.file.uploadData(name, joinUrl(path, file.name), new Uint8Array(data))
}
reload()
resolve()
}
})
}}
onCreateDirectory={async (path: string) => {
// eslint-disable-next-line no-alert
const newDirectoryName = prompt('Directory name')
if (!newDirectoryName) {
return
}
await fdp.directory.create(name, joinUrl(path, newDirectoryName))
reload()
}}
// eslint-disable-next-line require-await
onSync={async () => {
setReloader(reloader + 1)
}}
download={async (path: string) => {
const data = await fdp.file.downloadData(name, path)
const url = URL.createObjectURL(new Blob([data]))
const a = document.createElement('a')
a.href = url
a.download = path.split('/').pop() || 'Untitled'
a.click()
}}
list={async (path: string) => {
const fdpResponse = await fdp.directory.read(name, path)
const items: FsItem[] = []
for (const directory of fdpResponse.directories) {
items.push({
name: directory.name,
$type: FsItemType.DIRECTORY,
id: directory.name,
})
}
for (const file of fdpResponse.files) {
items.push({
name: file.name,
$type: FsItemType.FILE,
id: file.name,
})
}
return items
}}
/>
)
}
+30
View File
@@ -0,0 +1,30 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { FdpPod } from './FdpPod'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
pods: Pod[]
loadingPods: boolean
}
export function FdpPods({ fdp, pods, loadingPods }: Props) {
if (loadingPods) {
return (
<Vertical gap={32} full>
<CircularProgress />
<Typography>Loading your pods...</Typography>
</Vertical>
)
}
return (
<Vertical gap={16} full left>
{pods.map(pod => (
<FdpPod key={pod.index} fdp={fdp} name={pod.name} />
))}
</Vertical>
)
}
+22
View File
@@ -0,0 +1,22 @@
interface Props {
children: React.ReactNode
p?: string
gap?: number
between?: boolean
background?: string
}
export function Horizontal({ children, p = '0', gap = 8, between, background }: Props) {
const style = {
display: 'flex',
flexDirection: 'row' as 'row', //eslint-disable-line
alignItems: 'center',
justifyContent: between ? 'space-between' : 'flex-start',
gap: `${gap}px`,
padding: p,
background,
width: between ? '100%' : 'auto',
}
return <div style={style}>{children}</div>
}
+20
View File
@@ -0,0 +1,20 @@
interface Props {
children: React.ReactNode
p?: number
gap?: number
left?: boolean
full?: boolean
}
export function Vertical({ children, p = 0, gap = 0, left = false, full = false }: Props) {
const style = {
display: 'flex',
flexDirection: 'column' as 'column', //eslint-disable-line
alignItems: left ? 'flex-start' : 'center',
gap: `${gap}px`,
width: full ? '100%' : 'auto',
padding: `${p}px`,
}
return <div style={style}>{children}</div>
}
+163
View File
@@ -0,0 +1,163 @@
import { Bee } from '@ethersphere/bee-js'
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useEffect, useState } from 'react'
import ImportIcon from 'remixicon-react/AddBoxLineIcon'
import PlusCircle from 'remixicon-react/AddCircleLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { joinUrl } from '../../react-fs/Utility'
import { ManifestJs } from '../../utils/manifest'
import { FdpLogin } from './FdpLogin'
import { FdpPods } from './FdpPods'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
async function makeFdp(): Promise<FdpStorage | null> {
const bee = new Bee('http://localhost:1633')
const sepolia = localStorage.getItem('sepolia') ?? 'https://sepolia.drpc.org'
const postageBatches = await bee.getAllPostageBatch()
const usableBatches = postageBatches.filter(batch => batch.usable)
const highestCapacityBatch = usableBatches.length ? usableBatches.reduce((a, b) => (a.depth > b.depth ? a : b)) : null
if (!highestCapacityBatch) {
return null
}
return new FdpStorage('http://localhost:1633', highestCapacityBatch.batchID, {
ensOptions: {
rpcUrl: sepolia,
contractAddresses: {
ensRegistry: '0x42a96D45d787685ac4b36292d218B106Fb39be7F',
fdsRegistrar: '0xFBF00389140C00384d88d458239833E3231a7414',
nameResolver: '0xE20ECe6Ea93c4edE41e4d3B973f6679F1E89986A',
publicResolver: '0xC904989B579c2B216A75723688C784038AA99B56',
reverseResolver: '0xbDC8D98d3cbFd68EA9c165E1f15Df6e77A2ae0C5',
},
gasEstimation: 1,
performChecks: true,
},
providerOptions: {
url: sepolia,
},
ensDomain: 'fds',
})
}
export default function FDP(): ReactElement {
const [fdp, setFdp] = useState<FdpStorage | null>(null)
const [pods, setPods] = useState<Pod[]>([])
const [loggedIn, setLoggedIn] = useState<boolean>(false)
const [loadingPods, setLoadingPods] = useState<boolean>(false)
const [creatingPod, setCreatingPod] = useState<boolean>(false)
const { enqueueSnackbar } = useSnackbar()
useEffect(() => {
makeFdp().then(fdp => {
if (!fdp) {
enqueueSnackbar('FDP could not be initialized. Do you have a postage batch?', { variant: 'error' })
}
setFdp(fdp)
})
}, [enqueueSnackbar])
useEffect(() => {
if (fdp && loggedIn) {
setLoadingPods(true)
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setLoadingPods(false)
})
}
}, [fdp, loggedIn])
function onSuccessfulLogin() {
setLoggedIn(true)
}
function onCreatePod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
if (name) {
setCreatingPod(true)
fdp.personalStorage.create(name).then(() => {
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setCreatingPod(false)
})
})
}
}
async function onImportPod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
// eslint-disable-next-line no-alert
const importHash = prompt('Enter the Swarm reference')
if (!name || !importHash) {
return
}
setCreatingPod(true)
const bee = new Bee('http://localhost:1633')
const manifestJs = new ManifestJs(bee)
const entries = await manifestJs.getHashes(importHash)
await fdp.personalStorage.create(name)
for (const [path, hash] of Object.entries(entries)) {
await fdp.file.uploadData(name, joinUrl('/', path), await bee.downloadData(hash))
}
const pods = await fdp.personalStorage.list()
setPods(pods.pods)
setCreatingPod(false)
}
if (!fdp) {
return <CircularProgress />
}
return (
<Vertical gap={32} full left>
<Horizontal between>
<Typography variant="h1">Files</Typography>
{loggedIn && (
<Horizontal gap={4}>
<SwarmButton onClick={onCreatePod} iconType={PlusCircle}>
Create
</SwarmButton>
<SwarmButton onClick={onImportPod} iconType={ImportIcon}>
Import
</SwarmButton>
</Horizontal>
)}
</Horizontal>
{!loggedIn && <FdpLogin fdp={fdp} onSuccessfulLogin={onSuccessfulLogin} />}
{loggedIn && <FdpPods fdp={fdp} pods={pods} loadingPods={loadingPods || creatingPod} />}
{loggedIn && !loadingPods && !creatingPod && pods.length === 0 && (
<Typography>
<strong>You do not have any pods yet.</strong> Get started by clicking the Create or Import button on the top
right.
</Typography>
)}
</Vertical>
)
}
+32 -15
View File
@@ -1,6 +1,6 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { Web } from '@material-ui/icons'
import { ReactElement } from 'react'
import { ReactElement, useMemo } from 'react'
import File from 'remixicon-react/FileLineIcon'
import Folder from 'remixicon-react/FolderLineIcon'
import { FitImage } from '../../components/FitImage'
@@ -8,35 +8,52 @@ import { shortenText } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
import { shortenHash } from '../../utils/hash'
import { AssetIcon } from './AssetIcon'
import { FitVideo } from '../../components/FitVideo'
interface Props {
previewUri?: string
metadata?: Metadata
}
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
/* eslint-disable react/display-name */
const getPreviewComponent = (previewUri?: string, metadata?: Metadata) => {
if (metadata?.isVideo) {
return () => <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
}
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
let previewComponent = <File />
let type = metadata?.type
if (metadata?.isImage) {
return () => <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
}
if (metadata?.isWebsite) {
previewComponent = <Web />
type = 'Website'
} else if (metadata?.type === 'folder') {
previewComponent = <Folder />
type = 'Folder'
return () => <AssetIcon icon={<Web />} />
}
if (metadata?.type === 'folder') {
return () => <AssetIcon icon={<Folder />} />
}
return () => <AssetIcon icon={<File />} />
}
const getType = (metadata?: Metadata) => {
if (metadata?.isWebsite) return 'Website'
if (metadata?.type === 'folder') return 'Folder'
return metadata?.type
}
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
const PreviewAssetComponent = useMemo(() => getPreviewComponent(previewUri, metadata), [metadata, previewUri])
const type = useMemo(() => getType(metadata), [metadata])
return (
<Box mb={4}>
<Box bgcolor="background.paper">
<Grid container direction="row">
{previewUri ? (
<FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
) : (
<AssetIcon icon={previewComponent} />
)}
<PreviewAssetComponent />
<Box p={2}>
{metadata?.hash && <Typography>Swarm Hash: {shortenHash(metadata.hash)}</Typography>}
{metadata?.name && metadata?.name !== metadata?.hash && (
+14 -7
View File
@@ -3,6 +3,7 @@ import { Box } from '@material-ui/core'
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import { LinearProgressWithLabel } from '../../components/ProgressBar'
import { Tag } from '@ethersphere/bee-js'
interface Props {
reference: string
@@ -16,12 +17,20 @@ export function AssetSyncing({ reference }: Props): ReactElement {
const [syncProgress, setSyncProgress] = useState<number>(0)
const syncCheck = async () => {
if (!beeApi) {
return
}
if (!beeApi) return
const tags = await beeApi.getAllTags()
const tag = tags.find(t => t.address === reference)
let allTags: Tag[] = []
let offset = 0
const limit = 1000
let tagsBatch
do {
tagsBatch = await beeApi.getAllTags({ limit, offset })
allTags = allTags.concat(tagsBatch)
offset += limit
} while (tagsBatch.length === limit) // Continue if the batch is full, stop if fewer than the limit
const tag = allTags.find(t => t.address === reference)
if (tag) {
const progress = ((tag.seen + tag.synced) / tag.split) * 100
@@ -51,8 +60,6 @@ export function AssetSyncing({ reference }: Props): ReactElement {
There are instances when it seems that the content isn't synchronized, despite being already available.
To ensure it's not due to invalid synchronization data,
verify availability from at least 70% using one of the stewardship endpoints.
TODO: is 70 a good number?
*/
if (beeApi && !isRetrieveChecking && syncProgress > 10 && syncProgress < 100) {
// It's a long running task make sure only one run occurs at a time.
+24 -27
View File
@@ -7,7 +7,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
@@ -50,38 +50,35 @@ export function Share(): ReactElement {
return
}
const entries = await manifestJs.getHashes(reference)
const entries = await manifestJs.getHashes(reference, { exclude: [META_FILE_NAME] })
setSwarmEntries(entries)
const indexDocument = await manifestJs.getIndexDocumentPath(reference)
setIndexDocument(indexDocument)
const previewFile = entries[PREVIEW_FILE_NAME]
delete entries[META_FILE_NAME]
delete entries[PREVIEW_FILE_NAME]
setSwarmEntries(entries)
const count = Object.keys(entries).length
let metadata: Metadata | undefined = {
hash,
size: 0,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
isWebsite: Boolean(indexDocument) && count > 1,
count,
}
try {
const mtdt = await beeApi.downloadFile(reference, META_FILE_NAME)
const remoteMetadata = mtdt.data.text()
metadata = { ...metadata, ...(JSON.parse(remoteMetadata) as Metadata) }
} catch (e) {} // eslint-disable-line no-empty
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
const formattedMetadata = JSON.parse(remoteMetadata.data.text()) as Metadata
if (previewFile) {
setPreview(`${apiUrl}/bzz/${reference}/${PREVIEW_FILE_NAME}`)
if (formattedMetadata.isVideo || formattedMetadata.isImage) {
setPreview(`${apiUrl}/bzz/${reference}`)
}
setMetadata({ ...formattedMetadata, hash })
} catch (e) {
// if metadata is not available or invalid go with the default one
const count = Object.keys(entries).length
setMetadata({
hash,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
count,
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogg|mp3|ogg|wav)$/i.test(indexDocument)),
isImage: Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument)),
// naive assumption based on indexDocument, we don't want to donwload the whole manifest
})
}
setMetadata(metadata)
}
function onOpen() {
+4 -20
View File
@@ -6,7 +6,7 @@ import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { Context as FileContext } from '../../providers/File'
@@ -33,7 +33,7 @@ export function Upload(): ReactElement {
const { stamps, refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
const { files, setFiles, uploadOrigin, metadata, previewUri, previewBlob } = useContext(FileContext)
const { files, setFiles, uploadOrigin, metadata, previewUri } = useContext(FileContext)
const { identities, setIdentities } = useContext(IdentityContext)
const { status } = useContext(BeeContext)
@@ -98,31 +98,15 @@ export function Upload(): ReactElement {
}
}
}
const lastModified = files[0].lastModified
// We want to store only some metadata
const mtd: SwarmMetadata = {
name: metadata.name,
size: metadata.size,
}
// Type of the file only makes sense for a single file
if (files.length === 1) mtd.type = metadata.type
const metafile = new File([JSON.stringify(mtd)], META_FILE_NAME, {
const metafile = new File([JSON.stringify(metadata)], META_FILE_NAME, {
type: 'application/json',
lastModified,
})
fls.push(packageFile(metafile))
if (previewBlob) {
const previewFile = new File([previewBlob], PREVIEW_FILE_NAME, {
type: 'image/jpeg',
lastModified,
})
fls.push(packageFile(previewFile))
}
setUploading(true)
await waitUntilStampUsable(stamp.batchID, beeApi)
+16 -6
View File
@@ -41,7 +41,8 @@ export function Provider({ children }: Props): ReactElement {
const [previewBlob, setPreviewBlob] = useState<Blob | undefined>(undefined)
useEffect(() => {
setMetadata(getMetadata(files))
const metadata = getMetadata(files)
setMetadata(metadata)
if (previewUri) {
URL.revokeObjectURL(previewUri) // Clear the preview from memory
@@ -49,12 +50,21 @@ export function Provider({ children }: Props): ReactElement {
setPreviewBlob(undefined)
}
if (files.length !== 1 || !files[0].type.startsWith('image')) return
if (files.length !== 1) return
resize(files[0], PREVIEW_DIMENSIONS.maxWidth, PREVIEW_DIMENSIONS.maxHeight).then(blob => {
setPreviewUri(URL.createObjectURL(blob)) // NOTE: Until it is cleared with URL.revokeObjectURL, the file stays allocated in memory
setPreviewBlob(blob)
})
if (metadata.isVideo) {
const videoFile = files[0]
const videoBlob = new Blob([videoFile], { type: videoFile.type })
setPreviewUri(URL.createObjectURL(videoBlob))
setPreviewBlob(videoBlob)
}
if (metadata.isImage) {
resize(files[0], PREVIEW_DIMENSIONS.maxWidth, PREVIEW_DIMENSIONS.maxHeight).then(blob => {
setPreviewUri(URL.createObjectURL(blob)) // NOTE: Until it is cleared with URL.revokeObjectURL, the file stays allocated in memory
setPreviewBlob(blob)
})
}
return () => {
if (previewUri) {
+6 -7
View File
@@ -65,17 +65,15 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
const propsProviderUrl =
localStorage.getItem(LocalStorageKeys.providerUrl) || propsSettings.defaultRpcUrl || DEFAULT_RPC_URL
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
const [apiUrl, setApiUrl] = useState<string>(
sessionStorage.getItem('api_host') ?? propsSettings.beeApiUrl ?? initialValues.apiUrl,
)
const [beeApi, setBeeApi] = useState<Bee | null>(null)
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
const [rpcProviderUrl, setRpcProviderUrl] = useState(propsProviderUrl)
const [rpcProvider, setRpcProvider] = useState(new providers.JsonRpcProvider(propsProviderUrl))
const { config, isLoading, error } = useGetBeeConfig(desktopUrl)
const url = makeHttpUrl(
config?.['api-addr'] ?? sessionStorage.getItem('api_host') ?? propsSettings.beeApiUrl ?? apiUrl,
)
useEffect(() => {
const urlSearchParams = new URLSearchParams(window.location.search)
const newApiKey = urlSearchParams.get('v')
@@ -88,18 +86,19 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
}, [])
useEffect(() => {
const url = makeHttpUrl(config?.['api-addr'] ?? apiUrl)
try {
setBeeApi(new Bee(url))
sessionStorage.setItem('api_host', url)
} catch (e) {
setBeeApi(null)
}
}, [url])
}, [config, apiUrl])
return (
<Context.Provider
value={{
apiUrl: url,
apiUrl,
beeApi,
setApiUrl,
lockedApiSettings: Boolean(propsSettings.lockedApiSettings),
+4 -2
View File
@@ -6,14 +6,16 @@ interface LatestBeeRelease {
}
interface SwarmMetadata {
size: number
size?: number
name: string
type?: string
}
interface Metadata extends SwarmMetadata {
type: string
isWebsite: boolean
isWebsite?: boolean
isVideo?: boolean
isImage?: boolean
count?: number
hash?: string
}
+118
View File
@@ -0,0 +1,118 @@
import { useEffect, useState } from 'react'
import { CafeReactFsCreate } from './CafeReactFsCreate'
import { CafeReactFsItem } from './CafeReactFsItem'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsPath } from './CafeReactFsPath'
import { CafeReactFsSync } from './CafeReactFsSync'
import { CafeReactFsUpload } from './CafeReactFsUpload'
import { FsItem } from './CafeReactType'
const DEFAULT_BACKGROUND_COLOR = '#f0f0f0'
interface Props {
download: (path: string) => Promise<void>
list: (path: string) => Promise<FsItem[]>
onUpload: (path: string) => Promise<void>
onCreateDirectory: (path: string) => Promise<void>
onDeleteFile: (path: string) => Promise<void>
onDeleteDirectory: (path: string) => Promise<void>
onSync: () => Promise<void>
reloader: number
backgroundColor?: string
rootAlias?: string
}
export function CafeReactFs({
download,
list,
onUpload,
onCreateDirectory,
onDeleteFile,
onDeleteDirectory,
onSync,
reloader,
backgroundColor,
rootAlias,
}: Props) {
const [path, setPath] = useState('/')
const [items, setItems] = useState<FsItem[]>([])
const [loading, setLoading] = useState(false)
function setItemsSorted(items: FsItem[]) {
// directories first, all alphabetically
const sortedItems = items.slice().sort((a, b) => {
if (a.$type === b.$type) {
return a.name.localeCompare(b.name)
}
return a.$type === 'directory' ? -1 : 1
})
setItems(sortedItems)
}
useEffect(() => {
setLoading(true)
list(path)
.then(setItemsSorted)
.finally(() => setLoading(false))
}, [reloader, list, path])
const pathParts = ['/', ...path.split('/').filter(x => x)]
function jumpToDirectory(fullPath: string) {
setPath(fullPath)
setLoading(true)
list(fullPath)
.then(setItemsSorted)
.finally(() => setLoading(false))
}
function enterDirectory(name: string) {
const newPath = path.endsWith('/') ? `${path}${name}` : `${path}/${name}`
setPath(newPath)
setLoading(true)
list(newPath)
.then(setItemsSorted)
.finally(() => setLoading(false))
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<CafeReactFsPath
pathParts={pathParts}
jumpToDirectory={jumpToDirectory}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
rootAlias={rootAlias}
/>
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: '4px' }}>
{loading && <CafeReactFsLoading backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR} />}
{!loading &&
items.map(item => (
<CafeReactFsItem
key={item.id}
path={path}
item={item}
enterDirectory={enterDirectory}
onDeleteFile={onDeleteFile}
onDeleteDirectory={onDeleteDirectory}
download={download}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
))}
{!loading && (
<>
<CafeReactFsUpload
onUpload={() => onUpload(path)}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
<CafeReactFsCreate
onCreateDirectory={() => onCreateDirectory(path)}
backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR}
/>
<CafeReactFsSync backgroundColor={backgroundColor ?? DEFAULT_BACKGROUND_COLOR} onSync={onSync} />
</>
)}
</div>
</div>
)
}
+54
View File
@@ -0,0 +1,54 @@
import { useState } from 'react'
import { CafeReactFsLoading } from './CafeReactFsLoading'
interface Props {
backgroundColor: string
onCreateDirectory: () => Promise<void>
}
export function CafeReactFsCreate({ backgroundColor, onCreateDirectory }: Props) {
const [loading, setLoading] = useState(false)
function proxyUpload() {
setLoading(true)
onCreateDirectory().finally(() => setLoading(false))
}
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={proxyUpload}
>
<img
alt="Create"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20106%20131%20l%20100%200%20l%2025%2025%20l%20175%200%20l%200%20200%20l%20-300%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20206%20256%20l%20100%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20256%20206%20l%200%20100%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
New Folder
</p>
</div>
)
}
+25
View File
@@ -0,0 +1,25 @@
interface Props {
onDelete: () => Promise<void>
}
export function CafeReactFsDelete({ onDelete }: Props) {
return (
<img
alt="Delete"
style={{
position: 'absolute',
top: '2px',
right: '2px',
width: '20px',
height: '20px',
cursor: 'pointer',
zIndex: 1,
}}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22128%22%20height%3D%22128%22%20viewBox%3D%220%200%20128%20128%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2264%22%20cy%3D%2264%22%20r%3D%2264%22%20fill%3D%22%231F2D3D%22%20stroke%3D%22none%22%20stroke-width%3D%220%22%20%20%2F%3E%3Cpath%20d%3D%22M%2032%2064%20l%2064%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
onClick={event => {
onDelete()
event.stopPropagation()
}}
/>
)
}
+51
View File
@@ -0,0 +1,51 @@
import { useState } from 'react'
import { CafeReactFsDelete } from './CafeReactFsDelete'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsName } from './CafeReactFsName'
import { VirtualDirectory } from './CafeReactType'
interface Props {
directory: VirtualDirectory
enterDirectory: (name: string) => void
deleteDirectory: (name: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsDirectory({ directory, enterDirectory, deleteDirectory, backgroundColor }: Props) {
const [hovered, setHovered] = useState(false)
const [loading, setLoading] = useState(false)
function proxyDelete() {
setLoading(true)
return deleteDirectory(directory.name).finally(() => setLoading(false))
}
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={() => enterDirectory(directory.name)}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{hovered && <CafeReactFsDelete onDelete={proxyDelete} />}
<img
alt="Directory"
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20106%20131%20l%20100%200%20l%2025%2025%20l%20175%200%20l%200%20200%20l%20-300%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
/>
<CafeReactFsName name={directory.name} />
</div>
)
}
+53
View File
@@ -0,0 +1,53 @@
import { useState } from 'react'
import { CafeReactFsDelete } from './CafeReactFsDelete'
import { CafeReactFsLoading } from './CafeReactFsLoading'
import { CafeReactFsName } from './CafeReactFsName'
import { VirtualFile } from './CafeReactType'
import { joinUrl } from './Utility'
interface Props {
path: string
file: VirtualFile
download: (path: string) => Promise<void>
deleteFile: (path: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsFile({ path, file, download, deleteFile, backgroundColor }: Props) {
const [hovered, setHovered] = useState(false)
const [loading, setLoading] = useState(false)
if (loading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
function proxyDelete() {
setLoading(true)
return deleteFile(joinUrl(path, file.name)).finally(() => setLoading(false))
}
return (
<div
onClick={() => download(joinUrl(path, file.name))}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
>
{hovered && <CafeReactFsDelete onDelete={proxyDelete} />}
<img
alt="File"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20131%20l%20150%200%20l%2050%2050%20l%200%20200%20l%20-200%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20306%20131%20l%200%2050%20l%2050%200%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<CafeReactFsName name={file.name} />
</div>
)
}
+48
View File
@@ -0,0 +1,48 @@
import { CafeReactFsDirectory } from './CafeReactFsDirectory'
import { CafeReactFsFile } from './CafeReactFsFile'
import { FsItem, isVirtualDirectory, isVirtualFile } from './CafeReactType'
interface Props {
path: string
item: FsItem
download: (path: string) => Promise<void>
enterDirectory: (name: string) => void
onDeleteFile: (path: string) => Promise<void>
onDeleteDirectory: (path: string) => Promise<void>
backgroundColor: string
}
export function CafeReactFsItem({
path,
item,
download,
enterDirectory,
onDeleteFile,
onDeleteDirectory,
backgroundColor,
}: Props) {
if (isVirtualFile(item)) {
return (
<CafeReactFsFile
path={path}
file={item}
download={download}
deleteFile={onDeleteFile}
backgroundColor={backgroundColor}
/>
)
}
if (isVirtualDirectory(item)) {
return (
<CafeReactFsDirectory
directory={item}
enterDirectory={enterDirectory}
deleteDirectory={onDeleteDirectory}
backgroundColor={backgroundColor}
/>
)
}
return null
}
+52
View File
@@ -0,0 +1,52 @@
import { CSSProperties } from 'react'
interface Props {
backgroundColor: string
}
export function CafeReactFsLoading({ backgroundColor }: Props) {
const spinnerStyle = {
width: '80px',
height: '80px',
borderRadius: '2px',
position: 'relative',
background: backgroundColor,
} as CSSProperties
const bounceStyle = {
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: '#333',
top: '24px',
left: '24px',
opacity: 0.6,
position: 'absolute',
animation: 'bounce 2.0s infinite ease-in-out',
} as CSSProperties
const bounceStyle2 = {
...bounceStyle,
animationDelay: '-1.0s',
}
const keyframes = `
@keyframes bounce {
0%, 100% {
transform: scale(0.0);
} 50% {
transform: scale(1.0);
}
}
`
return (
<>
<style>{keyframes}</style>
<div style={spinnerStyle}>
<div style={bounceStyle}></div>
<div style={bounceStyle2}></div>
</div>
</>
)
}
+25
View File
@@ -0,0 +1,25 @@
interface Props {
name: string
}
export function CafeReactFsName({ name }: Props) {
const shortName = name.length > 10 ? name.slice(0, 10) + '...' : name
return (
<p
title={name}
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
{shortName}
</p>
)
}
+44
View File
@@ -0,0 +1,44 @@
import { joinUrl } from './Utility'
interface Props {
pathParts: string[]
jumpToDirectory: (fullPath: string) => void
backgroundColor: string
rootAlias?: string
}
export function CafeReactFsPath({ pathParts, jumpToDirectory, backgroundColor, rootAlias }: Props) {
const absolutePaths: string[] = []
for (const pathPart of pathParts) {
if (absolutePaths.length === 0) {
absolutePaths.push(pathPart)
} else {
absolutePaths.push(joinUrl(absolutePaths[absolutePaths.length - 1], pathPart))
}
}
return (
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: '4px' }}>
{pathParts.map((part, index) => (
<button
key={index}
style={{
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
outline: 'none',
border: 'none',
fontSize: '20px',
padding: '4px 16px',
}}
onClick={() => {
jumpToDirectory(absolutePaths[index])
}}
>
{rootAlias && part === '/' ? rootAlias : part}
</button>
))}
</div>
)
}
+40
View File
@@ -0,0 +1,40 @@
interface Props {
backgroundColor: string
onSync: () => Promise<void>
}
export function CafeReactFsSync({ backgroundColor, onSync }: Props) {
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={onSync}
>
<img
alt="Sync"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20156%20l%20250%200%20l%200%20150%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20356%20256%20l%2050%2050%20l%2050%20-50%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20356%20356%20l%20-250%200%20l%200%20-150%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20156%20256%20l%20-50%20-50%20l%20-50%2050%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
Sync
</p>
</div>
)
}
+54
View File
@@ -0,0 +1,54 @@
import { useState } from 'react'
import { CafeReactFsLoading } from './CafeReactFsLoading'
interface Props {
onUpload: () => Promise<void>
backgroundColor: string
}
export function CafeReactFsUpload({ onUpload, backgroundColor }: Props) {
const [uploading, setUploading] = useState(false)
function proxyUpload() {
setUploading(true)
onUpload().finally(() => setUploading(false))
}
if (uploading) {
return <CafeReactFsLoading backgroundColor={backgroundColor} />
}
return (
<div
style={{
width: '80px',
height: '80px',
position: 'relative',
background: backgroundColor,
borderRadius: '2px',
cursor: 'pointer',
}}
onClick={proxyUpload}
>
<img
alt="Upload"
style={{ width: '64px', height: '64px', position: 'absolute', left: '8px', top: 0 }}
src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%20156%20131%20l%20150%200%20l%2050%2050%20l%200%20200%20l%20-200%200%20z%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20306%20131%20l%200%2050%20l%2050%200%22%20stroke%3D%22%231F2D3D%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20206%20256%20l%20100%200%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3Cpath%20d%3D%22M%20256%20206%20l%200%20100%22%20stroke%3D%22%23B83B5E%22%20stroke-width%3D%2220%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%20%2F%3E%3C%2Fsvg%3E"
/>
<p
style={{
margin: 0,
fontFamily: 'sans-serif',
fontSize: '12px',
textAlign: 'center',
width: '80px',
position: 'absolute',
bottom: '5px',
left: 0,
}}
>
Upload
</p>
</div>
)
}
+26
View File
@@ -0,0 +1,26 @@
export enum FsItemType {
FILE = 'file',
DIRECTORY = 'directory',
}
export interface VirtualFile {
id: string | number
name: string
$type: FsItemType.FILE
}
export interface VirtualDirectory {
id: string | number
name: string
$type: FsItemType.DIRECTORY
}
export type FsItem = VirtualFile | VirtualDirectory
export function isVirtualFile(item: FsItem): item is VirtualFile {
return item.$type === FsItemType.FILE
}
export function isVirtualDirectory(item: FsItem): item is VirtualDirectory {
return item.$type === FsItemType.DIRECTORY
}
+6
View File
@@ -0,0 +1,6 @@
export function joinUrl(...parts: unknown[]): string {
return parts
.filter(x => x)
.join('/')
.replace(/(?<!:)\/+/g, '/')
}
+4 -1
View File
@@ -5,6 +5,7 @@ import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
import { AccountStaking } from './pages/account/staking/AccountStaking'
import { AccountStamps } from './pages/account/stamps/AccountStamps'
import { AccountWallet } from './pages/account/wallet/AccountWallet'
import FDP from './pages/fdp'
import CreateNewFeed from './pages/feeds/CreateNewFeed'
import { FeedSubpage } from './pages/feeds/FeedSubpage'
import UpdateFeed from './pages/feeds/UpdateFeed'
@@ -17,6 +18,7 @@ import Info from './pages/info'
import LightModeRestart from './pages/restart/LightModeRestart'
import Settings from './pages/settings'
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage'
import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage'
import Status from './pages/status'
import TopUp from './pages/top-up'
import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex'
@@ -25,7 +27,6 @@ import { GiftCardFund } from './pages/top-up/GiftCardFund'
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
import { Swap } from './pages/top-up/Swap'
import { Context as SettingsContext } from './providers/Settings'
import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage'
export enum ROUTES {
INFO = '/',
@@ -55,6 +56,7 @@ export enum ROUTES {
ACCOUNT_FEEDS_VIEW = '/account/feeds/:uuid',
ACCOUNT_INVITATIONS = '/account/invitations',
ACCOUNT_STAKING = '/account/staking',
FDP = '/fdp',
}
export const ACCOUNT_TABS = [
@@ -95,6 +97,7 @@ const BaseRouter = (): ReactElement => {
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
<Route path={ROUTES.ACCOUNT_STAKING} element={<AccountStaking />} />
<Route path={ROUTES.FDP} element={<FDP />} />
{isDesktop && <Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />}
</Routes>
)
+7 -2
View File
@@ -1,3 +1,6 @@
import { isSupportedImageType } from './image'
import { isSupportedVideoType } from './video'
const indexHtmls = ['index.html', 'index.htm']
interface DetectedIndex {
@@ -83,12 +86,14 @@ export function getAssetNameFromFiles(files: FilePath[]): string {
export function getMetadata(files: FilePath[]): Metadata {
const size = files.reduce((total, item) => total + item.size, 0)
const isWebsite = Boolean(detectIndexHtml(files))
const name = getAssetNameFromFiles(files)
const type = files.length === 1 ? files[0].type : 'folder'
const count = files.length
const isWebsite = Boolean(detectIndexHtml(files))
const isVideo = isSupportedVideoType(type)
const isImage = isSupportedImageType(type)
return { size, name, type, isWebsite, count }
return { size, name, type, isWebsite, count, isVideo, isImage }
}
export function getPath(file: FilePath): string {
+23 -10
View File
@@ -25,6 +25,28 @@ export function getDimensions(imgWidth: number, imgHeight: number, maxWidth?: nu
return { width: imgWidth / ratio, height: imgHeight / ratio }
}
function getAllowedTypes(): string[] {
return [
'image/bmp',
'image/gif',
'image/vnd.microsoft.icon',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
]
}
/**
* Check if the image type is supported
*
* @param type Image MIME type
*
* @returns True if the type is supported, false otherwise
*/
export const isSupportedImageType = (type: string): boolean => getAllowedTypes().includes(type)
/**
* Resize image passed to fit in the bounding box defined with maxWidth and maxHeight.
* Note that one or both of the bounding box dimensions may be omitted
@@ -37,16 +59,7 @@ export function getDimensions(imgWidth: number, imgHeight: number, maxWidth?: nu
*/
export function resize(file: File, maxWidth?: number, maxHeight?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
const allowedTypes = [
'image/bmp',
'image/gif',
'image/vnd.microsoft.icon',
'image/jpeg',
'image/png',
'image/svg+xml',
'image/tiff',
'image/webp',
]
const allowedTypes = getAllowedTypes()
if (!file.size || !file.type || !allowedTypes.includes(file.type)) return reject('File not supported!')
+8 -2
View File
@@ -50,14 +50,20 @@ export class ManifestJs {
/**
* Retrieves all paths with the associated hashes from a Swarm manifest
*/
public async getHashes(hash: string): Promise<Record<string, string>> {
public async getHashes(hash: string, options?: { exclude: string[] }): Promise<Record<string, string>> {
const data = await this.bee.downloadData(hash)
const node = new MantarayNode()
node.deserialize(data)
await loadAllNodes(this.load.bind(this), node)
const result = {}
const result: Record<string, string> = {}
this.extractHashes(result, node)
if (options?.exclude) {
for (const path of options.exclude) {
delete result[path]
}
}
return result
}
+7 -1
View File
@@ -66,7 +66,13 @@ export async function sendNativeTransaction(
): Promise<TransferResponse> {
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
const gasPrice = externalGasPrice ?? (await signer.getGasPrice())
const transaction = await signer.sendTransaction({ to, value, gasPrice })
const transaction = await signer.sendTransaction({
to,
value: BN.from(value),
gasPrice,
gasLimit: BN.from(21000),
type: 0,
})
const receipt = await transaction.wait(1)
return { transaction, receipt }
+8
View File
@@ -0,0 +1,8 @@
export function isSupportedVideoType(type: string) {
const video = document.createElement('video')
const result = video.canPlayType(type)
const isDefinitelySupported = result && result !== 'maybe'
return Boolean(isDefinitelySupported)
}
+3 -1
View File
@@ -18,7 +18,9 @@ async function getData(url) {
function processData(data) {
const db = new Map()
data.nodes.forEach(node => {
db.set(node.overlay, { lat: node.location.latitude, lng: node.location.longitude })
if (node.location) {
db.set(node.overlay, { lat: node.location.latitude, lng: node.location.longitude })
}
})
return Object.fromEntries([...db.entries()].sort())