Merge branch 'NewUI-flat' into NewUI-flat-4.0.5c
@ -2,4 +2,5 @@ app/scripts/lib/extension-instance.js
|
||||
test/integration/bundle.js
|
||||
test/integration/jquery-3.1.0.min.js
|
||||
test/integration/helpers.js
|
||||
test/integration/lib/first-time.js
|
||||
test/integration/lib/first-time.js
|
||||
ui/lib/blockies.js
|
5
.gitignore
vendored
@ -6,6 +6,8 @@ app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
.idea
|
||||
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
@ -24,6 +26,9 @@ test/background.js
|
||||
test/bundle.js
|
||||
test/test-bundle.js
|
||||
|
||||
#ignore css output and sourcemaps
|
||||
ui/app/css/output/
|
||||
|
||||
notes.txt
|
||||
|
||||
.coveralls.yml
|
||||
|
10
.stylelintignore
Normal file
@ -0,0 +1,10 @@
|
||||
app/
|
||||
development/
|
||||
dist/
|
||||
docs/
|
||||
fonts/
|
||||
images/
|
||||
mascara/
|
||||
node_modules/
|
||||
notices/
|
||||
test/
|
50
.stylelintrc
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"color-named": "never",
|
||||
"font-family-name-quotes": "always-where-recommended",
|
||||
"font-weight-notation": "numeric",
|
||||
"function-url-quotes": "always",
|
||||
"number-leading-zero": "never",
|
||||
"value-no-vendor-prefix": true,
|
||||
"value-list-comma-newline-before": "never-multi-line",
|
||||
"custom-property-empty-line-before": "never",
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": [
|
||||
"composes",
|
||||
"all",
|
||||
"-webkit-appearance"
|
||||
]
|
||||
}
|
||||
],
|
||||
"declaration-block-semicolon-newline-after": "always",
|
||||
"block-opening-brace-newline-after": "always",
|
||||
"selector-attribute-quotes": "always",
|
||||
"selector-max-specificity": "0,5,2",
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["local", "global"]
|
||||
}
|
||||
],
|
||||
"at-rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"ignore": [
|
||||
"after-comment",
|
||||
]
|
||||
}
|
||||
],
|
||||
"indentation": [
|
||||
2,
|
||||
{
|
||||
"indentInsideParens": "once-at-root-twice-in-block"
|
||||
}
|
||||
],
|
||||
"max-nesting-depth": 3,
|
||||
"no-duplicate-selectors": true,
|
||||
"no-unknown-animations": true
|
||||
}
|
||||
}
|
BIN
app/fonts/DIN Next/DIN Next W01 Bold.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W01 Regular.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Black.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Italic.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Light.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Medium.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Bold 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-BoldItalic.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Italic 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Medium 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-MediumItalic 2.otf
Normal file
BIN
app/fonts/Lato/Lato-Black.ttf
Executable file
BIN
app/fonts/Lato/Lato-BlackItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Bold.ttf
Executable file
BIN
app/fonts/Lato/Lato-BoldItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Hairline.ttf
Executable file
BIN
app/fonts/Lato/Lato-HairlineItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Italic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Light.ttf
Executable file
BIN
app/fonts/Lato/Lato-LightItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Regular.ttf
Executable file
93
app/fonts/Lato/OFL.txt
Executable file
@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
app/fonts/Roboto/Roboto-Black.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BlackItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Bold.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Italic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Light.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Medium.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-MediumItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Regular.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Thin.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-ThinItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Bold.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Italic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Light.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Regular.ttf
Normal file
BIN
app/images/.DS_Store
vendored
14
app/images/check-white.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="13px" viewBox="0 0 16 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>check-white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-17.000000, -80.000000)" fill-rule="nonzero" fill="#FFFFFF">
|
||||
<g id="Group-11" transform="translate(18.000000, 74.000000)">
|
||||
<polygon id="check-white" points="4.2 15.5712828 0.714212839 12.0143571 -0.714212839 13.4142143 4.2 18.4287172 14.7142128 7.69992858 13.2857872 6.30007142"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 872 B |
11
app/images/eth_logo.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
|
||||
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
|
||||
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
|
||||
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 854 B |
18
app/images/import-account.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>import-account</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -718.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="import-account" transform="translate(21.000000, 72.000000)">
|
||||
<rect id="Rectangle-49" fill="#FFFFFF" x="0" y="13.1721326" width="14.4893459" height="1.08397642"></rect>
|
||||
<rect id="Rectangle" fill="#FFFFFF" x="6.5860663" y="0" width="1.08397642" height="10.5377061"></rect>
|
||||
<polyline id="Path-12" stroke="#FFFFFF" points="2.63442652 6.5860663 7.24467293 10.5377061 11.8549193 6.5860663"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/images/info-logo.png
Normal file
After Width: | Height: | Size: 32 KiB |
11
app/images/mm-bolt.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 252 251.7" style="enable-background:new 0 0 252 251.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#757575;}
|
||||
</style>
|
||||
<path class="st0" d="M211.3,103.9h-60.7c-2,0-3.6-1.6-3.6-3.6V3.6c0-3.5-4.5-5-6.6-2.2l-102.7,140c-1.8,2.4,0,5.8,2.9,5.8h60.7
|
||||
c2,0,3.6,1.6,3.6,3.6v96.6c0,3.5,4.5,5,6.6,2.2l102.7-140C216,107.3,214.3,103.9,211.3,103.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 732 B |
11
app/images/mm-info-icon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#B8B8B8;}
|
||||
</style>
|
||||
<path class="st0" d="M5,0C2.2,0,0,2.2,0,5s2.2,5,5,5s5-2.2,5-5S7.8,0,5,0z M5,2c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7
|
||||
S4.3,3.2,4.3,2.8C4.3,2.4,4.6,2,5,2z M5.7,8H4.3V4.3h1.5V8z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 689 B |
15
app/images/open.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>open</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Mobile-screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="MetaMascara-Mobile---structured" transform="translate(-329.000000, -93.000000)">
|
||||
<g id="open" transform="translate(330.000000, 94.000000)">
|
||||
<path d="M26,13 C26,20.1799 20.1799,26 13,26 C5.8201,26 0,20.1799 0,13 C0,5.8201 5.8201,0 13,0 C20.1799,0 26,5.8201 26,13 Z" id="Stroke-3" stroke="#4A4A4A"></path>
|
||||
<path d="M6,17 C6,17 7.78735344,10.8360387 13.7616996,10.8360387 L13.7616996,8 L19,12.3733433 L13.7616996,17 L13.7616996,14.1639613 C13.7616996,14.1639613 9.54083576,13.4629933 6,17" id="Fill-5" fill="#4A4A4A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
17
app/images/plus-btn-white.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>plus-btn-white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-24.000000, -669.000000)" fill="#FFFFFF">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="plus-btn-white" transform="translate(20.000000, 23.000000)">
|
||||
<rect id="Rectangle-48" x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
<rect id="Rectangle-48" transform="translate(8.000000, 8.000000) rotate(-90.000000) translate(-8.000000, -8.000000) " x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,24 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24.088px" height="24px" viewBox="0 0 24.088 24" enable-background="new 0 0 24.088 24" xml:space="preserve">
|
||||
<path d="M21.525,10.147c-0.41-0.059-0.847-0.428-0.974-0.82l-0.608-1.481c-0.191-0.365-0.146-0.935,0.1-1.264l0.99-1.318
|
||||
c0.246-0.33,0.227-0.854-0.047-1.162l-1.084-1.086c-0.31-0.272-0.832-0.293-1.164-0.045l-1.316,0.988
|
||||
c-0.33,0.248-0.898,0.293-1.264,0.101l-1.48-0.609c-0.395-0.126-0.764-0.562-0.82-0.971l-0.233-1.629
|
||||
c-0.058-0.409-0.44-0.778-0.851-0.822c0,0-0.254-0.026-0.77-0.026c-0.514,0-0.77,0.026-0.77,0.026
|
||||
c-0.41,0.044-0.793,0.413-0.852,0.822L10.15,2.48c-0.059,0.409-0.428,0.845-0.82,0.971L7.85,4.06
|
||||
C7.484,4.251,6.916,4.207,6.586,3.959L5.268,2.97c-0.33-0.248-0.854-0.228-1.162,0.045L3.021,4.101
|
||||
C2.749,4.41,2.727,4.933,2.975,5.263l0.988,1.318c0.249,0.33,0.293,0.899,0.102,1.264l-0.61,1.482
|
||||
c-0.125,0.393-0.562,0.762-0.972,0.82l-1.629,0.231c-0.408,0.059-0.776,0.442-0.82,0.853c0,0-0.026,0.255-0.026,0.77
|
||||
c0,0.516,0.026,0.77,0.026,0.77c0.044,0.412,0.412,0.793,0.82,0.853l1.629,0.231c0.408,0.06,0.847,0.429,0.972,0.82l0.61,1.48
|
||||
c0.191,0.365,0.146,0.936-0.102,1.264l-0.988,1.318c-0.248,0.33-0.308,0.779-0.132,0.994c0.175,0.217,0.677,0.752,0.679,0.754
|
||||
c0,0.002,0.17,0.156,0.375,0.344c0.203,0.188,1.041,0.449,1.371,0.203l1.317-0.99c0.33-0.246,0.897-0.293,1.265-0.1l1.479,0.608
|
||||
c0.394,0.125,0.763,0.562,0.819,0.972l0.233,1.629c0.058,0.408,0.44,0.779,0.853,0.822c0,0,0.254,0.026,0.769,0.026
|
||||
s0.771-0.026,0.771-0.026c0.408-0.043,0.793-0.414,0.85-0.822l0.234-1.629c0.057-0.408,0.426-0.847,0.819-0.972l1.479-0.61
|
||||
c0.365-0.191,0.935-0.146,1.265,0.102l1.317,0.99c0.332,0.246,0.854,0.227,1.164-0.047l1.082-1.084
|
||||
c0.273-0.312,0.293-0.834,0.047-1.164l-0.989-1.318c-0.246-0.328-0.291-0.898-0.101-1.264l0.609-1.48
|
||||
c0.127-0.393,0.562-0.762,0.973-0.82l1.627-0.231c0.41-0.06,0.779-0.44,0.822-0.853c0,0,0.027-0.254,0.027-0.77
|
||||
c0-0.515-0.027-0.77-0.027-0.77c-0.043-0.41-0.412-0.794-0.822-0.853L21.525,10.147z M12.004,15.001c-1.657,0-3-1.344-3-3
|
||||
c0-1.657,1.343-3,3-3s3,1.344,3,3S13.66,15.001,12.004,15.001z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>settings</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="20 10 20 19.9998 0 19.9998 0 10 0 0.0002 20 0.0002"></polygon>
|
||||
</defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -826.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="settings" transform="translate(21.000000, 180.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path d="M10,13.6602 C7.979,13.6602 6.34,12.0212 6.34,10.0002 C6.34,7.9782 7.979,6.3402 10,6.3402 C12.021,6.3402 13.66,7.9782 13.66,10.0002 C13.66,12.0212 12.021,13.6602 10,13.6602 L10,13.6602 Z M19.157,11.8112 C19.53,11.8112 19.878,11.5092 19.929,11.1392 C19.929,11.1392 20,10.6182 20,10.0002 C20,9.3822 19.929,8.8622 19.929,8.8622 C19.878,8.4922 19.53,8.1892 19.157,8.1892 L17.228,8.1892 C16.854,8.1892 16.466,7.9512 16.365,7.6602 C16.265,7.3682 16.127,6.4352 16.391,6.1712 L17.755,4.8072 C18.019,4.5432 18.039,4.0922 17.8,3.8052 L16.195,2.2002 C15.908,1.9602 15.458,1.9812 15.193,2.2452 L13.829,3.6092 C13.565,3.8732 13.125,3.9802 12.852,3.8462 C12.578,3.7122 11.812,3.1462 11.812,2.7732 L11.812,0.8432 C11.812,0.4702 11.509,0.1222 11.139,0.0722 C11.139,0.0722 10.619,0.0002 10,0.0002 C9.382,0.0002 8.862,0.0722 8.862,0.0722 C8.492,0.1222 8.189,0.4702 8.189,0.8432 L8.189,2.7732 C8.189,3.1462 7.951,3.5352 7.66,3.6352 C7.369,3.7352 6.435,3.8732 6.171,3.6092 L4.807,2.2452 C4.542,1.9812 4.092,1.9612 3.805,2.2002 L2.2,3.8052 C1.96,4.0922 1.981,4.5432 2.245,4.8072 L3.609,6.1712 C3.873,6.4352 3.98,6.8752 3.846,7.1482 C3.711,7.4222 3.146,8.1892 2.773,8.1892 L0.843,8.1892 C0.47,8.1892 0.123,8.4922 0.072,8.8622 C0.072,8.8622 0,9.3822 0,10.0002 C0,10.6182 0.072,11.1392 0.072,11.1392 C0.123,11.5092 0.47,11.8112 0.843,11.8112 L2.773,11.8112 C3.146,11.8112 3.535,12.0502 3.635,12.3412 C3.735,12.6322 3.874,13.5642 3.609,13.8292 L2.246,15.1932 C1.981,15.4572 1.961,15.9082 2.2,16.1952 L3.805,17.8002 C4.092,18.0392 4.542,18.0192 4.807,17.7552 L6.171,16.3902 C6.435,16.1272 6.875,16.0202 7.148,16.1542 C7.422,16.2882 8.189,16.8532 8.189,17.2272 L8.189,19.1572 C8.189,19.5302 8.492,19.8782 8.862,19.9292 C8.862,19.9292 9.382,20.0002 10,20.0002 C10.619,20.0002 11.139,19.9292 11.139,19.9292 C11.509,19.8772 11.812,19.5302 11.812,19.1572 L11.812,17.2272 C11.812,16.8532 12.05,16.4662 12.341,16.3652 C12.632,16.2642 13.565,16.1272 13.829,16.3902 L15.193,17.7552 C15.458,18.0182 15.908,18.0392 16.195,17.8002 L17.8,16.1952 C18.039,15.9082 18.02,15.4582 17.755,15.1932 L16.391,13.8292 C16.127,13.5652 16.021,13.1252 16.154,12.8512 C16.288,12.5782 16.854,11.8112 17.228,11.8112 L19.157,11.8112 Z" id="Fill-1" fill="#B3B3B3" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.2 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "3.13.3",
|
||||
"version": "4.0.5",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html style="height:600px;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MetaMask Notification</title>
|
||||
@ -9,7 +9,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="notification" style="height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html style="width:350px; height:600px;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
</head>
|
||||
<body style="width:357px; height:500px;">
|
||||
<body style="width:350px; height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
|
@ -4,6 +4,15 @@ const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||
|
||||
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
|
||||
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
|
||||
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
|
||||
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
|
||||
|
||||
const DEFAULT_RPC = 'rinkeby'
|
||||
const OLD_UI_NETWORK_TYPE = 'network'
|
||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
module.exports = {
|
||||
@ -14,9 +23,22 @@ module.exports = {
|
||||
kovan: KOVAN_RPC_URL,
|
||||
rinkeby: RINKEBY_RPC_URL,
|
||||
},
|
||||
// Used for beta UI
|
||||
networkBeta: {
|
||||
localhost: LOCALHOST_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL_BETA,
|
||||
ropsten: ROPSTEN_RPC_URL_BETA,
|
||||
kovan: KOVAN_RPC_URL_BETA,
|
||||
rinkeby: RINKEBY_RPC_URL_BETA,
|
||||
},
|
||||
networkNames: {
|
||||
3: 'Ropsten',
|
||||
4: 'Rinkeby',
|
||||
42: 'Kovan',
|
||||
},
|
||||
enums: {
|
||||
DEFAULT_RPC,
|
||||
OLD_UI_NETWORK_TYPE,
|
||||
BETA_UI_NETWORK_TYPE,
|
||||
},
|
||||
}
|
||||
|
@ -7,14 +7,19 @@ const ComposedStore = require('obs-store/lib/composed')
|
||||
const extend = require('xtend')
|
||||
const EthQuery = require('eth-query')
|
||||
const createEventEmitterProxy = require('../lib/events-proxy.js')
|
||||
const RPC_ADDRESS_LIST = require('../config.js').network
|
||||
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
|
||||
const networkConfig = require('../config.js')
|
||||
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
|
||||
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
|
||||
|
||||
module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (config) {
|
||||
super()
|
||||
|
||||
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
|
||||
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
|
||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.providerStore = new ObservableStore(config.provider)
|
||||
@ -24,6 +29,23 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
}
|
||||
|
||||
async setNetworkEndpoints (version) {
|
||||
if (version === this._networkEndpointVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
this._networkEndpointVersion = version
|
||||
this._networkEndpoints = this.getNetworkEndpoints(version)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
const { type } = this.getProviderConfig()
|
||||
|
||||
return this.setProviderType(type, true)
|
||||
}
|
||||
|
||||
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
|
||||
return networkConfig[version]
|
||||
}
|
||||
|
||||
initializeProvider (_providerParams) {
|
||||
this._baseProviderParams = _providerParams
|
||||
const { type, rpcTarget } = this.providerStore.getState()
|
||||
@ -83,10 +105,13 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
return this.getRpcAddressForType(provider.type)
|
||||
}
|
||||
|
||||
async setProviderType (type) {
|
||||
async setProviderType (type, forceUpdate = false) {
|
||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
||||
// skip if type already matches
|
||||
if (type === this.getProviderConfig().type) return
|
||||
if (type === this.getProviderConfig().type && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
const rpcTarget = this.getRpcAddressForType(type)
|
||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
||||
this.providerStore.updateState({ type, rpcTarget })
|
||||
@ -98,8 +123,11 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
}
|
||||
|
||||
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
||||
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type]
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
|
||||
if (this._networkEndpoints[type]) {
|
||||
return this._networkEndpoints[type]
|
||||
}
|
||||
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -9,11 +9,21 @@ class PreferencesController {
|
||||
frequentRpcList: [],
|
||||
currentAccountTab: 'history',
|
||||
tokens: [],
|
||||
useBlockie: false,
|
||||
featureFlags: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
|
||||
setUseBlockie (val) {
|
||||
this.store.updateState({ useBlockie: val })
|
||||
}
|
||||
|
||||
getUseBlockie () {
|
||||
return this.store.getState().useBlockie
|
||||
}
|
||||
|
||||
setSelectedAddress (_address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const address = normalizeAddress(_address)
|
||||
@ -43,6 +53,17 @@ class PreferencesController {
|
||||
}
|
||||
|
||||
this.store.updateState({ tokens })
|
||||
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
removeToken (rawAddress) {
|
||||
const tokens = this.store.getState().tokens
|
||||
|
||||
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
||||
|
||||
this.store.updateState({ tokens: updatedTokens })
|
||||
return Promise.resolve(updatedTokens)
|
||||
}
|
||||
|
||||
getTokens () {
|
||||
@ -82,6 +103,22 @@ class PreferencesController {
|
||||
getFrequentRpcList () {
|
||||
return this.store.getState().frequentRpcList
|
||||
}
|
||||
|
||||
setFeatureFlag (feature, activated) {
|
||||
const currentFeatureFlags = this.store.getState().featureFlags
|
||||
const updatedFeatureFlags = {
|
||||
...currentFeatureFlags,
|
||||
[feature]: activated,
|
||||
}
|
||||
|
||||
this.store.updateState({ featureFlags: updatedFeatureFlags })
|
||||
|
||||
return Promise.resolve(updatedFeatureFlags)
|
||||
}
|
||||
|
||||
getFeatureFlags () {
|
||||
return this.store.getState().featureFlags
|
||||
}
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
@ -193,6 +193,10 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
|
||||
}
|
||||
|
||||
async updateTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||
}
|
||||
|
||||
async updateAndApproveTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||
await this.approveTransaction(txMeta.id)
|
||||
|
10
app/scripts/lib/environment-type.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = function environmentType () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
} else if (url.match(/home.html$/)) {
|
||||
return 'responsive'
|
||||
} else {
|
||||
return 'notification'
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
module.exports = function isPopupOrNotification () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
// if (url.match(/popup.html$/) || url.match(/home.html$/)) {
|
||||
// Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js)
|
||||
// Revert below regexes to above commented out regexes before merge to master
|
||||
if (url.match(/popup.html(?:\?.+)*$/) || url.match(/home.html(?:\?.+)*$/)) {
|
||||
return 'popup'
|
||||
} else {
|
||||
return 'notification'
|
||||
|
@ -1,5 +1,5 @@
|
||||
const extension = require('extensionizer')
|
||||
const height = 520
|
||||
const height = 620
|
||||
const width = 360
|
||||
|
||||
|
||||
|
@ -335,6 +335,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// etc
|
||||
getState: (cb) => cb(null, this.getState()),
|
||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||
setUseBlockie: this.setUseBlockie.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
|
||||
// coinbase
|
||||
@ -352,13 +353,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||
|
||||
// network management
|
||||
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
|
||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||
setCustomRpc: nodeify(this.setCustomRpc, this),
|
||||
|
||||
// PreferencesController
|
||||
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
|
||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
|
||||
@ -373,6 +377,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// txController
|
||||
cancelTransaction: nodeify(txController.cancelTransaction, txController),
|
||||
updateTransaction: nodeify(txController.updateTransaction, txController),
|
||||
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
||||
retryTransaction: nodeify(this.retryTransaction, this),
|
||||
|
||||
@ -821,6 +826,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return rpcTarget
|
||||
}
|
||||
|
||||
setUseBlockie (val, cb) {
|
||||
try {
|
||||
this.preferencesController.setUseBlockie(val)
|
||||
cb(null)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
recordFirstTimeInfo (initState) {
|
||||
if (!('firstTimeInfo' in initState)) {
|
||||
initState.firstTimeInfo = {
|
||||
|
@ -17,6 +17,11 @@ class ExtensionPlatform {
|
||||
return extension.runtime.getManifest().version
|
||||
}
|
||||
|
||||
openExtensionInBrowser () {
|
||||
const extensionURL = extension.runtime.getURL('home.html')
|
||||
this.openWindow({ url: extensionURL })
|
||||
}
|
||||
|
||||
getPlatformInfo (cb) {
|
||||
try {
|
||||
extension.runtime.getPlatformInfo((platform) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const async = require('async')
|
||||
const Dnode = require('dnode')
|
||||
const Eth = require('ethjs')
|
||||
const EthQuery = require('eth-query')
|
||||
const launchMetamaskUi = require('../../ui')
|
||||
const StreamProvider = require('web3-stream-provider')
|
||||
@ -34,6 +35,7 @@ function setupWeb3Connection (connectionStream) {
|
||||
providerStream.on('error', console.error.bind(console))
|
||||
global.ethereumProvider = providerStream
|
||||
global.ethQuery = new EthQuery(providerStream)
|
||||
global.eth = new Eth(providerStream)
|
||||
}
|
||||
|
||||
function setupControllerConnection (connectionStream, cb) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const injectCss = require('inject-css')
|
||||
const MetaMaskUiCss = require('../../ui/css')
|
||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||
const NewMetaMaskUiCss = require('../../ui/css')
|
||||
const startPopup = require('./popup-core')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
@ -11,10 +12,6 @@ const notificationManager = new NotificationManager()
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform()
|
||||
|
||||
// inject css
|
||||
const css = MetaMaskUiCss()
|
||||
injectCss(css)
|
||||
|
||||
// identify window type (popup, notification)
|
||||
const windowType = isPopupOrNotification()
|
||||
global.METAMASK_UI_TYPE = windowType
|
||||
@ -28,8 +25,21 @@ const connectionStream = new PortStream(extensionPort)
|
||||
const container = document.getElementById('app-content')
|
||||
startPopup({ container, connectionStream }, (err, store) => {
|
||||
if (err) return displayCriticalError(err)
|
||||
|
||||
let betaUIState = store.getState().metamask.featureFlags.betaUI
|
||||
let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
let deleteInjectedCss = injectCss(css)
|
||||
let newBetaUIState
|
||||
|
||||
store.subscribe(() => {
|
||||
const state = store.getState()
|
||||
newBetaUIState = state.metamask.featureFlags.betaUI
|
||||
if (newBetaUIState !== betaUIState) {
|
||||
deleteInjectedCss()
|
||||
betaUIState = newBetaUIState
|
||||
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
deleteInjectedCss = injectCss(css)
|
||||
}
|
||||
if (state.appState.shouldClose) notificationManager.closePopup()
|
||||
})
|
||||
})
|
||||
|
@ -8,6 +8,7 @@
|
||||
"frequentRpcList": [],
|
||||
"unapprovedTxs": {},
|
||||
"currentCurrency": "USD",
|
||||
"featureFlags": {"betaUI": true},
|
||||
"conversionRate": 12.7527416,
|
||||
"conversionDate": 1487624341,
|
||||
"noActiveNotices": false,
|
||||
|
43
gulpfile.js
@ -19,10 +19,16 @@ var manifest = require('./app/manifest.json')
|
||||
var gulpif = require('gulp-if')
|
||||
var replace = require('gulp-replace')
|
||||
var mkdirp = require('mkdirp')
|
||||
var sass = require('gulp-sass')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
var gulpStylelint = require('gulp-stylelint')
|
||||
var stylefmt = require('gulp-stylefmt')
|
||||
|
||||
|
||||
var disableDebugTools = gutil.env.disableDebugTools
|
||||
var debug = gutil.env.debug
|
||||
|
||||
|
||||
// browser reload
|
||||
|
||||
gulp.task('dev:reload', function() {
|
||||
@ -189,6 +195,37 @@ const jsFiles = [
|
||||
'popup',
|
||||
]
|
||||
|
||||
// scss compilation and autoprefixing tasks
|
||||
|
||||
gulp.task('build:scss', function () {
|
||||
return gulp.src('ui/app/css/index.scss')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('ui/app/css/output'))
|
||||
})
|
||||
gulp.task('watch:scss', function() {
|
||||
gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss']))
|
||||
})
|
||||
|
||||
gulp.task('lint-scss', function() {
|
||||
return gulp
|
||||
.src('ui/app/css/itcss/**/*.scss')
|
||||
.pipe(gulpStylelint({
|
||||
reporters: [
|
||||
{formatter: 'string', console: true}
|
||||
],
|
||||
fix: true,
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('fmt-scss', function () {
|
||||
return gulp.src('ui/app/css/itcss/**/*.scss')
|
||||
.pipe(stylefmt())
|
||||
.pipe(gulp.dest('ui/app/css/itcss'));
|
||||
});
|
||||
|
||||
// bundle tasks
|
||||
|
||||
var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
|
||||
@ -232,9 +269,9 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:ope
|
||||
|
||||
// high level tasks
|
||||
|
||||
gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload')))
|
||||
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
|
||||
|
||||
gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('dist', gulp.series('build', 'zip'))
|
||||
|
||||
// task generators
|
||||
@ -262,7 +299,7 @@ function zipTask(target) {
|
||||
return () => {
|
||||
return gulp.src(`dist/${target}/**`)
|
||||
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
.pipe(gulp.dest('builds'))
|
||||
}
|
||||
}
|
||||
|
||||
|
197
mascara/src/app/buy-ether-widget/index.js
Normal file
@ -0,0 +1,197 @@
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import {connect} from 'react-redux'
|
||||
import {qrcode} from 'qrcode-npm'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import ShapeShiftForm from '../shapeshift-form'
|
||||
import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
|
||||
|
||||
const OPTION_VALUES = {
|
||||
COINBASE: 'coinbase',
|
||||
SHAPESHIFT: 'shapeshift',
|
||||
QR_CODE: 'qr_code',
|
||||
}
|
||||
|
||||
const OPTIONS = [
|
||||
{
|
||||
name: 'Direct Deposit',
|
||||
value: OPTION_VALUES.QR_CODE,
|
||||
},
|
||||
{
|
||||
name: 'Buy with Dollars',
|
||||
value: OPTION_VALUES.COINBASE,
|
||||
},
|
||||
{
|
||||
name: 'Buy with Cryptos',
|
||||
value: OPTION_VALUES.SHAPESHIFT,
|
||||
},
|
||||
]
|
||||
|
||||
class BuyEtherWidget extends Component {
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string,
|
||||
skipText: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
onSkip: PropTypes.func,
|
||||
goToCoinbase: PropTypes.func,
|
||||
showAccountDetail: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
selectedOption: OPTION_VALUES.QR_CODE,
|
||||
};
|
||||
|
||||
|
||||
copyToClipboard = () => {
|
||||
const { address } = this.props
|
||||
|
||||
this.setState({ justCopied: true }, () => copyToClipboard(address))
|
||||
|
||||
setTimeout(() => this.setState({ justCopied: false }), 1000)
|
||||
}
|
||||
|
||||
renderSkip () {
|
||||
const {showAccountDetail, address, skipText, onSkip} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className="buy-ether__do-it-later"
|
||||
onClick={() => {
|
||||
if (onSkip) return onSkip()
|
||||
showAccountDetail(address)
|
||||
}}
|
||||
>
|
||||
{skipText || 'Do it later'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderCoinbaseLogo () {
|
||||
return (
|
||||
<svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
|
||||
<g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
|
||||
<g id="Imported-Layers" fill="#0081C9">
|
||||
<path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
|
||||
<path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
|
||||
<path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
|
||||
<path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
|
||||
<path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
|
||||
<path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
|
||||
<path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
|
||||
<path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
renderCoinbaseForm () {
|
||||
const {goToCoinbase, address} = this.props
|
||||
|
||||
return (
|
||||
<div className="buy-ether__action-content-wrapper">
|
||||
<div>{this.renderCoinbaseLogo()}</div>
|
||||
<div className="buy-ether__body-text">Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
|
||||
<a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
|
||||
<div className="buy-ether__buttons">
|
||||
<button
|
||||
className="first-time-flow__button"
|
||||
onClick={() => goToCoinbase(address)}
|
||||
>
|
||||
Buy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { address } = this.props
|
||||
const { justCopied } = this.state
|
||||
const qrImage = qrcode(4, 'M')
|
||||
qrImage.addData(address)
|
||||
qrImage.make()
|
||||
|
||||
switch (this.state.selectedOption) {
|
||||
case OPTION_VALUES.COINBASE:
|
||||
return this.renderCoinbaseForm()
|
||||
case OPTION_VALUES.SHAPESHIFT:
|
||||
return (
|
||||
<div className="buy-ether__action-content-wrapper">
|
||||
<div className="shapeshift-logo" />
|
||||
<div className="buy-ether__body-text">
|
||||
Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
|
||||
</div>
|
||||
<ShapeShiftForm btnClass="first-time-flow__button" />
|
||||
</div>
|
||||
)
|
||||
case OPTION_VALUES.QR_CODE:
|
||||
return (
|
||||
<div className="buy-ether__action-content-wrapper">
|
||||
<div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
||||
<div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
|
||||
<div className="buy-ether__small-body-text">(This is the account address that MetaMask created for you to recieve funds.)</div>
|
||||
<div className="buy-ether__buttons">
|
||||
<button
|
||||
className="first-time-flow__button"
|
||||
onClick={this.copyToClipboard}
|
||||
disabled={justCopied}
|
||||
>
|
||||
{ justCopied ? 'Copied' : 'Copy' }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className = '' } = this.props
|
||||
const { selectedOption } = this.state
|
||||
|
||||
return (
|
||||
<div className={`${className} buy-ether__content-wrapper`}>
|
||||
<div className="buy-ether__content-headline-wrapper">
|
||||
<div className="buy-ether__content-headline">Deposit Options</div>
|
||||
{this.renderSkip()}
|
||||
</div>
|
||||
<div className="buy-ether__content">
|
||||
<div className="buy-ether__side-panel">
|
||||
{OPTIONS.map(({ name, value }) => (
|
||||
<div
|
||||
key={value}
|
||||
className={classnames('buy-ether__side-panel-item', {
|
||||
'buy-ether__side-panel-item--selected': value === selectedOption,
|
||||
})}
|
||||
onClick={() => this.setState({ selectedOption: value })}
|
||||
>
|
||||
<div className="buy-ether__side-panel-item-name">{name}</div>
|
||||
{value === selectedOption && (
|
||||
<svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
|
||||
<path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="buy-ether__action-content">
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
({ metamask: { selectedAddress } }) => ({
|
||||
address: selectedAddress,
|
||||
}),
|
||||
dispatch => ({
|
||||
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
|
||||
showAccountDetail: address => dispatch(showAccountDetail(address)),
|
||||
})
|
||||
)(BuyEtherWidget)
|
@ -75,7 +75,7 @@
|
||||
|
||||
.backup-phrase__tips {
|
||||
margin: 40px 0 !important;
|
||||
width: initial !important;
|
||||
width: initial !important;
|
||||
}
|
||||
|
||||
.backup-phrase__confirm-secret,
|
||||
@ -337,7 +337,7 @@ button.backup-phrase__confirm-seed-option:hover {
|
||||
padding: 14px 21px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-moz-appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -540,10 +540,10 @@ button.backup-phrase__confirm-seed-option:hover {
|
||||
text-transform: uppercase;
|
||||
margin: 35px 0 14px;
|
||||
transition: 200ms ease-in-out;
|
||||
background-color: rgba(247, 134, 28, 0.9);
|
||||
}
|
||||
|
||||
button.first-time-flow__button[disabled] {
|
||||
background-color: rgba(247, 134, 28, 0.9);
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
|
66
old-ui/.gitignore
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,node
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
289
old-ui/app/account-detail.js
Normal file
@ -0,0 +1,289 @@
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const valuesFor = require('./util').valuesFor
|
||||
const Identicon = require('./components/identicon')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const TransactionList = require('./components/transaction-list')
|
||||
const ExportAccountView = require('./components/account-export')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const EditableLabel = require('./components/editable-label')
|
||||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
metamask: state.metamask,
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
address: state.metamask.selectedAddress,
|
||||
accountDetail: state.appState.accountDetail,
|
||||
network: state.metamask.network,
|
||||
unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs),
|
||||
shapeShiftTxList: state.metamask.shapeShiftTxList,
|
||||
transactions: state.metamask.selectedAddressTxList || [],
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
currentAccountTab: state.metamask.currentAccountTab,
|
||||
tokens: state.metamask.tokens,
|
||||
computedBalances: state.metamask.computedBalances,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountDetailScreen, Component)
|
||||
function AccountDetailScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.render = function () {
|
||||
var props = this.props
|
||||
var selected = props.address || Object.keys(props.accounts)[0]
|
||||
var checksumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
var identity = props.identities[selected]
|
||||
var account = props.accounts[selected]
|
||||
const { network, conversionRate, currentCurrency } = props
|
||||
|
||||
return (
|
||||
|
||||
h('.account-detail-section.full-flex-height', [
|
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header - identicon + nav
|
||||
h('div', {
|
||||
style: {
|
||||
paddingTop: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
// large identicon and addresses
|
||||
h('.identicon-wrapper.select-none', [
|
||||
h(Identicon, {
|
||||
diameter: 62,
|
||||
address: selected,
|
||||
}),
|
||||
]),
|
||||
h('div.flex-column', {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h(EditableLabel, {
|
||||
textValue: identity ? identity.name : '',
|
||||
state: {
|
||||
isEditingLabel: false,
|
||||
},
|
||||
saveText: (text) => {
|
||||
props.dispatch(actions.saveAccountLabel(selected, text))
|
||||
},
|
||||
}, [
|
||||
|
||||
// What is shown when not editing + edit text:
|
||||
h('label.editing-label', [h('.edit-text', 'edit')]),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
'div.font-medium.color-forest',
|
||||
{
|
||||
name: 'edit',
|
||||
style: {
|
||||
},
|
||||
},
|
||||
[
|
||||
h('h2', {
|
||||
style: {
|
||||
maxWidth: '180px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
padding: '5px 0px',
|
||||
lineHeight: '25px',
|
||||
},
|
||||
}, [
|
||||
identity && identity.name,
|
||||
]),
|
||||
]
|
||||
),
|
||||
h(
|
||||
AccountDropdowns,
|
||||
{
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
selected,
|
||||
network,
|
||||
identities: props.identities,
|
||||
enableAccountOptions: true,
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
}, [
|
||||
|
||||
// address
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
paddingTop: '3px',
|
||||
width: '5em',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginTop: '15px',
|
||||
marginBottom: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
]),
|
||||
|
||||
// account ballence
|
||||
|
||||
]),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
marginTop: '10px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('div', {}, [
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
this.subview(),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.subview = function () {
|
||||
var subview
|
||||
try {
|
||||
subview = this.props.accountDetail.subview
|
||||
} catch (e) {
|
||||
subview = null
|
||||
}
|
||||
|
||||
switch (subview) {
|
||||
case 'transactions':
|
||||
return this.tabSections()
|
||||
case 'export':
|
||||
var state = extend({key: 'export'}, this.props)
|
||||
return h(ExportAccountView, state)
|
||||
default:
|
||||
return this.tabSections()
|
||||
}
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.tabSections = function () {
|
||||
const { currentAccountTab } = this.props
|
||||
|
||||
return h('section.tabSection.full-flex-height.grow-tenx', [
|
||||
|
||||
h(TabBar, {
|
||||
tabs: [
|
||||
{ content: 'Sent', key: 'history' },
|
||||
{ content: 'Tokens', key: 'tokens' },
|
||||
],
|
||||
defaultTab: currentAccountTab || 'history',
|
||||
tabSelected: (key) => {
|
||||
this.props.dispatch(actions.setCurrentAccountTab(key))
|
||||
},
|
||||
}),
|
||||
|
||||
this.tabSwitchView(),
|
||||
])
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.tabSwitchView = function () {
|
||||
const props = this.props
|
||||
const { address, network } = props
|
||||
const { currentAccountTab, tokens } = this.props
|
||||
|
||||
switch (currentAccountTab) {
|
||||
case 'tokens':
|
||||
return h(TokenList, {
|
||||
userAddress: address,
|
||||
network,
|
||||
tokens,
|
||||
addToken: () => this.props.dispatch(actions.showAddTokenPage()),
|
||||
})
|
||||
default:
|
||||
return this.transactionList()
|
||||
}
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.transactionList = function () {
|
||||
const {transactions, unapprovedMsgs, address,
|
||||
network, shapeShiftTxList, conversionRate } = this.props
|
||||
|
||||
return h(TransactionList, {
|
||||
transactions: transactions.sort((a, b) => b.time - a.time),
|
||||
network,
|
||||
unapprovedMsgs,
|
||||
conversionRate,
|
||||
address,
|
||||
shapeShiftTxList,
|
||||
viewPendingTx: (txId) => {
|
||||
this.props.dispatch(actions.viewPendingTx(txId))
|
||||
},
|
||||
})
|
||||
}
|
101
old-ui/app/accounts/import/index.js
Normal file
@ -0,0 +1,101 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
import Select from 'react-select'
|
||||
|
||||
// Subviews
|
||||
const JsonImportView = require('./json.js')
|
||||
const PrivateKeyImportView = require('./private-key.js')
|
||||
|
||||
const menuItems = [
|
||||
'Private Key',
|
||||
'JSON File',
|
||||
]
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
menuItems,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountImportSubview, Component)
|
||||
function AccountImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { menuItems } = props
|
||||
const { type } = state
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Import Accounts'),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
padding: '10px',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
|
||||
|
||||
h('style', `
|
||||
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
|
||||
color: rgb(174,174,174);
|
||||
}
|
||||
`),
|
||||
|
||||
h(Select, {
|
||||
name: 'import-type-select',
|
||||
clearable: false,
|
||||
value: type || menuItems[0],
|
||||
options: menuItems.map((type) => {
|
||||
return {
|
||||
value: type,
|
||||
label: type,
|
||||
}
|
||||
}),
|
||||
onChange: (opt) => {
|
||||
props.dispatch(actions.showImportPage())
|
||||
this.setState({ type: opt.value })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderImportView(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.renderImportView = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { type } = state
|
||||
const { menuItems } = props
|
||||
const current = type || menuItems[0]
|
||||
|
||||
switch (current) {
|
||||
case 'Private Key':
|
||||
return h(PrivateKeyImportView)
|
||||
case 'JSON File':
|
||||
return h(JsonImportView)
|
||||
default:
|
||||
return h(JsonImportView)
|
||||
}
|
||||
}
|
100
old-ui/app/accounts/import/json.js
Normal file
@ -0,0 +1,100 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
const FileInput = require('react-simple-file-input').default
|
||||
|
||||
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file'
|
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(JsonImportSubview, Component)
|
||||
function JsonImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.render = function () {
|
||||
const { error } = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '5px 15px 0px 15px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('p', 'Used by a variety of different clients'),
|
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'),
|
||||
|
||||
h(FileInput, {
|
||||
readAs: 'text',
|
||||
onLoad: this.onLoad.bind(this),
|
||||
style: {
|
||||
margin: '20px 0px 12px 20px',
|
||||
fontSize: '15px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
placeholder: 'Enter password',
|
||||
id: 'json-password-box',
|
||||
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.createNewKeychain.bind(this),
|
||||
style: {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Import'),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) {
|
||||
this.setState({file: file, fileContents: event.target.result})
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () {
|
||||
const state = this.state
|
||||
const { fileContents } = state
|
||||
|
||||
if (!fileContents) {
|
||||
const message = 'You must select a file to import.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
const passwordInput = document.getElementById('json-password-box')
|
||||
const password = passwordInput.value
|
||||
|
||||
if (!password) {
|
||||
const message = 'You must enter a password for the selected file.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ]))
|
||||
}
|
67
old-ui/app/accounts/import/private-key.js
Normal file
@ -0,0 +1,67 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(PrivateKeyImportView, Component)
|
||||
function PrivateKeyImportView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.render = function () {
|
||||
const { error } = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '5px 15px 0px 15px',
|
||||
},
|
||||
}, [
|
||||
h('span', 'Paste your private key string here'),
|
||||
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'private-key-box',
|
||||
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.createNewKeychain.bind(this),
|
||||
style: {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Import'),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createNewKeychain = function () {
|
||||
const input = document.getElementById('private-key-box')
|
||||
const privateKey = input.value
|
||||
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
|
||||
}
|
30
old-ui/app/accounts/import/seed.js
Normal file
@ -0,0 +1,30 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
module.exports = connect(mapStateToProps)(SeedImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(SeedImportSubview, Component)
|
||||
function SeedImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
SeedImportSubview.prototype.render = function () {
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
`Paste your seed phrase here!`,
|
||||
h('textarea'),
|
||||
h('br'),
|
||||
h('button', 'Submit'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
238
old-ui/app/add-token.js
Normal file
@ -0,0 +1,238 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const Tooltip = require('./components/tooltip.js')
|
||||
|
||||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const abi = require('human-standard-token-abi')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
|
||||
const emptyAddr = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
module.exports = connect(mapStateToProps)(AddTokenScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AddTokenScreen, Component)
|
||||
function AddTokenScreen () {
|
||||
this.state = {
|
||||
warning: null,
|
||||
address: null,
|
||||
symbol: 'TOKEN',
|
||||
decimals: 18,
|
||||
}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.render = function () {
|
||||
const state = this.state
|
||||
const props = this.props
|
||||
const { warning, symbol, decimals } = state
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Add Token'),
|
||||
]),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around', {
|
||||
style: {
|
||||
padding: '20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div', [
|
||||
h(Tooltip, {
|
||||
position: 'top',
|
||||
title: 'The contract of the actual token contract. Click for more info.',
|
||||
}, [
|
||||
h('a', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||
target: '_blank',
|
||||
}, [
|
||||
h('span', 'Token Contract Address '),
|
||||
h('i.fa.fa-question-circle'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('section.flex-row.flex-center', [
|
||||
h('input#token-address', {
|
||||
name: 'address',
|
||||
placeholder: 'Token Contract Address',
|
||||
onChange: this.tokenAddressDidChange.bind(this),
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Token Symbol'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_symbol', {
|
||||
placeholder: `Like "ETH"`,
|
||||
value: symbol,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var symbol = element.value
|
||||
this.setState({ symbol })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Decimals of Precision'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_decimals', {
|
||||
value: decimals,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 36,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var decimals = element.value.trim()
|
||||
this.setState({ decimals })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick: (event) => {
|
||||
const valid = this.validateInputs()
|
||||
if (!valid) return
|
||||
|
||||
const { address, symbol, decimals } = this.state
|
||||
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
|
||||
},
|
||||
}, 'Add'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.componentWillMount = function () {
|
||||
if (typeof global.ethereumProvider === 'undefined') return
|
||||
|
||||
this.eth = new Eth(global.ethereumProvider)
|
||||
this.contract = new EthContract(this.eth)
|
||||
this.TokenContract = this.contract(abi)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.tokenAddressDidChange = function (event) {
|
||||
const el = event.target
|
||||
const address = el.value.trim()
|
||||
if (ethUtil.isValidAddress(address) && address !== emptyAddr) {
|
||||
this.setState({ address })
|
||||
this.attemptToAutoFillTokenParams(address)
|
||||
}
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.validateInputs = function () {
|
||||
let msg = ''
|
||||
const state = this.state
|
||||
const identitiesList = Object.keys(this.props.identities)
|
||||
const { address, symbol, decimals } = state
|
||||
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
|
||||
|
||||
const validAddress = ethUtil.isValidAddress(address)
|
||||
if (!validAddress) {
|
||||
msg += 'Address is invalid. '
|
||||
}
|
||||
|
||||
const validDecimals = decimals >= 0 && decimals < 36
|
||||
if (!validDecimals) {
|
||||
msg += 'Decimals must be at least 0, and not over 36. '
|
||||
}
|
||||
|
||||
const symbolLen = symbol.trim().length
|
||||
const validSymbol = symbolLen > 0 && symbolLen < 10
|
||||
if (!validSymbol) {
|
||||
msg += 'Symbol must be between 0 and 10 characters.'
|
||||
}
|
||||
|
||||
const ownAddress = identitiesList.includes(standardAddress)
|
||||
if (ownAddress) {
|
||||
msg = 'Personal address detected. Input the token contract address.'
|
||||
}
|
||||
|
||||
const isValid = validAddress && validDecimals && !ownAddress
|
||||
|
||||
if (!isValid) {
|
||||
this.setState({
|
||||
warning: msg,
|
||||
})
|
||||
} else {
|
||||
this.setState({ warning: null })
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) {
|
||||
const contract = this.TokenContract.at(address)
|
||||
|
||||
const results = await Promise.all([
|
||||
contract.symbol(),
|
||||
contract.decimals(),
|
||||
])
|
||||
|
||||
const [ symbol, decimals ] = results
|
||||
if (symbol && decimals) {
|
||||
console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals })
|
||||
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
|
||||
}
|
||||
}
|
684
old-ui/app/app.js
Normal file
@ -0,0 +1,684 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../ui/app/actions')
|
||||
// mascara
|
||||
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
|
||||
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
|
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./components/notice')
|
||||
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||
// other views
|
||||
const ConfigScreen = require('./config')
|
||||
const AddTokenScreen = require('./add-token')
|
||||
const Import = require('./accounts/import')
|
||||
const InfoScreen = require('./info')
|
||||
const Loading = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
inherits(App, Component)
|
||||
function App () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
address,
|
||||
keyrings,
|
||||
isInitialized,
|
||||
noActiveNotices,
|
||||
seedWords,
|
||||
featureFlags,
|
||||
} = state.metamask
|
||||
const selected = address || Object.keys(accounts)[0]
|
||||
|
||||
return {
|
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading,
|
||||
loadingMessage: state.appState.loadingMessage,
|
||||
noActiveNotices: state.metamask.noActiveNotices,
|
||||
isInitialized: state.metamask.isInitialized,
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
currentView: state.appState.currentView,
|
||||
activeAddress: state.appState.activeAddress,
|
||||
transForward: state.appState.transForward,
|
||||
isMascara: state.metamask.isMascara,
|
||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
||||
seedWords: state.metamask.seedWords,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
menuOpen: state.appState.menuOpen,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
featureFlags,
|
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
identities,
|
||||
selected,
|
||||
keyrings,
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { isLoading, loadingMessage, transForward, network } = props
|
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||
`Connecting to ${this.getNetworkName()}` : null
|
||||
log.debug('Main ui render function')
|
||||
|
||||
return (
|
||||
h('.flex-column.full-height', {
|
||||
style: {
|
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// app bar
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
|
||||
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
|
||||
|
||||
// panel content
|
||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||
const {isMascara, isOnboarding} = props
|
||||
|
||||
// Do not render header if user is in mascara onboarding
|
||||
if (isMascara && isOnboarding) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Do not render header if user is in mascara buy ether
|
||||
if (isMascara && props.currentView.name === 'buyEth') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
h('.full-width', {
|
||||
height: '38px',
|
||||
}, [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div.left-menu-section', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: '/images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
props.isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
props.isUnlocked && h(AccountDropdowns, {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: this.props.identities,
|
||||
selected: this.props.currentView.context,
|
||||
network: this.props.network,
|
||||
keyrings: this.props.keyrings,
|
||||
}, []),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !state.isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '2px',
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('localhost')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => this.props.dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '38px',
|
||||
},
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Lock'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
||||
},
|
||||
}, 'Try Beta!'),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
|
||||
const { isMascara } = this.props
|
||||
|
||||
return isMascara
|
||||
? null
|
||||
: h(Loading, {
|
||||
isLoading: isLoading || isLoadingNetwork,
|
||||
loadingMessage: loadMessage,
|
||||
})
|
||||
}
|
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
return (
|
||||
h('.flex-row', {
|
||||
key: 'leftArrow',
|
||||
style: style,
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.cursor-pointer'),
|
||||
justArrow ? null : h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginLeft: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering primary')
|
||||
var props = this.props
|
||||
const {isMascara, isOnboarding} = props
|
||||
|
||||
if (isMascara && isOnboarding) {
|
||||
return h(MascaraFirstTime)
|
||||
}
|
||||
|
||||
// notices
|
||||
if (!props.noActiveNotices) {
|
||||
log.debug('rendering notice screen for unread notices.')
|
||||
return h(NoticeScreen, {
|
||||
notice: props.lastUnreadNotice,
|
||||
key: 'NoticeScreen',
|
||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||
})
|
||||
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||
log.debug('rendering notice screen for lost accounts view.')
|
||||
return h(NoticeScreen, {
|
||||
notice: generateLostAccountsNotice(props.lostAccounts),
|
||||
key: 'LostAccountsNotice',
|
||||
onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
||||
})
|
||||
}
|
||||
|
||||
if (props.seedWords) {
|
||||
log.debug('rendering seed words')
|
||||
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
||||
}
|
||||
|
||||
// show initialize screen
|
||||
if (!props.isInitialized || props.forgottenPassword) {
|
||||
// show current view
|
||||
log.debug('rendering an initialize screen')
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
log.debug('rendering restore vault screen')
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
default:
|
||||
log.debug('rendering menu screen')
|
||||
return h(InitializeMenuScreen, {key: 'menuScreenInit'})
|
||||
}
|
||||
}
|
||||
|
||||
// show unlock screen
|
||||
if (!props.isUnlocked) {
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
log.debug('rendering restore vault screen')
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
case 'config':
|
||||
log.debug('rendering config screen from unlock screen.')
|
||||
return h(ConfigScreen, {key: 'config'})
|
||||
|
||||
default:
|
||||
log.debug('rendering locked screen')
|
||||
return h(UnlockScreen, {key: 'locked'})
|
||||
}
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'accountDetail':
|
||||
log.debug('rendering account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
|
||||
case 'sendTransaction':
|
||||
log.debug('rendering send tx screen')
|
||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||
|
||||
case 'newKeychain':
|
||||
log.debug('rendering new keychain screen')
|
||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||
|
||||
case 'confTx':
|
||||
log.debug('rendering confirm tx screen')
|
||||
return h(ConfirmTxScreen, {key: 'confirm-tx'})
|
||||
|
||||
case 'add-token':
|
||||
log.debug('rendering add-token screen from unlock screen.')
|
||||
return h(AddTokenScreen, {key: 'add-token'})
|
||||
|
||||
case 'config':
|
||||
log.debug('rendering config screen')
|
||||
return h(ConfigScreen, {key: 'config'})
|
||||
|
||||
case 'import-menu':
|
||||
log.debug('rendering import screen')
|
||||
return h(Import, {key: 'import-menu'})
|
||||
|
||||
case 'reveal-seed-conf':
|
||||
log.debug('rendering reveal seed confirmation screen')
|
||||
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
|
||||
|
||||
case 'info':
|
||||
log.debug('rendering info screen')
|
||||
return h(InfoScreen, {key: 'info'})
|
||||
|
||||
case 'buyEth':
|
||||
log.debug('rendering buy ether screen')
|
||||
return h(BuyView, {key: 'buyEthView'})
|
||||
|
||||
case 'onboardingBuyEth':
|
||||
log.debug('rendering onboarding buy ether screen')
|
||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
|
||||
|
||||
case 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
console.log(`QrView`, QrView);
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
marginTop: '50px',
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '44px',
|
||||
width: '285px',
|
||||
},
|
||||
}, [
|
||||
h(QrView, {key: 'qr'}),
|
||||
]),
|
||||
])
|
||||
|
||||
default:
|
||||
log.debug('rendering default, account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.toggleMetamaskActive = function () {
|
||||
if (!this.props.isUnlocked) {
|
||||
// currently inactive: redirect to password box
|
||||
var passwordBox = document.querySelector('input[type=password]')
|
||||
if (!passwordBox) return
|
||||
passwordBox.focus()
|
||||
} else {
|
||||
// currently active: deactivate
|
||||
this.props.dispatch(actions.lockMetamask(false))
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.getNetworkName = function () {
|
||||
const { provider } = this.props
|
||||
const providerName = provider.type
|
||||
|
||||
let name
|
||||
|
||||
if (providerName === 'mainnet') {
|
||||
name = 'Main Ethereum Network'
|
||||
} else if (providerName === 'ropsten') {
|
||||
name = 'Ropsten Test Network'
|
||||
} else if (providerName === 'kovan') {
|
||||
name = 'Kovan Test Network'
|
||||
} else if (providerName === 'rinkeby') {
|
||||
name = 'Rinkeby Test Network'
|
||||
} else {
|
||||
name = 'Unknown Private Network'
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
319
old-ui/app/components/account-dropdowns.js
Normal file
@ -0,0 +1,319 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('react').PropTypes
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const genAccountLink = require('etherscan-link').createAccountLink
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('./identicon')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
}
|
||||
this.accountSelectorToggleClassName = 'accounts-selector'
|
||||
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { identities, selected, keyrings } = this.props
|
||||
|
||||
return Object.keys(identities).map((key, index) => {
|
||||
const identity = identities[key]
|
||||
const isSelected = identity.address === selected
|
||||
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress) ||
|
||||
kr.accounts.includes(identity.address)
|
||||
})
|
||||
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
this.props.actions.showAccountDetail(identity.address)
|
||||
},
|
||||
style: {
|
||||
marginTop: index === 0 ? '5px' : '',
|
||||
fontSize: '24px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
address: identity.address,
|
||||
diameter: 32,
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
},
|
||||
),
|
||||
this.indicateIfLoose(keyring),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '20px',
|
||||
fontSize: '24px',
|
||||
maxWidth: '145px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}, identity.name || ''),
|
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
|
||||
]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
indicateIfLoose (keyring) {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
renderAccountSelector () {
|
||||
const { actions } = this.props
|
||||
const { accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
|
||||
style: {
|
||||
marginLeft: '-238px',
|
||||
marginTop: '38px',
|
||||
minWidth: '180px',
|
||||
overflowY: 'auto',
|
||||
maxHeight: '300px',
|
||||
width: '300px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '8px 25px',
|
||||
},
|
||||
isOpen: accountSelectorActive,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
|
||||
if (accountSelectorActive && isNotToggleElement) {
|
||||
this.setState({ accountSelectorActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
...this.renderAccounts(),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.addNewAccount(),
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
diameter: 32,
|
||||
},
|
||||
),
|
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'),
|
||||
],
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.showImportPage(),
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
diameter: 32,
|
||||
},
|
||||
),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '20px',
|
||||
fontSize: '24px',
|
||||
marginBottom: '5px',
|
||||
},
|
||||
}, 'Import Account'),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountOptions () {
|
||||
const { actions } = this.props
|
||||
const { optionsMenuActive } = this.state
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '-215px',
|
||||
minWidth: '180px',
|
||||
},
|
||||
isOpen: optionsMenuActive,
|
||||
onClickOutside: () => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
|
||||
if (optionsMenuActive && isNotToggleElement) {
|
||||
this.setState({ optionsMenuActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, network } = this.props
|
||||
const url = genAccountLink(selected, network)
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
},
|
||||
'View account on Etherscan',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, identities } = this.props
|
||||
var identity = identities[selected]
|
||||
actions.showQrView(selected, identity ? identity.name : '')
|
||||
},
|
||||
},
|
||||
'Show QR Code',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected } = this.props
|
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
copyToClipboard(checkSumAddress)
|
||||
},
|
||||
},
|
||||
'Copy Address to clipboard',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
actions.requestAccountExport()
|
||||
},
|
||||
},
|
||||
'Export Private Key',
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { style, enableAccountsSelector, enableAccountOptions } = this.props
|
||||
const { optionsMenuActive, accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
style: style,
|
||||
},
|
||||
[
|
||||
enableAccountsSelector && h(
|
||||
// 'i.fa.fa-angle-down',
|
||||
'div.cursor-pointer.color-orange.accounts-selector',
|
||||
{
|
||||
style: {
|
||||
// fontSize: '1.8em',
|
||||
background: 'url(images/switch_acc.svg) white center center no-repeat',
|
||||
height: '25px',
|
||||
width: '25px',
|
||||
transform: 'scale(0.75)',
|
||||
marginRight: '3px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: !accountSelectorActive,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountSelector(),
|
||||
),
|
||||
enableAccountOptions && h(
|
||||
'i.fa.fa-ellipsis-h',
|
||||
{
|
||||
style: {
|
||||
fontSize: '1.8em',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: !optionsMenuActive,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountOptions()
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AccountDropdowns.defaultProps = {
|
||||
enableAccountsSelector: false,
|
||||
enableAccountOptions: false,
|
||||
}
|
||||
|
||||
AccountDropdowns.propTypes = {
|
||||
identities: PropTypes.objectOf(PropTypes.object),
|
||||
selected: PropTypes.string,
|
||||
keyrings: PropTypes.array,
|
||||
actions: PropTypes.objectOf(PropTypes.func),
|
||||
network: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
enableAccountOptions: PropTypes.bool,
|
||||
enableAccountsSelector: PropTypes.bool,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
actions: {
|
||||
showConfigPage: () => dispatch(actions.showConfigPage()),
|
||||
requestAccountExport: () => dispatch(actions.requestExportAccount()),
|
||||
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
|
||||
addNewAccount: () => dispatch(actions.addNewAccount()),
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
|
||||
}
|
132
old-ui/app/components/account-export.js
Normal file
@ -0,0 +1,132 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const exportAsFile = require('../util').exportAsFile
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
module.exports = connect(mapStateToProps)(ExportAccountView)
|
||||
|
||||
inherits(ExportAccountView, Component)
|
||||
function ExportAccountView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
ExportAccountView.prototype.render = function () {
|
||||
const state = this.props
|
||||
const accountDetail = state.accountDetail
|
||||
const nickname = state.identities[state.address].name
|
||||
|
||||
if (!accountDetail) return h('div')
|
||||
const accountExport = accountDetail.accountExport
|
||||
|
||||
const notExporting = accountExport === 'none'
|
||||
const exportRequested = accountExport === 'requested'
|
||||
const accountExported = accountExport === 'completed'
|
||||
|
||||
if (notExporting) return h('div')
|
||||
|
||||
if (exportRequested) {
|
||||
const warning = `Export private keys at your own risk.`
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('div', {
|
||||
key: 'exporting',
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
h('p.error', warning),
|
||||
h('input#exportAccount.sizing-input', {
|
||||
type: 'password',
|
||||
placeholder: 'confirm password',
|
||||
onKeyPress: this.onExportKeyPress.bind(this),
|
||||
style: {
|
||||
position: 'relative',
|
||||
top: '1.5px',
|
||||
marginBottom: '7px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
h('div', {
|
||||
key: 'buttons',
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('button', {
|
||||
onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }),
|
||||
style: {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}, 'Submit'),
|
||||
h('button', {
|
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
|
||||
}, 'Cancel'),
|
||||
]),
|
||||
(this.props.warning) && (
|
||||
h('span.error', {
|
||||
style: {
|
||||
margin: '20px',
|
||||
},
|
||||
}, this.props.warning.split('-'))
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (accountExported) {
|
||||
const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
|
||||
|
||||
return h('div.privateKey', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
h('label', 'Your private key (click to copy):'),
|
||||
h('p.error.cursor-pointer', {
|
||||
style: {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
webkitUserSelect: 'text',
|
||||
maxWidth: '275px',
|
||||
},
|
||||
onClick: function (event) {
|
||||
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
|
||||
},
|
||||
}, plainKey),
|
||||
h('button', {
|
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
|
||||
}, 'Done'),
|
||||
h('button', {
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
|
||||
}, 'Save as File'),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
ExportAccountView.prototype.onExportKeyPress = function (event) {
|
||||
if (event.key !== 'Enter') return
|
||||
event.preventDefault()
|
||||
|
||||
const input = document.getElementById('exportAccount').value
|
||||
this.props.dispatch(actions.exportAccount(input, this.props.address))
|
||||
}
|
86
old-ui/app/components/account-panel.js
Normal file
@ -0,0 +1,86 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const Identicon = require('./identicon')
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
module.exports = AccountPanel
|
||||
|
||||
|
||||
inherits(AccountPanel, Component)
|
||||
function AccountPanel () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountPanel.prototype.render = function () {
|
||||
var state = this.props
|
||||
var identity = state.identity || {}
|
||||
var account = state.account || {}
|
||||
var isFauceting = state.isFauceting
|
||||
|
||||
var panelState = {
|
||||
key: `accountPanel${identity.address}`,
|
||||
identiconKey: identity.address,
|
||||
identiconLabel: identity.name || '',
|
||||
attributes: [
|
||||
{
|
||||
key: 'ADDRESS',
|
||||
value: addressSummary(identity.address),
|
||||
},
|
||||
balanceOrFaucetingIndication(account, isFauceting),
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
h('.identity-panel.flex-row.flex-space-between', {
|
||||
style: {
|
||||
flex: '1 0 auto',
|
||||
cursor: panelState.onClick ? 'pointer' : undefined,
|
||||
},
|
||||
onClick: panelState.onClick,
|
||||
}, [
|
||||
|
||||
// account identicon
|
||||
h('.identicon-wrapper.flex-column.select-none', [
|
||||
h(Identicon, {
|
||||
address: panelState.identiconKey,
|
||||
imageify: state.imageifyIdenticons,
|
||||
}),
|
||||
h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'),
|
||||
]),
|
||||
|
||||
// account address, balance
|
||||
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
|
||||
panelState.attributes.map((attr) => {
|
||||
return h('.flex-row.flex-space-between', {
|
||||
key: '' + Math.round(Math.random() * 1000000),
|
||||
}, [
|
||||
h('label.font-small.no-select', attr.key),
|
||||
h('span.font-small', attr.value),
|
||||
])
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
function balanceOrFaucetingIndication (account, isFauceting) {
|
||||
// Temporarily deactivating isFauceting indication
|
||||
// because it shows fauceting for empty restored accounts.
|
||||
if (/* isFauceting*/ false) {
|
||||
return {
|
||||
key: 'Account is auto-funding.',
|
||||
value: 'Please wait.',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
key: 'BALANCE',
|
||||
value: formatBalance(account.balance),
|
||||
}
|
||||
}
|
||||
}
|
89
old-ui/app/components/balance.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
|
||||
h('.ether-balance.ether-balance-amount', {
|
||||
style: style,
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline',
|
||||
width: width,
|
||||
},
|
||||
}, this.renderBalance(value)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (props.shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
},
|
||||
}, this.props.incoming ? `+${balance}` : balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value }) : null,
|
||||
]))
|
||||
)
|
||||
}
|
46
old-ui/app/components/binary-renderer.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = BinaryRenderer
|
||||
|
||||
inherits(BinaryRenderer, Component)
|
||||
function BinaryRenderer () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
BinaryRenderer.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { value, style } = props
|
||||
const text = this.hexToText(value)
|
||||
|
||||
const defaultStyle = extend({
|
||||
width: '315px',
|
||||
maxHeight: '210px',
|
||||
resize: 'none',
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
padding: '3px',
|
||||
}, style)
|
||||
|
||||
return (
|
||||
h('textarea.font-small', {
|
||||
readOnly: true,
|
||||
style: defaultStyle,
|
||||
defaultValue: text,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
BinaryRenderer.prototype.hexToText = function (hex) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(hex)
|
||||
const buff = Buffer.from(stripped, 'hex')
|
||||
return buff.toString('utf8')
|
||||
} catch (e) {
|
||||
return hex
|
||||
}
|
||||
}
|
||||
|
181
old-ui/app/components/bn-as-decimal-input.js
Normal file
@ -0,0 +1,181 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = BnAsDecimalInput
|
||||
|
||||
inherits(BnAsDecimalInput, Component)
|
||||
function BnAsDecimalInput () {
|
||||
this.state = { invalid: null }
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
/* Bn as Decimal Input
|
||||
*
|
||||
* A component for allowing easy, decimal editing
|
||||
* of a passed in bn string value.
|
||||
*
|
||||
* On change, calls back its `onChange` function parameter
|
||||
* and passes it an updated bn string.
|
||||
*/
|
||||
|
||||
BnAsDecimalInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
const { value, scale, precision, onChange, min, max } = props
|
||||
|
||||
const suffix = props.suffix
|
||||
const style = props.style
|
||||
const valueString = value.toString(10)
|
||||
const newMin = min && this.downsize(min.toString(10), scale)
|
||||
const newMax = max && this.downsize(max.toString(10), scale)
|
||||
const newValue = this.downsize(valueString, scale)
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('input.hex-input', {
|
||||
type: 'number',
|
||||
step: 'any',
|
||||
required: true,
|
||||
min: newMin,
|
||||
max: newMax,
|
||||
style: extend({
|
||||
display: 'block',
|
||||
textAlign: 'right',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #bdbdbd',
|
||||
|
||||
}, style),
|
||||
value: newValue,
|
||||
onBlur: (event) => {
|
||||
this.updateValidity(event)
|
||||
},
|
||||
onChange: (event) => {
|
||||
this.updateValidity(event)
|
||||
const value = (event.target.value === '') ? '' : event.target.value
|
||||
|
||||
|
||||
const scaledNumber = this.upsize(value, scale, precision)
|
||||
const precisionBN = new BN(scaledNumber, 10)
|
||||
onChange(precisionBN, event.target.checkValidity())
|
||||
},
|
||||
onInvalid: (event) => {
|
||||
const msg = this.constructWarning()
|
||||
if (msg === state.invalid) {
|
||||
return
|
||||
}
|
||||
this.setState({ invalid: msg })
|
||||
event.preventDefault()
|
||||
return false
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '6px',
|
||||
width: '20px',
|
||||
},
|
||||
}, suffix),
|
||||
]),
|
||||
|
||||
state.invalid ? h('span.error', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '0px',
|
||||
textAlign: 'right',
|
||||
transform: 'translateY(26px)',
|
||||
padding: '3px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
zIndex: '1',
|
||||
textTransform: 'capitalize',
|
||||
border: '2px solid #E20202',
|
||||
},
|
||||
}, state.invalid) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.setValid = function (message) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.updateValidity = function (event) {
|
||||
const target = event.target
|
||||
const value = this.props.value
|
||||
const newValue = target.value
|
||||
|
||||
if (value === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const valid = target.checkValidity()
|
||||
|
||||
if (valid) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.constructWarning = function () {
|
||||
const { name, min, max, scale, suffix } = this.props
|
||||
const newMin = min && this.downsize(min.toString(10), scale)
|
||||
const newMax = max && this.downsize(max.toString(10), scale)
|
||||
let message = name ? name + ' ' : ''
|
||||
|
||||
if (min && max) {
|
||||
message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
|
||||
} else if (min) {
|
||||
message += `must be greater than or equal to ${newMin} ${suffix}.`
|
||||
} else if (max) {
|
||||
message += `must be less than or equal to ${newMax} ${suffix}.`
|
||||
} else {
|
||||
message += 'Invalid input.'
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale) {
|
||||
// if there is no scaling, simply return the number
|
||||
if (scale === 0) {
|
||||
return Number(number)
|
||||
} else {
|
||||
// if the scale is the same as the precision, account for this edge case.
|
||||
var adjustedNumber = number
|
||||
while (adjustedNumber.length < scale) {
|
||||
adjustedNumber = '0' + adjustedNumber
|
||||
}
|
||||
return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale))
|
||||
}
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.upsize = function (number, scale, precision) {
|
||||
var stringArray = number.toString().split('.')
|
||||
var decimalLength = stringArray[1] ? stringArray[1].length : 0
|
||||
var newString = stringArray[0]
|
||||
|
||||
// If there is scaling and decimal parts exist, integrate them in.
|
||||
if ((scale !== 0) && (decimalLength !== 0)) {
|
||||
newString += stringArray[1].slice(0, precision)
|
||||
}
|
||||
|
||||
// Add 0s to account for the upscaling.
|
||||
for (var i = decimalLength; i < scale; i++) {
|
||||
newString += '0'
|
||||
}
|
||||
return newString
|
||||
}
|
262
old-ui/app/components/buy-button-subview.js
Normal file
@ -0,0 +1,262 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const CoinbaseForm = require('./coinbase-form')
|
||||
const ShapeshiftForm = require('./shapeshift-form')
|
||||
const Loading = require('./loading')
|
||||
const AccountPanel = require('./account-panel')
|
||||
const RadioList = require('./custom-radio-list')
|
||||
const networkNames = require('../../../app/scripts/config.js').networkNames
|
||||
|
||||
module.exports = connect(mapStateToProps)(BuyButtonSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
identity: state.appState.identity,
|
||||
account: state.metamask.accounts[state.appState.buyView.buyAddress],
|
||||
warning: state.appState.warning,
|
||||
buyView: state.appState.buyView,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
context: state.appState.currentView.context,
|
||||
isSubLoading: state.appState.isSubLoading,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(BuyButtonSubview, Component)
|
||||
function BuyButtonSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.render = function () {
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
this.headerSubview(),
|
||||
this.primarySubview(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.headerSubview = function () {
|
||||
const props = this.props
|
||||
const isLoading = props.isSubLoading
|
||||
return (
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header bar (back button, label)
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: this.backButtonContext.bind(this),
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '10px',
|
||||
},
|
||||
}),
|
||||
h('h2.text-transform-uppercase.flex-center', {
|
||||
style: {
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, 'Buy Eth'),
|
||||
]),
|
||||
|
||||
// loading indication
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '57vh',
|
||||
left: '49vw',
|
||||
},
|
||||
}, [
|
||||
h(Loading, { isLoading }),
|
||||
]),
|
||||
|
||||
// account panel
|
||||
h('div', {
|
||||
style: {
|
||||
width: '80%',
|
||||
},
|
||||
}, [
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: props.identity,
|
||||
account: props.account,
|
||||
}),
|
||||
]),
|
||||
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('h3.text-transform-uppercase.flex-center', {
|
||||
style: {
|
||||
paddingLeft: '15px',
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, 'Select Service'),
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
BuyButtonSubview.prototype.primarySubview = function () {
|
||||
const props = this.props
|
||||
const network = props.network
|
||||
|
||||
switch (network) {
|
||||
case 'loading':
|
||||
return
|
||||
|
||||
case '1':
|
||||
return this.mainnetSubview()
|
||||
|
||||
// Ropsten, Rinkeby, Kovan
|
||||
case '3':
|
||||
case '4':
|
||||
case '42':
|
||||
const networkName = networkNames[network]
|
||||
const label = `${networkName} Test Faucet`
|
||||
return (
|
||||
h('div.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
margin: '20px 50px',
|
||||
},
|
||||
}, [
|
||||
h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, label),
|
||||
// Kovan only: Dharma loans beta
|
||||
network === '42' ? (
|
||||
h('button.text-transform-uppercase', {
|
||||
onClick: () => this.navigateTo('https://borrow.dharma.io/'),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, 'Borrow With Dharma (Beta)')
|
||||
) : null,
|
||||
])
|
||||
)
|
||||
|
||||
default:
|
||||
return (
|
||||
h('h2.error', 'Unknown network ID')
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.mainnetSubview = function () {
|
||||
const props = this.props
|
||||
|
||||
return (
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('.flex-row.selected-exchange', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
right: '35px',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
}, [
|
||||
h(RadioList, {
|
||||
defaultFocus: props.buyView.subview,
|
||||
labels: [
|
||||
'Coinbase',
|
||||
'ShapeShift',
|
||||
],
|
||||
subtext: {
|
||||
'Coinbase': 'Crypto/FIAT (USA only)',
|
||||
'ShapeShift': 'Crypto',
|
||||
},
|
||||
onClick: this.radioHandler.bind(this),
|
||||
}),
|
||||
]),
|
||||
|
||||
h('h3.text-transform-uppercase', {
|
||||
style: {
|
||||
paddingLeft: '15px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, props.buyView.subview),
|
||||
|
||||
this.formVersionSubview(),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.formVersionSubview = function () {
|
||||
const network = this.props.network
|
||||
if (network === '1') {
|
||||
if (this.props.buyView.formView.coinbase) {
|
||||
return h(CoinbaseForm, this.props)
|
||||
} else if (this.props.buyView.formView.shapeshift) {
|
||||
return h(ShapeshiftForm, this.props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.navigateTo = function (url) {
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.backButtonContext = function () {
|
||||
if (this.props.context === 'confTx') {
|
||||
this.props.dispatch(actions.showConfTxPage(false))
|
||||
} else {
|
||||
console.log(`actions.goHome`, actions.goHome);
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.radioHandler = function (event) {
|
||||
switch (event.target.title) {
|
||||
case 'Coinbase':
|
||||
return this.props.dispatch(actions.coinBaseSubview())
|
||||
case 'ShapeShift':
|
||||
return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type))
|
||||
}
|
||||
}
|
63
old-ui/app/components/coinbase-form.js
Normal file
@ -0,0 +1,63 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(CoinbaseForm)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(CoinbaseForm, Component)
|
||||
|
||||
function CoinbaseForm () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.render = function () {
|
||||
var props = this.props
|
||||
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
marginTop: '35px',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-around',
|
||||
margin: '33px',
|
||||
marginTop: '0px',
|
||||
},
|
||||
}, [
|
||||
h('button.btn-green', {
|
||||
onClick: this.toCoinbase.bind(this),
|
||||
}, 'Continue to Coinbase'),
|
||||
|
||||
h('button.btn-red', {
|
||||
onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)),
|
||||
}, 'Cancel'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.toCoinbase = function () {
|
||||
const props = this.props
|
||||
const address = props.buyView.buyAddress
|
||||
props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.renderLoading = function () {
|
||||
return h('img', {
|
||||
style: {
|
||||
width: '27px',
|
||||
marginRight: '-27px',
|
||||
},
|
||||
src: 'images/loading.svg',
|
||||
})
|
||||
}
|
59
old-ui/app/components/copyButton.js
Normal file
@ -0,0 +1,59 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
module.exports = CopyButton
|
||||
|
||||
inherits(CopyButton, Component)
|
||||
function CopyButton () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
CopyButton.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
|
||||
const value = props.value
|
||||
const copied = state.copied
|
||||
|
||||
const message = copied ? 'Copied' : props.title || ' Copy '
|
||||
|
||||
return h('.copy-button', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(Tooltip, {
|
||||
title: message,
|
||||
}, [
|
||||
h('i.fa.fa-clipboard.cursor-pointer.color-orange', {
|
||||
style: {
|
||||
margin: '5px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
CopyButton.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
46
old-ui/app/components/copyable.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
module.exports = Copyable
|
||||
|
||||
inherits(Copyable, Component)
|
||||
function Copyable () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
copied: false,
|
||||
}
|
||||
}
|
||||
|
||||
Copyable.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
const { value, children } = props
|
||||
const { copied } = state
|
||||
|
||||
return h(Tooltip, {
|
||||
title: copied ? 'Copied!' : 'Copy',
|
||||
position: 'bottom',
|
||||
}, h('span', {
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}, children))
|
||||
}
|
||||
|
||||
Copyable.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
60
old-ui/app/components/custom-radio-list.js
Normal file
@ -0,0 +1,60 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = RadioList
|
||||
|
||||
inherits(RadioList, Component)
|
||||
function RadioList () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
RadioList.prototype.render = function () {
|
||||
const props = this.props
|
||||
const activeClass = '.custom-radio-selected'
|
||||
const inactiveClass = '.custom-radio-inactive'
|
||||
const {
|
||||
labels,
|
||||
defaultFocus,
|
||||
} = props
|
||||
|
||||
|
||||
return (
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, [
|
||||
h('.flex-column.custom-radios', {
|
||||
style: {
|
||||
marginRight: '5px',
|
||||
},
|
||||
},
|
||||
labels.map((lable, i) => {
|
||||
let isSelcted = (this.state !== null)
|
||||
isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable)
|
||||
return h(isSelcted ? activeClass : inactiveClass, {
|
||||
title: lable,
|
||||
onClick: (event) => {
|
||||
this.setState({selected: event.target.title})
|
||||
props.onClick(event)
|
||||
},
|
||||
})
|
||||
})
|
||||
),
|
||||
h('.text', {},
|
||||
labels.map((lable) => {
|
||||
if (props.subtext) {
|
||||
return h('.flex-row', {}, [
|
||||
h('.radio-titles', lable),
|
||||
h('.radio-titles-subtext', `- ${props.subtext[lable]}`),
|
||||
])
|
||||
} else {
|
||||
return h('.radio-titles', lable)
|
||||
}
|
||||
})
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
57
old-ui/app/components/editable-label.js
Normal file
@ -0,0 +1,57 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
|
||||
module.exports = EditableLabel
|
||||
|
||||
inherits(EditableLabel, Component)
|
||||
function EditableLabel () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EditableLabel.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
if (state && state.isEditingLabel) {
|
||||
return h('div.editable-label', [
|
||||
h('input.sizing-input', {
|
||||
defaultValue: props.textValue,
|
||||
maxLength: '20',
|
||||
onKeyPress: (event) => {
|
||||
this.saveIfEnter(event)
|
||||
},
|
||||
}),
|
||||
h('button.editable-button', {
|
||||
onClick: () => this.saveText(),
|
||||
}, 'Save'),
|
||||
])
|
||||
} else {
|
||||
return h('div.name-label', {
|
||||
onClick: (event) => {
|
||||
const nameAttribute = event.target.getAttribute('name')
|
||||
// checks for class to handle smaller CTA above the account name
|
||||
const classAttribute = event.target.getAttribute('class')
|
||||
if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
|
||||
this.setState({ isEditingLabel: true })
|
||||
}
|
||||
},
|
||||
}, this.props.children)
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveIfEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.saveText()
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveText = function () {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
var text = container.querySelector('.editable-label input').value
|
||||
var truncatedText = text.substring(0, 20)
|
||||
this.props.saveText(truncatedText)
|
||||
this.setState({ isEditingLabel: false, textLabel: truncatedText })
|
||||
}
|
170
old-ui/app/components/ens-input.js
Normal file
@ -0,0 +1,170 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
const debounce = require('debounce')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const ENS = require('ethjs-ens')
|
||||
const networkMap = require('ethjs-ens/lib/network-map.json')
|
||||
const ensRE = /.+\..+$/
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
|
||||
module.exports = EnsInput
|
||||
|
||||
inherits(EnsInput, Component)
|
||||
function EnsInput () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EnsInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const opts = extend(props, {
|
||||
list: 'addresses',
|
||||
onChange: () => {
|
||||
const network = this.props.network
|
||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||
if (!networkHasEnsSupport) return
|
||||
|
||||
const recipient = document.querySelector('input[name="address"]').value
|
||||
if (recipient.match(ensRE) === null) {
|
||||
return this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: null,
|
||||
ensFailure: null,
|
||||
})
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loadingEns: true,
|
||||
})
|
||||
this.checkName()
|
||||
},
|
||||
})
|
||||
return h('div', {
|
||||
style: { width: '100%' },
|
||||
}, [
|
||||
h('input.large-input', opts),
|
||||
// The address book functionality.
|
||||
h('datalist#addresses',
|
||||
[
|
||||
// Corresponds to the addresses owned.
|
||||
Object.keys(props.identities).map((key) => {
|
||||
const identity = props.identities[key]
|
||||
return h('option', {
|
||||
value: identity.address,
|
||||
label: identity.name,
|
||||
key: identity.address,
|
||||
})
|
||||
}),
|
||||
// Corresponds to previously sent-to addresses.
|
||||
props.addressBook.map((identity) => {
|
||||
return h('option', {
|
||||
value: identity.address,
|
||||
label: identity.name,
|
||||
key: identity.address,
|
||||
})
|
||||
}),
|
||||
]),
|
||||
this.ensIcon(),
|
||||
])
|
||||
}
|
||||
|
||||
EnsInput.prototype.componentDidMount = function () {
|
||||
const network = this.props.network
|
||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||
this.setState({ ensResolution: ZERO_ADDRESS })
|
||||
|
||||
if (networkHasEnsSupport) {
|
||||
const provider = global.ethereumProvider
|
||||
this.ens = new ENS({ provider, network })
|
||||
this.checkName = debounce(this.lookupEnsName.bind(this), 200)
|
||||
}
|
||||
}
|
||||
|
||||
EnsInput.prototype.lookupEnsName = function () {
|
||||
const recipient = document.querySelector('input[name="address"]').value
|
||||
const { ensResolution } = this.state
|
||||
|
||||
log.info(`ENS attempting to resolve name: ${recipient}`)
|
||||
this.ens.lookup(recipient.trim())
|
||||
.then((address) => {
|
||||
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.')
|
||||
if (address !== ensResolution) {
|
||||
this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: address,
|
||||
nickname: recipient.trim(),
|
||||
hoverText: address + '\nClick to Copy',
|
||||
ensFailure: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(reason)
|
||||
return this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: ZERO_ADDRESS,
|
||||
ensFailure: true,
|
||||
hoverText: reason.message,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
||||
const state = this.state || {}
|
||||
const ensResolution = state.ensResolution
|
||||
// If an address is sent without a nickname, meaning not from ENS or from
|
||||
// the user's own accounts, a default of a one-space string is used.
|
||||
const nickname = state.nickname || ' '
|
||||
if (prevState && ensResolution && this.props.onChange &&
|
||||
ensResolution !== prevState.ensResolution) {
|
||||
this.props.onChange(ensResolution, nickname)
|
||||
}
|
||||
}
|
||||
|
||||
EnsInput.prototype.ensIcon = function (recipient) {
|
||||
const { hoverText } = this.state || {}
|
||||
return h('span', {
|
||||
title: hoverText,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
padding: '9px',
|
||||
transform: 'translatex(-40px)',
|
||||
},
|
||||
}, this.ensIconContents(recipient))
|
||||
}
|
||||
|
||||
EnsInput.prototype.ensIconContents = function (recipient) {
|
||||
const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS}
|
||||
|
||||
if (loadingEns) {
|
||||
return h('img', {
|
||||
src: 'images/loading.svg',
|
||||
style: {
|
||||
width: '30px',
|
||||
height: '30px',
|
||||
transform: 'translateY(-6px)',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (ensFailure) {
|
||||
return h('i.fa.fa-warning.fa-lg.warning')
|
||||
}
|
||||
|
||||
if (ensResolution && (ensResolution !== ZERO_ADDRESS)) {
|
||||
return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', {
|
||||
style: { color: 'green' },
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(ensResolution)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkEnsSupport (network) {
|
||||
return Boolean(networkMap[network])
|
||||
}
|
89
old-ui/app/components/eth-balance.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { style, width } = props
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
|
||||
return (
|
||||
|
||||
h('.ether-balance.ether-balance-amount', {
|
||||
style,
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline',
|
||||
width,
|
||||
},
|
||||
}, this.renderBalance(value)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
const { conversionRate, shorten, incoming, currentCurrency } = props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
},
|
||||
}, incoming ? `+${balance}` : balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null,
|
||||
]))
|
||||
)
|
||||
}
|
64
old-ui/app/components/fiat-value.js
Normal file
@ -0,0 +1,64 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
|
||||
module.exports = FiatValue
|
||||
|
||||
inherits(FiatValue, Component)
|
||||
function FiatValue () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
FiatValue.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { conversionRate, currentCurrency } = props
|
||||
const renderedCurrency = currentCurrency || ''
|
||||
|
||||
const value = formatBalance(props.value, 6)
|
||||
|
||||
if (value === 'None') return value
|
||||
var fiatDisplayNumber, fiatTooltipNumber
|
||||
var splitBalance = value.split(' ')
|
||||
|
||||
if (conversionRate !== 0) {
|
||||
fiatTooltipNumber = Number(splitBalance[0]) * conversionRate
|
||||
fiatDisplayNumber = fiatTooltipNumber.toFixed(2)
|
||||
} else {
|
||||
fiatDisplayNumber = 'N/A'
|
||||
fiatTooltipNumber = 'Unknown'
|
||||
}
|
||||
|
||||
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase())
|
||||
}
|
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
if (fiatDisplayNumber !== 'N/A') {
|
||||
return h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
},
|
||||
}, fiatDisplayNumber),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, fiatSuffix),
|
||||
])
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
}
|
154
old-ui/app/components/hex-as-decimal-input.js
Normal file
@ -0,0 +1,154 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = HexAsDecimalInput
|
||||
|
||||
inherits(HexAsDecimalInput, Component)
|
||||
function HexAsDecimalInput () {
|
||||
this.state = { invalid: null }
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
/* Hex as Decimal Input
|
||||
*
|
||||
* A component for allowing easy, decimal editing
|
||||
* of a passed in hex string value.
|
||||
*
|
||||
* On change, calls back its `onChange` function parameter
|
||||
* and passes it an updated hex string.
|
||||
*/
|
||||
|
||||
HexAsDecimalInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
const { value, onChange, min, max } = props
|
||||
|
||||
const toEth = props.toEth
|
||||
const suffix = props.suffix
|
||||
const decimalValue = decimalize(value, toEth)
|
||||
const style = props.style
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('input.hex-input', {
|
||||
type: 'number',
|
||||
required: true,
|
||||
min: min,
|
||||
max: max,
|
||||
style: extend({
|
||||
display: 'block',
|
||||
textAlign: 'right',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #bdbdbd',
|
||||
|
||||
}, style),
|
||||
value: parseInt(decimalValue),
|
||||
onBlur: (event) => {
|
||||
this.updateValidity(event)
|
||||
},
|
||||
onChange: (event) => {
|
||||
this.updateValidity(event)
|
||||
const hexString = (event.target.value === '') ? '' : hexify(event.target.value)
|
||||
onChange(hexString)
|
||||
},
|
||||
onInvalid: (event) => {
|
||||
const msg = this.constructWarning()
|
||||
if (msg === state.invalid) {
|
||||
return
|
||||
}
|
||||
this.setState({ invalid: msg })
|
||||
event.preventDefault()
|
||||
return false
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '6px',
|
||||
width: '20px',
|
||||
},
|
||||
}, suffix),
|
||||
]),
|
||||
|
||||
state.invalid ? h('span.error', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '0px',
|
||||
textAlign: 'right',
|
||||
transform: 'translateY(26px)',
|
||||
padding: '3px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
zIndex: '1',
|
||||
textTransform: 'capitalize',
|
||||
border: '2px solid #E20202',
|
||||
},
|
||||
}, state.invalid) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.setValid = function (message) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.updateValidity = function (event) {
|
||||
const target = event.target
|
||||
const value = this.props.value
|
||||
const newValue = target.value
|
||||
|
||||
if (value === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const valid = target.checkValidity()
|
||||
if (valid) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.constructWarning = function () {
|
||||
const { name, min, max } = this.props
|
||||
let message = name ? name + ' ' : ''
|
||||
|
||||
if (min && max) {
|
||||
message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
|
||||
} else if (min) {
|
||||
message += `must be greater than or equal to ${min}.`
|
||||
} else if (max) {
|
||||
message += `must be less than or equal to ${max}.`
|
||||
} else {
|
||||
message += 'Invalid input.'
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
function hexify (decimalString) {
|
||||
const hexBN = new BN(parseInt(decimalString), 10)
|
||||
return '0x' + hexBN.toString('hex')
|
||||
}
|
||||
|
||||
function decimalize (input, toEth) {
|
||||
if (input === '') {
|
||||
return ''
|
||||
} else {
|
||||
const strippedInput = ethUtil.stripHexPrefix(input)
|
||||
const inputBN = new BN(strippedInput, 'hex')
|
||||
return inputBN.toString(10)
|
||||
}
|
||||
}
|
74
old-ui/app/components/identicon.js
Normal file
@ -0,0 +1,74 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const isNode = require('detect-node')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const jazzicon = require('jazzicon')
|
||||
const iconFactoryGen = require('../../lib/icon-factory')
|
||||
const iconFactory = iconFactoryGen(jazzicon)
|
||||
|
||||
module.exports = IdenticonComponent
|
||||
|
||||
inherits(IdenticonComponent, Component)
|
||||
function IdenticonComponent () {
|
||||
Component.call(this)
|
||||
|
||||
this.defaultDiameter = 46
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
return (
|
||||
h('div', {
|
||||
key: 'identicon-' + this.props.address,
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () {
|
||||
var props = this.props
|
||||
const { address } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
if (!isNode) {
|
||||
var img = iconFactory.iconForAddress(address, diameter)
|
||||
container.appendChild(img)
|
||||
}
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () {
|
||||
var props = this.props
|
||||
const { address } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var children = container.children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
container.removeChild(children[i])
|
||||
}
|
||||
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
if (!isNode) {
|
||||
var img = iconFactory.iconForAddress(address, diameter)
|
||||
container.appendChild(img)
|
||||
}
|
||||
}
|
||||
|
45
old-ui/app/components/loading.js
Normal file
@ -0,0 +1,45 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
|
||||
|
||||
inherits(LoadingIndicator, Component)
|
||||
module.exports = LoadingIndicator
|
||||
|
||||
function LoadingIndicator () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
LoadingIndicator.prototype.render = function () {
|
||||
const { isLoading, loadingMessage } = this.props
|
||||
|
||||
return (
|
||||
isLoading ? h('.full-flex-height', {
|
||||
style: {
|
||||
left: '0px',
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
|
||||
h('br'),
|
||||
|
||||
showMessageIfAny(loadingMessage),
|
||||
]) : null
|
||||
)
|
||||
}
|
||||
|
||||
function showMessageIfAny (loadingMessage) {
|
||||
if (!loadingMessage) return null
|
||||
return h('span', loadingMessage)
|
||||
}
|