diff options
| -rw-r--r-- | gui/package-lock.json | 371 | ||||
| -rw-r--r-- | gui/package.json | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Accordion.tsx | 160 | ||||
| -rw-r--r-- | gui/src/renderer/components/Account.tsx | 129 | ||||
| -rw-r--r-- | gui/src/renderer/components/AccountStyles.tsx | 110 | ||||
| -rw-r--r-- | gui/src/renderer/components/AccountTokenLabel.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/ClipboardLabel.tsx | 37 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorView.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx | 17 | ||||
| -rw-r--r-- | gui/src/renderer/components/Layout.tsx | 31 | ||||
| -rw-r--r-- | gui/src/renderer/components/LayoutStyles.tsx | 10 | ||||
| -rw-r--r-- | gui/src/renderer/components/Switch.tsx | 242 |
12 files changed, 653 insertions, 469 deletions
diff --git a/gui/package-lock.json b/gui/package-lock.json index 170143882b..ac16b2f6a7 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -25,6 +25,64 @@ "@babel/highlight": "^7.0.0" } }, + "@babel/generator": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "requires": { + "@babel/types": "^7.9.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + }, "@babel/highlight": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", @@ -67,6 +125,11 @@ } } }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + }, "@babel/runtime": { "version": "7.9.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", @@ -75,6 +138,152 @@ "regenerator-runtime": "^0.13.4" } }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/traverse": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/types": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "@develar/schema-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.1.0.tgz", @@ -144,6 +353,29 @@ } } }, + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -341,9 +573,9 @@ "dev": true }, "@types/node": { - "version": "10.17.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.21.tgz", - "integrity": "sha512-PQKsydPxYxF1DsAFWmunaxd3sOi3iMt6Zmx/tgaagHYmwJ/9cRH91hQkeJZaUGWbvn0K5HlSVEXkn5U/llWPpQ==", + "version": "10.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.3.tgz", + "integrity": "sha512-sfGmOtSMSbQ/AKG8V9xD1gmjquC9awIIZ/Kj309pHb2n3bcRAcGMQv5nJ6gCXZVsneGE4+ve8DXKRCsrg3TFzg==", "dev": true }, "@types/node-gettext": { @@ -359,9 +591,9 @@ "dev": true }, "@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz", + "integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg==", "dev": true }, "@types/rbush": { @@ -389,6 +621,16 @@ "@types/react": "*" } }, + "@types/react-native": { + "version": "0.57.42", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.57.42.tgz", + "integrity": "sha512-Ms4RI8Oyi8HOIwlteFhgRE7TA9chP/mliLeJCzjKBOywYpile5TrXQF8lRDYzcC1zyTyoopu/u8VMlF+FS7VnA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz", @@ -453,6 +695,18 @@ "@types/stream-chain": "*" } }, + "@types/styled-components": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.0.tgz", + "integrity": "sha512-ZFlLCuwF5r+4Vb7JUmd+Yr2S0UBdBGmI7ctFTgJMypIp3xOHI4LCFVn2dKMvpk6xDB2hLRykrEWMBwJEpUAUIQ==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "@types/react-native": "*", + "csstype": "^2.2.0" + } + }, "@types/topojson-specification": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.1.tgz", @@ -1194,6 +1448,22 @@ } } }, + "babel-plugin-styled-components": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz", + "integrity": "sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -1780,6 +2050,11 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -2009,7 +2284,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2017,8 +2291,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-support": { "version": "1.1.3", @@ -2218,6 +2491,11 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -2236,6 +2514,16 @@ "integrity": "sha1-XxrUPi2O77/cME/NOaUhZklD4+s=", "dev": true }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", @@ -2243,9 +2531,9 @@ "dev": true }, "csstype": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", - "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.3.tgz", + "integrity": "sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg==", "dev": true }, "custom-error-instance": { @@ -3546,8 +3834,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "6.8.0", @@ -5516,8 +5803,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", @@ -6328,6 +6614,11 @@ "esprima": "^4.0.0" } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -6586,8 +6877,7 @@ "lodash": { "version": "4.17.14", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "lodash.escape": { "version": "4.0.1", @@ -8200,6 +8490,11 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss-value-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -9260,6 +9555,11 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", "dev": true }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -9668,8 +9968,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.2", @@ -10018,6 +10317,33 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "styled-components": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.0.tgz", + "integrity": "sha512-0Qs2wEkFBXHFlysz6CV831VG6HedcrFUwChjnWylNivsx14MtmqQsohi21rMHZxzuTba063dEyoe/SR6VGJI7Q==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "subscribableevent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/subscribableevent/-/subscribableevent-1.0.1.tgz", @@ -10292,6 +10618,11 @@ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", diff --git a/gui/package.json b/gui/package.json index ce34c6a301..915d498906 100644 --- a/gui/package.json +++ b/gui/package.json @@ -31,6 +31,7 @@ "redux": "^4.0.5", "sprintf-js": "^1.1.2", "stream-json": "^1.3.0", + "styled-components": "^5.1.0", "uuid": "^3.0.1", "validated": "^2.0.1" }, @@ -57,6 +58,7 @@ "@types/sinon": "^7.0.5", "@types/sprintf-js": "^1.1.2", "@types/stream-json": "^1.0.0", + "@types/styled-components": "^5.1.0", "@types/topojson-specification": "^1.0.1", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^2.29.0", diff --git a/gui/src/renderer/components/Accordion.tsx b/gui/src/renderer/components/Accordion.tsx index cf6603774e..ca37235845 100644 --- a/gui/src/renderer/components/Accordion.tsx +++ b/gui/src/renderer/components/Accordion.tsx @@ -1,137 +1,93 @@ import * as React from 'react'; -import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp'; -import consumePromise from '../../shared/promise'; +import styled from 'styled-components'; interface IProps { expanded: boolean; animationDuration: number; - style?: Types.AnimatedViewStyleRuleSet; children?: React.ReactNode; } interface IState { - applyAnimatedStyle: boolean; mountChildren: boolean; + containerHeight: string; } -const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); +const Container = styled.div((props: { height: string; animationDuration: number }) => ({ + display: 'flex', + height: props.height, + overflow: 'hidden', + transition: `height ${props.animationDuration}ms ease-in-out`, +})); + +const Content = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + height: 'fit-content', +}); + +export default class Accordion extends React.Component<IProps, IState> { + private containerRef = React.createRef<HTMLDivElement>(); -export default class Accordion extends Component<IProps, IState> { public static defaultProps = { expanded: true, animationDuration: 350, }; public state: IState = { - applyAnimatedStyle: false, - mountChildren: false, + mountChildren: this.props.expanded, + containerHeight: this.props.expanded ? 'auto' : '0', }; - private heightValue = Animated.createValue(0); - private animatedStyle = Styles.createAnimatedViewStyle({ - height: this.heightValue, - }); - - private containerRef = React.createRef<Animated.View>(); - private contentRef = React.createRef<View>(); - private animation?: Types.Animated.CompositeAnimation = undefined; - - constructor(props: IProps) { - super(props); - - this.state = { - applyAnimatedStyle: !props.expanded, - mountChildren: props.expanded, - }; - } - - public componentWillUnmount() { - if (this.animation) { - this.animation.stop(); - } - } - - public componentDidUpdate(oldProps: IProps, oldState: IState) { - if (this.props.expanded !== oldProps.expanded) { - // make sure the children are mounted first before expanding the accordion - if (this.props.expanded && !this.state.mountChildren) { - this.setState({ mountChildren: true }); - } else { - consumePromise(this.animate(this.props.expanded)); - } - } else if (this.state.mountChildren && !oldState.mountChildren) { - // run animations once the children are mounted - consumePromise(this.animate(this.props.expanded)); + public componentDidUpdate(oldProps: IProps) { + if (this.props.expanded && !oldProps.expanded) { + this.expand(); + } else if (!this.props.expanded && oldProps.expanded) { + this.collapse(); } } public render() { - const { style, children, expanded, animationDuration, ...otherProps } = this.props; - const containerStyles = this.state.applyAnimatedStyle - ? [style, containerOverflowStyle, this.animatedStyle] - : [style]; - return ( - <Animated.View {...otherProps} style={containerStyles} ref={this.containerRef}> - <View ref={this.contentRef}>{this.state.mountChildren && children}</View> - </Animated.View> + <Container + ref={this.containerRef} + height={this.state.containerHeight} + animationDuration={this.props.animationDuration} + onTransitionEnd={this.onTransitionEnd}> + <Content>{this.state.mountChildren && this.props.children}</Content> + </Container> ); } - private async animate(expand: boolean) { - const containerView = this.containerRef.current; - const contentView = this.contentRef.current; - if (!containerView || !contentView) { - return; - } - - if (this.animation) { - this.animation.stop(); - this.animation = undefined; - } - - const containerLayout = await UserInterface.measureLayoutRelativeToWindow(containerView); - const contentLayout = await UserInterface.measureLayoutRelativeToAncestor( - contentView, - containerView, - ); - - // the content is expanded when the animated style is not applied, - // so reset the initial animated value to the current layout's height. - if (!this.state.applyAnimatedStyle) { - this.heightValue.setValue(containerLayout.height); + private expand() { + // Make sure the children are mounted first before expanding the accordion + if (!this.state.mountChildren) { + this.setState({ mountChildren: true }, () => { + this.setState({ containerHeight: this.getContentHeight() }); + }); + } else { + this.setState({ containerHeight: this.getContentHeight() }); } + } - const toValue = expand ? contentLayout.height : 0; - - // calculate the animation duration based on travel distance - const multiplier = - Math.abs(toValue - containerLayout.height) / Math.max(1, contentLayout.height); - const duration = Math.ceil(this.props.animationDuration * multiplier); - - const animation = Animated.timing(this.heightValue, { - toValue, - easing: Animated.Easing.InOut(), - duration, - useNativeDriver: true, + private collapse() { + // First change height to height in px since it's not possible to transition to/from auto + this.setState({ containerHeight: this.getContentHeight() }, () => { + // Make sure new height has been applied + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this.containerRef.current?.offsetHeight; + this.setState({ containerHeight: '0' }); }); + } - this.animation = animation; - - const onAnimationEnd = ({ finished }: Types.Animated.EndResult) => { - if (finished) { - this.animation = undefined; - - // reset the height after transition to let element layout naturally - // if animation finished without interruption - if (expand) { - this.setState({ applyAnimatedStyle: false }); - } - } - }; - - this.setState({ applyAnimatedStyle: true }, () => { - animation.start(onAnimationEnd); - }); + private getContentHeight(): string { + return (this.containerRef.current?.scrollHeight ?? 0) + 'px'; } + + private onTransitionEnd = () => { + if (this.props.expanded) { + // Height auto enables the container to grow if the content changes size + this.setState({ containerHeight: 'auto' }); + } + }; } diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx index f0cbbe0495..73cf77dd5a 100644 --- a/gui/src/renderer/components/Account.tsx +++ b/gui/src/renderer/components/Account.tsx @@ -1,11 +1,18 @@ import * as React from 'react'; -import { Component, Text, View } from 'reactxp'; import AccountExpiry from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; -import styles from './AccountStyles'; +import styles, { + AccountContainer, + AccountFooter, + AccountOutOfTime, + AccountRow, + AccountRowLabel, + AccountRowValue, + StyledContainer, +} from './AccountStyles'; import AccountTokenLabel from './AccountTokenLabel'; import * as AppButton from './AppButton'; -import { Container, Layout } from './Layout'; +import { Layout } from './Layout'; import { BackBarItem, NavigationBar, NavigationItems } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; @@ -21,70 +28,62 @@ interface IProps { onBuyMore: () => Promise<void>; } -export default class Account extends Component<IProps> { +export default class Account extends React.Component<IProps> { public render() { return ( <Layout> - <Container> - <View style={styles.account}> - <NavigationBar> - <NavigationItems> - <BackBarItem action={this.props.onClose}> - { - // TRANSLATORS: Back button in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </BackBarItem> - </NavigationItems> - </NavigationBar> + <StyledContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + { + // TRANSLATORS: Back button in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </BackBarItem> + </NavigationItems> + </NavigationBar> - <View style={styles.account__container}> - <SettingsHeader> - <HeaderTitle>{messages.pgettext('account-view', 'Account')}</HeaderTitle> - </SettingsHeader> + <AccountContainer> + <SettingsHeader> + <HeaderTitle>{messages.pgettext('account-view', 'Account')}</HeaderTitle> + </SettingsHeader> - <View style={styles.account__content}> - <View style={styles.account__main}> - <View style={styles.account__row}> - <Text style={styles.account__row_label}> - {messages.pgettext('account-view', 'Account number')} - </Text> - <AccountTokenLabel - style={styles.account__row_value} - accountToken={this.props.accountToken || ''} - /> - </View> + <AccountRow> + <AccountRowLabel> + {messages.pgettext('account-view', 'Account number')} + </AccountRowLabel> + <AccountRowValue + as={AccountTokenLabel} + accountToken={this.props.accountToken || ''} + /> + </AccountRow> - <View style={styles.account__row}> - <Text style={styles.account__row_label}> - {messages.pgettext('account-view', 'Paid until')} - </Text> - <FormattedAccountExpiry - expiry={this.props.accountExpiry} - locale={this.props.expiryLocale} - /> - </View> + <AccountRow> + <AccountRowLabel>{messages.pgettext('account-view', 'Paid until')}</AccountRowLabel> + <FormattedAccountExpiry + expiry={this.props.accountExpiry} + locale={this.props.expiryLocale} + /> + </AccountRow> - <View style={styles.account__footer}> - <AppButton.BlockingButton - disabled={this.props.isOffline} - onPress={this.props.onBuyMore}> - <AppButton.GreenButton style={styles.account__buy_button}> - <AppButton.Label> - {messages.pgettext('account-view', 'Buy more credit')} - </AppButton.Label> - <AppButton.Icon source="icon-extLink" height={16} width={16} /> - </AppButton.GreenButton> - </AppButton.BlockingButton> - <AppButton.RedButton onPress={this.props.onLogout}> - {messages.pgettext('account-view', 'Log out')} - </AppButton.RedButton> - </View> - </View> - </View> - </View> - </View> - </Container> + <AccountFooter> + <AppButton.BlockingButton + disabled={this.props.isOffline} + onPress={this.props.onBuyMore}> + <AppButton.GreenButton style={styles.account__buy_button}> + <AppButton.Label> + {messages.pgettext('account-view', 'Buy more credit')} + </AppButton.Label> + <AppButton.Icon source="icon-extLink" height={16} width={16} /> + </AppButton.GreenButton> + </AppButton.BlockingButton> + <AppButton.RedButton onPress={this.props.onLogout}> + {messages.pgettext('account-view', 'Log out')} + </AppButton.RedButton> + </AccountFooter> + </AccountContainer> + </StyledContainer> </Layout> ); } @@ -96,18 +95,16 @@ function FormattedAccountExpiry(props: { expiry?: string; locale: string }) { if (expiry.hasExpired()) { return ( - <Text style={styles.account__out_of_time}> - {messages.pgettext('account-view', 'OUT OF TIME')} - </Text> + <AccountOutOfTime>{messages.pgettext('account-view', 'OUT OF TIME')}</AccountOutOfTime> ); } else { - return <Text style={styles.account__row_value}>{expiry.formattedDate()}</Text>; + return <AccountRowValue>{expiry.formattedDate()}</AccountRowValue>; } } else { return ( - <Text style={styles.account__row_value}> + <AccountRowValue> {messages.pgettext('account-view', 'Currently unavailable')} - </Text> + </AccountRowValue> ); } } diff --git a/gui/src/renderer/components/AccountStyles.tsx b/gui/src/renderer/components/AccountStyles.tsx index bb230ee759..c793b96a1b 100644 --- a/gui/src/renderer/components/AccountStyles.tsx +++ b/gui/src/renderer/components/AccountStyles.tsx @@ -1,68 +1,58 @@ import { Styles } from 'reactxp'; +import styled from 'styled-components'; import { colors } from '../../config.json'; +import { Container } from './Layout'; + +export const StyledContainer = styled(Container)({ + backgroundColor: colors.darkBlue, + flexDirection: 'column', +}); + +export const AccountContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + paddingBottom: '48px', +}); + +export const AccountRow = styled.div({ + padding: '0 24px', + marginBottom: '24px', +}); + +const AccountRowText = styled.span({ + display: 'block', + fontFamily: 'Open Sans', +}); + +export const AccountRowLabel = styled(AccountRowText)({ + fontSize: '13px', + fontWeight: 600, + lineHeight: '20px', + letterSpacing: -0.2, + marginBottom: '9px', + color: colors.white60, +}); + +export const AccountRowValue = styled(AccountRowText)({ + fontSize: '16px', + lineHeight: '19px', + fontWeight: 800, + color: colors.white, +}); + +export const AccountOutOfTime = styled(AccountRowValue)({ + color: colors.red, +}); + +export const AccountFooter = styled.div({ + display: 'flex', + flexDirection: 'column', + padding: '0 24px', +}); export default { - account: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - account__container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - paddingBottom: 48, - }), - account__scrollview: Styles.createViewStyle({ - flex: 1, - }), - account__content: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - account__main: Styles.createViewStyle({ - marginBottom: 24, - }), - account__row: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 24, - }), - account__footer: Styles.createViewStyle({ - paddingLeft: 24, - paddingRight: 24, - }), account__buy_button: Styles.createViewStyle({ marginBottom: 24, }), - account__row_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white60, - marginBottom: 9, - }), - account__row_value: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - lineHeight: 19, - fontWeight: '800', - color: colors.white, - }), - account__out_of_time: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - color: colors.red, - }), - account__footer_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white80, - }), }; diff --git a/gui/src/renderer/components/AccountTokenLabel.tsx b/gui/src/renderer/components/AccountTokenLabel.tsx index d581749871..a2459ee61a 100644 --- a/gui/src/renderer/components/AccountTokenLabel.tsx +++ b/gui/src/renderer/components/AccountTokenLabel.tsx @@ -1,19 +1,18 @@ import * as React from 'react'; -import { Types } from 'reactxp'; import { formatAccountToken } from '../lib/account'; import ClipboardLabel from './ClipboardLabel'; interface IAccountTokenLabelProps { accountToken: string; - style?: Types.StyleRuleSetRecursive<Types.TextStyleRuleSet>; + className?: string; } export default function AccountTokenLabel(props: IAccountTokenLabelProps) { return ( <ClipboardLabel - style={props.style} value={props.accountToken} displayValue={formatAccountToken(props.accountToken)} + className={props.className} /> ); } diff --git a/gui/src/renderer/components/ClipboardLabel.tsx b/gui/src/renderer/components/ClipboardLabel.tsx index 567c41e45c..e0a507cc48 100644 --- a/gui/src/renderer/components/ClipboardLabel.tsx +++ b/gui/src/renderer/components/ClipboardLabel.tsx @@ -1,20 +1,26 @@ +import log from 'electron-log'; import * as React from 'react'; -import { Clipboard, Component, Text, Types } from 'reactxp'; +import styled from 'styled-components'; import { messages } from '../../shared/gettext'; +import { Scheduler } from '../../shared/scheduler'; interface IProps { value: string; displayValue?: string; delay: number; message: string; - style?: Types.StyleRuleSetRecursive<Types.TextStyleRuleSet>; + className?: string; } interface IState { showsMessage: boolean; } -export default class ClipboardLabel extends Component<IProps, IState> { +const Label = styled.span({ + cursor: 'pointer', +}); + +export default class ClipboardLabel extends React.Component<IProps, IState> { public static defaultProps: Partial<IProps> = { delay: 3000, message: messages.gettext('COPIED TO CLIPBOARD!'), @@ -24,31 +30,28 @@ export default class ClipboardLabel extends Component<IProps, IState> { showsMessage: false, }; - private timer?: NodeJS.Timeout; + private scheduler = new Scheduler(); public componentWillUnmount() { - if (this.timer) { - clearTimeout(this.timer); - } + this.scheduler.cancel(); } public render() { const displayValue = this.props.displayValue || this.props.value; return ( - <Text style={this.props.style} onPress={this.handlePress}> + <Label className={this.props.className} onClick={this.handlePress}> {this.state.showsMessage ? this.props.message : displayValue} - </Text> + </Label> ); } - private handlePress = () => { - if (this.timer) { - clearTimeout(this.timer); + private handlePress = async () => { + try { + await navigator.clipboard.writeText(this.props.value); + this.scheduler.schedule(() => this.setState({ showsMessage: false }), this.props.delay); + this.setState({ showsMessage: true }); + } catch (error) { + log.error(`Failed to copy to clipboard: ${error.message}`); } - - this.timer = global.setTimeout(() => this.setState({ showsMessage: false }), this.props.delay); - this.setState({ showsMessage: true }); - - Clipboard.setText(this.props.value); }; } diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx index 68f0c9c30b..b2ae12f9b5 100644 --- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx +++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx @@ -7,10 +7,9 @@ import { AccountToken } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import RedeemVoucherContainer from '../containers/RedeemVoucherContainer'; import { LoginState } from '../redux/account/reducers'; -import AccountTokenLabel from './AccountTokenLabel'; import * as AppButton from './AppButton'; import * as Cell from './Cell'; -import styles from './ExpiredAccountErrorViewStyles'; +import styles, { StyledAccountTokenLabel } from './ExpiredAccountErrorViewStyles'; import ImageView from './ImageView'; import { ModalAlert, ModalAlertType } from './Modal'; import { @@ -121,10 +120,7 @@ export default class ExpiredAccountErrorView extends Component< {messages.pgettext('connect-view', 'Here’s your account number. Save it!')} </Text> <View style={styles.accountTokenContainer}> - <AccountTokenLabel - style={styles.accountToken} - accountToken={this.props.accountToken || ''} - /> + <StyledAccountTokenLabel accountToken={this.props.accountToken || ''} /> </View> </View> diff --git a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx index 93535a043b..15891dd230 100644 --- a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx +++ b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx @@ -1,5 +1,15 @@ import { Styles } from 'reactxp'; +import styled from 'styled-components'; import { colors } from '../../config.json'; +import AccountTokenLabel from './AccountTokenLabel'; + +export const StyledAccountTokenLabel = styled(AccountTokenLabel)({ + fontFamily: 'Open Sans', + lineHeight: '24px', + fontSize: '24px', + fontWeight: 800, + color: colors.white, +}); export default { container: Styles.createViewStyle({ @@ -56,13 +66,6 @@ export default { height: 68, justifyContent: 'center', }), - accountToken: Styles.createTextStyle({ - fontFamily: 'Open Sans', - lineHeight: 24, - fontSize: 24, - fontWeight: '800', - color: colors.white, - }), button: Styles.createViewStyle({ marginBottom: 24, }), diff --git a/gui/src/renderer/components/Layout.tsx b/gui/src/renderer/components/Layout.tsx index 01f4aa701d..825cbf9190 100644 --- a/gui/src/renderer/components/Layout.tsx +++ b/gui/src/renderer/components/Layout.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { Component, View } from 'reactxp'; +import styled from 'styled-components'; +import { colors } from '../../config.json'; import HeaderBar from './HeaderBar'; import styles from './LayoutStyles'; @@ -15,20 +17,17 @@ export class Header extends Component<HeaderBar['props']> { } } -interface IContainerProps { - children: React.ReactNode; -} -export class Container extends Component<IContainerProps> { - public render() { - return <View style={styles.container}>{this.props.children}</View>; - } -} +export const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + backgroundColor: colors.blue, + overflow: 'hidden', +}); -interface ILayoutProps { - children: React.ReactNode; -} -export class Layout extends Component<ILayoutProps> { - public render() { - return <View style={styles.layout}>{this.props.children}</View>; - } -} +export const Layout = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + height: '100vh', +}); diff --git a/gui/src/renderer/components/LayoutStyles.tsx b/gui/src/renderer/components/LayoutStyles.tsx index cebe3f2588..aa27549f39 100644 --- a/gui/src/renderer/components/LayoutStyles.tsx +++ b/gui/src/renderer/components/LayoutStyles.tsx @@ -1,17 +1,7 @@ import { Styles } from 'reactxp'; -import { colors } from '../../config.json'; export default { - layout: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), header: Styles.createViewStyle({ flex: 0, }), - container: Styles.createViewStyle({ - flex: 1, - backgroundColor: colors.blue, - overflow: 'hidden', - }), }; diff --git a/gui/src/renderer/components/Switch.tsx b/gui/src/renderer/components/Switch.tsx index 3e693706f9..ceda826fb0 100644 --- a/gui/src/renderer/components/Switch.tsx +++ b/gui/src/renderer/components/Switch.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import { Animated, Component, GestureView, Styles, Types, View } from 'reactxp'; +import React from 'react'; +import styled from 'styled-components'; import { colors } from '../../config.json'; interface IProps { @@ -12,80 +12,43 @@ interface IState { isPressed: boolean; } -const styles = { - holder: Styles.createViewStyle({ - width: 52, - height: 32, - borderColor: colors.white, - borderWidth: 2, - borderStyle: 'solid', - borderRadius: 16, - padding: 2, - }), - knob: Styles.createViewStyle({ - height: 24, - borderRadius: 24, - }), -}; +const PAN_DISTANCE = 10; -interface IPosition { - x: number; - y: number; -} - -const SWITCH_DEFAULT_WIDTH = 24; -const SWITCH_PRESSED_WIDTH = 28; +const SwitchContainer = styled.div({ + position: 'relative', + width: '52px', + height: '32px', + borderColor: colors.white, + borderWidth: '2px', + borderStyle: 'solid', + borderRadius: '16px', + padding: '2px', +}); -export default class Switch extends Component<IProps, IState> { - public static defaultProps: Partial<IProps> = { - isOn: false, - onChange: undefined, - }; +const Knob = styled.div({}, (props: { isOn: boolean; isPressed: boolean }) => ({ + position: 'absolute', + height: '24px', + borderRadius: '12px', + transition: 'all 200ms linear', + width: props.isPressed ? '28px' : '24px', + backgroundColor: props.isOn ? colors.green : colors.red, + // When enabled the button should be placed all the way to the right (100%) minus padding (2px). + left: props.isOn ? 'calc(100% - 2px)' : '2px', + // This moves the knob to the left making the right side aligned with the parent's right side. + transform: `translateX(${props.isOn ? '-100%' : '0'})`, +})); +export default class Switch extends React.Component<IProps, IState> { public state: IState = { - isOn: false, + isOn: this.props.isOn, isPressed: false, }; - private isPanning = false; - private startPos = { x: 0, y: 0 }; - private startValue = false; - - private translationValue = Animated.createValue(0); - private widthValue = Animated.createValue(SWITCH_DEFAULT_WIDTH); - private colorValue = Animated.createValue(0); - private interpolatedColorValue = Animated.interpolate( - this.colorValue, - [0, 1], - [colors.red, colors.green], - ); - private animatedStyle = Styles.createAnimatedViewStyle({ - width: this.widthValue, - backgroundColor: this.interpolatedColorValue, - transform: [ - { - translateX: this.translationValue, - }, - ], - }); - private animation?: Types.Animated.CompositeAnimation; - - constructor(props: IProps) { - super(props); + private containerRef = React.createRef<HTMLDivElement>(); - this.state.isOn = props.isOn; - - if (props.isOn) { - this.translationValue.setValue(this.computeTranslation(props.isOn, false)); - this.colorValue.setValue(1); - } - } - - public componentWillUnmount() { - if (this.animation) { - this.animation.stop(); - } - } + private isPanning = false; + private startPos = 0; + private changedDuringPan = false; public shouldComponentUpdate(nextProps: IProps, nextState: IState) { return ( @@ -95,131 +58,86 @@ export default class Switch extends Component<IProps, IState> { ); } - public componentDidUpdate(prevProps: IProps, prevState: IState) { + public componentDidUpdate(prevProps: IProps, _prevState: IState) { if ( this.props.isOn !== prevProps.isOn && this.props.isOn !== this.state.isOn && !this.isPanning ) { this.setState({ isOn: this.props.isOn }); - } else if (prevState.isOn !== this.state.isOn || prevState.isPressed !== this.state.isPressed) { - this.animate(); } } public render() { return ( - <GestureView - preferredPan={Types.PreferredPanGesture.Horizontal} - onPanHorizontal={this.onPanHorizontal} - onTap={this.onTap}> - <View style={styles.holder}> - <Animated.View style={[styles.knob, this.animatedStyle]} /> - </View> - </GestureView> + <SwitchContainer ref={this.containerRef} onClick={this.handleClick}> + <Knob + isOn={this.state.isOn} + isPressed={this.state.isPressed} + onMouseDown={this.handleMouseDown} + /> + </SwitchContainer> ); } - private onTap = (_gesture: Types.TapGestureState) => { - this.setState( - (state) => ({ isOn: !state.isOn, isPressed: false }), - () => { - this.notify(); - }, - ); + private handleClick = () => { + if (!this.changedDuringPan) { + this.setState((state) => ({ isOn: !state.isOn }), this.notify); + } + + // Needs to be reset to allow clicks on container after panning. + this.changedDuringPan = false; }; - private onPanHorizontal = (gesture: Types.PanGestureState) => { - if (this.isPanning) { - if (gesture.isComplete) { - this.isPanning = false; + private handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => { + this.isPanning = true; + this.startPos = event.clientX; + this.changedDuringPan = false; - this.setState({ isPressed: false }, () => { - if (this.startValue !== this.state.isOn) { - this.notify(); - } - }); - } else { - const currentPos = { x: gesture.clientX, y: gesture.clientY }; - const nextOn = this.computeNextState(this.startPos, currentPos); + document.addEventListener('mouseup', this.handleMouseUp); + document.addEventListener('mousemove', this.handleMouseMove); + }; - if (this.state.isOn !== nextOn) { - this.startPos = currentPos; + private handleMouseUp = (event: MouseEvent) => { + document.removeEventListener('mouseup', this.handleMouseUp); + document.removeEventListener('mousemove', this.handleMouseMove); - this.setState({ isOn: nextOn }); - } - } - } else { - if (gesture.isComplete) { - return; - } + this.setState({ isPressed: false }); + this.isPanning = false; + // Reset changedDuringPan when onClick wont be called. + if (event.target instanceof Element && !this.containerRef.current?.contains(event.target)) { + this.changedDuringPan = false; + } - this.isPanning = true; - this.startPos = { x: gesture.clientX, y: gesture.clientY }; - this.startValue = this.state.isOn; + if (this.props.isOn !== this.state.isOn) { + this.notify(); + } + }; + + private handleMouseMove = (event: MouseEvent) => { + if (this.isPanning) { this.setState({ isPressed: true }); + + const nextOn = this.computeNextState(event.clientX); + if (this.state.isOn !== nextOn) { + this.startPos = event.clientX; + this.changedDuringPan = true; + this.setState({ isOn: nextOn }); + } } }; - private computeNextState(initialPos: IPosition, currentPos: IPosition): boolean { - if (currentPos.x < initialPos.x && this.state.isOn) { + private computeNextState(currentPos: number): boolean { + if (currentPos + PAN_DISTANCE < this.startPos && this.state.isOn) { return false; - } else if (currentPos.x > initialPos.x && !this.state.isOn) { + } else if (currentPos - PAN_DISTANCE > this.startPos && !this.state.isOn) { return true; } else { return this.state.isOn; } } - private computeKnobWidth(isPressed: boolean) { - return isPressed ? SWITCH_PRESSED_WIDTH : SWITCH_DEFAULT_WIDTH; - } - - private computeTranslation(isOn: boolean, isPressed: boolean) { - if (isOn) { - return isPressed ? 16 : 20; - } else { - return 0; - } - } - - private animate(onFinish?: (done: boolean) => void) { - const duration = 200; - const animation = Animated.parallel([ - Animated.timing(this.translationValue, { - toValue: this.computeTranslation(this.state.isOn, this.state.isPressed), - duration, - }), - Animated.timing(this.widthValue, { - toValue: this.computeKnobWidth(this.state.isPressed), - duration, - }), - Animated.timing(this.colorValue, { - toValue: this.state.isOn ? 1 : 0, - duration, - }), - ]); - - if (this.animation) { - this.animation.stop(); - } - - animation.start((options) => { - if (options.finished) { - this.animation = undefined; - } - - if (onFinish) { - onFinish(options.finished); - } - }); - - this.animation = animation; - } - private notify() { - if (this.props.onChange) { - this.props.onChange(this.state.isOn); - } + this.props.onChange?.(this.state.isOn); } } |
