From 0f7377abe9c4f3a729acc787bbcb238da17cbae1 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:08:59 +0200 Subject: [PATCH 01/22] Update next config to handle images from other domain --- next.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/next.config.js b/next.config.js index 310e00b..5fd8bbc 100644 --- a/next.config.js +++ b/next.config.js @@ -10,6 +10,10 @@ const nextConfig = { }, ]; }, + images: { + domains: ['defillama.com'], + formats: ['image/avif', 'image/webp'], + }, pageExtensions: ['page.tsx'], }; From dce7868184713a24c2b710c5eed2b1a881cf342b Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:09:11 +0200 Subject: [PATCH 02/22] Add colorthief --- package.json | 1 + yarn.lock | 185 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 172 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 7782ce8..70a22b4 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@storybook/client-api": "^6.5.10", "@storybook/manager-webpack5": "^6.4.19", "bignumber.js": "^9.0.2", + "colorthief": "^2.3.2", "esm": "^3.2.25", "lodash": "^4.17.21", "next": "12.1.0", diff --git a/yarn.lock b/yarn.lock index 1dfbb89..1ae832a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1768,9 +1768,9 @@ "@hapi/hoek" "^9.0.0" "@headlessui/react@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.5.0.tgz#483b44ba2c8b8d4391e1d2c863898d7dd0cc0296" - integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ== + version "1.6.6" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.6.tgz#3073c066b85535c9d28783da0a4d9288b5354d0c" + integrity sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q== "@humanwhocodes/config-array@^0.9.2": version "0.9.5" @@ -4375,7 +4375,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -6464,6 +6464,14 @@ colors@1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +colorthief@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/colorthief/-/colorthief-2.3.2.tgz#00b984f421abe5a2af71c4d464c9d80d407fd53d" + integrity sha512-1r4nPW553JviRcFRvN3fS2V9nUSQGjRIws8UfEeFLIxk8j1tvtaX+AAYTkH3A4B5Muiys8SA1WJxf+00xVTXyg== + dependencies: + get-pixels "^3.3.2" + quantize "github:lokesh/quantize" + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -6898,6 +6906,13 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== +cwise-compiler@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5" + integrity sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ== + dependencies: + uniq "^1.0.0" + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -6972,6 +6987,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz#18ae979a6a0ca994b0625853916d2662bbae0b1a" + integrity sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw== + data-urls@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.1.tgz#597fc2ae30f8bc4dbcf731fcd1b1954353afc6f8" @@ -8574,6 +8594,23 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-pixels@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/get-pixels/-/get-pixels-3.3.3.tgz#71e2dfd4befb810b5478a61c6354800976ce01c7" + integrity sha512-5kyGBn90i9tSMUVHTqkgCHsoWoR+/lGbl4yC83Gefyr0HLIhgSWEx/2F/3YgsZ7UpYNuM6pDhDK7zebrUJ5nXg== + dependencies: + data-uri-to-buffer "0.0.3" + jpeg-js "^0.4.1" + mime-types "^2.0.1" + ndarray "^1.0.13" + ndarray-pack "^1.1.1" + node-bitmap "0.0.1" + omggif "^1.0.5" + parse-data-uri "^0.2.0" + pngjs "^3.3.3" + request "^2.44.0" + through "^2.3.4" + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -8775,6 +8812,19 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -9106,6 +9156,15 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -9256,6 +9315,11 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +iota-array@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087" + integrity sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA== + ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -9340,7 +9404,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -9901,6 +9965,11 @@ joi@^17.6.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +jpeg-js@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -10071,6 +10140,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -10631,6 +10710,13 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +mime-types@^2.0.1, mime-types@^2.1.27, mime-types@^2.1.30, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@^2.1.12: version "2.1.34" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" @@ -10638,13 +10724,6 @@ mime-types@^2.1.12: dependencies: mime-db "1.51.0" -mime-types@^2.1.27, mime-types@^2.1.30, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -10893,6 +10972,22 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +ndarray-pack@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ndarray-pack/-/ndarray-pack-1.2.1.tgz#8caebeaaa24d5ecf70ff86020637977da8ee585a" + integrity sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g== + dependencies: + cwise-compiler "^1.1.2" + ndarray "^1.0.13" + +ndarray@^1.0.13: + version "1.0.19" + resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e" + integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ== + dependencies: + iota-array "^1.0.0" + is-buffer "^1.0.2" + negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -10965,6 +11060,11 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-bitmap@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/node-bitmap/-/node-bitmap-0.0.1.tgz#180eac7003e0c707618ef31368f62f84b2a69091" + integrity sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA== + node-dir@^0.1.10: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -11091,6 +11191,11 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -11193,6 +11298,11 @@ objectorarray@^1.0.5: resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5" integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg== +omggif@^1.0.5: + version "1.0.10" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" + integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -11432,6 +11542,13 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-data-uri@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" + integrity sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w== + dependencies: + data-uri-to-buffer "0.0.3" + parse-entities@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" @@ -11628,6 +11745,11 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" +pngjs@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -12038,6 +12160,10 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +"quantize@github:lokesh/quantize": + version "1.2.0" + resolved "https://codeload.github.com/lokesh/quantize/tar.gz/f572abd2646b5944852535c8a26fdb958a5d7c4b" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -12581,6 +12707,32 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" +request@^2.44.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -13158,7 +13310,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.14.1: +sshpk@^1.14.1, sshpk@^1.7.0: version "1.17.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== @@ -13671,7 +13823,7 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.8: +through@^2.3.4, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -14036,6 +14188,11 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +uniq@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" From fc823367d2bb8a6efa24650b479fff172df59672 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:09:24 +0200 Subject: [PATCH 03/22] Add animations to tailwind config --- tailwind.config.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tailwind.config.js b/tailwind.config.js index c3d8b9d..4bccf4c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -38,9 +38,42 @@ module.exports = { transform: 'translateX(100%)', }, }, + 'gradient-y': { + '0%, 100%': { + 'background-size': '400% 400%', + 'background-position': 'center top', + }, + '50%': { + 'background-size': '200% 200%', + 'background-position': 'center center', + }, + }, + 'gradient-x': { + '0%, 100%': { + 'background-size': '200% 200%', + 'background-position': 'left center', + }, + '50%': { + 'background-size': '200% 200%', + 'background-position': 'right center', + }, + }, + 'gradient-xy': { + '0%, 100%': { + 'background-size': '400% 400%', + 'background-position': 'left center', + }, + '50%': { + 'background-size': '200% 200%', + 'background-position': 'right center', + }, + }, }, animation: { shimmer: 'shimmer 2s ease-in-out infinite', + 'gradient-x': 'gradient-x 6s ease infinite', + 'gradient-y': 'gradient-y 6s ease infinite', + 'gradient-xy': 'gradient-xy 6s ease infinite', }, typography: (theme) => ({ DEFAULT: { From 7dbd5a32fa2644ac3d1e8cea21f12e4e7cd0af68 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:09:45 +0200 Subject: [PATCH 04/22] Move safeFetch to separate file --- src/misc/safeFetch.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/misc/safeFetch.ts diff --git a/src/misc/safeFetch.ts b/src/misc/safeFetch.ts new file mode 100644 index 0000000..6b0d0e5 --- /dev/null +++ b/src/misc/safeFetch.ts @@ -0,0 +1,11 @@ +export async function safeFetch( + ...args: Parameters +): Promise { + return fetch(...args).then(async (response) => { + if (response.status === 200) { + return response.json() as unknown as T; + } else { + throw new Error(`${response.status} ${response.statusText}`); + } + }); +} From 912c90212e30e5015cf62e57e9189ec60d47d574 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:09:59 +0200 Subject: [PATCH 05/22] Update imports --- src/lib/decodeBySigHash.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/lib/decodeBySigHash.ts b/src/lib/decodeBySigHash.ts index 5576adc..41ee18a 100644 --- a/src/lib/decodeBySigHash.ts +++ b/src/lib/decodeBySigHash.ts @@ -1,5 +1,5 @@ import { Interface } from '@ethersproject/abi'; -import fetch from 'node-fetch'; +import { safeFetch } from 'src/misc/safeFetch'; import { hexSchema } from '../misc/validation/schemas/hexSchema'; import { decodeCalldata, DecodeResult } from './decodeCalldata'; @@ -78,16 +78,6 @@ async function fetch4Bytes( return result; } -async function safeFetch(...args: Parameters): Promise { - return fetch(...args).then(async (response) => { - if (response.status === 200) { - return response.json() as unknown as T; - } else { - throw new Error(`${response.status} ${response.statusText}`); - } - }); -} - // @internal interface FourBytesReponseEntry { id: number; From 34df78dd7543a63343dfb35df2403394e34626fb Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:10:15 +0200 Subject: [PATCH 06/22] Create wip Table component --- src/components/Table/Table.tsx | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/components/Table/Table.tsx diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx new file mode 100644 index 0000000..89aa0bd --- /dev/null +++ b/src/components/Table/Table.tsx @@ -0,0 +1,78 @@ +import { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react'; + +function TableRoot({ + children, + className, + ...props +}: TableProps): ReactElement { + return ( +
+ + {children} +
+
+ ); +} + +function Head({ children }: HeadProps): ReactElement { + return ( + + + {children} + + + ); +} + +interface HeadProps { + children: ReactNode; +} + +function HeadRow({ children }: HeadRowProps): ReactElement { + return ( + + {children} + + ); +} + +interface HeadRowProps { + children: ReactNode; +} + +function Rows({ children }: RowsProps): ReactElement { + return ( + + + {children} + + + ); +} + +interface RowsProps { + children: ReactNode; +} + +function Row({ children }: RowProps): ReactElement { + return {children}; +} + +interface RowProps { + children: ReactNode; +} + +export const Table = Object.assign(TableRoot, { Rows, Row, Head, HeadRow }); + +interface TableProps extends ComponentPropsWithoutRef<'div'> { + children: ReactNode; +} From b1c12ccf349ff53549731ee6362185ebdef8385a Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:11:04 +0200 Subject: [PATCH 07/22] Add missing export --- src/components/Table/index.tsx | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/components/Table/index.tsx diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx new file mode 100644 index 0000000..75193ad --- /dev/null +++ b/src/components/Table/index.tsx @@ -0,0 +1 @@ +export * from './Table'; From 7646465aa395f38954ae2c85a65de201b9ccbb36 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:11:31 +0200 Subject: [PATCH 08/22] Minor changes, add icons --- src/components/icons/CoinStackIcon.tsx | 19 +++++++++++++++++++ src/components/icons/EthereumIcon.tsx | 18 ++++++++++++++++++ src/components/icons/ExploreIcon.tsx | 19 +++++++++++++++++++ src/components/icons/LinkIcon.tsx | 19 +++++++++++++++++++ src/components/icons/StackIcon.tsx | 19 +++++++++++++++++++ src/components/lib/Button/Button.tsx | 4 ++-- src/components/lib/Entity/Entity.tsx | 2 +- 7 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/components/icons/CoinStackIcon.tsx create mode 100644 src/components/icons/EthereumIcon.tsx create mode 100644 src/components/icons/ExploreIcon.tsx create mode 100644 src/components/icons/LinkIcon.tsx create mode 100644 src/components/icons/StackIcon.tsx diff --git a/src/components/icons/CoinStackIcon.tsx b/src/components/icons/CoinStackIcon.tsx new file mode 100644 index 0000000..7f83469 --- /dev/null +++ b/src/components/icons/CoinStackIcon.tsx @@ -0,0 +1,19 @@ +import React, { ReactElement, SVGProps } from 'react'; + +export function CoinStackIcon(props: SVGProps): ReactElement { + return ( + + + + ); +} diff --git a/src/components/icons/EthereumIcon.tsx b/src/components/icons/EthereumIcon.tsx new file mode 100644 index 0000000..10963c2 --- /dev/null +++ b/src/components/icons/EthereumIcon.tsx @@ -0,0 +1,18 @@ +import React, { ReactElement, SVGProps } from 'react'; + +export function EthereumIcon(props: SVGProps): ReactElement { + return ( + + + + ); +} diff --git a/src/components/icons/ExploreIcon.tsx b/src/components/icons/ExploreIcon.tsx new file mode 100644 index 0000000..1cdb344 --- /dev/null +++ b/src/components/icons/ExploreIcon.tsx @@ -0,0 +1,19 @@ +import React, { ReactElement, SVGProps } from 'react'; + +export function ExploreIcon(props: SVGProps): ReactElement { + return ( + + + + ); +} diff --git a/src/components/icons/LinkIcon.tsx b/src/components/icons/LinkIcon.tsx new file mode 100644 index 0000000..ffd4d6c --- /dev/null +++ b/src/components/icons/LinkIcon.tsx @@ -0,0 +1,19 @@ +import React, { ReactElement, SVGProps } from 'react'; + +export function LinkIcon(props: SVGProps): ReactElement { + return ( + + + + ); +} diff --git a/src/components/icons/StackIcon.tsx b/src/components/icons/StackIcon.tsx new file mode 100644 index 0000000..805ad24 --- /dev/null +++ b/src/components/icons/StackIcon.tsx @@ -0,0 +1,19 @@ +import React, { ReactElement, SVGProps } from 'react'; + +export function StackIcon(props: SVGProps): ReactElement { + return ( + + + + ); +} diff --git a/src/components/lib/Button/Button.tsx b/src/components/lib/Button/Button.tsx index e0fea5b..5b21bd1 100644 --- a/src/components/lib/Button/Button.tsx +++ b/src/components/lib/Button/Button.tsx @@ -21,8 +21,8 @@ export const Button = forwardRef( /** @internal */ export const variants = { primary: - 'bg-gradient-to-r from-pink to-purple hover:outline-gray-50 ' + - 'hover:shadow-lg hover:shadow-pink/25', + 'bg-gradient-to-tr from-pink to-purple hover:outline-gray-50 ' + + 'hover:shadow-md hover:shadow-pink/25', secondary: 'bg-gray-600 text-gray-300 hover:shadow-lg hover:shadow-white/10', tertiary: 'border border-gray-600 bg-gray-900 text-gray-400', text: '', diff --git a/src/components/lib/Entity/Entity.tsx b/src/components/lib/Entity/Entity.tsx index 9e6f2d8..84c52ac 100644 --- a/src/components/lib/Entity/Entity.tsx +++ b/src/components/lib/Entity/Entity.tsx @@ -9,7 +9,7 @@ export function Entity({ }: EntityProps): ReactElement { return (
Date: Tue, 23 Aug 2022 15:11:46 +0200 Subject: [PATCH 09/22] Add wip Chain component --- src/components/Chain/Chain.tsx | 382 +++++++++++++++++++++++++++++++++ src/components/Chain/index.tsx | 1 + 2 files changed, 383 insertions(+) create mode 100644 src/components/Chain/Chain.tsx create mode 100644 src/components/Chain/index.tsx diff --git a/src/components/Chain/Chain.tsx b/src/components/Chain/Chain.tsx new file mode 100644 index 0000000..e1601d9 --- /dev/null +++ b/src/components/Chain/Chain.tsx @@ -0,0 +1,382 @@ +// @ts-ignore +import colorthief from 'colorthief'; +import Image from 'next/image'; +import Link from 'next/link'; +import React, { + ComponentPropsWithoutRef, + Dispatch, + FC, + ReactElement, + ReactNode, + SetStateAction, + useMemo, + useState, +} from 'react'; + +import { Chain } from '../../../pages/chain-list/index.page'; +import { CoinStackIcon } from '../icons/CoinStackIcon'; +import { EthereumIcon } from '../icons/EthereumIcon'; +import { ExploreIcon } from '../icons/ExploreIcon'; +import { LinkIcon } from '../icons/LinkIcon'; +import { Table } from '../Table'; + +const colors = require('../../../tailwind.config').theme.colors; + +const rgbToHex = (r: number, g: number, b: number): string => + '#' + + [r, g, b] + .map((x) => { + const hex = x.toString(16); + return hex.length === 1 ? '0' + hex : hex; + }) + .join(''); + +const ChainDetailPanelElements = [ + 'RPC', + 'Explorers', + 'Faucets', + 'URL', +] as const; + +type ChainDetailPanelElement = typeof ChainDetailPanelElements[number]; + +export function ChainEntity({ + chain, + className, + ...props +}: ChainEntityProps): ReactElement { + const [selected, setSelected] = useState< + ChainDetailPanelElement | undefined + >(); + + const icon = useMemo(() => { + try { + return ( + chain.icon && `https://defillama.com/chain-icons/rsz_${chain.icon}.jpg` + ); + } catch (error) {} + }, [chain]); + + const [dominantColors, setDominantColors] = useState(); + + return ( +
+ {dominantColors ? ( + <> +
+
+ + ) : ( + <> +
+
+ + )} +
+
+
+ {icon ? ( +
+ { + const img = document.getElementById( + `chain-icon-${chain.chainId}`, + ); + const colorThief = new colorthief(); + try { + const result = colorThief.getPalette( + img, + 3, + ) as number[][]; + const hexArray = result.map((rgb) => + rgbToHex(rgb[0], rgb[1], rgb[2]), + ); + setDominantColors(hexArray); + } catch (error) {} + }} + className="absolute rounded blur" + src={icon} + layout="fill" + objectFit="contain" + /> +
+ ) : ( +
+ +
+ )} + +
+
+

chain id:

+ {chain.chainId} +
+ +

+ {chain.name} +

+

+ TVL: {chain.tvl?.toString().split('.')[0]}$ +

+
+
+ +
+ + {chain.nativeCurrency?.symbol} + {chain.nativeCurrency?.decimals} + +

+ {chain.nativeCurrency?.name} +

+
+
+ + +
+ + 0 || false} + name="RPC" + selectedState={[selected, setSelected]} + > + RPC + + + + {chain.infoURL} + + + 0) || false} + name="Explorers" + type="button" + selectedState={[selected, setSelected]} + > + + + + 0) || false} + name="Faucets" + type="button" + selectedState={[selected, setSelected]} + > + + + +
+ {chain.ens ? ( +

ENS ✔

+ ) : ( +

+ ENS +

+ )} +
+
+ + + {chain.explorers && + chain.explorers.length > 0 && + selected === 'Explorers' && ( + + + name + standard + url + + {chain.explorers.map((explorer) => { + return ( + + {explorer.name} + {explorer.standard} + + +

+ {explorer.url} +

+ +
+
+ ); + })} +
+ )} + + {selected === 'RPC' && chain.rpc.length > 0 && ( + <> + + + alive + latency + url + + {chain.rpc.map((url) => { + return ( + + yes + 0ms + {url} + + ); + })} +
+ + )} + + {chain.faucets && + chain.faucets.length > 0 && + selected === 'Faucets' && ( + <> + + + id + url + + {chain.faucets.map((faucet, index) => { + return ( + + {index} + {faucet} + + ); + })} +
+ + )} +
+
+
+
+ ); +} + +interface ChainEntityProps extends ComponentPropsWithoutRef<'div'> { + chain: Chain; +} + +function ChainDetailsRoot({ + children, + ...props +}: ChainDetailsRootProps): ReactElement { + return ( +
+
{children}
+
+ ); +} + +interface ChainDetailsRootProps extends ComponentPropsWithoutRef<'div'> { + children: ReactNode; +} + +// this type guard is really narrowed, as ReactNode +// could be also a boolean, null etc., +// but for this use case it does its job +function isChildReactElement( + children: ReactElement | ReactNode, +): children is ReactElement { + return ( + (children && typeof children === 'object' && 'type' in children) || false + ); +} + +const Element: FC = ({ + children, + type = 'button', + selectedState, + show = true, + name, +}): ReactElement | null => { + const [selected, setSelected] = selectedState; + + return show ? ( + <> + {type === 'button' ? ( + + ) : ( + + + + )} + + ) : null; +}; + +interface ElementProps { + type?: 'button' | 'link'; + show?: boolean; + name: ChainDetailPanelElement; + children: ReactElement | string; + selectedState: [ + ChainDetailPanelElement | undefined, + Dispatch>, + ]; +} + +const Panel: FC = ({ children }) => { + return
{children}
; +}; + +const Data: FC = ({ children, selected }) => { + return <>{children}; +}; + +interface DataProps { + selected?: ChainDetailPanelElement; +} + +const ChainDetails = Object.assign(ChainDetailsRoot, { Panel, Data, Element }); diff --git a/src/components/Chain/index.tsx b/src/components/Chain/index.tsx new file mode 100644 index 0000000..b7930ab --- /dev/null +++ b/src/components/Chain/index.tsx @@ -0,0 +1 @@ +export * from './Chain'; From f2bf83c324d620d62445fceda83d461d0db229c4 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:12:08 +0200 Subject: [PATCH 10/22] Add util classes to global css --- src/globals.css | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/globals.css b/src/globals.css index 734d7bd..019dc84 100644 --- a/src/globals.css +++ b/src/globals.css @@ -29,3 +29,29 @@ input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { opacity: 1; } + +.blur { + content: ''; + -webkit-filter: blur(0.4px); + filter: blur(0.4px); +} + +.blur-xl { + filter: blur(36px); +} + +.animate-gradient { + animation: gradient 15s ease infinite; +} + +@keyframes gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} From d25f1a9c0403d2ac44ed8639537c9e2ed42f8ade Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:12:35 +0200 Subject: [PATCH 11/22] Add chain list page, add new tool to the tool tree --- pages/chain-list/index.page.tsx | 288 ++++++++++++++++++++++++++++++++ src/components/ToolTree.tsx | 10 ++ 2 files changed, 298 insertions(+) create mode 100644 pages/chain-list/index.page.tsx diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx new file mode 100644 index 0000000..90d5b6f --- /dev/null +++ b/pages/chain-list/index.page.tsx @@ -0,0 +1,288 @@ +import { GetStaticProps, NextPage } from 'next'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { ChainEntity } from 'src/components/Chain'; +import { StackIcon } from 'src/components/icons/StackIcon'; +import { Header } from 'src/components/lib/Header'; +import { ToolContainer } from 'src/components/ToolContainer'; +import { safeFetch } from 'src/misc/safeFetch'; + +import { Button } from 'src/components/lib/Button'; + +import { Combobox, Dialog } from '@headlessui/react'; +import { uniqBy } from 'lodash'; +import { Entity } from 'src/components/lib/Entity'; + +function MyDialog({ isOpenState, selectedChain }: MyDialogProps): ReactElement { + const [isOpen, setIsOpen] = isOpenState; + + return ( + setIsOpen(false)} + className="relative z-50" + > + + ); +} + +interface MyDialogProps { + selectedChain: Chain; + isOpenState: [boolean, (isOpen: boolean) => void]; +} + +function MyCombobox({ + queryState, + filtered, + selectedItemState, + setIsOpen, +}: MyComboboxProps): ReactElement { + const [selectedItem, setSelectedItem] = selectedItemState; + const [query, setQuery] = queryState; + + return ( + { + const found = filtered.find( + (chain) => chain.name === (item as unknown as string), + ); + setSelectedItem(found); + setIsOpen(true); + }} + > +
+ setQuery(event.target.value)} + onClick={() => setSelectedItem(undefined)} + /> + +
+ +
+ {filtered.map((chain) => ( + + {({ active, selected }) => ( +

+ {chain.name} +

+ )} +
+ ))} +
+ +
+ + ); +} + +interface MyComboboxProps { + filtered: T[]; + queryState: [string, (query: string) => void]; + selectedItemState: [T | undefined, (chain: T | undefined) => void]; + setIsOpen: (isOpen: boolean) => void; +} + +export const getStaticProps: GetStaticProps = async (): Promise<{ + props: PageProps; +}> => { + let fetchedChains = await safeFetch( + 'https://chainid.network/chains.json', + ).then((response) => uniqBy(response, 'chain')); + + const chainTvls = await safeFetch('https://api.llama.fi/chains'); + + fetchedChains = fetchedChains + .map((chain) => { + const found = chainTvls.find( + (chainTvl) => chainTvl.chainId === chain.chainId, + ); + return found + ? { + ...chain, + tvl: found.tvl, + } + : chain; + }) + .sort((a, b) => (b.tvl ?? 0) - (a.tvl ?? 0)); + + return { + props: { + fetchedChains, + }, + }; +}; + +const ChainList: NextPage = ({ fetchedChains }) => { + const [chains, setChains] = useState(fetchedChains); + const [filtered, setFiltered] = useState([]); + const [page, setPage] = useState({ currentPage: 1, chainsPerPage: 10 }); + + const [selectedChain, setSelectedChain] = useState(); + const [isOpen, setIsOpen] = useState(false); + const [query, setQuery] = useState(''); + + useEffect(() => { + const filtered = + query === '' + ? chains.slice(0, 20) + : chains + .filter(({ name }) => { + return name.toLowerCase().includes(query.toLowerCase()); + }) + .slice(0, 20); + setFiltered(filtered); + }, [query, chains]); + + const indexOfLastPost = page.currentPage * page.chainsPerPage; + const indexOfFirstPost = indexOfLastPost - page.chainsPerPage; + const currentChains = chains.slice(indexOfFirstPost, indexOfLastPost); + + return ( + +
} + text={['Lists', 'Chain List']} + /> + +
+ + + + + + + + +
+ 0 ? filtered : currentChains} + /> + + ); +}; + +function PaginatedChains({ chains }: PaginatedChainsProps): ReactElement { + return ( +
+ {chains.map((chain) => { + return ( + + ); + })} +
+ ); +} + +interface PaginatedChainsProps { + chains: Chain[]; +} + +export interface ChainTVL { + chainId: number; + cmcId: string; + gecko_id: string; + name: string; + tokenSymbol: string; + tvl: number; +} + +export interface Explorer { + name: string; + standard: string; + url: string; +} + +export interface NativeCurrency { + name: string; + symbol: string; + decimals: number; +} + +export interface Chain { + name: string; + shortName: string; + chain: string; + chainId: number; + networkId: number; + infoURL: string; + nativeCurrency?: NativeCurrency; + rpc: string[]; + slip44?: number; + ens?: { + // hex str + registry: string; + }; + explorers?: Explorer[]; + faucets?: string[]; + icon?: string; + tvl?: number; +} + +// @internal +interface PageProps { + fetchedChains: Array; +} + +export default ChainList; diff --git a/src/components/ToolTree.tsx b/src/components/ToolTree.tsx index e046858..4cf811a 100644 --- a/src/components/ToolTree.tsx +++ b/src/components/ToolTree.tsx @@ -8,6 +8,7 @@ import { EncodersIcon } from './icons/EncodersIcon'; import { GeneratorIcon } from './icons/GeneratorIcon'; import { MinusIcon } from './icons/MinusIcon'; import { PlusIcon } from './icons/PlusIcon'; +import { StackIcon } from './icons/StackIcon'; import { NavigationSocial } from './NavigationSocial'; interface ToolTreeProps extends React.ComponentPropsWithoutRef<'section'> { @@ -129,6 +130,15 @@ const tree: Tree = { }, ], }, + lists: { + icon: , + tools: [ + { + title: 'Chain list', + pageHref: 'chain-list', + }, + ], + }, }; // @internal From 2d4c42e7cef03cda1574ce1ec535e72844dfbee4 Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:13:18 +0200 Subject: [PATCH 12/22] Run lint & format --- pages/chain-list/index.page.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx index 90d5b6f..d4213fb 100644 --- a/pages/chain-list/index.page.tsx +++ b/pages/chain-list/index.page.tsx @@ -1,17 +1,15 @@ +import { Combobox, Dialog } from '@headlessui/react'; +import { uniqBy } from 'lodash'; import { GetStaticProps, NextPage } from 'next'; import React, { ReactElement, useEffect, useState } from 'react'; import { ChainEntity } from 'src/components/Chain'; import { StackIcon } from 'src/components/icons/StackIcon'; +import { Button } from 'src/components/lib/Button'; +import { Entity } from 'src/components/lib/Entity'; import { Header } from 'src/components/lib/Header'; import { ToolContainer } from 'src/components/ToolContainer'; import { safeFetch } from 'src/misc/safeFetch'; -import { Button } from 'src/components/lib/Button'; - -import { Combobox, Dialog } from '@headlessui/react'; -import { uniqBy } from 'lodash'; -import { Entity } from 'src/components/lib/Entity'; - function MyDialog({ isOpenState, selectedChain }: MyDialogProps): ReactElement { const [isOpen, setIsOpen] = isOpenState; From 3608fbe1a8f2e6dabb0bddec6afb2927dc92363e Mon Sep 17 00:00:00 2001 From: 7sne Date: Tue, 23 Aug 2022 15:14:57 +0200 Subject: [PATCH 13/22] Fix lint issues to make build pass --- pages/chain-list/index.page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx index d4213fb..b219081 100644 --- a/pages/chain-list/index.page.tsx +++ b/pages/chain-list/index.page.tsx @@ -43,7 +43,7 @@ function MyCombobox({ setIsOpen, }: MyComboboxProps): ReactElement { const [selectedItem, setSelectedItem] = selectedItemState; - const [query, setQuery] = queryState; + const [, setQuery] = queryState; return ( - {({ active, selected }) => ( + {({ active }) => (

= ({ fetchedChains }) => { - const [chains, setChains] = useState(fetchedChains); + const [chains] = useState(fetchedChains); const [filtered, setFiltered] = useState([]); const [page, setPage] = useState({ currentPage: 1, chainsPerPage: 10 }); From 9c577812c8fcb687506b2864467132d8d8188e7a Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:26:35 +0200 Subject: [PATCH 14/22] Add chain id slugs --- src/misc/liamaChainSlugs.ts | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/misc/liamaChainSlugs.ts diff --git a/src/misc/liamaChainSlugs.ts b/src/misc/liamaChainSlugs.ts new file mode 100644 index 0000000..754aef7 --- /dev/null +++ b/src/misc/liamaChainSlugs.ts @@ -0,0 +1,79 @@ +const chainIds: Record = { + 0: 'kardia', + 1: 'ethereum', + 2: 'expanse', + 8: 'ubiq', + 10: 'optimism', + 19: 'songbird', + 20: 'elastos', + 25: 'cronos', + 30: 'rsk', + 40: 'telos', + 50: 'xdc', + 52: 'csc', + 55: 'zyx', + 56: 'binance', + 57: 'syscoin', + 60: 'gochain', + 61: 'ethereumclassic', + 66: 'okexchain', + 70: 'hoo', + 82: 'meter', + 87: 'nova network', + 88: 'tomochain', + 100: 'xdai', + 106: 'velas', + 108: 'thundercore', + 122: 'fuse', + 128: 'heco', + 137: 'polygon', + 200: 'xdaiarb', + 246: 'energyweb', + 250: 'fantom', + 269: 'hpb', + 288: 'boba', + 321: 'kucoin', + 336: 'shiden', + 361: 'theta', + 416: 'sx', + 534: 'candle', + 592: 'astar', + 820: 'callisto', + 888: 'wanchain', + 970: 'ccn', + 971: 'ccnbeta', + 1088: 'metis', + 1246: 'omchain', + 1284: 'moonbeam', + 1285: 'moonriver', + 2000: 'dogechain', + 2020: 'ronin', + 2222: 'kava', + 2612: 'ezchain', + 4181: 'phi', + 4689: 'iotex', + 5050: 'xlc', + 5551: 'nahmii', + 7777: 'nmactest', + 8217: 'klaytn', + 9001: 'evmos', + 10000: 'smartbch', + 900000: 'posichain', + 103090: 'crystaleum', + 32659: 'fusion', + 42161: 'arbitrum', + 42170: 'arb-nova', + 42220: 'celo', + 42262: 'oasis', + 43114: 'avalanche', + 71402: 'godwoken', + 100100: 'chiado', + 200625: 'akroma', + 333999: 'polis', + 1313161554: 'aurora', + 1666600000: 'harmony', + 11297108109: 'palm', + 836542336838601: 'curio', +}; + +export default chainIds; From 8265669a814c90bcd285f5b7339ed52e952af003 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:26:49 +0200 Subject: [PATCH 15/22] Add Ilama API types --- src/types/liamaChainAPI.d.ts | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/types/liamaChainAPI.d.ts diff --git a/src/types/liamaChainAPI.d.ts b/src/types/liamaChainAPI.d.ts new file mode 100644 index 0000000..c14ecca --- /dev/null +++ b/src/types/liamaChainAPI.d.ts @@ -0,0 +1,41 @@ +export interface ChainTVL { + chainId: number; + cmcId: string; + gecko_id: string; + name: string; + tokenSymbol: string; + tvl: number; +} + +export interface Explorer { + name: string; + standard: string; + url: string; +} + +export interface NativeCurrency { + name: string; + symbol: string; + decimals: number; +} + +export interface Chain { + name: string; + shortName: string; + chain: string; + chainId: number; + networkId: number; + infoURL: string; + nativeCurrency?: NativeCurrency; + rpc: string[]; + slip44?: number; + ens?: { + // hex str + registry: string; + }; + chainSlug?: string; + explorers?: Explorer[]; + faucets?: string[]; + icon?: string; + tvl?: number; +} From b3d63628f95fb5d48af94e8d45e55debc4104256 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:27:25 +0200 Subject: [PATCH 16/22] Organise files, minor updates, optimize performance --- src/components/Chain/Chain.tsx | 155 ++++++-------------------- src/components/Chain/ChainDetails.tsx | 114 +++++++++++++++++++ src/types/util.d.ts | 4 + 3 files changed, 150 insertions(+), 123 deletions(-) create mode 100644 src/components/Chain/ChainDetails.tsx diff --git a/src/components/Chain/Chain.tsx b/src/components/Chain/Chain.tsx index e1601d9..8bfdc66 100644 --- a/src/components/Chain/Chain.tsx +++ b/src/components/Chain/Chain.tsx @@ -4,23 +4,17 @@ import Image from 'next/image'; import Link from 'next/link'; import React, { ComponentPropsWithoutRef, - Dispatch, - FC, ReactElement, - ReactNode, - SetStateAction, useMemo, useState, } from 'react'; +import { Chain } from 'src/types/liamaChainAPI'; -import { Chain } from '../../../pages/chain-list/index.page'; import { CoinStackIcon } from '../icons/CoinStackIcon'; import { EthereumIcon } from '../icons/EthereumIcon'; import { ExploreIcon } from '../icons/ExploreIcon'; -import { LinkIcon } from '../icons/LinkIcon'; import { Table } from '../Table'; - -const colors = require('../../../tailwind.config').theme.colors; +import { ChainDetailPanelElement, ChainDetails } from './ChainDetails'; const rgbToHex = (r: number, g: number, b: number): string => '#' + @@ -31,14 +25,9 @@ const rgbToHex = (r: number, g: number, b: number): string => }) .join(''); -const ChainDetailPanelElements = [ - 'RPC', - 'Explorers', - 'Faucets', - 'URL', -] as const; +type ChainEntityColorCacheKey = `chain-icon-${number}`; -type ChainDetailPanelElement = typeof ChainDetailPanelElements[number]; +const chainEntityColorCache: Record = {}; export function ChainEntity({ chain, @@ -52,7 +41,8 @@ export function ChainEntity({ const icon = useMemo(() => { try { return ( - chain.icon && `https://defillama.com/chain-icons/rsz_${chain.icon}.jpg` + chain.chainSlug && + `https://defillama.com/chain-icons/rsz_${chain.chainSlug}.jpg` ); } catch (error) {} }, [chain]); @@ -61,8 +51,9 @@ export function ChainEntity({ return (
{dominantColors ? ( @@ -75,16 +66,19 @@ export function ChainEntity({ style={{ background: `linear-gradient(-45deg, ${dominantColors[0]}, ${dominantColors[1]}, ${dominantColors[2]})`, }} - className={`mb-1 h-2 animate-gradient-x rounded-t bg-gradient-to-r from-pink to-purple`} + className={`mb-1 h-2 animate-gradient-x rounded-t + bg-gradient-to-r from-pink to-purple`} /> ) : ( <>
)} @@ -99,10 +93,18 @@ export function ChainEntity({ { + const currentKey: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; + if (currentKey in chainEntityColorCache) + return setDominantColors( + chainEntityColorCache[currentKey], + ); + const img = document.getElementById( `chain-icon-${chain.chainId}`, ); + const colorThief = new colorthief(); + try { const result = colorThief.getPalette( img, @@ -111,6 +113,10 @@ export function ChainEntity({ const hexArray = result.map((rgb) => rgbToHex(rgb[0], rgb[1], rgb[2]), ); + + const cacheChainId: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; + chainEntityColorCache[cacheChainId] = hexArray; + setDominantColors(hexArray); } catch (error) {} }} @@ -155,7 +161,7 @@ export function ChainEntity({
- +
- + {chain.explorers && chain.explorers.length > 0 && selected === 'Explorers' && ( @@ -238,15 +244,13 @@ export function ChainEntity({ <> - alive - latency + id url - {chain.rpc.map((url) => { + {chain.rpc.map((url, id) => { return ( - yes - 0ms + {id + 1} {url} ); @@ -285,98 +289,3 @@ export function ChainEntity({ interface ChainEntityProps extends ComponentPropsWithoutRef<'div'> { chain: Chain; } - -function ChainDetailsRoot({ - children, - ...props -}: ChainDetailsRootProps): ReactElement { - return ( -
-
{children}
-
- ); -} - -interface ChainDetailsRootProps extends ComponentPropsWithoutRef<'div'> { - children: ReactNode; -} - -// this type guard is really narrowed, as ReactNode -// could be also a boolean, null etc., -// but for this use case it does its job -function isChildReactElement( - children: ReactElement | ReactNode, -): children is ReactElement { - return ( - (children && typeof children === 'object' && 'type' in children) || false - ); -} - -const Element: FC = ({ - children, - type = 'button', - selectedState, - show = true, - name, -}): ReactElement | null => { - const [selected, setSelected] = selectedState; - - return show ? ( - <> - {type === 'button' ? ( - - ) : ( - - - - )} - - ) : null; -}; - -interface ElementProps { - type?: 'button' | 'link'; - show?: boolean; - name: ChainDetailPanelElement; - children: ReactElement | string; - selectedState: [ - ChainDetailPanelElement | undefined, - Dispatch>, - ]; -} - -const Panel: FC = ({ children }) => { - return
{children}
; -}; - -const Data: FC = ({ children, selected }) => { - return <>{children}; -}; - -interface DataProps { - selected?: ChainDetailPanelElement; -} - -const ChainDetails = Object.assign(ChainDetailsRoot, { Panel, Data, Element }); diff --git a/src/components/Chain/ChainDetails.tsx b/src/components/Chain/ChainDetails.tsx new file mode 100644 index 0000000..5825e9b --- /dev/null +++ b/src/components/Chain/ChainDetails.tsx @@ -0,0 +1,114 @@ +import React, { + ComponentPropsWithoutRef, + Dispatch, + ReactElement, + ReactNode, + SetStateAction, +} from 'react'; +import { ReactChildren } from 'src/types/util'; + +import { LinkIcon } from '../icons/LinkIcon'; + +const colors = require('../../../tailwind.config').theme.colors; + +export type ChainDetailPanelElement = typeof ChainDetailPanelElements[number]; + +const ChainDetailPanelElements = [ + 'RPC', + 'Explorers', + 'Faucets', + 'URL', +] as const; + +function ChainDetailsRoot({ + children, + ...props +}: ChainDetailsRootProps): ReactElement { + return ( +
+
{children}
+
+ ); +} + +interface ChainDetailsRootProps extends ComponentPropsWithoutRef<'div'> { + children: ReactNode; +} + +// this type guard is really narrowed, as ReactNode +// could be also a boolean, null etc., +// but for this use case it does its job +function isChildReactElement( + children: ReactElement | ReactNode, +): children is ReactElement { + return ( + (children && typeof children === 'object' && 'type' in children) || false + ); +} + +function Element({ + children, + type = 'button', + selectedState, + show = true, + name, +}: ElementProps): ReactElement | null { + const [selected, setSelected] = selectedState; + + return show ? ( + type === 'button' ? ( + + ) : ( + + + + ) + ) : null; +} + +interface ElementProps { + type?: 'button' | 'link'; + show?: boolean; + name: ChainDetailPanelElement; + children: ReactElement | string; + selectedState: [ + ChainDetailPanelElement | undefined, + Dispatch>, + ]; +} + +function Panel({ children }: ReactChildren): ReactElement { + return
{children}
; +} + +function Data({ children }: ReactChildren): ReactElement { + return <>{children}; +} + +export const ChainDetails = Object.assign(ChainDetailsRoot, { + Panel, + Data, + Element, +}); diff --git a/src/types/util.d.ts b/src/types/util.d.ts index 2f3700c..f9078f2 100644 --- a/src/types/util.d.ts +++ b/src/types/util.d.ts @@ -5,3 +5,7 @@ export type RequireAtLeastOne = Pick< { [K in Keys]-?: Required> & Partial>>; }[Keys]; + +export type ReactChildren = { children: ReactNode }; + +export type WithReactChildren = T & { children: ReactNode }; From 6fa80b8882be0b8ba871d4846541949a1a93b1d8 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:28:33 +0200 Subject: [PATCH 17/22] Make filtering and pagination better, move types and components out --- pages/chain-list/index.page.tsx | 291 +++++++++++++++----------------- 1 file changed, 136 insertions(+), 155 deletions(-) diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx index b219081..1b13458 100644 --- a/pages/chain-list/index.page.tsx +++ b/pages/chain-list/index.page.tsx @@ -8,97 +8,11 @@ import { Button } from 'src/components/lib/Button'; import { Entity } from 'src/components/lib/Entity'; import { Header } from 'src/components/lib/Header'; import { ToolContainer } from 'src/components/ToolContainer'; +import chainIds from 'src/misc/liamaChainSlugs'; import { safeFetch } from 'src/misc/safeFetch'; +import { Chain, ChainTVL } from 'src/types/liamaChainAPI'; -function MyDialog({ isOpenState, selectedChain }: MyDialogProps): ReactElement { - const [isOpen, setIsOpen] = isOpenState; - - return ( - setIsOpen(false)} - className="relative z-50" - > - - ); -} - -interface MyDialogProps { - selectedChain: Chain; - isOpenState: [boolean, (isOpen: boolean) => void]; -} - -function MyCombobox({ - queryState, - filtered, - selectedItemState, - setIsOpen, -}: MyComboboxProps): ReactElement { - const [selectedItem, setSelectedItem] = selectedItemState; - const [, setQuery] = queryState; - - return ( - { - const found = filtered.find( - (chain) => chain.name === (item as unknown as string), - ); - setSelectedItem(found); - setIsOpen(true); - }} - > -
- setQuery(event.target.value)} - onClick={() => setSelectedItem(undefined)} - /> - -
- -
- {filtered.map((chain) => ( - - {({ active }) => ( -

- {chain.name} -

- )} -
- ))} -
- -
- - ); -} - -interface MyComboboxProps { +interface ChainFilterProps { filtered: T[]; queryState: [string, (query: string) => void]; selectedItemState: [T | undefined, (chain: T | undefined) => void]; @@ -119,9 +33,11 @@ export const getStaticProps: GetStaticProps = async (): Promise<{ const found = chainTvls.find( (chainTvl) => chainTvl.chainId === chain.chainId, ); - return found + const chainSlug = chainIds[chain.chainId]; + return found && chainSlug ? { ...chain, + chainSlug, tvl: found.tvl, } : chain; @@ -136,7 +52,6 @@ export const getStaticProps: GetStaticProps = async (): Promise<{ }; const ChainList: NextPage = ({ fetchedChains }) => { - const [chains] = useState(fetchedChains); const [filtered, setFiltered] = useState([]); const [page, setPage] = useState({ currentPage: 1, chainsPerPage: 10 }); @@ -144,21 +59,24 @@ const ChainList: NextPage = ({ fetchedChains }) => { const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(''); + useEffect(() => { + if (query === '' && !selectedChain) setFiltered([]); + }, [query, selectedChain]); + useEffect(() => { const filtered = - query === '' - ? chains.slice(0, 20) - : chains - .filter(({ name }) => { - return name.toLowerCase().includes(query.toLowerCase()); - }) - .slice(0, 20); - setFiltered(filtered); - }, [query, chains]); + query.length > 0 && + fetchedChains + .filter(({ name }) => { + return name.toLowerCase().includes(query.toLowerCase()); + }) + .slice(0, 20); + if (filtered) setFiltered(filtered); + }, [query, fetchedChains]); const indexOfLastPost = page.currentPage * page.chainsPerPage; const indexOfFirstPost = indexOfLastPost - page.chainsPerPage; - const currentChains = chains.slice(indexOfFirstPost, indexOfLastPost); + const currentChains = fetchedChains.slice(indexOfFirstPost, indexOfLastPost); return ( @@ -166,13 +84,13 @@ const ChainList: NextPage = ({ fetchedChains }) => { icon={} text={['Lists', 'Chain List']} /> -
- = ({ fetchedChains }) => { + @@ -237,50 +160,108 @@ interface PaginatedChainsProps { chains: Chain[]; } -export interface ChainTVL { - chainId: number; - cmcId: string; - gecko_id: string; - name: string; - tokenSymbol: string; - tvl: number; +// @internal +interface PageProps { + fetchedChains: Array; } -export interface Explorer { - name: string; - standard: string; - url: string; -} +// @internal +function ChainDialog({ + isOpenState, + selectedChain, +}: ChainDialogProps): ReactElement { + const [isOpen, setIsOpen] = isOpenState; -export interface NativeCurrency { - name: string; - symbol: string; - decimals: number; + return ( + setIsOpen(false)} + className="relative z-50" + > + + ); } -export interface Chain { - name: string; - shortName: string; - chain: string; - chainId: number; - networkId: number; - infoURL: string; - nativeCurrency?: NativeCurrency; - rpc: string[]; - slip44?: number; - ens?: { - // hex str - registry: string; - }; - explorers?: Explorer[]; - faucets?: string[]; - icon?: string; - tvl?: number; +interface ChainDialogProps { + selectedChain: Chain; + isOpenState: [boolean, (isOpen: boolean) => void]; } // @internal -interface PageProps { - fetchedChains: Array; +function ChainFilter({ + queryState, + filtered, + selectedItemState, + setIsOpen, +}: ChainFilterProps): ReactElement { + const [selectedItem, setSelectedItem] = selectedItemState; + const [, setQuery] = queryState; + + return ( + { + const found = filtered.find( + (chain) => chain.name === (item as unknown as string), + ); + setSelectedItem(found); + setIsOpen(true); + }} + > +
+ setQuery(event.target.value)} + onClick={() => setSelectedItem(undefined)} + /> + +
+ +
+ {filtered.length > 0 ? ( + filtered.map((chain) => ( + + {({ active }) => ( +

+ {chain.name} +

+ )} +
+ )) + ) : ( +
+ Looks like there are no results for this query :( +
+ )} +
+ +
+ + ); } export default ChainList; From 073b730dc00df7656da303e73495b147c26a07b5 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:29:01 +0200 Subject: [PATCH 18/22] Fix styling bug when focus-visible is active --- src/globals.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/globals.css b/src/globals.css index 019dc84..4f1fb0b 100644 --- a/src/globals.css +++ b/src/globals.css @@ -19,6 +19,10 @@ html { height: 100vh; } +* { + @apply focus-visible:outline-none; +} + @layer base { a { @apply no-underline; From b32fb7819155e8c95317ab3452dc3ac33004c127 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:34:45 +0200 Subject: [PATCH 19/22] Make path relative --- src/lib/decodeBySigHash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/decodeBySigHash.ts b/src/lib/decodeBySigHash.ts index 41ee18a..b2b13a1 100644 --- a/src/lib/decodeBySigHash.ts +++ b/src/lib/decodeBySigHash.ts @@ -1,5 +1,5 @@ import { Interface } from '@ethersproject/abi'; -import { safeFetch } from 'src/misc/safeFetch'; +import { safeFetch } from '../../src/misc/safeFetch'; import { hexSchema } from '../misc/validation/schemas/hexSchema'; import { decodeCalldata, DecodeResult } from './decodeCalldata'; From d53b38dbea238034b478aa8236e77a3ec06ba54e Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 14:37:36 +0200 Subject: [PATCH 20/22] Fix lint --- src/lib/decodeBySigHash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/decodeBySigHash.ts b/src/lib/decodeBySigHash.ts index b2b13a1..01dc837 100644 --- a/src/lib/decodeBySigHash.ts +++ b/src/lib/decodeBySigHash.ts @@ -1,6 +1,6 @@ import { Interface } from '@ethersproject/abi'; -import { safeFetch } from '../../src/misc/safeFetch'; +import { safeFetch } from '../../src/misc/safeFetch'; import { hexSchema } from '../misc/validation/schemas/hexSchema'; import { decodeCalldata, DecodeResult } from './decodeCalldata'; import { DecodedEventResult, decodeEvent, EventProps } from './decodeEvent'; From ce8a0ea9d4140b8898309a9612a4c73168d59d3b Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 17:02:39 +0200 Subject: [PATCH 21/22] Add loading state --- pages/chain-list/index.page.tsx | 39 +- src/components/Chain/Chain.tsx | 417 +++++++++--------- .../lib/LoadingEntity/LoadingEntity.tsx | 4 +- 3 files changed, 248 insertions(+), 212 deletions(-) diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx index 1b13458..0facff2 100644 --- a/pages/chain-list/index.page.tsx +++ b/pages/chain-list/index.page.tsx @@ -2,15 +2,16 @@ import { Combobox, Dialog } from '@headlessui/react'; import { uniqBy } from 'lodash'; import { GetStaticProps, NextPage } from 'next'; import React, { ReactElement, useEffect, useState } from 'react'; -import { ChainEntity } from 'src/components/Chain'; -import { StackIcon } from 'src/components/icons/StackIcon'; -import { Button } from 'src/components/lib/Button'; -import { Entity } from 'src/components/lib/Entity'; -import { Header } from 'src/components/lib/Header'; -import { ToolContainer } from 'src/components/ToolContainer'; -import chainIds from 'src/misc/liamaChainSlugs'; -import { safeFetch } from 'src/misc/safeFetch'; -import { Chain, ChainTVL } from 'src/types/liamaChainAPI'; + +import { ChainEntity } from '../../src/components/Chain'; +import { StackIcon } from '../../src/components/icons/StackIcon'; +import { Button } from '../../src/components/lib/Button'; +import { Entity } from '../../src/components/lib/Entity'; +import { Header } from '../../src/components/lib/Header'; +import { ToolContainer } from '../../src/components/ToolContainer'; +import chainIds from '../../src/misc/liamaChainSlugs'; +import { safeFetch } from '../../src/misc/safeFetch'; +import { Chain, ChainTVL } from '../../src/types/liamaChainAPI'; interface ChainFilterProps { filtered: T[]; @@ -51,7 +52,7 @@ export const getStaticProps: GetStaticProps = async (): Promise<{ }; }; -const ChainList: NextPage = ({ fetchedChains }) => { +const ChainList: NextPage = ({ fetchedChains, mockIcon }) => { const [filtered, setFiltered] = useState([]); const [page, setPage] = useState({ currentPage: 1, chainsPerPage: 10 }); @@ -78,6 +79,10 @@ const ChainList: NextPage = ({ fetchedChains }) => { const indexOfFirstPost = indexOfLastPost - page.chainsPerPage; const currentChains = fetchedChains.slice(indexOfFirstPost, indexOfLastPost); + const isPreviousButtonDisabled = page.currentPage <= 1; + const isNextButtonDisabled = + page.currentPage * page.chainsPerPage >= fetchedChains.length; + return (
= ({ fetchedChains }) => {
0 ? filtered : currentChains} /> ); }; -function PaginatedChains({ chains }: PaginatedChainsProps): ReactElement { +function PaginatedChains({ + chains, + mockIcon, +}: PaginatedChainsProps): ReactElement { return (
{chains.map((chain) => { return ( @@ -158,11 +170,13 @@ function PaginatedChains({ chains }: PaginatedChainsProps): ReactElement { interface PaginatedChainsProps { chains: Chain[]; + mockIcon?: JSX.Element; } // @internal interface PageProps { fetchedChains: Array; + mockIcon?: JSX.Element; } // @internal @@ -219,7 +233,8 @@ function ChainFilter({
setQuery(event.target.value)} onClick={() => setSelectedItem(undefined)} /> diff --git a/src/components/Chain/Chain.tsx b/src/components/Chain/Chain.tsx index 8bfdc66..b307290 100644 --- a/src/components/Chain/Chain.tsx +++ b/src/components/Chain/Chain.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import React, { ComponentPropsWithoutRef, ReactElement, + useEffect, useMemo, useState, } from 'react'; @@ -32,8 +33,10 @@ const chainEntityColorCache: Record = {}; export function ChainEntity({ chain, className, + mockIcon, ...props }: ChainEntityProps): ReactElement { + const [isLoading, setIsLoading] = useState(true); const [selected, setSelected] = useState< ChainDetailPanelElement | undefined >(); @@ -47,245 +50,263 @@ export function ChainEntity({ } catch (error) {} }, [chain]); + useEffect(() => { + if (!icon) setIsLoading(false); + }, [icon]); + + function handleLoadImage(): void { + const currentKey: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; + if (currentKey in chainEntityColorCache) { + setIsLoading(false); + return setDominantColors(chainEntityColorCache[currentKey]); + } + + const img = document.getElementById(`chain-icon-${chain.chainId}`); + + const colorThief = new colorthief(); + try { + const result = colorThief.getPalette(img, 3) as number[][]; + const hexArray = result.map((rgb) => rgbToHex(rgb[0], rgb[1], rgb[2])); + + const cacheChainId: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; + chainEntityColorCache[cacheChainId] = hexArray; + + setDominantColors(hexArray); + } catch (error) { + setIsLoading(false); + } finally { + setIsLoading(false); + } + } + const [dominantColors, setDominantColors] = useState(); return ( -
- {dominantColors ? ( - <> + <> +
+ {isLoading && (
-
+
+
- - ) : ( - <> -
+ + ) : ( + <> +
-
+
- - )} -
-
-
- {icon ? ( -
+
+
+ {icon ? ( +
- { - const currentKey: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; - if (currentKey in chainEntityColorCache) - return setDominantColors( - chainEntityColorCache[currentKey], - ); - - const img = document.getElementById( - `chain-icon-${chain.chainId}`, - ); - - const colorThief = new colorthief(); - - try { - const result = colorThief.getPalette( - img, - 3, - ) as number[][]; - const hexArray = result.map((rgb) => - rgbToHex(rgb[0], rgb[1], rgb[2]), - ); - - const cacheChainId: ChainEntityColorCacheKey = `chain-icon-${chain.chainId}`; - chainEntityColorCache[cacheChainId] = hexArray; - - setDominantColors(hexArray); - } catch (error) {} - }} - className="absolute rounded blur" - src={icon} - layout="fill" - objectFit="contain" - /> -
- ) : ( -
+ )} +
+ ) : ( +
- -
- )} + > + +
+ )} -
-
-

chain id:

- {chain.chainId} -
+
+
+

chain id:

+ {chain.chainId} +
-

- {chain.name} -

-

- TVL: {chain.tvl?.toString().split('.')[0]}$ -

+

+ {chain.name} +

+

+ TVL: {chain.tvl?.toString().split('.')[0]}$ +

+
-
-
- - {chain.nativeCurrency?.symbol} - {chain.nativeCurrency?.decimals} - -

- {chain.nativeCurrency?.name} -

+
+ + {chain.nativeCurrency?.symbol} + {chain.nativeCurrency?.decimals} + +

+ {chain.nativeCurrency?.name} +

+
-
- -
- - 0 || false} - name="RPC" - selectedState={[selected, setSelected]} - > - RPC - + +
+ + 0 || false} + name="RPC" + selectedState={[selected, setSelected]} + > + RPC + - - {chain.infoURL} - + + {chain.infoURL} + - 0) || false} - name="Explorers" - type="button" - selectedState={[selected, setSelected]} - > - - + 0) || false + } + name="Explorers" + type="button" + selectedState={[selected, setSelected]} + > + + - 0) || false} - name="Faucets" - type="button" - selectedState={[selected, setSelected]} - > - - - -
- {chain.ens ? ( -

ENS ✔

- ) : ( -

0) || false} + name="Faucets" + type="button" + selectedState={[selected, setSelected]} > - ENS -

- )} + + + +
+ {chain.ens ? ( +

ENS ✔

+ ) : ( +

+ ENS +

+ )} +
-
- - {chain.explorers && - chain.explorers.length > 0 && - selected === 'Explorers' && ( -
- - name - standard - url - - {chain.explorers.map((explorer) => { - return ( - - {explorer.name} - {explorer.standard} - - -

- {explorer.url} -

- -
-
- ); - })} -
- )} - - {selected === 'RPC' && chain.rpc.length > 0 && ( - <> - - - id - url - - {chain.rpc.map((url, id) => { - return ( - - {id + 1} - {url} - - ); - })} -
- - )} + + {chain.explorers && + chain.explorers.length > 0 && + selected === 'Explorers' && ( + + + name + standard + url + + {chain.explorers.map((explorer) => { + return ( + + {explorer.name} + {explorer.standard} + + +

+ {explorer.url} +

+ +
+
+ ); + })} +
+ )} - {chain.faucets && - chain.faucets.length > 0 && - selected === 'Faucets' && ( + {selected === 'RPC' && chain.rpc.length > 0 && ( <> id url - {chain.faucets.map((faucet, index) => { + {chain.rpc.map((url, id) => { return ( - {index} - {faucet} + {id + 1} + {url} ); })}
)} -
-
+ + {chain.faucets && + chain.faucets.length > 0 && + selected === 'Faucets' && ( + <> + + + id + url + + {chain.faucets.map((faucet, index) => { + return ( + + {index} + {faucet} + + ); + })} +
+ + )} + +
+

-
+ ); } interface ChainEntityProps extends ComponentPropsWithoutRef<'div'> { + mockIcon?: JSX.Element; chain: Chain; } diff --git a/src/components/lib/LoadingEntity/LoadingEntity.tsx b/src/components/lib/LoadingEntity/LoadingEntity.tsx index 21ea44c..a5e0c9d 100644 --- a/src/components/lib/LoadingEntity/LoadingEntity.tsx +++ b/src/components/lib/LoadingEntity/LoadingEntity.tsx @@ -25,8 +25,8 @@ export function LoadingEntity({ ${ isLoading ? `before:absolute before:inset-0 before:h-full before:w-full - before:-translate-x-full before:animate-shimmer before:bg-gradient-to-r - before:from-transparent before:via-gray-700 before:to-transparent` + before:-translate-x-full before:animate-shimmer before:bg-gradient-to-r + before:from-transparent before:via-gray-700 before:to-transparent` : '' }`} /> From c7ced00e096a4362f3c82e3e02372b47f6b15916 Mon Sep 17 00:00:00 2001 From: 7sne Date: Wed, 24 Aug 2022 17:48:55 +0200 Subject: [PATCH 22/22] Add tests, minor improvements and fixes --- pages/chain-list/chain-list.test.tsx | 172 +++++++++++++++++++++++++++ pages/chain-list/index.page.tsx | 124 ++++++++++--------- 2 files changed, 242 insertions(+), 54 deletions(-) create mode 100644 pages/chain-list/chain-list.test.tsx diff --git a/pages/chain-list/chain-list.test.tsx b/pages/chain-list/chain-list.test.tsx new file mode 100644 index 0000000..ffc3d92 --- /dev/null +++ b/pages/chain-list/chain-list.test.tsx @@ -0,0 +1,172 @@ +import { render } from '@testing-library/react'; +import { expect } from 'earljs'; + +import { changeTargetValue } from '../../test/helpers/changeTargetValue'; +import ChainList from './index.page'; + +// test pagination +// test searching for chain + +const mockChain = { + name: 'Mock chain', + chain: 'ETH', + icon: 'ethereum', + rpc: [ + 'https://mainnet.infura.io/v3/${INFURA_API_KEY}', + 'wss://mainnet.infura.io/ws/v3/${INFURA_API_KEY}', + 'https://api.mycryptoapi.com/eth', + 'https://cloudflare-eth.com', + ], + faucets: [], + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + infoURL: 'https://ethereum.org', + shortName: 'eth', + chainId: 1, + networkId: 1, + slip44: 60, + ens: { + registry: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, + explorers: [ + { + name: 'etherscan', + url: 'https://etherscan.io', + standard: 'EIP3091', + }, + ], + chainSlug: 'ethereum', + tvl: 55101671827.85486, +}; + +const fetchedChains = [ + ...Array.from({ length: 16 }).map((_, index) => ({ + ...mockChain, + chainId: index + 3, + })), + { + name: 'Binance Smart Chain Mainnet', + chain: 'BSC', + rpc: [ + 'https://bsc-dataseed1.binance.org', + 'https://bsc-dataseed2.binance.org', + 'https://bsc-dataseed3.binance.org', + 'https://bsc-dataseed4.binance.org', + 'https://bsc-dataseed1.defibit.io', + 'https://bsc-dataseed2.defibit.io', + 'https://bsc-dataseed3.defibit.io', + 'https://bsc-dataseed4.defibit.io', + 'https://bsc-dataseed1.ninicoin.io', + 'https://bsc-dataseed2.ninicoin.io', + 'https://bsc-dataseed3.ninicoin.io', + 'https://bsc-dataseed4.ninicoin.io', + 'wss://bsc-ws-node.nariox.org', + ], + faucets: ['https://free-online-app.com/faucet-for-eth-evm-chains/'], + nativeCurrency: { + name: 'Binance Chain Native Token', + symbol: 'BNB', + decimals: 18, + }, + infoURL: 'https://www.binance.org', + shortName: 'bnb', + chainId: 56, + networkId: 56, + slip44: 714, + explorers: [ + { + name: 'bscscan', + url: 'https://bscscan.com', + standard: 'EIP3091', + }, + ], + chainSlug: 'binance', + tvl: 6664169203.011698, + }, + { + name: 'Polygon Mainnet', + chain: 'Polygon', + rpc: [ + 'https://polygon-rpc.com/', + 'https://rpc-mainnet.matic.network', + 'https://matic-mainnet.chainstacklabs.com', + 'https://rpc-mainnet.maticvigil.com', + 'https://rpc-mainnet.matic.quiknode.pro', + 'https://matic-mainnet-full-rpc.bwarelabs.com', + ], + faucets: [], + nativeCurrency: { + name: 'MATIC', + symbol: 'MATIC', + decimals: 18, + }, + infoURL: 'https://polygon.technology/', + shortName: 'matic', + chainId: 137, + networkId: 137, + slip44: 966, + explorers: [ + { + name: 'polygonscan', + url: 'https://polygonscan.com', + standard: 'EIP3091', + }, + ], + chainSlug: 'polygon', + tvl: 2313673290.8639984, + }, +]; + +describe(ChainList.name, () => { + it('changes pages', () => { + const root = render( + } fetchedChains={fetchedChains} />, + ); + { + const chainList = root.getByLabelText('chain list'); + + expect(chainList.children.length).toEqual(10); + } + const previousPageButton = root.getByText( + 'Previous page', + ) as HTMLButtonElement; + + expect(previousPageButton.disabled).toEqual(true); + + const nextPageButton = root.getByText('Next Page') as HTMLButtonElement; + + nextPageButton.click(); + + const chainList = root.getByLabelText('chain list'); + + expect(chainList.children.length).toEqual(8); + expect(nextPageButton.disabled).toEqual(true); + expect(previousPageButton.disabled).toEqual(false); + }); + + it('filters chains', () => { + const root = render( + } fetchedChains={fetchedChains} />, + ); + const filterInput = root.getByPlaceholderText('Ethereum Mainnet'); + + changeTargetValue(filterInput, 'Bina'); + + const filterQueryResults = root.getByLabelText( + 'chain filter query results', + ); + + expect(filterQueryResults.children.length).toEqual(1); + expect(filterQueryResults.children[0].textContent).toEqual( + 'Binance Smart Chain Mainnet', + ); + + changeTargetValue(filterInput, ''); + + // Empty filter query always results in hidden results + expect(() => root.getByLabelText('chain filter query results')).toThrow(); + }); +}); diff --git a/pages/chain-list/index.page.tsx b/pages/chain-list/index.page.tsx index 0facff2..a950585 100644 --- a/pages/chain-list/index.page.tsx +++ b/pages/chain-list/index.page.tsx @@ -75,6 +75,28 @@ const ChainList: NextPage = ({ fetchedChains, mockIcon }) => { if (filtered) setFiltered(filtered); }, [query, fetchedChains]); + function handleNextPage(): void { + setFiltered([]); + if (page.currentPage * page.chainsPerPage < fetchedChains.length) + setPage((page) => { + return { + ...page, + currentPage: page.currentPage + 1, + }; + }); + } + + function handlePreviousPage(): void { + setFiltered([]); + if (page.currentPage > 1) + setPage((page) => { + return { + ...page, + currentPage: page.currentPage - 1, + }; + }); + } + const indexOfLastPost = page.currentPage * page.chainsPerPage; const indexOfFirstPost = indexOfLastPost - page.chainsPerPage; const currentChains = fetchedChains.slice(indexOfFirstPost, indexOfLastPost); @@ -107,16 +129,7 @@ const ChainList: NextPage = ({ fetchedChains, mockIcon }) => { @@ -125,16 +138,7 @@ const ChainList: NextPage = ({ fetchedChains, mockIcon }) => { className="ml-2" disabled={isNextButtonDisabled} variant="primary" - onClick={() => { - setFiltered([]); - if (page.currentPage * page.chainsPerPage < fetchedChains.length) - setPage((page) => { - return { - ...page, - currentPage: page.currentPage + 1, - }; - }); - }} + onClick={handleNextPage} > Next Page @@ -153,7 +157,7 @@ function PaginatedChains({ mockIcon, }: PaginatedChainsProps): ReactElement { return ( -
+
{chains.map((chain) => { return ( ): ReactElement { const [selectedItem, setSelectedItem] = selectedItemState; + const [showResults, setShowResults] = useState(true); const [, setQuery] = queryState; + function handleChangeInput(newValue: string): void { + if (newValue === '') setShowResults(false); + else setShowResults(true); + setQuery(newValue); + } + return ( setQuery(event.target.value)} + onChange={({ target }) => handleChangeInput(target.value)} onClick={() => setSelectedItem(undefined)} /> - -
+ > +
-
- {filtered.length > 0 ? ( - filtered.map((chain) => ( - + {filtered.length > 0 ? ( + filtered.map((chain) => ( + - {({ active }) => ( -

- {chain.name} -

- )} -
- )) - ) : ( -
- Looks like there are no results for this query :( -
- )} -
- + key={chain.chainId} + value={chain.name} + > + {({ active }) => ( +

+ {chain.name} +

+ )} + + )) + ) : ( +
+ Looks like there are no results for this query :( +
+ )} +
+ + )}
);