summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gui/package-lock.json371
-rw-r--r--gui/package.json2
-rw-r--r--gui/src/renderer/components/Accordion.tsx160
-rw-r--r--gui/src/renderer/components/Account.tsx129
-rw-r--r--gui/src/renderer/components/AccountStyles.tsx110
-rw-r--r--gui/src/renderer/components/AccountTokenLabel.tsx5
-rw-r--r--gui/src/renderer/components/ClipboardLabel.tsx37
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx8
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx17
-rw-r--r--gui/src/renderer/components/Layout.tsx31
-rw-r--r--gui/src/renderer/components/LayoutStyles.tsx10
-rw-r--r--gui/src/renderer/components/Switch.tsx242
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);
}
}