From 3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 21 Nov 2017 14:03:08 -0600 Subject: Add website to mono repo, update packages to align with existing sub-packages, use new subscribeAsync 0x.js method --- .gitignore | 2 + packages/website/README.md | 60 + packages/website/contracts/Mintable.json | 189 + packages/website/less/all.less | 136 + packages/website/package.json | 105 + packages/website/public/css/atom-one-light.css | 96 + .../public/css/basscss_responsive_custom.css | 58 + .../public/css/basscss_responsive_margin.css | 160 + .../public/css/basscss_responsive_padding.css | 134 + .../public/css/basscss_responsive_type_scale.css | 35 + .../public/css/material-design-iconic-font.css | 5166 ++++++++++++++++++++ .../public/css/material-design-iconic-font.min.css | 1 + packages/website/public/css/roboto.css | 83 + packages/website/public/css/roboto_mono.css | 69 + .../public/fonts/Material-Design-Iconic-Font.eot | Bin 0 -> 42495 bytes .../public/fonts/Material-Design-Iconic-Font.svg | 787 +++ .../public/fonts/Material-Design-Iconic-Font.ttf | Bin 0 -> 99212 bytes .../public/fonts/Material-Design-Iconic-Font.woff | Bin 0 -> 50312 bytes .../public/fonts/Material-Design-Iconic-Font.woff2 | Bin 0 -> 38384 bytes packages/website/public/fonts/Roboto-Black.ttf | Bin 0 -> 171480 bytes .../website/public/fonts/Roboto-BlackItalic.ttf | Bin 0 -> 177552 bytes packages/website/public/fonts/Roboto-Bold.ttf | Bin 0 -> 170760 bytes .../website/public/fonts/Roboto-BoldItalic.ttf | Bin 0 -> 174952 bytes packages/website/public/fonts/Roboto-Italic.ttf | Bin 0 -> 173932 bytes packages/website/public/fonts/Roboto-Light.ttf | Bin 0 -> 170420 bytes .../website/public/fonts/Roboto-LightItalic.ttf | Bin 0 -> 176616 bytes packages/website/public/fonts/Roboto-Medium.ttf | Bin 0 -> 172064 bytes .../website/public/fonts/Roboto-MediumItalic.ttf | Bin 0 -> 176864 bytes packages/website/public/fonts/Roboto-Regular.ttf | Bin 0 -> 171676 bytes packages/website/public/fonts/Roboto-Thin.ttf | Bin 0 -> 171904 bytes .../website/public/fonts/Roboto-ThinItalic.ttf | Bin 0 -> 176300 bytes packages/website/public/fonts/RobotoMono-Bold.ttf | Bin 0 -> 114752 bytes .../website/public/fonts/RobotoMono-BoldItalic.ttf | Bin 0 -> 122808 bytes .../website/public/fonts/RobotoMono-Italic.ttf | Bin 0 -> 120832 bytes packages/website/public/fonts/RobotoMono-Light.ttf | Bin 0 -> 118976 bytes .../public/fonts/RobotoMono-LightItalic.ttf | Bin 0 -> 127568 bytes .../website/public/fonts/RobotoMono-Medium.ttf | Bin 0 -> 114696 bytes .../public/fonts/RobotoMono-MediumItalic.ttf | Bin 0 -> 123640 bytes .../website/public/fonts/RobotoMono-Regular.ttf | Bin 0 -> 114624 bytes packages/website/public/fonts/RobotoMono-Thin.ttf | Bin 0 -> 118132 bytes .../website/public/fonts/RobotoMono-ThinItalic.ttf | Bin 0 -> 121456 bytes packages/website/public/gifs/0xAnimation.gif | Bin 0 -> 909585 bytes packages/website/public/gifs/genesis.gif | Bin 0 -> 735849 bytes packages/website/public/images/0x_logo.png | Bin 0 -> 64503 bytes packages/website/public/images/advisors/fred.jpg | Bin 0 -> 4619 bytes packages/website/public/images/advisors/joey.jpg | Bin 0 -> 13493 bytes packages/website/public/images/advisors/linda.jpg | Bin 0 -> 6580 bytes packages/website/public/images/advisors/olaf.png | Bin 0 -> 26365 bytes packages/website/public/images/ether.png | Bin 0 -> 2312 bytes .../public/images/favicon/favicon-2-16x16.png | Bin 0 -> 684 bytes .../public/images/favicon/favicon-2-32x32.png | Bin 0 -> 1567 bytes packages/website/public/images/favicon/favicon.ico | Bin 0 -> 5430 bytes .../website/public/images/landing/0x_chips.png | Bin 0 -> 170875 bytes packages/website/public/images/landing/aragon.png | Bin 0 -> 4738 bytes packages/website/public/images/landing/augur.png | Bin 0 -> 4935 bytes .../website/public/images/landing/currency.png | Bin 0 -> 3348 bytes packages/website/public/images/landing/dharma.png | Bin 0 -> 4754 bytes .../public/images/landing/digital_goods.png | Bin 0 -> 3880 bytes .../public/images/landing/distributed_network.png | Bin 0 -> 37483 bytes .../website/public/images/landing/ethfinex.png | Bin 0 -> 6733 bytes .../public/images/landing/fund_management_icon.png | Bin 0 -> 5552 bytes packages/website/public/images/landing/gnosis.png | Bin 0 -> 4888 bytes .../public/images/landing/governance_icon.png | Bin 0 -> 6230 bytes .../public/images/landing/hero_chip_image.png | Bin 0 -> 256493 bytes .../website/public/images/landing/lendroid.png | Bin 0 -> 4305 bytes .../website/public/images/landing/liquidity.png | Bin 0 -> 22140 bytes .../website/public/images/landing/loans_icon.png | Bin 0 -> 5900 bytes packages/website/public/images/landing/maker.png | Bin 0 -> 3501 bytes .../website/public/images/landing/melonport.png | Bin 0 -> 4841 bytes .../website/public/images/landing/open_source.png | Bin 0 -> 14696 bytes packages/website/public/images/landing/paradex.png | Bin 0 -> 6904 bytes .../images/landing/prediction_market_icon.png | Bin 0 -> 6211 bytes .../public/images/landing/project_logos/anx.png | Bin 0 -> 5836 bytes .../public/images/landing/project_logos/aragon.png | Bin 0 -> 4642 bytes .../public/images/landing/project_logos/auctus.png | Bin 0 -> 3751 bytes .../public/images/landing/project_logos/augur.png | Bin 0 -> 4618 bytes .../images/landing/project_logos/blocknet.png | Bin 0 -> 4697 bytes .../images/landing/project_logos/chronobank.png | Bin 0 -> 6209 bytes .../public/images/landing/project_logos/dharma.png | Bin 0 -> 5429 bytes .../images/landing/project_logos/district0x.png | Bin 0 -> 5515 bytes .../public/images/landing/project_logos/dydx.png | Bin 0 -> 4191 bytes .../images/landing/project_logos/ethfinex-top.png | Bin 0 -> 4376 bytes .../public/images/landing/project_logos/ethix.png | Bin 0 -> 3438 bytes .../images/landing/project_logos/lendroid.png | Bin 0 -> 4866 bytes .../public/images/landing/project_logos/maker.png | Bin 0 -> 3951 bytes .../images/landing/project_logos/melonport.png | Bin 0 -> 5186 bytes .../images/landing/project_logos/paradex_top.png | Bin 0 -> 5109 bytes .../landing/project_logos/radar_relay_top.png | Bin 0 -> 4898 bytes .../public/images/landing/project_logos/status.png | Bin 0 -> 4287 bytes .../images/landing/project_logos/the_ocean.png | Bin 0 -> 4766 bytes .../website/public/images/landing/radar_relay.png | Bin 0 -> 6650 bytes .../public/images/landing/relayer_diagram.png | Bin 0 -> 111870 bytes .../public/images/landing/stable_tokens_icon.png | Bin 0 -> 5853 bytes packages/website/public/images/landing/stocks.png | Bin 0 -> 2098 bytes .../public/images/landing/tokenized_world.png | Bin 0 -> 109220 bytes packages/website/public/images/loading_poster.png | Bin 0 -> 2505 bytes packages/website/public/images/logos/FBG.png | Bin 0 -> 73781 bytes packages/website/public/images/logos/aragon.png | Bin 0 -> 5501 bytes packages/website/public/images/logos/augur.png | Bin 0 -> 5051 bytes .../public/images/logos/blockchain_capital.png | Bin 0 -> 12366 bytes .../website/public/images/logos/chronobank.png | Bin 0 -> 5615 bytes packages/website/public/images/logos/dharma.png | Bin 0 -> 5015 bytes .../website/public/images/logos/district0x.png | Bin 0 -> 5537 bytes .../website/public/images/logos/jen_advisors.png | Bin 0 -> 158434 bytes packages/website/public/images/logos/maker.png | Bin 0 -> 3791 bytes packages/website/public/images/logos/melonport.png | Bin 0 -> 5218 bytes packages/website/public/images/logos/openANX.png | Bin 0 -> 4973 bytes .../public/images/logos/pantera_capital.png | Bin 0 -> 8437 bytes .../public/images/logos/polychain_capital.png | Bin 0 -> 21279 bytes packages/website/public/images/og_image.png | Bin 0 -> 192500 bytes .../website/public/images/protocol_logo_black.png | Bin 0 -> 4031 bytes .../website/public/images/protocol_logo_white.png | Bin 0 -> 3931 bytes packages/website/public/images/social/github.png | Bin 0 -> 1154 bytes packages/website/public/images/social/medium.png | Bin 0 -> 890 bytes packages/website/public/images/social/reddit.png | Bin 0 -> 1168 bytes .../website/public/images/social/rocketchat.png | Bin 0 -> 1587 bytes packages/website/public/images/social/slack.png | Bin 0 -> 1311 bytes packages/website/public/images/social/twitter.png | Bin 0 -> 901 bytes packages/website/public/images/team/alex.jpg | Bin 0 -> 7271 bytes packages/website/public/images/team/amir.jpeg | Bin 0 -> 21098 bytes packages/website/public/images/team/anyone.png | Bin 0 -> 4794 bytes packages/website/public/images/team/ben.jpg | Bin 0 -> 25486 bytes packages/website/public/images/team/brandon.png | Bin 0 -> 30180 bytes packages/website/public/images/team/fabio.jpg | Bin 0 -> 17125 bytes packages/website/public/images/team/leonid.png | Bin 0 -> 35014 bytes packages/website/public/images/team/philippe.png | Bin 0 -> 51419 bytes packages/website/public/images/team/will.jpg | Bin 0 -> 11493 bytes .../website/public/images/token_icons/adtoken.png | Bin 0 -> 5150 bytes .../website/public/images/token_icons/aragon.png | Bin 0 -> 14012 bytes .../website/public/images/token_icons/augur.png | Bin 0 -> 146258 bytes .../website/public/images/token_icons/bancor.png | Bin 0 -> 2274 bytes .../images/token_icons/basicattentiontoken.png | Bin 0 -> 7082 bytes .../public/images/token_icons/bitquence.png | Bin 0 -> 14293 bytes packages/website/public/images/token_icons/btc.png | Bin 0 -> 5742 bytes .../website/public/images/token_icons/civic.png | Bin 0 -> 10700 bytes .../website/public/images/token_icons/clams.png | Bin 0 -> 18866 bytes .../public/images/token_icons/cofound-it.png | Bin 0 -> 5403 bytes .../website/public/images/token_icons/default.png | Bin 0 -> 17037 bytes .../website/public/images/token_icons/digixdao.png | Bin 0 -> 34397 bytes .../public/images/token_icons/district0x.png | Bin 0 -> 67267 bytes .../website/public/images/token_icons/edgeless.png | Bin 0 -> 2712 bytes packages/website/public/images/token_icons/eos.png | Bin 0 -> 2168 bytes .../public/images/token_icons/ether_erc20.png | Bin 0 -> 69772 bytes .../website/public/images/token_icons/etheroll.png | Bin 0 -> 22736 bytes .../public/images/token_icons/firstblood.jpg | Bin 0 -> 72909 bytes .../website/public/images/token_icons/funfair.png | Bin 0 -> 18800 bytes .../website/public/images/token_icons/gnosis.png | Bin 0 -> 7554 bytes .../website/public/images/token_icons/golem.png | Bin 0 -> 2990 bytes .../website/public/images/token_icons/iconomi.png | Bin 0 -> 810 bytes .../website/public/images/token_icons/iexec.png | Bin 0 -> 1910 bytes .../website/public/images/token_icons/lunyr.png | Bin 0 -> 5734 bytes .../website/public/images/token_icons/makerdao.png | Bin 0 -> 3161 bytes .../website/public/images/token_icons/melon.png | Bin 0 -> 3408 bytes .../website/public/images/token_icons/metal.png | Bin 0 -> 1295 bytes .../website/public/images/token_icons/monaco.png | Bin 0 -> 6984 bytes .../public/images/token_icons/numeraire.png | Bin 0 -> 10272 bytes .../website/public/images/token_icons/omisego.png | Bin 0 -> 5976 bytes .../website/public/images/token_icons/qtum.png | Bin 0 -> 169182 bytes .../public/images/token_icons/santiment.png | Bin 0 -> 4698 bytes .../public/images/token_icons/singularity.png | Bin 0 -> 3849 bytes .../website/public/images/token_icons/status.png | Bin 0 -> 3445 bytes .../public/images/token_icons/storjcoinx.png | Bin 0 -> 9453 bytes .../website/public/images/token_icons/taas.png | Bin 0 -> 7823 bytes .../website/public/images/token_icons/tenx.png | Bin 0 -> 7276 bytes .../public/images/token_icons/tokencard.png | Bin 0 -> 14586 bytes .../website/public/images/token_icons/trust.png | Bin 0 -> 14428 bytes .../website/public/images/token_icons/wings.png | Bin 0 -> 16143 bytes .../website/public/images/token_icons/zero_ex.png | Bin 0 -> 162879 bytes packages/website/public/images/trade_arrows.png | Bin 0 -> 1740 bytes packages/website/public/images/zrx_pie_chart.png | Bin 0 -> 54185 bytes packages/website/public/images/zrx_token.png | Bin 0 -> 16534 bytes packages/website/public/index.html | 78 + .../website/public/js/rollbar.umd.nojson.min.js | 1 + packages/website/public/pdfs/0x_white_paper.pdf | Bin 0 -> 319570 bytes packages/website/public/videos/0xAnimation.mp4 | Bin 0 -> 970342 bytes packages/website/ts/blockchain.ts | 770 +++ .../components/dialogs/blockchain_err_dialog.tsx | 158 + .../dialogs/eth_weth_conversion_dialog.tsx | 139 + .../ts/components/dialogs/ledger_config_dialog.tsx | 288 ++ .../dialogs/portal_disclaimer_dialog.tsx | 44 + .../website/ts/components/dialogs/send_dialog.tsx | 126 + .../dialogs/track_token_confirmation_dialog.tsx | 99 + .../dialogs/u2f_not_supported_dialog.tsx | 53 + .../ts/components/eth_weth_conversion_button.tsx | 101 + packages/website/ts/components/fill_order.tsx | 714 +++ packages/website/ts/components/fill_order_json.tsx | 69 + .../website/ts/components/fill_warning_dialog.tsx | 47 + .../flash_messages/token_send_completed.tsx | 38 + .../flash_messages/transaction_submitted.tsx | 29 + packages/website/ts/components/footer.tsx | 255 + .../ts/components/generate_order/asset_picker.tsx | 291 ++ .../generate_order/generate_order_form.tsx | 348 ++ .../components/generate_order/new_token_form.tsx | 237 + .../website/ts/components/inputs/address_input.tsx | 74 + .../ts/components/inputs/allowance_toggle.tsx | 94 + .../ts/components/inputs/balance_bounded_input.tsx | 160 + .../ts/components/inputs/eth_amount_input.tsx | 51 + .../ts/components/inputs/expiration_input.tsx | 108 + .../website/ts/components/inputs/hash_input.tsx | 65 + .../components/inputs/identicon_address_input.tsx | 56 + .../ts/components/inputs/token_amount_input.tsx | 69 + .../website/ts/components/inputs/token_input.tsx | 107 + packages/website/ts/components/order_json.tsx | 164 + packages/website/ts/components/portal.tsx | 344 ++ packages/website/ts/components/portal_menu.tsx | 68 + packages/website/ts/components/send_button.tsx | 89 + packages/website/ts/components/token_balances.tsx | 697 +++ packages/website/ts/components/top_bar.tsx | 370 ++ .../website/ts/components/top_bar_menu_item.tsx | 53 + .../ts/components/track_token_confirmation.tsx | 65 + .../ts/components/trade_history/trade_history.tsx | 115 + .../trade_history/trade_history_item.tsx | 178 + packages/website/ts/components/ui/alert.tsx | 27 + packages/website/ts/components/ui/badge.tsx | 58 + packages/website/ts/components/ui/copy_icon.tsx | 81 + .../ts/components/ui/drop_down_menu_item.tsx | 117 + .../website/ts/components/ui/ethereum_address.tsx | 35 + .../website/ts/components/ui/etherscan_icon.tsx | 50 + .../website/ts/components/ui/fake_text_field.tsx | 35 + .../website/ts/components/ui/flash_message.tsx | 40 + packages/website/ts/components/ui/help_tooltip.tsx | 22 + packages/website/ts/components/ui/identicon.tsx | 36 + packages/website/ts/components/ui/input_label.tsx | 27 + .../website/ts/components/ui/labeled_switcher.tsx | 76 + .../ts/components/ui/lifecycle_raised_button.tsx | 105 + packages/website/ts/components/ui/loading.tsx | 36 + packages/website/ts/components/ui/menu_item.tsx | 54 + packages/website/ts/components/ui/party.tsx | 150 + .../website/ts/components/ui/required_label.tsx | 15 + .../website/ts/components/ui/simple_loading.tsx | 23 + packages/website/ts/components/ui/swap_icon.tsx | 46 + packages/website/ts/components/ui/token_icon.tsx | 29 + packages/website/ts/components/visual_order.tsx | 77 + .../website/ts/containers/generate_order_form.tsx | 54 + packages/website/ts/containers/portal.tsx | 94 + .../containers/smart_contracts_documentation.tsx | 31 + .../ts/containers/zero_ex_js_documentation.tsx | 33 + packages/website/ts/globals.d.ts | 154 + packages/website/ts/index.tsx | 116 + packages/website/ts/lazy_component.tsx | 66 + packages/website/ts/local_storage/local_storage.ts | 35 + .../ts/local_storage/tracked_token_storage.ts | 56 + .../ts/local_storage/trade_history_storage.tsx | 82 + packages/website/ts/pages/about/about.tsx | 253 + packages/website/ts/pages/about/profile.tsx | 99 + .../website/ts/pages/documentation/comment.tsx | 24 + .../website/ts/pages/documentation/custom_enum.tsx | 31 + packages/website/ts/pages/documentation/enum.tsx | 26 + .../ts/pages/documentation/event_definition.tsx | 80 + .../website/ts/pages/documentation/interface.tsx | 54 + .../ts/pages/documentation/method_block.tsx | 174 + .../ts/pages/documentation/method_signature.tsx | 62 + .../smart_contracts_documentation.tsx | 401 ++ .../website/ts/pages/documentation/source_link.tsx | 27 + packages/website/ts/pages/documentation/type.tsx | 187 + .../ts/pages/documentation/type_definition.tsx | 135 + .../documentation/zero_ex_js_documentation.tsx | 340 ++ packages/website/ts/pages/faq/faq.tsx | 497 ++ packages/website/ts/pages/faq/question.tsx | 52 + packages/website/ts/pages/landing/landing.tsx | 843 ++++ packages/website/ts/pages/not_found.tsx | 46 + packages/website/ts/pages/shared/anchor_title.tsx | 98 + .../ts/pages/shared/markdown_code_block.tsx | 20 + .../website/ts/pages/shared/markdown_section.tsx | 77 + .../ts/pages/shared/nested_sidebar_menu.tsx | 163 + .../website/ts/pages/shared/section_header.tsx | 50 + .../website/ts/pages/shared/version_drop_down.tsx | 46 + packages/website/ts/pages/wiki/wiki.tsx | 210 + packages/website/ts/redux/dispatcher.ts | 244 + packages/website/ts/redux/reducer.ts | 363 ++ packages/website/ts/schemas/order_schema.ts | 24 + packages/website/ts/schemas/order_taker_schema.ts | 11 + .../website/ts/schemas/signature_data_schema.ts | 11 + packages/website/ts/schemas/token_schema.ts | 11 + packages/website/ts/schemas/validator.ts | 19 + .../ts/subproviders/injected_web3_subprovider.ts | 44 + .../ledger_wallet_subprovider_factory.ts | 172 + .../ts/subproviders/redundant_rpc_subprovider.ts | 41 + packages/website/ts/types.ts | 692 +++ packages/website/ts/utils/configs.ts | 18 + packages/website/ts/utils/constants.ts | 270 + packages/website/ts/utils/doc_utils.ts | 55 + packages/website/ts/utils/doxity_utils.ts | 162 + packages/website/ts/utils/error_reporter.ts | 52 + packages/website/ts/utils/typedoc_utils.ts | 356 ++ packages/website/ts/utils/utils.ts | 215 + packages/website/ts/vendor/u2f_api.js | 760 +++ packages/website/ts/web3_wrapper.ts | 146 + packages/website/tsconfig.json | 20 + packages/website/tslint.json | 9 + packages/website/webpack.config.js | 86 + yarn.lock | 2691 +++++++++- 292 files changed, 26119 insertions(+), 101 deletions(-) create mode 100644 packages/website/README.md create mode 100644 packages/website/contracts/Mintable.json create mode 100644 packages/website/less/all.less create mode 100644 packages/website/package.json create mode 100644 packages/website/public/css/atom-one-light.css create mode 100644 packages/website/public/css/basscss_responsive_custom.css create mode 100644 packages/website/public/css/basscss_responsive_margin.css create mode 100644 packages/website/public/css/basscss_responsive_padding.css create mode 100644 packages/website/public/css/basscss_responsive_type_scale.css create mode 100755 packages/website/public/css/material-design-iconic-font.css create mode 100755 packages/website/public/css/material-design-iconic-font.min.css create mode 100644 packages/website/public/css/roboto.css create mode 100644 packages/website/public/css/roboto_mono.css create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.eot create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.svg create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.ttf create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.woff create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.woff2 create mode 100755 packages/website/public/fonts/Roboto-Black.ttf create mode 100755 packages/website/public/fonts/Roboto-BlackItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Bold.ttf create mode 100755 packages/website/public/fonts/Roboto-BoldItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Italic.ttf create mode 100755 packages/website/public/fonts/Roboto-Light.ttf create mode 100755 packages/website/public/fonts/Roboto-LightItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Medium.ttf create mode 100755 packages/website/public/fonts/Roboto-MediumItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Regular.ttf create mode 100755 packages/website/public/fonts/Roboto-Thin.ttf create mode 100755 packages/website/public/fonts/Roboto-ThinItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Bold.ttf create mode 100755 packages/website/public/fonts/RobotoMono-BoldItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Italic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Light.ttf create mode 100755 packages/website/public/fonts/RobotoMono-LightItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Medium.ttf create mode 100755 packages/website/public/fonts/RobotoMono-MediumItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Regular.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Thin.ttf create mode 100755 packages/website/public/fonts/RobotoMono-ThinItalic.ttf create mode 100644 packages/website/public/gifs/0xAnimation.gif create mode 100644 packages/website/public/gifs/genesis.gif create mode 100644 packages/website/public/images/0x_logo.png create mode 100644 packages/website/public/images/advisors/fred.jpg create mode 100644 packages/website/public/images/advisors/joey.jpg create mode 100644 packages/website/public/images/advisors/linda.jpg create mode 100644 packages/website/public/images/advisors/olaf.png create mode 100644 packages/website/public/images/ether.png create mode 100755 packages/website/public/images/favicon/favicon-2-16x16.png create mode 100755 packages/website/public/images/favicon/favicon-2-32x32.png create mode 100755 packages/website/public/images/favicon/favicon.ico create mode 100644 packages/website/public/images/landing/0x_chips.png create mode 100644 packages/website/public/images/landing/aragon.png create mode 100644 packages/website/public/images/landing/augur.png create mode 100644 packages/website/public/images/landing/currency.png create mode 100644 packages/website/public/images/landing/dharma.png create mode 100644 packages/website/public/images/landing/digital_goods.png create mode 100644 packages/website/public/images/landing/distributed_network.png create mode 100644 packages/website/public/images/landing/ethfinex.png create mode 100644 packages/website/public/images/landing/fund_management_icon.png create mode 100644 packages/website/public/images/landing/gnosis.png create mode 100644 packages/website/public/images/landing/governance_icon.png create mode 100644 packages/website/public/images/landing/hero_chip_image.png create mode 100644 packages/website/public/images/landing/lendroid.png create mode 100644 packages/website/public/images/landing/liquidity.png create mode 100644 packages/website/public/images/landing/loans_icon.png create mode 100644 packages/website/public/images/landing/maker.png create mode 100644 packages/website/public/images/landing/melonport.png create mode 100644 packages/website/public/images/landing/open_source.png create mode 100644 packages/website/public/images/landing/paradex.png create mode 100644 packages/website/public/images/landing/prediction_market_icon.png create mode 100644 packages/website/public/images/landing/project_logos/anx.png create mode 100644 packages/website/public/images/landing/project_logos/aragon.png create mode 100644 packages/website/public/images/landing/project_logos/auctus.png create mode 100644 packages/website/public/images/landing/project_logos/augur.png create mode 100644 packages/website/public/images/landing/project_logos/blocknet.png create mode 100644 packages/website/public/images/landing/project_logos/chronobank.png create mode 100644 packages/website/public/images/landing/project_logos/dharma.png create mode 100644 packages/website/public/images/landing/project_logos/district0x.png create mode 100644 packages/website/public/images/landing/project_logos/dydx.png create mode 100644 packages/website/public/images/landing/project_logos/ethfinex-top.png create mode 100644 packages/website/public/images/landing/project_logos/ethix.png create mode 100644 packages/website/public/images/landing/project_logos/lendroid.png create mode 100644 packages/website/public/images/landing/project_logos/maker.png create mode 100644 packages/website/public/images/landing/project_logos/melonport.png create mode 100644 packages/website/public/images/landing/project_logos/paradex_top.png create mode 100644 packages/website/public/images/landing/project_logos/radar_relay_top.png create mode 100644 packages/website/public/images/landing/project_logos/status.png create mode 100644 packages/website/public/images/landing/project_logos/the_ocean.png create mode 100644 packages/website/public/images/landing/radar_relay.png create mode 100644 packages/website/public/images/landing/relayer_diagram.png create mode 100644 packages/website/public/images/landing/stable_tokens_icon.png create mode 100644 packages/website/public/images/landing/stocks.png create mode 100644 packages/website/public/images/landing/tokenized_world.png create mode 100644 packages/website/public/images/loading_poster.png create mode 100644 packages/website/public/images/logos/FBG.png create mode 100644 packages/website/public/images/logos/aragon.png create mode 100644 packages/website/public/images/logos/augur.png create mode 100644 packages/website/public/images/logos/blockchain_capital.png create mode 100644 packages/website/public/images/logos/chronobank.png create mode 100644 packages/website/public/images/logos/dharma.png create mode 100644 packages/website/public/images/logos/district0x.png create mode 100644 packages/website/public/images/logos/jen_advisors.png create mode 100644 packages/website/public/images/logos/maker.png create mode 100644 packages/website/public/images/logos/melonport.png create mode 100644 packages/website/public/images/logos/openANX.png create mode 100644 packages/website/public/images/logos/pantera_capital.png create mode 100644 packages/website/public/images/logos/polychain_capital.png create mode 100644 packages/website/public/images/og_image.png create mode 100644 packages/website/public/images/protocol_logo_black.png create mode 100644 packages/website/public/images/protocol_logo_white.png create mode 100644 packages/website/public/images/social/github.png create mode 100644 packages/website/public/images/social/medium.png create mode 100644 packages/website/public/images/social/reddit.png create mode 100644 packages/website/public/images/social/rocketchat.png create mode 100644 packages/website/public/images/social/slack.png create mode 100644 packages/website/public/images/social/twitter.png create mode 100644 packages/website/public/images/team/alex.jpg create mode 100644 packages/website/public/images/team/amir.jpeg create mode 100644 packages/website/public/images/team/anyone.png create mode 100644 packages/website/public/images/team/ben.jpg create mode 100644 packages/website/public/images/team/brandon.png create mode 100644 packages/website/public/images/team/fabio.jpg create mode 100644 packages/website/public/images/team/leonid.png create mode 100644 packages/website/public/images/team/philippe.png create mode 100644 packages/website/public/images/team/will.jpg create mode 100644 packages/website/public/images/token_icons/adtoken.png create mode 100644 packages/website/public/images/token_icons/aragon.png create mode 100644 packages/website/public/images/token_icons/augur.png create mode 100644 packages/website/public/images/token_icons/bancor.png create mode 100644 packages/website/public/images/token_icons/basicattentiontoken.png create mode 100644 packages/website/public/images/token_icons/bitquence.png create mode 100644 packages/website/public/images/token_icons/btc.png create mode 100644 packages/website/public/images/token_icons/civic.png create mode 100644 packages/website/public/images/token_icons/clams.png create mode 100644 packages/website/public/images/token_icons/cofound-it.png create mode 100644 packages/website/public/images/token_icons/default.png create mode 100644 packages/website/public/images/token_icons/digixdao.png create mode 100644 packages/website/public/images/token_icons/district0x.png create mode 100644 packages/website/public/images/token_icons/edgeless.png create mode 100644 packages/website/public/images/token_icons/eos.png create mode 100644 packages/website/public/images/token_icons/ether_erc20.png create mode 100644 packages/website/public/images/token_icons/etheroll.png create mode 100644 packages/website/public/images/token_icons/firstblood.jpg create mode 100644 packages/website/public/images/token_icons/funfair.png create mode 100644 packages/website/public/images/token_icons/gnosis.png create mode 100644 packages/website/public/images/token_icons/golem.png create mode 100644 packages/website/public/images/token_icons/iconomi.png create mode 100644 packages/website/public/images/token_icons/iexec.png create mode 100644 packages/website/public/images/token_icons/lunyr.png create mode 100644 packages/website/public/images/token_icons/makerdao.png create mode 100644 packages/website/public/images/token_icons/melon.png create mode 100644 packages/website/public/images/token_icons/metal.png create mode 100644 packages/website/public/images/token_icons/monaco.png create mode 100644 packages/website/public/images/token_icons/numeraire.png create mode 100644 packages/website/public/images/token_icons/omisego.png create mode 100644 packages/website/public/images/token_icons/qtum.png create mode 100644 packages/website/public/images/token_icons/santiment.png create mode 100644 packages/website/public/images/token_icons/singularity.png create mode 100644 packages/website/public/images/token_icons/status.png create mode 100644 packages/website/public/images/token_icons/storjcoinx.png create mode 100644 packages/website/public/images/token_icons/taas.png create mode 100644 packages/website/public/images/token_icons/tenx.png create mode 100644 packages/website/public/images/token_icons/tokencard.png create mode 100644 packages/website/public/images/token_icons/trust.png create mode 100644 packages/website/public/images/token_icons/wings.png create mode 100644 packages/website/public/images/token_icons/zero_ex.png create mode 100644 packages/website/public/images/trade_arrows.png create mode 100644 packages/website/public/images/zrx_pie_chart.png create mode 100644 packages/website/public/images/zrx_token.png create mode 100644 packages/website/public/index.html create mode 100644 packages/website/public/js/rollbar.umd.nojson.min.js create mode 100644 packages/website/public/pdfs/0x_white_paper.pdf create mode 100644 packages/website/public/videos/0xAnimation.mp4 create mode 100644 packages/website/ts/blockchain.ts create mode 100644 packages/website/ts/components/dialogs/blockchain_err_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/ledger_config_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/send_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx create mode 100644 packages/website/ts/components/eth_weth_conversion_button.tsx create mode 100644 packages/website/ts/components/fill_order.tsx create mode 100644 packages/website/ts/components/fill_order_json.tsx create mode 100644 packages/website/ts/components/fill_warning_dialog.tsx create mode 100644 packages/website/ts/components/flash_messages/token_send_completed.tsx create mode 100644 packages/website/ts/components/flash_messages/transaction_submitted.tsx create mode 100644 packages/website/ts/components/footer.tsx create mode 100644 packages/website/ts/components/generate_order/asset_picker.tsx create mode 100644 packages/website/ts/components/generate_order/generate_order_form.tsx create mode 100644 packages/website/ts/components/generate_order/new_token_form.tsx create mode 100644 packages/website/ts/components/inputs/address_input.tsx create mode 100644 packages/website/ts/components/inputs/allowance_toggle.tsx create mode 100644 packages/website/ts/components/inputs/balance_bounded_input.tsx create mode 100644 packages/website/ts/components/inputs/eth_amount_input.tsx create mode 100644 packages/website/ts/components/inputs/expiration_input.tsx create mode 100644 packages/website/ts/components/inputs/hash_input.tsx create mode 100644 packages/website/ts/components/inputs/identicon_address_input.tsx create mode 100644 packages/website/ts/components/inputs/token_amount_input.tsx create mode 100644 packages/website/ts/components/inputs/token_input.tsx create mode 100644 packages/website/ts/components/order_json.tsx create mode 100644 packages/website/ts/components/portal.tsx create mode 100644 packages/website/ts/components/portal_menu.tsx create mode 100644 packages/website/ts/components/send_button.tsx create mode 100644 packages/website/ts/components/token_balances.tsx create mode 100644 packages/website/ts/components/top_bar.tsx create mode 100644 packages/website/ts/components/top_bar_menu_item.tsx create mode 100644 packages/website/ts/components/track_token_confirmation.tsx create mode 100644 packages/website/ts/components/trade_history/trade_history.tsx create mode 100644 packages/website/ts/components/trade_history/trade_history_item.tsx create mode 100644 packages/website/ts/components/ui/alert.tsx create mode 100644 packages/website/ts/components/ui/badge.tsx create mode 100644 packages/website/ts/components/ui/copy_icon.tsx create mode 100644 packages/website/ts/components/ui/drop_down_menu_item.tsx create mode 100644 packages/website/ts/components/ui/ethereum_address.tsx create mode 100644 packages/website/ts/components/ui/etherscan_icon.tsx create mode 100644 packages/website/ts/components/ui/fake_text_field.tsx create mode 100644 packages/website/ts/components/ui/flash_message.tsx create mode 100644 packages/website/ts/components/ui/help_tooltip.tsx create mode 100644 packages/website/ts/components/ui/identicon.tsx create mode 100644 packages/website/ts/components/ui/input_label.tsx create mode 100644 packages/website/ts/components/ui/labeled_switcher.tsx create mode 100644 packages/website/ts/components/ui/lifecycle_raised_button.tsx create mode 100644 packages/website/ts/components/ui/loading.tsx create mode 100644 packages/website/ts/components/ui/menu_item.tsx create mode 100644 packages/website/ts/components/ui/party.tsx create mode 100644 packages/website/ts/components/ui/required_label.tsx create mode 100644 packages/website/ts/components/ui/simple_loading.tsx create mode 100644 packages/website/ts/components/ui/swap_icon.tsx create mode 100644 packages/website/ts/components/ui/token_icon.tsx create mode 100644 packages/website/ts/components/visual_order.tsx create mode 100644 packages/website/ts/containers/generate_order_form.tsx create mode 100644 packages/website/ts/containers/portal.tsx create mode 100644 packages/website/ts/containers/smart_contracts_documentation.tsx create mode 100644 packages/website/ts/containers/zero_ex_js_documentation.tsx create mode 100644 packages/website/ts/globals.d.ts create mode 100644 packages/website/ts/index.tsx create mode 100644 packages/website/ts/lazy_component.tsx create mode 100644 packages/website/ts/local_storage/local_storage.ts create mode 100644 packages/website/ts/local_storage/tracked_token_storage.ts create mode 100644 packages/website/ts/local_storage/trade_history_storage.tsx create mode 100644 packages/website/ts/pages/about/about.tsx create mode 100644 packages/website/ts/pages/about/profile.tsx create mode 100644 packages/website/ts/pages/documentation/comment.tsx create mode 100644 packages/website/ts/pages/documentation/custom_enum.tsx create mode 100644 packages/website/ts/pages/documentation/enum.tsx create mode 100644 packages/website/ts/pages/documentation/event_definition.tsx create mode 100644 packages/website/ts/pages/documentation/interface.tsx create mode 100644 packages/website/ts/pages/documentation/method_block.tsx create mode 100644 packages/website/ts/pages/documentation/method_signature.tsx create mode 100644 packages/website/ts/pages/documentation/smart_contracts_documentation.tsx create mode 100644 packages/website/ts/pages/documentation/source_link.tsx create mode 100644 packages/website/ts/pages/documentation/type.tsx create mode 100644 packages/website/ts/pages/documentation/type_definition.tsx create mode 100644 packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx create mode 100644 packages/website/ts/pages/faq/faq.tsx create mode 100644 packages/website/ts/pages/faq/question.tsx create mode 100644 packages/website/ts/pages/landing/landing.tsx create mode 100644 packages/website/ts/pages/not_found.tsx create mode 100644 packages/website/ts/pages/shared/anchor_title.tsx create mode 100644 packages/website/ts/pages/shared/markdown_code_block.tsx create mode 100644 packages/website/ts/pages/shared/markdown_section.tsx create mode 100644 packages/website/ts/pages/shared/nested_sidebar_menu.tsx create mode 100644 packages/website/ts/pages/shared/section_header.tsx create mode 100644 packages/website/ts/pages/shared/version_drop_down.tsx create mode 100644 packages/website/ts/pages/wiki/wiki.tsx create mode 100644 packages/website/ts/redux/dispatcher.ts create mode 100644 packages/website/ts/redux/reducer.ts create mode 100644 packages/website/ts/schemas/order_schema.ts create mode 100644 packages/website/ts/schemas/order_taker_schema.ts create mode 100644 packages/website/ts/schemas/signature_data_schema.ts create mode 100644 packages/website/ts/schemas/token_schema.ts create mode 100644 packages/website/ts/schemas/validator.ts create mode 100644 packages/website/ts/subproviders/injected_web3_subprovider.ts create mode 100644 packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts create mode 100644 packages/website/ts/subproviders/redundant_rpc_subprovider.ts create mode 100644 packages/website/ts/types.ts create mode 100644 packages/website/ts/utils/configs.ts create mode 100644 packages/website/ts/utils/constants.ts create mode 100644 packages/website/ts/utils/doc_utils.ts create mode 100644 packages/website/ts/utils/doxity_utils.ts create mode 100644 packages/website/ts/utils/error_reporter.ts create mode 100644 packages/website/ts/utils/typedoc_utils.ts create mode 100644 packages/website/ts/utils/utils.ts create mode 100644 packages/website/ts/vendor/u2f_api.js create mode 100644 packages/website/ts/web3_wrapper.ts create mode 100644 packages/website/tsconfig.json create mode 100644 packages/website/tslint.json create mode 100644 packages/website/webpack.config.js diff --git a/.gitignore b/.gitignore index 17219f0d9..b461f33de 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ _bundles # generated documentation docs/ + +TODO.md diff --git a/packages/website/README.md b/packages/website/README.md new file mode 100644 index 000000000..40df07cb7 --- /dev/null +++ b/packages/website/README.md @@ -0,0 +1,60 @@ + + +--- + +[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. A full description of the protocol may be found in our [whitepaper][whitepaper-url]. + +This repository contains our website and [0x Portal DApp][portal-url] (over-the-counter exchange), facilitating trustless over-the-counter trading of Ethereum-based tokens using 0x protocol. + +[website-url]: https://0xproject.com/ +[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf +[portal-url]: https://0xproject.com/portal + +[![Join the chat at https://gitter.im/0xProject/contracts](https://badges.gitter.im/0xProject/contracts.svg)](https://gitter.im/0xProject/contracts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +### Local Dev Setup + +Requires Node version 6.9.5 or higher. + +Add the following to your `/etc/hosts` file: + +``` +127.0.0.1 0xproject.dev +``` + +Clone the [0x contracts repo](https://github.com/0xProject/contracts) into the same parent directory as this project. + +Install [yarn](https://yarnpkg.com/lang/en/docs/install/) in order to install the project dependencies more deterministically. + +Install dependencies: + +``` +yarn +``` + +Import smart contract artifacts from `contracts` repo: + +``` +yarn run update_contracts +``` + +Start dev server: + +``` +yarn run dev +``` + +Visit [0xproject.dev:3572](http://0xproject.dev:3572) in your browser. + + +##### Recommended Atom packages: + +- [atom-typescript](https://atom.io/packages/atom-typescript) +- [linter-tslint](https://atom.io/packages/linter-tslint) + +##### Resources + +- [Material Design Icon Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html#directional) +- [BassCSS toolkit](http://basscss.com/) +- [Material-UI](http://www.material-ui.com/#/) diff --git a/packages/website/contracts/Mintable.json b/packages/website/contracts/Mintable.json new file mode 100644 index 000000000..52dc7507f --- /dev/null +++ b/packages/website/contracts/Mintable.json @@ -0,0 +1,189 @@ +{ + "contract_name": "Mintable", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } + ], + "unlinked_binary": "0x6060604052341561000c57fe5b5b6105018061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a0360043516610335565b60408051918252519081900360200190f35b341561012d57fe5b610138600435610354565b005b341561014257fe5b610088600160a060020a03600435166024356103bc565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a036004358116906024351661046e565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906102555750828110155b801561027b5750600160a060020a03841660009081526020819052604090205483810110155b1561032757600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156102e557600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206104b6833981519152856040518082815260200191505060405180910390a36001915061032c565b600091505b5b509392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561036b5760006000fd5b600160a060020a03331660009081526020819052604090205461038f90829061049b565b600160a060020a0333166000908152602081905260409020556002546103b5908261049b565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103ff5750600160a060020a03831660009081526020819052604090205482810110155b1561045f57600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206104b6833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156104aa57fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820998c8326b9629e063eb4867166e72c68a8c2e3ebca6a9d35ebc78c041c7aa47b0029", + "networks": {}, + "schema_version": "0.0.5", + "updated_at": 1503413048892 +} \ No newline at end of file diff --git a/packages/website/less/all.less b/packages/website/less/all.less new file mode 100644 index 000000000..5212df959 --- /dev/null +++ b/packages/website/less/all.less @@ -0,0 +1,136 @@ +body { + font-family: 'Roboto'; +} + +.robotoMono { + font-family: 'Roboto Mono'; +} + +a { + color: black; +} + +#faq { + li { + padding-bottom: 5px; + } + + a { + color: rgb(66, 66, 66); + } +} + +#landing { + .h1, .h2, .h3, .h4 { + font-family: 'Roboto Mono'; + } +} + + +#portal { + h1, h2, h3, h4 { + font-weight: 100; + } +} + +#documentation { + p { + line-height: 1.5; + } + + .comment { + p { + margin: 0px; + } + } + + .typeTooltip { + border: 1px solid lightgray; + opacity: 1; + } +} + +/* + * Adds always visible scrollbars on OSX so that user knows the content is scrollable + * Source: https://davidwalsh.name/osx-overflow + */ +::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; +} +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5); +} + +// Hack: For some reason the animation applied to the material-ui textfield causes the overflow +// applied to other elements to fail while the animation is underway. Adding this class to the +// affected component fixes the issue +// Source: http://stackoverflow.com/questions/14383632/webkit-border-radius-and-overflow-bug-when-using-any-animation-transition +.transitionFix { + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); +} + +.thin { + font-weight: 100; +} + +code { + font-family: 'Roboto'; + background-color: #f3f4f4; + color: rgb(36, 41, 46); + padding: 3px; + + &.hljs { + background-color: #dde4e9 !important; // blue gray + border-left: 5px solid #0091EA !important; // colors.lightBlueA700 + padding: 30px; + } +} + +#wiki { + p, blockquote, ul, ol, dl, li, table, pre { + margin: 15px 0; + } + + ol, ul { + padding-bottom: 20px; + } + + table { + padding: 0; + border-collapse: collapse; + } + table tr { + border-top: 1px solid #cccccc; + background-color: white; + margin: 0; + padding: 0; + } + table tr:nth-child(2n) { + background-color: #f8f8f8; + } + table tr th { + font-weight: bold; + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; + } + table tr td { + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; + } + table tr th :first-child, table tr td :first-child { + margin-top: 0; + } + table tr th :last-child, table tr td :last-child { + margin-bottom: 0; + } +} diff --git a/packages/website/package.json b/packages/website/package.json new file mode 100644 index 000000000..ba71ee0eb --- /dev/null +++ b/packages/website/package.json @@ -0,0 +1,105 @@ +{ + "name": "website", + "version": "0.0.0", + "description": "Website and 0x portal dapp", + "scripts": { + "build": "NODE_ENV=production webpack", + "clean": "shx rm -f public/bundle*", + "dev": "webpack-dev-server --content-base public --https", + "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", + "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", + "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" + }, + "config": { + "artifacts": "Mintable" + }, + "author": "Fabio Berger", + "license": "Apache-2.0", + "dependencies": { + "accounting": "^0.4.1", + "basscss": "^8.0.3", + "bignumber.js": "~4.1.0", + "blockies": "^0.0.2", + "compare-versions": "^3.0.1", + "dateformat": "^2.0.0", + "deep-equal": "^1.0.1", + "dharma-loan-frame": "^0.0.12", + "es6-promisify": "^5.0.0", + "ethereum-address": "^0.0.4", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.1", + "find-versions": "^2.0.0", + "is-mobile": "^0.2.2", + "jsonschema": "^1.1.1", + "ledgerco": "0xProject/ledger-node-js-api", + "less": "^2.7.2", + "lodash": "^4.17.4", + "material-ui": "^0.17.1", + "moment": "^2.18.1", + "query-string": "^5.0.0", + "react": "15.6.1", + "react-copy-to-clipboard": "^4.2.3", + "react-document-title": "^2.0.3", + "react-dom": "15.6.1", + "react-highlight": "^0.10.0", + "react-html5video": "^2.1.0", + "react-inlinesvg": "^0.5.5", + "react-markdown": "^2.5.0", + "react-recaptcha": "^2.3.2", + "react-redux": "^5.0.3", + "react-router-dom": "^4.1.1", + "react-router-hash-link": "^1.1.0", + "react-scroll": "^1.5.2", + "react-tap-event-plugin": "^2.0.1", + "react-tooltip": "^3.2.7", + "react-waypoint": "^7.0.4", + "redux": "^3.6.0", + "scroll-to-element": "^2.0.0", + "semver-sort": "0.0.4", + "thenby": "^1.2.3", + "truffle-contract": "2.0.1", + "tslint-config-0xproject": "^0.0.2", + "typescript": "^2.4.1", + "web3": "^0.20.0", + "web3-provider-engine": "^11.0.0", + "whatwg-fetch": "^2.0.3", + "xml-js": "^1.3.2" + }, + "devDependencies": { + "@types/accounting": "^0.4.1", + "@types/dateformat": "^1.0.1", + "@types/deep-equal": "^1.0.0", + "@types/es6-promise": "0.0.32", + "@types/jsonschema": "^1.1.1", + "@types/lodash": "^4.14.55", + "@types/material-ui": "0.18.0", + "@types/moment": "^2.13.0", + "@types/node": "^7.0.8", + "@types/query-string": "^5.0.0", + "@types/react": "^15.0.15", + "@types/react-copy-to-clipboard": "^4.2.0", + "@types/react-dom": "^0.14.23", + "@types/react-redux": "^4.4.37", + "@types/react-router-dom": "^4.0.4", + "@types/react-scroll": "0.0.31", + "@types/react-tap-event-plugin": "0.0.30", + "@types/redux": "^3.6.0", + "awesome-typescript-loader": "^3.1.3", + "copy-webpack-plugin": "^4.0.1", + "copyfiles": "^1.2.0", + "css-loader": "0.23.x", + "exports-loader": "0.6.x", + "imports-loader": "0.6.x", + "json-loader": "^0.5.4", + "less-loader": "^2.2.3", + "raw-loader": "^0.5.1", + "shx": "^0.2.2", + "source-map-loader": "^0.1.6", + "style-loader": "0.13.x", + "tslint": "5.8.0", + "web3-typescript-typings": "^0.7.1", + "webpack": "3.1.0", + "webpack-dev-middleware": "^1.10.0", + "webpack-dev-server": "^2.5.0" + } +} diff --git a/packages/website/public/css/atom-one-light.css b/packages/website/public/css/atom-one-light.css new file mode 100644 index 000000000..d5bd1d2a9 --- /dev/null +++ b/packages/website/public/css/atom-one-light.css @@ -0,0 +1,96 @@ +/* + +Atom One Light by Daniel Gamage +Original One Light Syntax theme from https://github.com/atom/one-light-syntax + +base: #fafafa +mono-1: #383a42 +mono-2: #686b77 +mono-3: #a0a1a7 +hue-1: #0184bb +hue-2: #4078f2 +hue-3: #a626a4 +hue-4: #50a14f +hue-5: #e45649 +hue-5-2: #c91243 +hue-6: #986801 +hue-6-2: #c18401 + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #383a42; + background: #fafafa; +} + +.hljs-comment, +.hljs-quote { + color: #a0a1a7; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #a626a4; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e45649; +} + +.hljs-literal { + color: #0184bb; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #50a14f; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #c18401; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #986801; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #4078f2; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/website/public/css/basscss_responsive_custom.css b/packages/website/public/css/basscss_responsive_custom.css new file mode 100644 index 000000000..c2e802125 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_custom.css @@ -0,0 +1,58 @@ +/* Custom Basscss Responsive Utilities */ + +@media (max-width: 52em) { + .sm-center { + text-align: center; + } + .sm-left-align { + text-align: left; + } + .sm-right-align { + text-align: right; + } + .sm-mx-auto { + margin-left: auto; + margin-right: auto; + } + .sm-right { + float: right; + } +} + +@media (min-width: 52em) { + .md-center { + text-align: center; + } + .md-left-align { + text-align: left; + } + .md-right-align { + text-align: right; + } + .md-mx-auto { + margin-left: auto; + margin-right: auto; + } + .md-right { + float: right; + } +} + +@media (min-width: 64em) { + .lg-center { + text-align: center; + } + .lg-left-align { + text-align: left; + } + .lg-right-align { + text-align: right; + } + .lg-mx-auto { + margin-left: auto; + margin-right: auto; + } + .lg-right { + float: right; + } +} diff --git a/packages/website/public/css/basscss_responsive_margin.css b/packages/website/public/css/basscss_responsive_margin.css new file mode 100644 index 000000000..b601bd491 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_margin.css @@ -0,0 +1,160 @@ +/* Basscss Responsive Margin */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-m0 { margin: 0 } + .sm-mt0 { margin-top: 0 } + .sm-mr0 { margin-right: 0 } + .sm-mb0 { margin-bottom: 0 } + .sm-ml0 { margin-left: 0 } + .sm-mx0 { margin-left: 0; margin-right: 0 } + .sm-my0 { margin-top: 0; margin-bottom: 0 } + + .sm-m1 { margin: .5rem } + .sm-mt1 { margin-top: .5rem } + .sm-mr1 { margin-right: .5rem } + .sm-mb1 { margin-bottom: .5rem } + .sm-ml1 { margin-left: .5rem } + .sm-mx1 { margin-left: .5rem; margin-right: .5rem } + .sm-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .sm-m2 { margin: 1rem } + .sm-mt2 { margin-top: 1rem } + .sm-mr2 { margin-right: 1rem } + .sm-mb2 { margin-bottom: 1rem } + .sm-ml2 { margin-left: 1rem } + .sm-mx2 { margin-left: 1rem; margin-right: 1rem } + .sm-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .sm-m3 { margin: 2rem } + .sm-mt3 { margin-top: 2rem } + .sm-mr3 { margin-right: 2rem } + .sm-mb3 { margin-bottom: 2rem } + .sm-ml3 { margin-left: 2rem } + .sm-mx3 { margin-left: 2rem; margin-right: 2rem } + .sm-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .sm-m4 { margin: 4rem } + .sm-mt4 { margin-top: 4rem } + .sm-mr4 { margin-right: 4rem } + .sm-mb4 { margin-bottom: 4rem } + .sm-ml4 { margin-left: 4rem } + .sm-mx4 { margin-left: 4rem; margin-right: 4rem } + .sm-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .sm-mxn1 { margin-left: -.5rem; margin-right: -.5rem } + .sm-mxn2 { margin-left: -1rem; margin-right: -1rem } + .sm-mxn3 { margin-left: -2rem; margin-right: -2rem } + .sm-mxn4 { margin-left: -4rem; margin-right: -4rem } + + .sm-ml-auto { margin-left: auto } + .sm-mr-auto { margin-right: auto } + .sm-mx-auto { margin-left: auto; margin-right: auto } + +} + +@media (min-width: 52em) { + + .md-m0 { margin: 0 } + .md-mt0 { margin-top: 0 } + .md-mr0 { margin-right: 0 } + .md-mb0 { margin-bottom: 0 } + .md-ml0 { margin-left: 0 } + .md-mx0 { margin-left: 0; margin-right: 0 } + .md-my0 { margin-top: 0; margin-bottom: 0 } + + .md-m1 { margin: .5rem } + .md-mt1 { margin-top: .5rem } + .md-mr1 { margin-right: .5rem } + .md-mb1 { margin-bottom: .5rem } + .md-ml1 { margin-left: .5rem } + .md-mx1 { margin-left: .5rem; margin-right: .5rem } + .md-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .md-m2 { margin: 1rem } + .md-mt2 { margin-top: 1rem } + .md-mr2 { margin-right: 1rem } + .md-mb2 { margin-bottom: 1rem } + .md-ml2 { margin-left: 1rem } + .md-mx2 { margin-left: 1rem; margin-right: 1rem } + .md-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .md-m3 { margin: 2rem } + .md-mt3 { margin-top: 2rem } + .md-mr3 { margin-right: 2rem } + .md-mb3 { margin-bottom: 2rem } + .md-ml3 { margin-left: 2rem } + .md-mx3 { margin-left: 2rem; margin-right: 2rem } + .md-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .md-m4 { margin: 4rem } + .md-mt4 { margin-top: 4rem } + .md-mr4 { margin-right: 4rem } + .md-mb4 { margin-bottom: 4rem } + .md-ml4 { margin-left: 4rem } + .md-mx4 { margin-left: 4rem; margin-right: 4rem } + .md-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .md-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .md-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .md-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .md-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .md-ml-auto { margin-left: auto } + .md-mr-auto { margin-right: auto } + .md-mx-auto { margin-left: auto; margin-right: auto; } + +} + +@media (min-width: 64em) { + + .lg-m0 { margin: 0 } + .lg-mt0 { margin-top: 0 } + .lg-mr0 { margin-right: 0 } + .lg-mb0 { margin-bottom: 0 } + .lg-ml0 { margin-left: 0 } + .lg-mx0 { margin-left: 0; margin-right: 0 } + .lg-my0 { margin-top: 0; margin-bottom: 0 } + + .lg-m1 { margin: .5rem } + .lg-mt1 { margin-top: .5rem } + .lg-mr1 { margin-right: .5rem } + .lg-mb1 { margin-bottom: .5rem } + .lg-ml1 { margin-left: .5rem } + .lg-mx1 { margin-left: .5rem; margin-right: .5rem } + .lg-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .lg-m2 { margin: 1rem } + .lg-mt2 { margin-top: 1rem } + .lg-mr2 { margin-right: 1rem } + .lg-mb2 { margin-bottom: 1rem } + .lg-ml2 { margin-left: 1rem } + .lg-mx2 { margin-left: 1rem; margin-right: 1rem } + .lg-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .lg-m3 { margin: 2rem } + .lg-mt3 { margin-top: 2rem } + .lg-mr3 { margin-right: 2rem } + .lg-mb3 { margin-bottom: 2rem } + .lg-ml3 { margin-left: 2rem } + .lg-mx3 { margin-left: 2rem; margin-right: 2rem } + .lg-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .lg-m4 { margin: 4rem } + .lg-mt4 { margin-top: 4rem } + .lg-mr4 { margin-right: 4rem } + .lg-mb4 { margin-bottom: 4rem } + .lg-ml4 { margin-left: 4rem } + .lg-mx4 { margin-left: 4rem; margin-right: 4rem } + .lg-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .lg-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .lg-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .lg-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .lg-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .lg-ml-auto { margin-left: auto } + .lg-mr-auto { margin-right: auto } + .lg-mx-auto { margin-left: auto; margin-right: auto; } + +} diff --git a/packages/website/public/css/basscss_responsive_padding.css b/packages/website/public/css/basscss_responsive_padding.css new file mode 100644 index 000000000..e027c2d65 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_padding.css @@ -0,0 +1,134 @@ +/* Basscss Responsive Padding */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-p0 { padding: 0 } + .sm-pt0 { padding-top: 0 } + .sm-pr0 { padding-right: 0 } + .sm-pb0 { padding-bottom: 0 } + .sm-pl0 { padding-left: 0 } + .sm-px0 { padding-left: 0; padding-right: 0 } + .sm-py0 { padding-top: 0; padding-bottom: 0 } + + .sm-p1 { padding: .5rem } + .sm-pt1 { padding-top: .5rem } + .sm-pr1 { padding-right: .5rem } + .sm-pb1 { padding-bottom: .5rem } + .sm-pl1 { padding-left: .5rem } + .sm-px1 { padding-left: .5rem; padding-right: .5rem } + .sm-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .sm-p2 { padding: 1rem } + .sm-pt2 { padding-top: 1rem } + .sm-pr2 { padding-right: 1rem } + .sm-pb2 { padding-bottom: 1rem } + .sm-pl2 { padding-left: 1rem } + .sm-px2 { padding-left: 1rem; padding-right: 1rem } + .sm-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .sm-p3 { padding: 2rem } + .sm-pt3 { padding-top: 2rem } + .sm-pr3 { padding-right: 2rem } + .sm-pb3 { padding-bottom: 2rem } + .sm-pl3 { padding-left: 2rem } + .sm-px3 { padding-left: 2rem; padding-right: 2rem } + .sm-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .sm-p4 { padding: 4rem } + .sm-pt4 { padding-top: 4rem } + .sm-pr4 { padding-right: 4rem } + .sm-pb4 { padding-bottom: 4rem } + .sm-pl4 { padding-left: 4rem } + .sm-px4 { padding-left: 4rem; padding-right: 4rem } + .sm-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 52em) { + + .md-p0 { padding: 0 } + .md-pt0 { padding-top: 0 } + .md-pr0 { padding-right: 0 } + .md-pb0 { padding-bottom: 0 } + .md-pl0 { padding-left: 0 } + .md-px0 { padding-left: 0; padding-right: 0 } + .md-py0 { padding-top: 0; padding-bottom: 0 } + + .md-p1 { padding: .5rem } + .md-pt1 { padding-top: .5rem } + .md-pr1 { padding-right: .5rem } + .md-pb1 { padding-bottom: .5rem } + .md-pl1 { padding-left: .5rem } + .md-px1 { padding-left: .5rem; padding-right: .5rem } + .md-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .md-p2 { padding: 1rem } + .md-pt2 { padding-top: 1rem } + .md-pr2 { padding-right: 1rem } + .md-pb2 { padding-bottom: 1rem } + .md-pl2 { padding-left: 1rem } + .md-px2 { padding-left: 1rem; padding-right: 1rem } + .md-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .md-p3 { padding: 2rem } + .md-pt3 { padding-top: 2rem } + .md-pr3 { padding-right: 2rem } + .md-pb3 { padding-bottom: 2rem } + .md-pl3 { padding-left: 2rem } + .md-px3 { padding-left: 2rem; padding-right: 2rem } + .md-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .md-p4 { padding: 4rem } + .md-pt4 { padding-top: 4rem } + .md-pr4 { padding-right: 4rem } + .md-pb4 { padding-bottom: 4rem } + .md-pl4 { padding-left: 4rem } + .md-px4 { padding-left: 4rem; padding-right: 4rem } + .md-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 64em) { + + .lg-p0 { padding: 0 } + .lg-pt0 { padding-top: 0 } + .lg-pr0 { padding-right: 0 } + .lg-pb0 { padding-bottom: 0 } + .lg-pl0 { padding-left: 0 } + .lg-px0 { padding-left: 0; padding-right: 0 } + .lg-py0 { padding-top: 0; padding-bottom: 0 } + + .lg-p1 { padding: .5rem } + .lg-pt1 { padding-top: .5rem } + .lg-pr1 { padding-right: .5rem } + .lg-pb1 { padding-bottom: .5rem } + .lg-pl1 { padding-left: .5rem } + .lg-px1 { padding-left: .5rem; padding-right: .5rem } + .lg-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .lg-p2 { padding: 1rem } + .lg-pt2 { padding-top: 1rem } + .lg-pr2 { padding-right: 1rem } + .lg-pb2 { padding-bottom: 1rem } + .lg-pl2 { padding-left: 1rem } + .lg-px2 { padding-left: 1rem; padding-right: 1rem } + .lg-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .lg-p3 { padding: 2rem } + .lg-pt3 { padding-top: 2rem } + .lg-pr3 { padding-right: 2rem } + .lg-pb3 { padding-bottom: 2rem } + .lg-pl3 { padding-left: 2rem } + .lg-px3 { padding-left: 2rem; padding-right: 2rem } + .lg-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .lg-p4 { padding: 4rem } + .lg-pt4 { padding-top: 4rem } + .lg-pr4 { padding-right: 4rem } + .lg-pb4 { padding-bottom: 4rem } + .lg-pl4 { padding-left: 4rem } + .lg-px4 { padding-left: 4rem; padding-right: 4rem } + .lg-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} diff --git a/packages/website/public/css/basscss_responsive_type_scale.css b/packages/website/public/css/basscss_responsive_type_scale.css new file mode 100644 index 000000000..cae23b4e7 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_type_scale.css @@ -0,0 +1,35 @@ +/* Basscss Responsive Type Scale */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + .sm-h00 { font-size: 4rem } + .sm-h0 { font-size: 3rem } + .sm-h1 { font-size: 2rem } + .sm-h2 { font-size: 1.5rem } + .sm-h3 { font-size: 1.25rem } + .sm-h4 { font-size: 1rem } + .sm-h5 { font-size: .875rem } + .sm-h6 { font-size: .75rem } +} + +@media (min-width: 52em) { + .md-h00 { font-size: 4rem } + .md-h0 { font-size: 3rem } + .md-h1 { font-size: 2rem } + .md-h2 { font-size: 1.5rem } + .md-h3 { font-size: 1.25rem } + .md-h4 { font-size: 1rem } + .md-h5 { font-size: .875rem } + .md-h6 { font-size: .75rem } +} + +@media (min-width: 64em) { + .lg-h00 { font-size: 4rem } + .lg-h0 { font-size: 3rem } + .lg-h1 { font-size: 2rem } + .lg-h2 { font-size: 1.5rem } + .lg-h3 { font-size: 1.25rem } + .lg-h4 { font-size: 1rem } + .lg-h5 { font-size: .875rem } + .lg-h6 { font-size: .75rem } +} diff --git a/packages/website/public/css/material-design-iconic-font.css b/packages/website/public/css/material-design-iconic-font.css new file mode 100755 index 000000000..81d090a8b --- /dev/null +++ b/packages/website/public/css/material-design-iconic-font.css @@ -0,0 +1,5166 @@ +/*! + * Material Design Iconic Font by Sergey Kupletsky (@zavoloklom) - http://zavoloklom.github.io/material-design-iconic-font/ + * License - http://zavoloklom.github.io/material-design-iconic-font/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +@font-face { + font-family: 'Material-Design-Iconic-Font'; + src: url('../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0') format('woff2'), url('../fonts/Material-Design-Iconic-Font.woff?v=2.2.0') format('woff'), url('../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0') format('truetype'); + font-weight: normal; + font-style: normal; +} +.zmdi { + display: inline-block; + font: normal normal normal 14px/1 'Material-Design-Iconic-Font'; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.zmdi-hc-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.zmdi-hc-2x { + font-size: 2em; +} +.zmdi-hc-3x { + font-size: 3em; +} +.zmdi-hc-4x { + font-size: 4em; +} +.zmdi-hc-5x { + font-size: 5em; +} +.zmdi-hc-fw { + width: 1.28571429em; + text-align: center; +} +.zmdi-hc-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.zmdi-hc-ul > li { + position: relative; +} +.zmdi-hc-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.zmdi-hc-li.zmdi-hc-lg { + left: -1.85714286em; +} +.zmdi-hc-border { + padding: .1em .25em; + border: solid 0.1em #9e9e9e; + border-radius: 2px; +} +.zmdi-hc-border-circle { + padding: .1em .25em; + border: solid 0.1em #9e9e9e; + border-radius: 50%; +} +.zmdi.pull-left { + float: left; + margin-right: .15em; +} +.zmdi.pull-right { + float: right; + margin-left: .15em; +} +.zmdi-hc-spin { + -webkit-animation: zmdi-spin 1.5s infinite linear; + animation: zmdi-spin 1.5s infinite linear; +} +.zmdi-hc-spin-reverse { + -webkit-animation: zmdi-spin-reverse 1.5s infinite linear; + animation: zmdi-spin-reverse 1.5s infinite linear; +} +@-webkit-keyframes zmdi-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes zmdi-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@-webkit-keyframes zmdi-spin-reverse { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(-359deg); + transform: rotate(-359deg); + } +} +@keyframes zmdi-spin-reverse { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(-359deg); + transform: rotate(-359deg); + } +} +.zmdi-hc-rotate-90 { + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.zmdi-hc-rotate-180 { + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.zmdi-hc-rotate-270 { + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.zmdi-hc-flip-horizontal { + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.zmdi-hc-flip-vertical { + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +.zmdi-hc-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.zmdi-hc-stack-1x, +.zmdi-hc-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.zmdi-hc-stack-1x { + line-height: inherit; +} +.zmdi-hc-stack-2x { + font-size: 2em; +} +.zmdi-hc-inverse { + color: #ffffff; +} +/* Material Design Iconic Font uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.zmdi-3d-rotation:before { + content: '\f101'; +} +.zmdi-airplane-off:before { + content: '\f102'; +} +.zmdi-airplane:before { + content: '\f103'; +} +.zmdi-album:before { + content: '\f104'; +} +.zmdi-archive:before { + content: '\f105'; +} +.zmdi-assignment-account:before { + content: '\f106'; +} +.zmdi-assignment-alert:before { + content: '\f107'; +} +.zmdi-assignment-check:before { + content: '\f108'; +} +.zmdi-assignment-o:before { + content: '\f109'; +} +.zmdi-assignment-return:before { + content: '\f10a'; +} +.zmdi-assignment-returned:before { + content: '\f10b'; +} +.zmdi-assignment:before { + content: '\f10c'; +} +.zmdi-attachment-alt:before { + content: '\f10d'; +} +.zmdi-attachment:before { + content: '\f10e'; +} +.zmdi-audio:before { + content: '\f10f'; +} +.zmdi-badge-check:before { + content: '\f110'; +} +.zmdi-balance-wallet:before { + content: '\f111'; +} +.zmdi-balance:before { + content: '\f112'; +} +.zmdi-battery-alert:before { + content: '\f113'; +} +.zmdi-battery-flash:before { + content: '\f114'; +} +.zmdi-battery-unknown:before { + content: '\f115'; +} +.zmdi-battery:before { + content: '\f116'; +} +.zmdi-bike:before { + content: '\f117'; +} +.zmdi-block-alt:before { + content: '\f118'; +} +.zmdi-block:before { + content: '\f119'; +} +.zmdi-boat:before { + content: '\f11a'; +} +.zmdi-book-image:before { + content: '\f11b'; +} +.zmdi-book:before { + content: '\f11c'; +} +.zmdi-bookmark-outline:before { + content: '\f11d'; +} +.zmdi-bookmark:before { + content: '\f11e'; +} +.zmdi-brush:before { + content: '\f11f'; +} +.zmdi-bug:before { + content: '\f120'; +} +.zmdi-bus:before { + content: '\f121'; +} +.zmdi-cake:before { + content: '\f122'; +} +.zmdi-car-taxi:before { + content: '\f123'; +} +.zmdi-car-wash:before { + content: '\f124'; +} +.zmdi-car:before { + content: '\f125'; +} +.zmdi-card-giftcard:before { + content: '\f126'; +} +.zmdi-card-membership:before { + content: '\f127'; +} +.zmdi-card-travel:before { + content: '\f128'; +} +.zmdi-card:before { + content: '\f129'; +} +.zmdi-case-check:before { + content: '\f12a'; +} +.zmdi-case-download:before { + content: '\f12b'; +} +.zmdi-case-play:before { + content: '\f12c'; +} +.zmdi-case:before { + content: '\f12d'; +} +.zmdi-cast-connected:before { + content: '\f12e'; +} +.zmdi-cast:before { + content: '\f12f'; +} +.zmdi-chart-donut:before { + content: '\f130'; +} +.zmdi-chart:before { + content: '\f131'; +} +.zmdi-city-alt:before { + content: '\f132'; +} +.zmdi-city:before { + content: '\f133'; +} +.zmdi-close-circle-o:before { + content: '\f134'; +} +.zmdi-close-circle:before { + content: '\f135'; +} +.zmdi-close:before { + content: '\f136'; +} +.zmdi-cocktail:before { + content: '\f137'; +} +.zmdi-code-setting:before { + content: '\f138'; +} +.zmdi-code-smartphone:before { + content: '\f139'; +} +.zmdi-code:before { + content: '\f13a'; +} +.zmdi-coffee:before { + content: '\f13b'; +} +.zmdi-collection-bookmark:before { + content: '\f13c'; +} +.zmdi-collection-case-play:before { + content: '\f13d'; +} +.zmdi-collection-folder-image:before { + content: '\f13e'; +} +.zmdi-collection-image-o:before { + content: '\f13f'; +} +.zmdi-collection-image:before { + content: '\f140'; +} +.zmdi-collection-item-1:before { + content: '\f141'; +} +.zmdi-collection-item-2:before { + content: '\f142'; +} +.zmdi-collection-item-3:before { + content: '\f143'; +} +.zmdi-collection-item-4:before { + content: '\f144'; +} +.zmdi-collection-item-5:before { + content: '\f145'; +} +.zmdi-collection-item-6:before { + content: '\f146'; +} +.zmdi-collection-item-7:before { + content: '\f147'; +} +.zmdi-collection-item-8:before { + content: '\f148'; +} +.zmdi-collection-item-9-plus:before { + content: '\f149'; +} +.zmdi-collection-item-9:before { + content: '\f14a'; +} +.zmdi-collection-item:before { + content: '\f14b'; +} +.zmdi-collection-music:before { + content: '\f14c'; +} +.zmdi-collection-pdf:before { + content: '\f14d'; +} +.zmdi-collection-plus:before { + content: '\f14e'; +} +.zmdi-collection-speaker:before { + content: '\f14f'; +} +.zmdi-collection-text:before { + content: '\f150'; +} +.zmdi-collection-video:before { + content: '\f151'; +} +.zmdi-compass:before { + content: '\f152'; +} +.zmdi-cutlery:before { + content: '\f153'; +} +.zmdi-delete:before { + content: '\f154'; +} +.zmdi-dialpad:before { + content: '\f155'; +} +.zmdi-dns:before { + content: '\f156'; +} +.zmdi-drink:before { + content: '\f157'; +} +.zmdi-edit:before { + content: '\f158'; +} +.zmdi-email-open:before { + content: '\f159'; +} +.zmdi-email:before { + content: '\f15a'; +} +.zmdi-eye-off:before { + content: '\f15b'; +} +.zmdi-eye:before { + content: '\f15c'; +} +.zmdi-eyedropper:before { + content: '\f15d'; +} +.zmdi-favorite-outline:before { + content: '\f15e'; +} +.zmdi-favorite:before { + content: '\f15f'; +} +.zmdi-filter-list:before { + content: '\f160'; +} +.zmdi-fire:before { + content: '\f161'; +} +.zmdi-flag:before { + content: '\f162'; +} +.zmdi-flare:before { + content: '\f163'; +} +.zmdi-flash-auto:before { + content: '\f164'; +} +.zmdi-flash-off:before { + content: '\f165'; +} +.zmdi-flash:before { + content: '\f166'; +} +.zmdi-flip:before { + content: '\f167'; +} +.zmdi-flower-alt:before { + content: '\f168'; +} +.zmdi-flower:before { + content: '\f169'; +} +.zmdi-font:before { + content: '\f16a'; +} +.zmdi-fullscreen-alt:before { + content: '\f16b'; +} +.zmdi-fullscreen-exit:before { + content: '\f16c'; +} +.zmdi-fullscreen:before { + content: '\f16d'; +} +.zmdi-functions:before { + content: '\f16e'; +} +.zmdi-gas-station:before { + content: '\f16f'; +} +.zmdi-gesture:before { + content: '\f170'; +} +.zmdi-globe-alt:before { + content: '\f171'; +} +.zmdi-globe-lock:before { + content: '\f172'; +} +.zmdi-globe:before { + content: '\f173'; +} +.zmdi-graduation-cap:before { + content: '\f174'; +} +.zmdi-home:before { + content: '\f175'; +} +.zmdi-hospital-alt:before { + content: '\f176'; +} +.zmdi-hospital:before { + content: '\f177'; +} +.zmdi-hotel:before { + content: '\f178'; +} +.zmdi-hourglass-alt:before { + content: '\f179'; +} +.zmdi-hourglass-outline:before { + content: '\f17a'; +} +.zmdi-hourglass:before { + content: '\f17b'; +} +.zmdi-http:before { + content: '\f17c'; +} +.zmdi-image-alt:before { + content: '\f17d'; +} +.zmdi-image-o:before { + content: '\f17e'; +} +.zmdi-image:before { + content: '\f17f'; +} +.zmdi-inbox:before { + content: '\f180'; +} +.zmdi-invert-colors-off:before { + content: '\f181'; +} +.zmdi-invert-colors:before { + content: '\f182'; +} +.zmdi-key:before { + content: '\f183'; +} +.zmdi-label-alt-outline:before { + content: '\f184'; +} +.zmdi-label-alt:before { + content: '\f185'; +} +.zmdi-label-heart:before { + content: '\f186'; +} +.zmdi-label:before { + content: '\f187'; +} +.zmdi-labels:before { + content: '\f188'; +} +.zmdi-lamp:before { + content: '\f189'; +} +.zmdi-landscape:before { + content: '\f18a'; +} +.zmdi-layers-off:before { + content: '\f18b'; +} +.zmdi-layers:before { + content: '\f18c'; +} +.zmdi-library:before { + content: '\f18d'; +} +.zmdi-link:before { + content: '\f18e'; +} +.zmdi-lock-open:before { + content: '\f18f'; +} +.zmdi-lock-outline:before { + content: '\f190'; +} +.zmdi-lock:before { + content: '\f191'; +} +.zmdi-mail-reply-all:before { + content: '\f192'; +} +.zmdi-mail-reply:before { + content: '\f193'; +} +.zmdi-mail-send:before { + content: '\f194'; +} +.zmdi-mall:before { + content: '\f195'; +} +.zmdi-map:before { + content: '\f196'; +} +.zmdi-menu:before { + content: '\f197'; +} +.zmdi-money-box:before { + content: '\f198'; +} +.zmdi-money-off:before { + content: '\f199'; +} +.zmdi-money:before { + content: '\f19a'; +} +.zmdi-more-vert:before { + content: '\f19b'; +} +.zmdi-more:before { + content: '\f19c'; +} +.zmdi-movie-alt:before { + content: '\f19d'; +} +.zmdi-movie:before { + content: '\f19e'; +} +.zmdi-nature-people:before { + content: '\f19f'; +} +.zmdi-nature:before { + content: '\f1a0'; +} +.zmdi-navigation:before { + content: '\f1a1'; +} +.zmdi-open-in-browser:before { + content: '\f1a2'; +} +.zmdi-open-in-new:before { + content: '\f1a3'; +} +.zmdi-palette:before { + content: '\f1a4'; +} +.zmdi-parking:before { + content: '\f1a5'; +} +.zmdi-pin-account:before { + content: '\f1a6'; +} +.zmdi-pin-assistant:before { + content: '\f1a7'; +} +.zmdi-pin-drop:before { + content: '\f1a8'; +} +.zmdi-pin-help:before { + content: '\f1a9'; +} +.zmdi-pin-off:before { + content: '\f1aa'; +} +.zmdi-pin:before { + content: '\f1ab'; +} +.zmdi-pizza:before { + content: '\f1ac'; +} +.zmdi-plaster:before { + content: '\f1ad'; +} +.zmdi-power-setting:before { + content: '\f1ae'; +} +.zmdi-power:before { + content: '\f1af'; +} +.zmdi-print:before { + content: '\f1b0'; +} +.zmdi-puzzle-piece:before { + content: '\f1b1'; +} +.zmdi-quote:before { + content: '\f1b2'; +} +.zmdi-railway:before { + content: '\f1b3'; +} +.zmdi-receipt:before { + content: '\f1b4'; +} +.zmdi-refresh-alt:before { + content: '\f1b5'; +} +.zmdi-refresh-sync-alert:before { + content: '\f1b6'; +} +.zmdi-refresh-sync-off:before { + content: '\f1b7'; +} +.zmdi-refresh-sync:before { + content: '\f1b8'; +} +.zmdi-refresh:before { + content: '\f1b9'; +} +.zmdi-roller:before { + content: '\f1ba'; +} +.zmdi-ruler:before { + content: '\f1bb'; +} +.zmdi-scissors:before { + content: '\f1bc'; +} +.zmdi-screen-rotation-lock:before { + content: '\f1bd'; +} +.zmdi-screen-rotation:before { + content: '\f1be'; +} +.zmdi-search-for:before { + content: '\f1bf'; +} +.zmdi-search-in-file:before { + content: '\f1c0'; +} +.zmdi-search-in-page:before { + content: '\f1c1'; +} +.zmdi-search-replace:before { + content: '\f1c2'; +} +.zmdi-search:before { + content: '\f1c3'; +} +.zmdi-seat:before { + content: '\f1c4'; +} +.zmdi-settings-square:before { + content: '\f1c5'; +} +.zmdi-settings:before { + content: '\f1c6'; +} +.zmdi-shield-check:before { + content: '\f1c7'; +} +.zmdi-shield-security:before { + content: '\f1c8'; +} +.zmdi-shopping-basket:before { + content: '\f1c9'; +} +.zmdi-shopping-cart-plus:before { + content: '\f1ca'; +} +.zmdi-shopping-cart:before { + content: '\f1cb'; +} +.zmdi-sign-in:before { + content: '\f1cc'; +} +.zmdi-sort-amount-asc:before { + content: '\f1cd'; +} +.zmdi-sort-amount-desc:before { + content: '\f1ce'; +} +.zmdi-sort-asc:before { + content: '\f1cf'; +} +.zmdi-sort-desc:before { + content: '\f1d0'; +} +.zmdi-spellcheck:before { + content: '\f1d1'; +} +.zmdi-storage:before { + content: '\f1d2'; +} +.zmdi-store-24:before { + content: '\f1d3'; +} +.zmdi-store:before { + content: '\f1d4'; +} +.zmdi-subway:before { + content: '\f1d5'; +} +.zmdi-sun:before { + content: '\f1d6'; +} +.zmdi-tab-unselected:before { + content: '\f1d7'; +} +.zmdi-tab:before { + content: '\f1d8'; +} +.zmdi-tag-close:before { + content: '\f1d9'; +} +.zmdi-tag-more:before { + content: '\f1da'; +} +.zmdi-tag:before { + content: '\f1db'; +} +.zmdi-thumb-down:before { + content: '\f1dc'; +} +.zmdi-thumb-up-down:before { + content: '\f1dd'; +} +.zmdi-thumb-up:before { + content: '\f1de'; +} +.zmdi-ticket-star:before { + content: '\f1df'; +} +.zmdi-toll:before { + content: '\f1e0'; +} +.zmdi-toys:before { + content: '\f1e1'; +} +.zmdi-traffic:before { + content: '\f1e2'; +} +.zmdi-translate:before { + content: '\f1e3'; +} +.zmdi-triangle-down:before { + content: '\f1e4'; +} +.zmdi-triangle-up:before { + content: '\f1e5'; +} +.zmdi-truck:before { + content: '\f1e6'; +} +.zmdi-turning-sign:before { + content: '\f1e7'; +} +.zmdi-wallpaper:before { + content: '\f1e8'; +} +.zmdi-washing-machine:before { + content: '\f1e9'; +} +.zmdi-window-maximize:before { + content: '\f1ea'; +} +.zmdi-window-minimize:before { + content: '\f1eb'; +} +.zmdi-window-restore:before { + content: '\f1ec'; +} +.zmdi-wrench:before { + content: '\f1ed'; +} +.zmdi-zoom-in:before { + content: '\f1ee'; +} +.zmdi-zoom-out:before { + content: '\f1ef'; +} +.zmdi-alert-circle-o:before { + content: '\f1f0'; +} +.zmdi-alert-circle:before { + content: '\f1f1'; +} +.zmdi-alert-octagon:before { + content: '\f1f2'; +} +.zmdi-alert-polygon:before { + content: '\f1f3'; +} +.zmdi-alert-triangle:before { + content: '\f1f4'; +} +.zmdi-help-outline:before { + content: '\f1f5'; +} +.zmdi-help:before { + content: '\f1f6'; +} +.zmdi-info-outline:before { + content: '\f1f7'; +} +.zmdi-info:before { + content: '\f1f8'; +} +.zmdi-notifications-active:before { + content: '\f1f9'; +} +.zmdi-notifications-add:before { + content: '\f1fa'; +} +.zmdi-notifications-none:before { + content: '\f1fb'; +} +.zmdi-notifications-off:before { + content: '\f1fc'; +} +.zmdi-notifications-paused:before { + content: '\f1fd'; +} +.zmdi-notifications:before { + content: '\f1fe'; +} +.zmdi-account-add:before { + content: '\f1ff'; +} +.zmdi-account-box-mail:before { + content: '\f200'; +} +.zmdi-account-box-o:before { + content: '\f201'; +} +.zmdi-account-box-phone:before { + content: '\f202'; +} +.zmdi-account-box:before { + content: '\f203'; +} +.zmdi-account-calendar:before { + content: '\f204'; +} +.zmdi-account-circle:before { + content: '\f205'; +} +.zmdi-account-o:before { + content: '\f206'; +} +.zmdi-account:before { + content: '\f207'; +} +.zmdi-accounts-add:before { + content: '\f208'; +} +.zmdi-accounts-alt:before { + content: '\f209'; +} +.zmdi-accounts-list-alt:before { + content: '\f20a'; +} +.zmdi-accounts-list:before { + content: '\f20b'; +} +.zmdi-accounts-outline:before { + content: '\f20c'; +} +.zmdi-accounts:before { + content: '\f20d'; +} +.zmdi-face:before { + content: '\f20e'; +} +.zmdi-female:before { + content: '\f20f'; +} +.zmdi-male-alt:before { + content: '\f210'; +} +.zmdi-male-female:before { + content: '\f211'; +} +.zmdi-male:before { + content: '\f212'; +} +.zmdi-mood-bad:before { + content: '\f213'; +} +.zmdi-mood:before { + content: '\f214'; +} +.zmdi-run:before { + content: '\f215'; +} +.zmdi-walk:before { + content: '\f216'; +} +.zmdi-cloud-box:before { + content: '\f217'; +} +.zmdi-cloud-circle:before { + content: '\f218'; +} +.zmdi-cloud-done:before { + content: '\f219'; +} +.zmdi-cloud-download:before { + content: '\f21a'; +} +.zmdi-cloud-off:before { + content: '\f21b'; +} +.zmdi-cloud-outline-alt:before { + content: '\f21c'; +} +.zmdi-cloud-outline:before { + content: '\f21d'; +} +.zmdi-cloud-upload:before { + content: '\f21e'; +} +.zmdi-cloud:before { + content: '\f21f'; +} +.zmdi-download:before { + content: '\f220'; +} +.zmdi-file-plus:before { + content: '\f221'; +} +.zmdi-file-text:before { + content: '\f222'; +} +.zmdi-file:before { + content: '\f223'; +} +.zmdi-folder-outline:before { + content: '\f224'; +} +.zmdi-folder-person:before { + content: '\f225'; +} +.zmdi-folder-star-alt:before { + content: '\f226'; +} +.zmdi-folder-star:before { + content: '\f227'; +} +.zmdi-folder:before { + content: '\f228'; +} +.zmdi-gif:before { + content: '\f229'; +} +.zmdi-upload:before { + content: '\f22a'; +} +.zmdi-border-all:before { + content: '\f22b'; +} +.zmdi-border-bottom:before { + content: '\f22c'; +} +.zmdi-border-clear:before { + content: '\f22d'; +} +.zmdi-border-color:before { + content: '\f22e'; +} +.zmdi-border-horizontal:before { + content: '\f22f'; +} +.zmdi-border-inner:before { + content: '\f230'; +} +.zmdi-border-left:before { + content: '\f231'; +} +.zmdi-border-outer:before { + content: '\f232'; +} +.zmdi-border-right:before { + content: '\f233'; +} +.zmdi-border-style:before { + content: '\f234'; +} +.zmdi-border-top:before { + content: '\f235'; +} +.zmdi-border-vertical:before { + content: '\f236'; +} +.zmdi-copy:before { + content: '\f237'; +} +.zmdi-crop:before { + content: '\f238'; +} +.zmdi-format-align-center:before { + content: '\f239'; +} +.zmdi-format-align-justify:before { + content: '\f23a'; +} +.zmdi-format-align-left:before { + content: '\f23b'; +} +.zmdi-format-align-right:before { + content: '\f23c'; +} +.zmdi-format-bold:before { + content: '\f23d'; +} +.zmdi-format-clear-all:before { + content: '\f23e'; +} +.zmdi-format-clear:before { + content: '\f23f'; +} +.zmdi-format-color-fill:before { + content: '\f240'; +} +.zmdi-format-color-reset:before { + content: '\f241'; +} +.zmdi-format-color-text:before { + content: '\f242'; +} +.zmdi-format-indent-decrease:before { + content: '\f243'; +} +.zmdi-format-indent-increase:before { + content: '\f244'; +} +.zmdi-format-italic:before { + content: '\f245'; +} +.zmdi-format-line-spacing:before { + content: '\f246'; +} +.zmdi-format-list-bulleted:before { + content: '\f247'; +} +.zmdi-format-list-numbered:before { + content: '\f248'; +} +.zmdi-format-ltr:before { + content: '\f249'; +} +.zmdi-format-rtl:before { + content: '\f24a'; +} +.zmdi-format-size:before { + content: '\f24b'; +} +.zmdi-format-strikethrough-s:before { + content: '\f24c'; +} +.zmdi-format-strikethrough:before { + content: '\f24d'; +} +.zmdi-format-subject:before { + content: '\f24e'; +} +.zmdi-format-underlined:before { + content: '\f24f'; +} +.zmdi-format-valign-bottom:before { + content: '\f250'; +} +.zmdi-format-valign-center:before { + content: '\f251'; +} +.zmdi-format-valign-top:before { + content: '\f252'; +} +.zmdi-redo:before { + content: '\f253'; +} +.zmdi-select-all:before { + content: '\f254'; +} +.zmdi-space-bar:before { + content: '\f255'; +} +.zmdi-text-format:before { + content: '\f256'; +} +.zmdi-transform:before { + content: '\f257'; +} +.zmdi-undo:before { + content: '\f258'; +} +.zmdi-wrap-text:before { + content: '\f259'; +} +.zmdi-comment-alert:before { + content: '\f25a'; +} +.zmdi-comment-alt-text:before { + content: '\f25b'; +} +.zmdi-comment-alt:before { + content: '\f25c'; +} +.zmdi-comment-edit:before { + content: '\f25d'; +} +.zmdi-comment-image:before { + content: '\f25e'; +} +.zmdi-comment-list:before { + content: '\f25f'; +} +.zmdi-comment-more:before { + content: '\f260'; +} +.zmdi-comment-outline:before { + content: '\f261'; +} +.zmdi-comment-text-alt:before { + content: '\f262'; +} +.zmdi-comment-text:before { + content: '\f263'; +} +.zmdi-comment-video:before { + content: '\f264'; +} +.zmdi-comment:before { + content: '\f265'; +} +.zmdi-comments:before { + content: '\f266'; +} +.zmdi-check-all:before { + content: '\f267'; +} +.zmdi-check-circle-u:before { + content: '\f268'; +} +.zmdi-check-circle:before { + content: '\f269'; +} +.zmdi-check-square:before { + content: '\f26a'; +} +.zmdi-check:before { + content: '\f26b'; +} +.zmdi-circle-o:before { + content: '\f26c'; +} +.zmdi-circle:before { + content: '\f26d'; +} +.zmdi-dot-circle-alt:before { + content: '\f26e'; +} +.zmdi-dot-circle:before { + content: '\f26f'; +} +.zmdi-minus-circle-outline:before { + content: '\f270'; +} +.zmdi-minus-circle:before { + content: '\f271'; +} +.zmdi-minus-square:before { + content: '\f272'; +} +.zmdi-minus:before { + content: '\f273'; +} +.zmdi-plus-circle-o-duplicate:before { + content: '\f274'; +} +.zmdi-plus-circle-o:before { + content: '\f275'; +} +.zmdi-plus-circle:before { + content: '\f276'; +} +.zmdi-plus-square:before { + content: '\f277'; +} +.zmdi-plus:before { + content: '\f278'; +} +.zmdi-square-o:before { + content: '\f279'; +} +.zmdi-star-circle:before { + content: '\f27a'; +} +.zmdi-star-half:before { + content: '\f27b'; +} +.zmdi-star-outline:before { + content: '\f27c'; +} +.zmdi-star:before { + content: '\f27d'; +} +.zmdi-bluetooth-connected:before { + content: '\f27e'; +} +.zmdi-bluetooth-off:before { + content: '\f27f'; +} +.zmdi-bluetooth-search:before { + content: '\f280'; +} +.zmdi-bluetooth-setting:before { + content: '\f281'; +} +.zmdi-bluetooth:before { + content: '\f282'; +} +.zmdi-camera-add:before { + content: '\f283'; +} +.zmdi-camera-alt:before { + content: '\f284'; +} +.zmdi-camera-bw:before { + content: '\f285'; +} +.zmdi-camera-front:before { + content: '\f286'; +} +.zmdi-camera-mic:before { + content: '\f287'; +} +.zmdi-camera-party-mode:before { + content: '\f288'; +} +.zmdi-camera-rear:before { + content: '\f289'; +} +.zmdi-camera-roll:before { + content: '\f28a'; +} +.zmdi-camera-switch:before { + content: '\f28b'; +} +.zmdi-camera:before { + content: '\f28c'; +} +.zmdi-card-alert:before { + content: '\f28d'; +} +.zmdi-card-off:before { + content: '\f28e'; +} +.zmdi-card-sd:before { + content: '\f28f'; +} +.zmdi-card-sim:before { + content: '\f290'; +} +.zmdi-desktop-mac:before { + content: '\f291'; +} +.zmdi-desktop-windows:before { + content: '\f292'; +} +.zmdi-device-hub:before { + content: '\f293'; +} +.zmdi-devices-off:before { + content: '\f294'; +} +.zmdi-devices:before { + content: '\f295'; +} +.zmdi-dock:before { + content: '\f296'; +} +.zmdi-floppy:before { + content: '\f297'; +} +.zmdi-gamepad:before { + content: '\f298'; +} +.zmdi-gps-dot:before { + content: '\f299'; +} +.zmdi-gps-off:before { + content: '\f29a'; +} +.zmdi-gps:before { + content: '\f29b'; +} +.zmdi-headset-mic:before { + content: '\f29c'; +} +.zmdi-headset:before { + content: '\f29d'; +} +.zmdi-input-antenna:before { + content: '\f29e'; +} +.zmdi-input-composite:before { + content: '\f29f'; +} +.zmdi-input-hdmi:before { + content: '\f2a0'; +} +.zmdi-input-power:before { + content: '\f2a1'; +} +.zmdi-input-svideo:before { + content: '\f2a2'; +} +.zmdi-keyboard-hide:before { + content: '\f2a3'; +} +.zmdi-keyboard:before { + content: '\f2a4'; +} +.zmdi-laptop-chromebook:before { + content: '\f2a5'; +} +.zmdi-laptop-mac:before { + content: '\f2a6'; +} +.zmdi-laptop:before { + content: '\f2a7'; +} +.zmdi-mic-off:before { + content: '\f2a8'; +} +.zmdi-mic-outline:before { + content: '\f2a9'; +} +.zmdi-mic-setting:before { + content: '\f2aa'; +} +.zmdi-mic:before { + content: '\f2ab'; +} +.zmdi-mouse:before { + content: '\f2ac'; +} +.zmdi-network-alert:before { + content: '\f2ad'; +} +.zmdi-network-locked:before { + content: '\f2ae'; +} +.zmdi-network-off:before { + content: '\f2af'; +} +.zmdi-network-outline:before { + content: '\f2b0'; +} +.zmdi-network-setting:before { + content: '\f2b1'; +} +.zmdi-network:before { + content: '\f2b2'; +} +.zmdi-phone-bluetooth:before { + content: '\f2b3'; +} +.zmdi-phone-end:before { + content: '\f2b4'; +} +.zmdi-phone-forwarded:before { + content: '\f2b5'; +} +.zmdi-phone-in-talk:before { + content: '\f2b6'; +} +.zmdi-phone-locked:before { + content: '\f2b7'; +} +.zmdi-phone-missed:before { + content: '\f2b8'; +} +.zmdi-phone-msg:before { + content: '\f2b9'; +} +.zmdi-phone-paused:before { + content: '\f2ba'; +} +.zmdi-phone-ring:before { + content: '\f2bb'; +} +.zmdi-phone-setting:before { + content: '\f2bc'; +} +.zmdi-phone-sip:before { + content: '\f2bd'; +} +.zmdi-phone:before { + content: '\f2be'; +} +.zmdi-portable-wifi-changes:before { + content: '\f2bf'; +} +.zmdi-portable-wifi-off:before { + content: '\f2c0'; +} +.zmdi-portable-wifi:before { + content: '\f2c1'; +} +.zmdi-radio:before { + content: '\f2c2'; +} +.zmdi-reader:before { + content: '\f2c3'; +} +.zmdi-remote-control-alt:before { + content: '\f2c4'; +} +.zmdi-remote-control:before { + content: '\f2c5'; +} +.zmdi-router:before { + content: '\f2c6'; +} +.zmdi-scanner:before { + content: '\f2c7'; +} +.zmdi-smartphone-android:before { + content: '\f2c8'; +} +.zmdi-smartphone-download:before { + content: '\f2c9'; +} +.zmdi-smartphone-erase:before { + content: '\f2ca'; +} +.zmdi-smartphone-info:before { + content: '\f2cb'; +} +.zmdi-smartphone-iphone:before { + content: '\f2cc'; +} +.zmdi-smartphone-landscape-lock:before { + content: '\f2cd'; +} +.zmdi-smartphone-landscape:before { + content: '\f2ce'; +} +.zmdi-smartphone-lock:before { + content: '\f2cf'; +} +.zmdi-smartphone-portrait-lock:before { + content: '\f2d0'; +} +.zmdi-smartphone-ring:before { + content: '\f2d1'; +} +.zmdi-smartphone-setting:before { + content: '\f2d2'; +} +.zmdi-smartphone-setup:before { + content: '\f2d3'; +} +.zmdi-smartphone:before { + content: '\f2d4'; +} +.zmdi-speaker:before { + content: '\f2d5'; +} +.zmdi-tablet-android:before { + content: '\f2d6'; +} +.zmdi-tablet-mac:before { + content: '\f2d7'; +} +.zmdi-tablet:before { + content: '\f2d8'; +} +.zmdi-tv-alt-play:before { + content: '\f2d9'; +} +.zmdi-tv-list:before { + content: '\f2da'; +} +.zmdi-tv-play:before { + content: '\f2db'; +} +.zmdi-tv:before { + content: '\f2dc'; +} +.zmdi-usb:before { + content: '\f2dd'; +} +.zmdi-videocam-off:before { + content: '\f2de'; +} +.zmdi-videocam-switch:before { + content: '\f2df'; +} +.zmdi-videocam:before { + content: '\f2e0'; +} +.zmdi-watch:before { + content: '\f2e1'; +} +.zmdi-wifi-alt-2:before { + content: '\f2e2'; +} +.zmdi-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-wifi-info:before { + content: '\f2e4'; +} +.zmdi-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-wifi-off:before { + content: '\f2e6'; +} +.zmdi-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-wifi:before { + content: '\f2e8'; +} +.zmdi-arrow-left-bottom:before { + content: '\f2e9'; +} +.zmdi-arrow-left:before { + content: '\f2ea'; +} +.zmdi-arrow-merge:before { + content: '\f2eb'; +} +.zmdi-arrow-missed:before { + content: '\f2ec'; +} +.zmdi-arrow-right-top:before { + content: '\f2ed'; +} +.zmdi-arrow-right:before { + content: '\f2ee'; +} +.zmdi-arrow-split:before { + content: '\f2ef'; +} +.zmdi-arrows:before { + content: '\f2f0'; +} +.zmdi-caret-down-circle:before { + content: '\f2f1'; +} +.zmdi-caret-down:before { + content: '\f2f2'; +} +.zmdi-caret-left-circle:before { + content: '\f2f3'; +} +.zmdi-caret-left:before { + content: '\f2f4'; +} +.zmdi-caret-right-circle:before { + content: '\f2f5'; +} +.zmdi-caret-right:before { + content: '\f2f6'; +} +.zmdi-caret-up-circle:before { + content: '\f2f7'; +} +.zmdi-caret-up:before { + content: '\f2f8'; +} +.zmdi-chevron-down:before { + content: '\f2f9'; +} +.zmdi-chevron-left:before { + content: '\f2fa'; +} +.zmdi-chevron-right:before { + content: '\f2fb'; +} +.zmdi-chevron-up:before { + content: '\f2fc'; +} +.zmdi-forward:before { + content: '\f2fd'; +} +.zmdi-long-arrow-down:before { + content: '\f2fe'; +} +.zmdi-long-arrow-left:before { + content: '\f2ff'; +} +.zmdi-long-arrow-return:before { + content: '\f300'; +} +.zmdi-long-arrow-right:before { + content: '\f301'; +} +.zmdi-long-arrow-tab:before { + content: '\f302'; +} +.zmdi-long-arrow-up:before { + content: '\f303'; +} +.zmdi-rotate-ccw:before { + content: '\f304'; +} +.zmdi-rotate-cw:before { + content: '\f305'; +} +.zmdi-rotate-left:before { + content: '\f306'; +} +.zmdi-rotate-right:before { + content: '\f307'; +} +.zmdi-square-down:before { + content: '\f308'; +} +.zmdi-square-right:before { + content: '\f309'; +} +.zmdi-swap-alt:before { + content: '\f30a'; +} +.zmdi-swap-vertical-circle:before { + content: '\f30b'; +} +.zmdi-swap-vertical:before { + content: '\f30c'; +} +.zmdi-swap:before { + content: '\f30d'; +} +.zmdi-trending-down:before { + content: '\f30e'; +} +.zmdi-trending-flat:before { + content: '\f30f'; +} +.zmdi-trending-up:before { + content: '\f310'; +} +.zmdi-unfold-less:before { + content: '\f311'; +} +.zmdi-unfold-more:before { + content: '\f312'; +} +.zmdi-apps:before { + content: '\f313'; +} +.zmdi-grid-off:before { + content: '\f314'; +} +.zmdi-grid:before { + content: '\f315'; +} +.zmdi-view-agenda:before { + content: '\f316'; +} +.zmdi-view-array:before { + content: '\f317'; +} +.zmdi-view-carousel:before { + content: '\f318'; +} +.zmdi-view-column:before { + content: '\f319'; +} +.zmdi-view-comfy:before { + content: '\f31a'; +} +.zmdi-view-compact:before { + content: '\f31b'; +} +.zmdi-view-dashboard:before { + content: '\f31c'; +} +.zmdi-view-day:before { + content: '\f31d'; +} +.zmdi-view-headline:before { + content: '\f31e'; +} +.zmdi-view-list-alt:before { + content: '\f31f'; +} +.zmdi-view-list:before { + content: '\f320'; +} +.zmdi-view-module:before { + content: '\f321'; +} +.zmdi-view-quilt:before { + content: '\f322'; +} +.zmdi-view-stream:before { + content: '\f323'; +} +.zmdi-view-subtitles:before { + content: '\f324'; +} +.zmdi-view-toc:before { + content: '\f325'; +} +.zmdi-view-web:before { + content: '\f326'; +} +.zmdi-view-week:before { + content: '\f327'; +} +.zmdi-widgets:before { + content: '\f328'; +} +.zmdi-alarm-check:before { + content: '\f329'; +} +.zmdi-alarm-off:before { + content: '\f32a'; +} +.zmdi-alarm-plus:before { + content: '\f32b'; +} +.zmdi-alarm-snooze:before { + content: '\f32c'; +} +.zmdi-alarm:before { + content: '\f32d'; +} +.zmdi-calendar-alt:before { + content: '\f32e'; +} +.zmdi-calendar-check:before { + content: '\f32f'; +} +.zmdi-calendar-close:before { + content: '\f330'; +} +.zmdi-calendar-note:before { + content: '\f331'; +} +.zmdi-calendar:before { + content: '\f332'; +} +.zmdi-time-countdown:before { + content: '\f333'; +} +.zmdi-time-interval:before { + content: '\f334'; +} +.zmdi-time-restore-setting:before { + content: '\f335'; +} +.zmdi-time-restore:before { + content: '\f336'; +} +.zmdi-time:before { + content: '\f337'; +} +.zmdi-timer-off:before { + content: '\f338'; +} +.zmdi-timer:before { + content: '\f339'; +} +.zmdi-android-alt:before { + content: '\f33a'; +} +.zmdi-android:before { + content: '\f33b'; +} +.zmdi-apple:before { + content: '\f33c'; +} +.zmdi-behance:before { + content: '\f33d'; +} +.zmdi-codepen:before { + content: '\f33e'; +} +.zmdi-dribbble:before { + content: '\f33f'; +} +.zmdi-dropbox:before { + content: '\f340'; +} +.zmdi-evernote:before { + content: '\f341'; +} +.zmdi-facebook-box:before { + content: '\f342'; +} +.zmdi-facebook:before { + content: '\f343'; +} +.zmdi-github-box:before { + content: '\f344'; +} +.zmdi-github:before { + content: '\f345'; +} +.zmdi-google-drive:before { + content: '\f346'; +} +.zmdi-google-earth:before { + content: '\f347'; +} +.zmdi-google-glass:before { + content: '\f348'; +} +.zmdi-google-maps:before { + content: '\f349'; +} +.zmdi-google-pages:before { + content: '\f34a'; +} +.zmdi-google-play:before { + content: '\f34b'; +} +.zmdi-google-plus-box:before { + content: '\f34c'; +} +.zmdi-google-plus:before { + content: '\f34d'; +} +.zmdi-google:before { + content: '\f34e'; +} +.zmdi-instagram:before { + content: '\f34f'; +} +.zmdi-language-css3:before { + content: '\f350'; +} +.zmdi-language-html5:before { + content: '\f351'; +} +.zmdi-language-javascript:before { + content: '\f352'; +} +.zmdi-language-python-alt:before { + content: '\f353'; +} +.zmdi-language-python:before { + content: '\f354'; +} +.zmdi-lastfm:before { + content: '\f355'; +} +.zmdi-linkedin-box:before { + content: '\f356'; +} +.zmdi-paypal:before { + content: '\f357'; +} +.zmdi-pinterest-box:before { + content: '\f358'; +} +.zmdi-pocket:before { + content: '\f359'; +} +.zmdi-polymer:before { + content: '\f35a'; +} +.zmdi-share:before { + content: '\f35b'; +} +.zmdi-stackoverflow:before { + content: '\f35c'; +} +.zmdi-steam-square:before { + content: '\f35d'; +} +.zmdi-steam:before { + content: '\f35e'; +} +.zmdi-twitter-box:before { + content: '\f35f'; +} +.zmdi-twitter:before { + content: '\f360'; +} +.zmdi-vk:before { + content: '\f361'; +} +.zmdi-wikipedia:before { + content: '\f362'; +} +.zmdi-windows:before { + content: '\f363'; +} +.zmdi-aspect-ratio-alt:before { + content: '\f364'; +} +.zmdi-aspect-ratio:before { + content: '\f365'; +} +.zmdi-blur-circular:before { + content: '\f366'; +} +.zmdi-blur-linear:before { + content: '\f367'; +} +.zmdi-blur-off:before { + content: '\f368'; +} +.zmdi-blur:before { + content: '\f369'; +} +.zmdi-brightness-2:before { + content: '\f36a'; +} +.zmdi-brightness-3:before { + content: '\f36b'; +} +.zmdi-brightness-4:before { + content: '\f36c'; +} +.zmdi-brightness-5:before { + content: '\f36d'; +} +.zmdi-brightness-6:before { + content: '\f36e'; +} +.zmdi-brightness-7:before { + content: '\f36f'; +} +.zmdi-brightness-auto:before { + content: '\f370'; +} +.zmdi-brightness-setting:before { + content: '\f371'; +} +.zmdi-broken-image:before { + content: '\f372'; +} +.zmdi-center-focus-strong:before { + content: '\f373'; +} +.zmdi-center-focus-weak:before { + content: '\f374'; +} +.zmdi-compare:before { + content: '\f375'; +} +.zmdi-crop-16-9:before { + content: '\f376'; +} +.zmdi-crop-3-2:before { + content: '\f377'; +} +.zmdi-crop-5-4:before { + content: '\f378'; +} +.zmdi-crop-7-5:before { + content: '\f379'; +} +.zmdi-crop-din:before { + content: '\f37a'; +} +.zmdi-crop-free:before { + content: '\f37b'; +} +.zmdi-crop-landscape:before { + content: '\f37c'; +} +.zmdi-crop-portrait:before { + content: '\f37d'; +} +.zmdi-crop-square:before { + content: '\f37e'; +} +.zmdi-exposure-alt:before { + content: '\f37f'; +} +.zmdi-exposure:before { + content: '\f380'; +} +.zmdi-filter-b-and-w:before { + content: '\f381'; +} +.zmdi-filter-center-focus:before { + content: '\f382'; +} +.zmdi-filter-frames:before { + content: '\f383'; +} +.zmdi-filter-tilt-shift:before { + content: '\f384'; +} +.zmdi-gradient:before { + content: '\f385'; +} +.zmdi-grain:before { + content: '\f386'; +} +.zmdi-graphic-eq:before { + content: '\f387'; +} +.zmdi-hdr-off:before { + content: '\f388'; +} +.zmdi-hdr-strong:before { + content: '\f389'; +} +.zmdi-hdr-weak:before { + content: '\f38a'; +} +.zmdi-hdr:before { + content: '\f38b'; +} +.zmdi-iridescent:before { + content: '\f38c'; +} +.zmdi-leak-off:before { + content: '\f38d'; +} +.zmdi-leak:before { + content: '\f38e'; +} +.zmdi-looks:before { + content: '\f38f'; +} +.zmdi-loupe:before { + content: '\f390'; +} +.zmdi-panorama-horizontal:before { + content: '\f391'; +} +.zmdi-panorama-vertical:before { + content: '\f392'; +} +.zmdi-panorama-wide-angle:before { + content: '\f393'; +} +.zmdi-photo-size-select-large:before { + content: '\f394'; +} +.zmdi-photo-size-select-small:before { + content: '\f395'; +} +.zmdi-picture-in-picture:before { + content: '\f396'; +} +.zmdi-slideshow:before { + content: '\f397'; +} +.zmdi-texture:before { + content: '\f398'; +} +.zmdi-tonality:before { + content: '\f399'; +} +.zmdi-vignette:before { + content: '\f39a'; +} +.zmdi-wb-auto:before { + content: '\f39b'; +} +.zmdi-eject-alt:before { + content: '\f39c'; +} +.zmdi-eject:before { + content: '\f39d'; +} +.zmdi-equalizer:before { + content: '\f39e'; +} +.zmdi-fast-forward:before { + content: '\f39f'; +} +.zmdi-fast-rewind:before { + content: '\f3a0'; +} +.zmdi-forward-10:before { + content: '\f3a1'; +} +.zmdi-forward-30:before { + content: '\f3a2'; +} +.zmdi-forward-5:before { + content: '\f3a3'; +} +.zmdi-hearing:before { + content: '\f3a4'; +} +.zmdi-pause-circle-outline:before { + content: '\f3a5'; +} +.zmdi-pause-circle:before { + content: '\f3a6'; +} +.zmdi-pause:before { + content: '\f3a7'; +} +.zmdi-play-circle-outline:before { + content: '\f3a8'; +} +.zmdi-play-circle:before { + content: '\f3a9'; +} +.zmdi-play:before { + content: '\f3aa'; +} +.zmdi-playlist-audio:before { + content: '\f3ab'; +} +.zmdi-playlist-plus:before { + content: '\f3ac'; +} +.zmdi-repeat-one:before { + content: '\f3ad'; +} +.zmdi-repeat:before { + content: '\f3ae'; +} +.zmdi-replay-10:before { + content: '\f3af'; +} +.zmdi-replay-30:before { + content: '\f3b0'; +} +.zmdi-replay-5:before { + content: '\f3b1'; +} +.zmdi-replay:before { + content: '\f3b2'; +} +.zmdi-shuffle:before { + content: '\f3b3'; +} +.zmdi-skip-next:before { + content: '\f3b4'; +} +.zmdi-skip-previous:before { + content: '\f3b5'; +} +.zmdi-stop:before { + content: '\f3b6'; +} +.zmdi-surround-sound:before { + content: '\f3b7'; +} +.zmdi-tune:before { + content: '\f3b8'; +} +.zmdi-volume-down:before { + content: '\f3b9'; +} +.zmdi-volume-mute:before { + content: '\f3ba'; +} +.zmdi-volume-off:before { + content: '\f3bb'; +} +.zmdi-volume-up:before { + content: '\f3bc'; +} +.zmdi-n-1-square:before { + content: '\f3bd'; +} +.zmdi-n-2-square:before { + content: '\f3be'; +} +.zmdi-n-3-square:before { + content: '\f3bf'; +} +.zmdi-n-4-square:before { + content: '\f3c0'; +} +.zmdi-n-5-square:before { + content: '\f3c1'; +} +.zmdi-n-6-square:before { + content: '\f3c2'; +} +.zmdi-neg-1:before { + content: '\f3c3'; +} +.zmdi-neg-2:before { + content: '\f3c4'; +} +.zmdi-plus-1:before { + content: '\f3c5'; +} +.zmdi-plus-2:before { + content: '\f3c6'; +} +.zmdi-sec-10:before { + content: '\f3c7'; +} +.zmdi-sec-3:before { + content: '\f3c8'; +} +.zmdi-zero:before { + content: '\f3c9'; +} +.zmdi-airline-seat-flat-angled:before { + content: '\f3ca'; +} +.zmdi-airline-seat-flat:before { + content: '\f3cb'; +} +.zmdi-airline-seat-individual-suite:before { + content: '\f3cc'; +} +.zmdi-airline-seat-legroom-extra:before { + content: '\f3cd'; +} +.zmdi-airline-seat-legroom-normal:before { + content: '\f3ce'; +} +.zmdi-airline-seat-legroom-reduced:before { + content: '\f3cf'; +} +.zmdi-airline-seat-recline-extra:before { + content: '\f3d0'; +} +.zmdi-airline-seat-recline-normal:before { + content: '\f3d1'; +} +.zmdi-airplay:before { + content: '\f3d2'; +} +.zmdi-closed-caption:before { + content: '\f3d3'; +} +.zmdi-confirmation-number:before { + content: '\f3d4'; +} +.zmdi-developer-board:before { + content: '\f3d5'; +} +.zmdi-disc-full:before { + content: '\f3d6'; +} +.zmdi-explicit:before { + content: '\f3d7'; +} +.zmdi-flight-land:before { + content: '\f3d8'; +} +.zmdi-flight-takeoff:before { + content: '\f3d9'; +} +.zmdi-flip-to-back:before { + content: '\f3da'; +} +.zmdi-flip-to-front:before { + content: '\f3db'; +} +.zmdi-group-work:before { + content: '\f3dc'; +} +.zmdi-hd:before { + content: '\f3dd'; +} +.zmdi-hq:before { + content: '\f3de'; +} +.zmdi-markunread-mailbox:before { + content: '\f3df'; +} +.zmdi-memory:before { + content: '\f3e0'; +} +.zmdi-nfc:before { + content: '\f3e1'; +} +.zmdi-play-for-work:before { + content: '\f3e2'; +} +.zmdi-power-input:before { + content: '\f3e3'; +} +.zmdi-present-to-all:before { + content: '\f3e4'; +} +.zmdi-satellite:before { + content: '\f3e5'; +} +.zmdi-tap-and-play:before { + content: '\f3e6'; +} +.zmdi-vibration:before { + content: '\f3e7'; +} +.zmdi-voicemail:before { + content: '\f3e8'; +} +.zmdi-group:before { + content: '\f3e9'; +} +.zmdi-rss:before { + content: '\f3ea'; +} +.zmdi-shape:before { + content: '\f3eb'; +} +.zmdi-spinner:before { + content: '\f3ec'; +} +.zmdi-ungroup:before { + content: '\f3ed'; +} +.zmdi-500px:before { + content: '\f3ee'; +} +.zmdi-8tracks:before { + content: '\f3ef'; +} +.zmdi-amazon:before { + content: '\f3f0'; +} +.zmdi-blogger:before { + content: '\f3f1'; +} +.zmdi-delicious:before { + content: '\f3f2'; +} +.zmdi-disqus:before { + content: '\f3f3'; +} +.zmdi-flattr:before { + content: '\f3f4'; +} +.zmdi-flickr:before { + content: '\f3f5'; +} +.zmdi-github-alt:before { + content: '\f3f6'; +} +.zmdi-google-old:before { + content: '\f3f7'; +} +.zmdi-linkedin:before { + content: '\f3f8'; +} +.zmdi-odnoklassniki:before { + content: '\f3f9'; +} +.zmdi-outlook:before { + content: '\f3fa'; +} +.zmdi-paypal-alt:before { + content: '\f3fb'; +} +.zmdi-pinterest:before { + content: '\f3fc'; +} +.zmdi-playstation:before { + content: '\f3fd'; +} +.zmdi-reddit:before { + content: '\f3fe'; +} +.zmdi-skype:before { + content: '\f3ff'; +} +.zmdi-slideshare:before { + content: '\f400'; +} +.zmdi-soundcloud:before { + content: '\f401'; +} +.zmdi-tumblr:before { + content: '\f402'; +} +.zmdi-twitch:before { + content: '\f403'; +} +.zmdi-vimeo:before { + content: '\f404'; +} +.zmdi-whatsapp:before { + content: '\f405'; +} +.zmdi-xbox:before { + content: '\f406'; +} +.zmdi-yahoo:before { + content: '\f407'; +} +.zmdi-youtube-play:before { + content: '\f408'; +} +.zmdi-youtube:before { + content: '\f409'; +} +.zmdi-3d-rotation:before { + content: '\f101'; +} +.zmdi-airplane-off:before { + content: '\f102'; +} +.zmdi-airplane:before { + content: '\f103'; +} +.zmdi-album:before { + content: '\f104'; +} +.zmdi-archive:before { + content: '\f105'; +} +.zmdi-assignment-account:before { + content: '\f106'; +} +.zmdi-assignment-alert:before { + content: '\f107'; +} +.zmdi-assignment-check:before { + content: '\f108'; +} +.zmdi-assignment-o:before { + content: '\f109'; +} +.zmdi-assignment-return:before { + content: '\f10a'; +} +.zmdi-assignment-returned:before { + content: '\f10b'; +} +.zmdi-assignment:before { + content: '\f10c'; +} +.zmdi-attachment-alt:before { + content: '\f10d'; +} +.zmdi-attachment:before { + content: '\f10e'; +} +.zmdi-audio:before { + content: '\f10f'; +} +.zmdi-badge-check:before { + content: '\f110'; +} +.zmdi-balance-wallet:before { + content: '\f111'; +} +.zmdi-balance:before { + content: '\f112'; +} +.zmdi-battery-alert:before { + content: '\f113'; +} +.zmdi-battery-flash:before { + content: '\f114'; +} +.zmdi-battery-unknown:before { + content: '\f115'; +} +.zmdi-battery:before { + content: '\f116'; +} +.zmdi-bike:before { + content: '\f117'; +} +.zmdi-block-alt:before { + content: '\f118'; +} +.zmdi-block:before { + content: '\f119'; +} +.zmdi-boat:before { + content: '\f11a'; +} +.zmdi-book-image:before { + content: '\f11b'; +} +.zmdi-book:before { + content: '\f11c'; +} +.zmdi-bookmark-outline:before { + content: '\f11d'; +} +.zmdi-bookmark:before { + content: '\f11e'; +} +.zmdi-brush:before { + content: '\f11f'; +} +.zmdi-bug:before { + content: '\f120'; +} +.zmdi-bus:before { + content: '\f121'; +} +.zmdi-cake:before { + content: '\f122'; +} +.zmdi-car-taxi:before { + content: '\f123'; +} +.zmdi-car-wash:before { + content: '\f124'; +} +.zmdi-car:before { + content: '\f125'; +} +.zmdi-card-giftcard:before { + content: '\f126'; +} +.zmdi-card-membership:before { + content: '\f127'; +} +.zmdi-card-travel:before { + content: '\f128'; +} +.zmdi-card:before { + content: '\f129'; +} +.zmdi-case-check:before { + content: '\f12a'; +} +.zmdi-case-download:before { + content: '\f12b'; +} +.zmdi-case-play:before { + content: '\f12c'; +} +.zmdi-case:before { + content: '\f12d'; +} +.zmdi-cast-connected:before { + content: '\f12e'; +} +.zmdi-cast:before { + content: '\f12f'; +} +.zmdi-chart-donut:before { + content: '\f130'; +} +.zmdi-chart:before { + content: '\f131'; +} +.zmdi-city-alt:before { + content: '\f132'; +} +.zmdi-city:before { + content: '\f133'; +} +.zmdi-close-circle-o:before { + content: '\f134'; +} +.zmdi-close-circle:before { + content: '\f135'; +} +.zmdi-close:before { + content: '\f136'; +} +.zmdi-cocktail:before { + content: '\f137'; +} +.zmdi-code-setting:before { + content: '\f138'; +} +.zmdi-code-smartphone:before { + content: '\f139'; +} +.zmdi-code:before { + content: '\f13a'; +} +.zmdi-coffee:before { + content: '\f13b'; +} +.zmdi-collection-bookmark:before { + content: '\f13c'; +} +.zmdi-collection-case-play:before { + content: '\f13d'; +} +.zmdi-collection-folder-image:before { + content: '\f13e'; +} +.zmdi-collection-image-o:before { + content: '\f13f'; +} +.zmdi-collection-image:before { + content: '\f140'; +} +.zmdi-collection-item-1:before { + content: '\f141'; +} +.zmdi-collection-item-2:before { + content: '\f142'; +} +.zmdi-collection-item-3:before { + content: '\f143'; +} +.zmdi-collection-item-4:before { + content: '\f144'; +} +.zmdi-collection-item-5:before { + content: '\f145'; +} +.zmdi-collection-item-6:before { + content: '\f146'; +} +.zmdi-collection-item-7:before { + content: '\f147'; +} +.zmdi-collection-item-8:before { + content: '\f148'; +} +.zmdi-collection-item-9-plus:before { + content: '\f149'; +} +.zmdi-collection-item-9:before { + content: '\f14a'; +} +.zmdi-collection-item:before { + content: '\f14b'; +} +.zmdi-collection-music:before { + content: '\f14c'; +} +.zmdi-collection-pdf:before { + content: '\f14d'; +} +.zmdi-collection-plus:before { + content: '\f14e'; +} +.zmdi-collection-speaker:before { + content: '\f14f'; +} +.zmdi-collection-text:before { + content: '\f150'; +} +.zmdi-collection-video:before { + content: '\f151'; +} +.zmdi-compass:before { + content: '\f152'; +} +.zmdi-cutlery:before { + content: '\f153'; +} +.zmdi-delete:before { + content: '\f154'; +} +.zmdi-dialpad:before { + content: '\f155'; +} +.zmdi-dns:before { + content: '\f156'; +} +.zmdi-drink:before { + content: '\f157'; +} +.zmdi-edit:before { + content: '\f158'; +} +.zmdi-email-open:before { + content: '\f159'; +} +.zmdi-email:before { + content: '\f15a'; +} +.zmdi-eye-off:before { + content: '\f15b'; +} +.zmdi-eye:before { + content: '\f15c'; +} +.zmdi-eyedropper:before { + content: '\f15d'; +} +.zmdi-favorite-outline:before { + content: '\f15e'; +} +.zmdi-favorite:before { + content: '\f15f'; +} +.zmdi-filter-list:before { + content: '\f160'; +} +.zmdi-fire:before { + content: '\f161'; +} +.zmdi-flag:before { + content: '\f162'; +} +.zmdi-flare:before { + content: '\f163'; +} +.zmdi-flash-auto:before { + content: '\f164'; +} +.zmdi-flash-off:before { + content: '\f165'; +} +.zmdi-flash:before { + content: '\f166'; +} +.zmdi-flip:before { + content: '\f167'; +} +.zmdi-flower-alt:before { + content: '\f168'; +} +.zmdi-flower:before { + content: '\f169'; +} +.zmdi-font:before { + content: '\f16a'; +} +.zmdi-fullscreen-alt:before { + content: '\f16b'; +} +.zmdi-fullscreen-exit:before { + content: '\f16c'; +} +.zmdi-fullscreen:before { + content: '\f16d'; +} +.zmdi-functions:before { + content: '\f16e'; +} +.zmdi-gas-station:before { + content: '\f16f'; +} +.zmdi-gesture:before { + content: '\f170'; +} +.zmdi-globe-alt:before { + content: '\f171'; +} +.zmdi-globe-lock:before { + content: '\f172'; +} +.zmdi-globe:before { + content: '\f173'; +} +.zmdi-graduation-cap:before { + content: '\f174'; +} +.zmdi-home:before { + content: '\f175'; +} +.zmdi-hospital-alt:before { + content: '\f176'; +} +.zmdi-hospital:before { + content: '\f177'; +} +.zmdi-hotel:before { + content: '\f178'; +} +.zmdi-hourglass-alt:before { + content: '\f179'; +} +.zmdi-hourglass-outline:before { + content: '\f17a'; +} +.zmdi-hourglass:before { + content: '\f17b'; +} +.zmdi-http:before { + content: '\f17c'; +} +.zmdi-image-alt:before { + content: '\f17d'; +} +.zmdi-image-o:before { + content: '\f17e'; +} +.zmdi-image:before { + content: '\f17f'; +} +.zmdi-inbox:before { + content: '\f180'; +} +.zmdi-invert-colors-off:before { + content: '\f181'; +} +.zmdi-invert-colors:before { + content: '\f182'; +} +.zmdi-key:before { + content: '\f183'; +} +.zmdi-label-alt-outline:before { + content: '\f184'; +} +.zmdi-label-alt:before { + content: '\f185'; +} +.zmdi-label-heart:before { + content: '\f186'; +} +.zmdi-label:before { + content: '\f187'; +} +.zmdi-labels:before { + content: '\f188'; +} +.zmdi-lamp:before { + content: '\f189'; +} +.zmdi-landscape:before { + content: '\f18a'; +} +.zmdi-layers-off:before { + content: '\f18b'; +} +.zmdi-layers:before { + content: '\f18c'; +} +.zmdi-library:before { + content: '\f18d'; +} +.zmdi-link:before { + content: '\f18e'; +} +.zmdi-lock-open:before { + content: '\f18f'; +} +.zmdi-lock-outline:before { + content: '\f190'; +} +.zmdi-lock:before { + content: '\f191'; +} +.zmdi-mail-reply-all:before { + content: '\f192'; +} +.zmdi-mail-reply:before { + content: '\f193'; +} +.zmdi-mail-send:before { + content: '\f194'; +} +.zmdi-mall:before { + content: '\f195'; +} +.zmdi-map:before { + content: '\f196'; +} +.zmdi-menu:before { + content: '\f197'; +} +.zmdi-money-box:before { + content: '\f198'; +} +.zmdi-money-off:before { + content: '\f199'; +} +.zmdi-money:before { + content: '\f19a'; +} +.zmdi-more-vert:before { + content: '\f19b'; +} +.zmdi-more:before { + content: '\f19c'; +} +.zmdi-movie-alt:before { + content: '\f19d'; +} +.zmdi-movie:before { + content: '\f19e'; +} +.zmdi-nature-people:before { + content: '\f19f'; +} +.zmdi-nature:before { + content: '\f1a0'; +} +.zmdi-navigation:before { + content: '\f1a1'; +} +.zmdi-open-in-browser:before { + content: '\f1a2'; +} +.zmdi-open-in-new:before { + content: '\f1a3'; +} +.zmdi-palette:before { + content: '\f1a4'; +} +.zmdi-parking:before { + content: '\f1a5'; +} +.zmdi-pin-account:before { + content: '\f1a6'; +} +.zmdi-pin-assistant:before { + content: '\f1a7'; +} +.zmdi-pin-drop:before { + content: '\f1a8'; +} +.zmdi-pin-help:before { + content: '\f1a9'; +} +.zmdi-pin-off:before { + content: '\f1aa'; +} +.zmdi-pin:before { + content: '\f1ab'; +} +.zmdi-pizza:before { + content: '\f1ac'; +} +.zmdi-plaster:before { + content: '\f1ad'; +} +.zmdi-power-setting:before { + content: '\f1ae'; +} +.zmdi-power:before { + content: '\f1af'; +} +.zmdi-print:before { + content: '\f1b0'; +} +.zmdi-puzzle-piece:before { + content: '\f1b1'; +} +.zmdi-quote:before { + content: '\f1b2'; +} +.zmdi-railway:before { + content: '\f1b3'; +} +.zmdi-receipt:before { + content: '\f1b4'; +} +.zmdi-refresh-alt:before { + content: '\f1b5'; +} +.zmdi-refresh-sync-alert:before { + content: '\f1b6'; +} +.zmdi-refresh-sync-off:before { + content: '\f1b7'; +} +.zmdi-refresh-sync:before { + content: '\f1b8'; +} +.zmdi-refresh:before { + content: '\f1b9'; +} +.zmdi-roller:before { + content: '\f1ba'; +} +.zmdi-ruler:before { + content: '\f1bb'; +} +.zmdi-scissors:before { + content: '\f1bc'; +} +.zmdi-screen-rotation-lock:before { + content: '\f1bd'; +} +.zmdi-screen-rotation:before { + content: '\f1be'; +} +.zmdi-search-for:before { + content: '\f1bf'; +} +.zmdi-search-in-file:before { + content: '\f1c0'; +} +.zmdi-search-in-page:before { + content: '\f1c1'; +} +.zmdi-search-replace:before { + content: '\f1c2'; +} +.zmdi-search:before { + content: '\f1c3'; +} +.zmdi-seat:before { + content: '\f1c4'; +} +.zmdi-settings-square:before { + content: '\f1c5'; +} +.zmdi-settings:before { + content: '\f1c6'; +} +.zmdi-shield-check:before { + content: '\f1c7'; +} +.zmdi-shield-security:before { + content: '\f1c8'; +} +.zmdi-shopping-basket:before { + content: '\f1c9'; +} +.zmdi-shopping-cart-plus:before { + content: '\f1ca'; +} +.zmdi-shopping-cart:before { + content: '\f1cb'; +} +.zmdi-sign-in:before { + content: '\f1cc'; +} +.zmdi-sort-amount-asc:before { + content: '\f1cd'; +} +.zmdi-sort-amount-desc:before { + content: '\f1ce'; +} +.zmdi-sort-asc:before { + content: '\f1cf'; +} +.zmdi-sort-desc:before { + content: '\f1d0'; +} +.zmdi-spellcheck:before { + content: '\f1d1'; +} +.zmdi-storage:before { + content: '\f1d2'; +} +.zmdi-store-24:before { + content: '\f1d3'; +} +.zmdi-store:before { + content: '\f1d4'; +} +.zmdi-subway:before { + content: '\f1d5'; +} +.zmdi-sun:before { + content: '\f1d6'; +} +.zmdi-tab-unselected:before { + content: '\f1d7'; +} +.zmdi-tab:before { + content: '\f1d8'; +} +.zmdi-tag-close:before { + content: '\f1d9'; +} +.zmdi-tag-more:before { + content: '\f1da'; +} +.zmdi-tag:before { + content: '\f1db'; +} +.zmdi-thumb-down:before { + content: '\f1dc'; +} +.zmdi-thumb-up-down:before { + content: '\f1dd'; +} +.zmdi-thumb-up:before { + content: '\f1de'; +} +.zmdi-ticket-star:before { + content: '\f1df'; +} +.zmdi-toll:before { + content: '\f1e0'; +} +.zmdi-toys:before { + content: '\f1e1'; +} +.zmdi-traffic:before { + content: '\f1e2'; +} +.zmdi-translate:before { + content: '\f1e3'; +} +.zmdi-triangle-down:before { + content: '\f1e4'; +} +.zmdi-triangle-up:before { + content: '\f1e5'; +} +.zmdi-truck:before { + content: '\f1e6'; +} +.zmdi-turning-sign:before { + content: '\f1e7'; +} +.zmdi-wallpaper:before { + content: '\f1e8'; +} +.zmdi-washing-machine:before { + content: '\f1e9'; +} +.zmdi-window-maximize:before { + content: '\f1ea'; +} +.zmdi-window-minimize:before { + content: '\f1eb'; +} +.zmdi-window-restore:before { + content: '\f1ec'; +} +.zmdi-wrench:before { + content: '\f1ed'; +} +.zmdi-zoom-in:before { + content: '\f1ee'; +} +.zmdi-zoom-out:before { + content: '\f1ef'; +} +.zmdi-alert-circle-o:before { + content: '\f1f0'; +} +.zmdi-alert-circle:before { + content: '\f1f1'; +} +.zmdi-alert-octagon:before { + content: '\f1f2'; +} +.zmdi-alert-polygon:before { + content: '\f1f3'; +} +.zmdi-alert-triangle:before { + content: '\f1f4'; +} +.zmdi-help-outline:before { + content: '\f1f5'; +} +.zmdi-help:before { + content: '\f1f6'; +} +.zmdi-info-outline:before { + content: '\f1f7'; +} +.zmdi-info:before { + content: '\f1f8'; +} +.zmdi-notifications-active:before { + content: '\f1f9'; +} +.zmdi-notifications-add:before { + content: '\f1fa'; +} +.zmdi-notifications-none:before { + content: '\f1fb'; +} +.zmdi-notifications-off:before { + content: '\f1fc'; +} +.zmdi-notifications-paused:before { + content: '\f1fd'; +} +.zmdi-notifications:before { + content: '\f1fe'; +} +.zmdi-account-add:before { + content: '\f1ff'; +} +.zmdi-account-box-mail:before { + content: '\f200'; +} +.zmdi-account-box-o:before { + content: '\f201'; +} +.zmdi-account-box-phone:before { + content: '\f202'; +} +.zmdi-account-box:before { + content: '\f203'; +} +.zmdi-account-calendar:before { + content: '\f204'; +} +.zmdi-account-circle:before { + content: '\f205'; +} +.zmdi-account-o:before { + content: '\f206'; +} +.zmdi-account:before { + content: '\f207'; +} +.zmdi-accounts-add:before { + content: '\f208'; +} +.zmdi-accounts-alt:before { + content: '\f209'; +} +.zmdi-accounts-list-alt:before { + content: '\f20a'; +} +.zmdi-accounts-list:before { + content: '\f20b'; +} +.zmdi-accounts-outline:before { + content: '\f20c'; +} +.zmdi-accounts:before { + content: '\f20d'; +} +.zmdi-face:before { + content: '\f20e'; +} +.zmdi-female:before { + content: '\f20f'; +} +.zmdi-male-alt:before { + content: '\f210'; +} +.zmdi-male-female:before { + content: '\f211'; +} +.zmdi-male:before { + content: '\f212'; +} +.zmdi-mood-bad:before { + content: '\f213'; +} +.zmdi-mood:before { + content: '\f214'; +} +.zmdi-run:before { + content: '\f215'; +} +.zmdi-walk:before { + content: '\f216'; +} +.zmdi-cloud-box:before { + content: '\f217'; +} +.zmdi-cloud-circle:before { + content: '\f218'; +} +.zmdi-cloud-done:before { + content: '\f219'; +} +.zmdi-cloud-download:before { + content: '\f21a'; +} +.zmdi-cloud-off:before { + content: '\f21b'; +} +.zmdi-cloud-outline-alt:before { + content: '\f21c'; +} +.zmdi-cloud-outline:before { + content: '\f21d'; +} +.zmdi-cloud-upload:before { + content: '\f21e'; +} +.zmdi-cloud:before { + content: '\f21f'; +} +.zmdi-download:before { + content: '\f220'; +} +.zmdi-file-plus:before { + content: '\f221'; +} +.zmdi-file-text:before { + content: '\f222'; +} +.zmdi-file:before { + content: '\f223'; +} +.zmdi-folder-outline:before { + content: '\f224'; +} +.zmdi-folder-person:before { + content: '\f225'; +} +.zmdi-folder-star-alt:before { + content: '\f226'; +} +.zmdi-folder-star:before { + content: '\f227'; +} +.zmdi-folder:before { + content: '\f228'; +} +.zmdi-gif:before { + content: '\f229'; +} +.zmdi-upload:before { + content: '\f22a'; +} +.zmdi-border-all:before { + content: '\f22b'; +} +.zmdi-border-bottom:before { + content: '\f22c'; +} +.zmdi-border-clear:before { + content: '\f22d'; +} +.zmdi-border-color:before { + content: '\f22e'; +} +.zmdi-border-horizontal:before { + content: '\f22f'; +} +.zmdi-border-inner:before { + content: '\f230'; +} +.zmdi-border-left:before { + content: '\f231'; +} +.zmdi-border-outer:before { + content: '\f232'; +} +.zmdi-border-right:before { + content: '\f233'; +} +.zmdi-border-style:before { + content: '\f234'; +} +.zmdi-border-top:before { + content: '\f235'; +} +.zmdi-border-vertical:before { + content: '\f236'; +} +.zmdi-copy:before { + content: '\f237'; +} +.zmdi-crop:before { + content: '\f238'; +} +.zmdi-format-align-center:before { + content: '\f239'; +} +.zmdi-format-align-justify:before { + content: '\f23a'; +} +.zmdi-format-align-left:before { + content: '\f23b'; +} +.zmdi-format-align-right:before { + content: '\f23c'; +} +.zmdi-format-bold:before { + content: '\f23d'; +} +.zmdi-format-clear-all:before { + content: '\f23e'; +} +.zmdi-format-clear:before { + content: '\f23f'; +} +.zmdi-format-color-fill:before { + content: '\f240'; +} +.zmdi-format-color-reset:before { + content: '\f241'; +} +.zmdi-format-color-text:before { + content: '\f242'; +} +.zmdi-format-indent-decrease:before { + content: '\f243'; +} +.zmdi-format-indent-increase:before { + content: '\f244'; +} +.zmdi-format-italic:before { + content: '\f245'; +} +.zmdi-format-line-spacing:before { + content: '\f246'; +} +.zmdi-format-list-bulleted:before { + content: '\f247'; +} +.zmdi-format-list-numbered:before { + content: '\f248'; +} +.zmdi-format-ltr:before { + content: '\f249'; +} +.zmdi-format-rtl:before { + content: '\f24a'; +} +.zmdi-format-size:before { + content: '\f24b'; +} +.zmdi-format-strikethrough-s:before { + content: '\f24c'; +} +.zmdi-format-strikethrough:before { + content: '\f24d'; +} +.zmdi-format-subject:before { + content: '\f24e'; +} +.zmdi-format-underlined:before { + content: '\f24f'; +} +.zmdi-format-valign-bottom:before { + content: '\f250'; +} +.zmdi-format-valign-center:before { + content: '\f251'; +} +.zmdi-format-valign-top:before { + content: '\f252'; +} +.zmdi-redo:before { + content: '\f253'; +} +.zmdi-select-all:before { + content: '\f254'; +} +.zmdi-space-bar:before { + content: '\f255'; +} +.zmdi-text-format:before { + content: '\f256'; +} +.zmdi-transform:before { + content: '\f257'; +} +.zmdi-undo:before { + content: '\f258'; +} +.zmdi-wrap-text:before { + content: '\f259'; +} +.zmdi-comment-alert:before { + content: '\f25a'; +} +.zmdi-comment-alt-text:before { + content: '\f25b'; +} +.zmdi-comment-alt:before { + content: '\f25c'; +} +.zmdi-comment-edit:before { + content: '\f25d'; +} +.zmdi-comment-image:before { + content: '\f25e'; +} +.zmdi-comment-list:before { + content: '\f25f'; +} +.zmdi-comment-more:before { + content: '\f260'; +} +.zmdi-comment-outline:before { + content: '\f261'; +} +.zmdi-comment-text-alt:before { + content: '\f262'; +} +.zmdi-comment-text:before { + content: '\f263'; +} +.zmdi-comment-video:before { + content: '\f264'; +} +.zmdi-comment:before { + content: '\f265'; +} +.zmdi-comments:before { + content: '\f266'; +} +.zmdi-check-all:before { + content: '\f267'; +} +.zmdi-check-circle-u:before { + content: '\f268'; +} +.zmdi-check-circle:before { + content: '\f269'; +} +.zmdi-check-square:before { + content: '\f26a'; +} +.zmdi-check:before { + content: '\f26b'; +} +.zmdi-circle-o:before { + content: '\f26c'; +} +.zmdi-circle:before { + content: '\f26d'; +} +.zmdi-dot-circle-alt:before { + content: '\f26e'; +} +.zmdi-dot-circle:before { + content: '\f26f'; +} +.zmdi-minus-circle-outline:before { + content: '\f270'; +} +.zmdi-minus-circle:before { + content: '\f271'; +} +.zmdi-minus-square:before { + content: '\f272'; +} +.zmdi-minus:before { + content: '\f273'; +} +.zmdi-plus-circle-o-duplicate:before { + content: '\f274'; +} +.zmdi-plus-circle-o:before { + content: '\f275'; +} +.zmdi-plus-circle:before { + content: '\f276'; +} +.zmdi-plus-square:before { + content: '\f277'; +} +.zmdi-plus:before { + content: '\f278'; +} +.zmdi-square-o:before { + content: '\f279'; +} +.zmdi-star-circle:before { + content: '\f27a'; +} +.zmdi-star-half:before { + content: '\f27b'; +} +.zmdi-star-outline:before { + content: '\f27c'; +} +.zmdi-star:before { + content: '\f27d'; +} +.zmdi-bluetooth-connected:before { + content: '\f27e'; +} +.zmdi-bluetooth-off:before { + content: '\f27f'; +} +.zmdi-bluetooth-search:before { + content: '\f280'; +} +.zmdi-bluetooth-setting:before { + content: '\f281'; +} +.zmdi-bluetooth:before { + content: '\f282'; +} +.zmdi-camera-add:before { + content: '\f283'; +} +.zmdi-camera-alt:before { + content: '\f284'; +} +.zmdi-camera-bw:before { + content: '\f285'; +} +.zmdi-camera-front:before { + content: '\f286'; +} +.zmdi-camera-mic:before { + content: '\f287'; +} +.zmdi-camera-party-mode:before { + content: '\f288'; +} +.zmdi-camera-rear:before { + content: '\f289'; +} +.zmdi-camera-roll:before { + content: '\f28a'; +} +.zmdi-camera-switch:before { + content: '\f28b'; +} +.zmdi-camera:before { + content: '\f28c'; +} +.zmdi-card-alert:before { + content: '\f28d'; +} +.zmdi-card-off:before { + content: '\f28e'; +} +.zmdi-card-sd:before { + content: '\f28f'; +} +.zmdi-card-sim:before { + content: '\f290'; +} +.zmdi-desktop-mac:before { + content: '\f291'; +} +.zmdi-desktop-windows:before { + content: '\f292'; +} +.zmdi-device-hub:before { + content: '\f293'; +} +.zmdi-devices-off:before { + content: '\f294'; +} +.zmdi-devices:before { + content: '\f295'; +} +.zmdi-dock:before { + content: '\f296'; +} +.zmdi-floppy:before { + content: '\f297'; +} +.zmdi-gamepad:before { + content: '\f298'; +} +.zmdi-gps-dot:before { + content: '\f299'; +} +.zmdi-gps-off:before { + content: '\f29a'; +} +.zmdi-gps:before { + content: '\f29b'; +} +.zmdi-headset-mic:before { + content: '\f29c'; +} +.zmdi-headset:before { + content: '\f29d'; +} +.zmdi-input-antenna:before { + content: '\f29e'; +} +.zmdi-input-composite:before { + content: '\f29f'; +} +.zmdi-input-hdmi:before { + content: '\f2a0'; +} +.zmdi-input-power:before { + content: '\f2a1'; +} +.zmdi-input-svideo:before { + content: '\f2a2'; +} +.zmdi-keyboard-hide:before { + content: '\f2a3'; +} +.zmdi-keyboard:before { + content: '\f2a4'; +} +.zmdi-laptop-chromebook:before { + content: '\f2a5'; +} +.zmdi-laptop-mac:before { + content: '\f2a6'; +} +.zmdi-laptop:before { + content: '\f2a7'; +} +.zmdi-mic-off:before { + content: '\f2a8'; +} +.zmdi-mic-outline:before { + content: '\f2a9'; +} +.zmdi-mic-setting:before { + content: '\f2aa'; +} +.zmdi-mic:before { + content: '\f2ab'; +} +.zmdi-mouse:before { + content: '\f2ac'; +} +.zmdi-network-alert:before { + content: '\f2ad'; +} +.zmdi-network-locked:before { + content: '\f2ae'; +} +.zmdi-network-off:before { + content: '\f2af'; +} +.zmdi-network-outline:before { + content: '\f2b0'; +} +.zmdi-network-setting:before { + content: '\f2b1'; +} +.zmdi-network:before { + content: '\f2b2'; +} +.zmdi-phone-bluetooth:before { + content: '\f2b3'; +} +.zmdi-phone-end:before { + content: '\f2b4'; +} +.zmdi-phone-forwarded:before { + content: '\f2b5'; +} +.zmdi-phone-in-talk:before { + content: '\f2b6'; +} +.zmdi-phone-locked:before { + content: '\f2b7'; +} +.zmdi-phone-missed:before { + content: '\f2b8'; +} +.zmdi-phone-msg:before { + content: '\f2b9'; +} +.zmdi-phone-paused:before { + content: '\f2ba'; +} +.zmdi-phone-ring:before { + content: '\f2bb'; +} +.zmdi-phone-setting:before { + content: '\f2bc'; +} +.zmdi-phone-sip:before { + content: '\f2bd'; +} +.zmdi-phone:before { + content: '\f2be'; +} +.zmdi-portable-wifi-changes:before { + content: '\f2bf'; +} +.zmdi-portable-wifi-off:before { + content: '\f2c0'; +} +.zmdi-portable-wifi:before { + content: '\f2c1'; +} +.zmdi-radio:before { + content: '\f2c2'; +} +.zmdi-reader:before { + content: '\f2c3'; +} +.zmdi-remote-control-alt:before { + content: '\f2c4'; +} +.zmdi-remote-control:before { + content: '\f2c5'; +} +.zmdi-router:before { + content: '\f2c6'; +} +.zmdi-scanner:before { + content: '\f2c7'; +} +.zmdi-smartphone-android:before { + content: '\f2c8'; +} +.zmdi-smartphone-download:before { + content: '\f2c9'; +} +.zmdi-smartphone-erase:before { + content: '\f2ca'; +} +.zmdi-smartphone-info:before { + content: '\f2cb'; +} +.zmdi-smartphone-iphone:before { + content: '\f2cc'; +} +.zmdi-smartphone-landscape-lock:before { + content: '\f2cd'; +} +.zmdi-smartphone-landscape:before { + content: '\f2ce'; +} +.zmdi-smartphone-lock:before { + content: '\f2cf'; +} +.zmdi-smartphone-portrait-lock:before { + content: '\f2d0'; +} +.zmdi-smartphone-ring:before { + content: '\f2d1'; +} +.zmdi-smartphone-setting:before { + content: '\f2d2'; +} +.zmdi-smartphone-setup:before { + content: '\f2d3'; +} +.zmdi-smartphone:before { + content: '\f2d4'; +} +.zmdi-speaker:before { + content: '\f2d5'; +} +.zmdi-tablet-android:before { + content: '\f2d6'; +} +.zmdi-tablet-mac:before { + content: '\f2d7'; +} +.zmdi-tablet:before { + content: '\f2d8'; +} +.zmdi-tv-alt-play:before { + content: '\f2d9'; +} +.zmdi-tv-list:before { + content: '\f2da'; +} +.zmdi-tv-play:before { + content: '\f2db'; +} +.zmdi-tv:before { + content: '\f2dc'; +} +.zmdi-usb:before { + content: '\f2dd'; +} +.zmdi-videocam-off:before { + content: '\f2de'; +} +.zmdi-videocam-switch:before { + content: '\f2df'; +} +.zmdi-videocam:before { + content: '\f2e0'; +} +.zmdi-watch:before { + content: '\f2e1'; +} +.zmdi-wifi-alt-2:before { + content: '\f2e2'; +} +.zmdi-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-wifi-info:before { + content: '\f2e4'; +} +.zmdi-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-wifi-off:before { + content: '\f2e6'; +} +.zmdi-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-wifi:before { + content: '\f2e8'; +} +.zmdi-arrow-left-bottom:before { + content: '\f2e9'; +} +.zmdi-arrow-left:before { + content: '\f2ea'; +} +.zmdi-arrow-merge:before { + content: '\f2eb'; +} +.zmdi-arrow-missed:before { + content: '\f2ec'; +} +.zmdi-arrow-right-top:before { + content: '\f2ed'; +} +.zmdi-arrow-right:before { + content: '\f2ee'; +} +.zmdi-arrow-split:before { + content: '\f2ef'; +} +.zmdi-arrows:before { + content: '\f2f0'; +} +.zmdi-caret-down-circle:before { + content: '\f2f1'; +} +.zmdi-caret-down:before { + content: '\f2f2'; +} +.zmdi-caret-left-circle:before { + content: '\f2f3'; +} +.zmdi-caret-left:before { + content: '\f2f4'; +} +.zmdi-caret-right-circle:before { + content: '\f2f5'; +} +.zmdi-caret-right:before { + content: '\f2f6'; +} +.zmdi-caret-up-circle:before { + content: '\f2f7'; +} +.zmdi-caret-up:before { + content: '\f2f8'; +} +.zmdi-chevron-down:before { + content: '\f2f9'; +} +.zmdi-chevron-left:before { + content: '\f2fa'; +} +.zmdi-chevron-right:before { + content: '\f2fb'; +} +.zmdi-chevron-up:before { + content: '\f2fc'; +} +.zmdi-forward:before { + content: '\f2fd'; +} +.zmdi-long-arrow-down:before { + content: '\f2fe'; +} +.zmdi-long-arrow-left:before { + content: '\f2ff'; +} +.zmdi-long-arrow-return:before { + content: '\f300'; +} +.zmdi-long-arrow-right:before { + content: '\f301'; +} +.zmdi-long-arrow-tab:before { + content: '\f302'; +} +.zmdi-long-arrow-up:before { + content: '\f303'; +} +.zmdi-rotate-ccw:before { + content: '\f304'; +} +.zmdi-rotate-cw:before { + content: '\f305'; +} +.zmdi-rotate-left:before { + content: '\f306'; +} +.zmdi-rotate-right:before { + content: '\f307'; +} +.zmdi-square-down:before { + content: '\f308'; +} +.zmdi-square-right:before { + content: '\f309'; +} +.zmdi-swap-alt:before { + content: '\f30a'; +} +.zmdi-swap-vertical-circle:before { + content: '\f30b'; +} +.zmdi-swap-vertical:before { + content: '\f30c'; +} +.zmdi-swap:before { + content: '\f30d'; +} +.zmdi-trending-down:before { + content: '\f30e'; +} +.zmdi-trending-flat:before { + content: '\f30f'; +} +.zmdi-trending-up:before { + content: '\f310'; +} +.zmdi-unfold-less:before { + content: '\f311'; +} +.zmdi-unfold-more:before { + content: '\f312'; +} +.zmdi-apps:before { + content: '\f313'; +} +.zmdi-grid-off:before { + content: '\f314'; +} +.zmdi-grid:before { + content: '\f315'; +} +.zmdi-view-agenda:before { + content: '\f316'; +} +.zmdi-view-array:before { + content: '\f317'; +} +.zmdi-view-carousel:before { + content: '\f318'; +} +.zmdi-view-column:before { + content: '\f319'; +} +.zmdi-view-comfy:before { + content: '\f31a'; +} +.zmdi-view-compact:before { + content: '\f31b'; +} +.zmdi-view-dashboard:before { + content: '\f31c'; +} +.zmdi-view-day:before { + content: '\f31d'; +} +.zmdi-view-headline:before { + content: '\f31e'; +} +.zmdi-view-list-alt:before { + content: '\f31f'; +} +.zmdi-view-list:before { + content: '\f320'; +} +.zmdi-view-module:before { + content: '\f321'; +} +.zmdi-view-quilt:before { + content: '\f322'; +} +.zmdi-view-stream:before { + content: '\f323'; +} +.zmdi-view-subtitles:before { + content: '\f324'; +} +.zmdi-view-toc:before { + content: '\f325'; +} +.zmdi-view-web:before { + content: '\f326'; +} +.zmdi-view-week:before { + content: '\f327'; +} +.zmdi-widgets:before { + content: '\f328'; +} +.zmdi-alarm-check:before { + content: '\f329'; +} +.zmdi-alarm-off:before { + content: '\f32a'; +} +.zmdi-alarm-plus:before { + content: '\f32b'; +} +.zmdi-alarm-snooze:before { + content: '\f32c'; +} +.zmdi-alarm:before { + content: '\f32d'; +} +.zmdi-calendar-alt:before { + content: '\f32e'; +} +.zmdi-calendar-check:before { + content: '\f32f'; +} +.zmdi-calendar-close:before { + content: '\f330'; +} +.zmdi-calendar-note:before { + content: '\f331'; +} +.zmdi-calendar:before { + content: '\f332'; +} +.zmdi-time-countdown:before { + content: '\f333'; +} +.zmdi-time-interval:before { + content: '\f334'; +} +.zmdi-time-restore-setting:before { + content: '\f335'; +} +.zmdi-time-restore:before { + content: '\f336'; +} +.zmdi-time:before { + content: '\f337'; +} +.zmdi-timer-off:before { + content: '\f338'; +} +.zmdi-timer:before { + content: '\f339'; +} +.zmdi-android-alt:before { + content: '\f33a'; +} +.zmdi-android:before { + content: '\f33b'; +} +.zmdi-apple:before { + content: '\f33c'; +} +.zmdi-behance:before { + content: '\f33d'; +} +.zmdi-codepen:before { + content: '\f33e'; +} +.zmdi-dribbble:before { + content: '\f33f'; +} +.zmdi-dropbox:before { + content: '\f340'; +} +.zmdi-evernote:before { + content: '\f341'; +} +.zmdi-facebook-box:before { + content: '\f342'; +} +.zmdi-facebook:before { + content: '\f343'; +} +.zmdi-github-box:before { + content: '\f344'; +} +.zmdi-github:before { + content: '\f345'; +} +.zmdi-google-drive:before { + content: '\f346'; +} +.zmdi-google-earth:before { + content: '\f347'; +} +.zmdi-google-glass:before { + content: '\f348'; +} +.zmdi-google-maps:before { + content: '\f349'; +} +.zmdi-google-pages:before { + content: '\f34a'; +} +.zmdi-google-play:before { + content: '\f34b'; +} +.zmdi-google-plus-box:before { + content: '\f34c'; +} +.zmdi-google-plus:before { + content: '\f34d'; +} +.zmdi-google:before { + content: '\f34e'; +} +.zmdi-instagram:before { + content: '\f34f'; +} +.zmdi-language-css3:before { + content: '\f350'; +} +.zmdi-language-html5:before { + content: '\f351'; +} +.zmdi-language-javascript:before { + content: '\f352'; +} +.zmdi-language-python-alt:before { + content: '\f353'; +} +.zmdi-language-python:before { + content: '\f354'; +} +.zmdi-lastfm:before { + content: '\f355'; +} +.zmdi-linkedin-box:before { + content: '\f356'; +} +.zmdi-paypal:before { + content: '\f357'; +} +.zmdi-pinterest-box:before { + content: '\f358'; +} +.zmdi-pocket:before { + content: '\f359'; +} +.zmdi-polymer:before { + content: '\f35a'; +} +.zmdi-share:before { + content: '\f35b'; +} +.zmdi-stackoverflow:before { + content: '\f35c'; +} +.zmdi-steam-square:before { + content: '\f35d'; +} +.zmdi-steam:before { + content: '\f35e'; +} +.zmdi-twitter-box:before { + content: '\f35f'; +} +.zmdi-twitter:before { + content: '\f360'; +} +.zmdi-vk:before { + content: '\f361'; +} +.zmdi-wikipedia:before { + content: '\f362'; +} +.zmdi-windows:before { + content: '\f363'; +} +.zmdi-aspect-ratio-alt:before { + content: '\f364'; +} +.zmdi-aspect-ratio:before { + content: '\f365'; +} +.zmdi-blur-circular:before { + content: '\f366'; +} +.zmdi-blur-linear:before { + content: '\f367'; +} +.zmdi-blur-off:before { + content: '\f368'; +} +.zmdi-blur:before { + content: '\f369'; +} +.zmdi-brightness-2:before { + content: '\f36a'; +} +.zmdi-brightness-3:before { + content: '\f36b'; +} +.zmdi-brightness-4:before { + content: '\f36c'; +} +.zmdi-brightness-5:before { + content: '\f36d'; +} +.zmdi-brightness-6:before { + content: '\f36e'; +} +.zmdi-brightness-7:before { + content: '\f36f'; +} +.zmdi-brightness-auto:before { + content: '\f370'; +} +.zmdi-brightness-setting:before { + content: '\f371'; +} +.zmdi-broken-image:before { + content: '\f372'; +} +.zmdi-center-focus-strong:before { + content: '\f373'; +} +.zmdi-center-focus-weak:before { + content: '\f374'; +} +.zmdi-compare:before { + content: '\f375'; +} +.zmdi-crop-16-9:before { + content: '\f376'; +} +.zmdi-crop-3-2:before { + content: '\f377'; +} +.zmdi-crop-5-4:before { + content: '\f378'; +} +.zmdi-crop-7-5:before { + content: '\f379'; +} +.zmdi-crop-din:before { + content: '\f37a'; +} +.zmdi-crop-free:before { + content: '\f37b'; +} +.zmdi-crop-landscape:before { + content: '\f37c'; +} +.zmdi-crop-portrait:before { + content: '\f37d'; +} +.zmdi-crop-square:before { + content: '\f37e'; +} +.zmdi-exposure-alt:before { + content: '\f37f'; +} +.zmdi-exposure:before { + content: '\f380'; +} +.zmdi-filter-b-and-w:before { + content: '\f381'; +} +.zmdi-filter-center-focus:before { + content: '\f382'; +} +.zmdi-filter-frames:before { + content: '\f383'; +} +.zmdi-filter-tilt-shift:before { + content: '\f384'; +} +.zmdi-gradient:before { + content: '\f385'; +} +.zmdi-grain:before { + content: '\f386'; +} +.zmdi-graphic-eq:before { + content: '\f387'; +} +.zmdi-hdr-off:before { + content: '\f388'; +} +.zmdi-hdr-strong:before { + content: '\f389'; +} +.zmdi-hdr-weak:before { + content: '\f38a'; +} +.zmdi-hdr:before { + content: '\f38b'; +} +.zmdi-iridescent:before { + content: '\f38c'; +} +.zmdi-leak-off:before { + content: '\f38d'; +} +.zmdi-leak:before { + content: '\f38e'; +} +.zmdi-looks:before { + content: '\f38f'; +} +.zmdi-loupe:before { + content: '\f390'; +} +.zmdi-panorama-horizontal:before { + content: '\f391'; +} +.zmdi-panorama-vertical:before { + content: '\f392'; +} +.zmdi-panorama-wide-angle:before { + content: '\f393'; +} +.zmdi-photo-size-select-large:before { + content: '\f394'; +} +.zmdi-photo-size-select-small:before { + content: '\f395'; +} +.zmdi-picture-in-picture:before { + content: '\f396'; +} +.zmdi-slideshow:before { + content: '\f397'; +} +.zmdi-texture:before { + content: '\f398'; +} +.zmdi-tonality:before { + content: '\f399'; +} +.zmdi-vignette:before { + content: '\f39a'; +} +.zmdi-wb-auto:before { + content: '\f39b'; +} +.zmdi-eject-alt:before { + content: '\f39c'; +} +.zmdi-eject:before { + content: '\f39d'; +} +.zmdi-equalizer:before { + content: '\f39e'; +} +.zmdi-fast-forward:before { + content: '\f39f'; +} +.zmdi-fast-rewind:before { + content: '\f3a0'; +} +.zmdi-forward-10:before { + content: '\f3a1'; +} +.zmdi-forward-30:before { + content: '\f3a2'; +} +.zmdi-forward-5:before { + content: '\f3a3'; +} +.zmdi-hearing:before { + content: '\f3a4'; +} +.zmdi-pause-circle-outline:before { + content: '\f3a5'; +} +.zmdi-pause-circle:before { + content: '\f3a6'; +} +.zmdi-pause:before { + content: '\f3a7'; +} +.zmdi-play-circle-outline:before { + content: '\f3a8'; +} +.zmdi-play-circle:before { + content: '\f3a9'; +} +.zmdi-play:before { + content: '\f3aa'; +} +.zmdi-playlist-audio:before { + content: '\f3ab'; +} +.zmdi-playlist-plus:before { + content: '\f3ac'; +} +.zmdi-repeat-one:before { + content: '\f3ad'; +} +.zmdi-repeat:before { + content: '\f3ae'; +} +.zmdi-replay-10:before { + content: '\f3af'; +} +.zmdi-replay-30:before { + content: '\f3b0'; +} +.zmdi-replay-5:before { + content: '\f3b1'; +} +.zmdi-replay:before { + content: '\f3b2'; +} +.zmdi-shuffle:before { + content: '\f3b3'; +} +.zmdi-skip-next:before { + content: '\f3b4'; +} +.zmdi-skip-previous:before { + content: '\f3b5'; +} +.zmdi-stop:before { + content: '\f3b6'; +} +.zmdi-surround-sound:before { + content: '\f3b7'; +} +.zmdi-tune:before { + content: '\f3b8'; +} +.zmdi-volume-down:before { + content: '\f3b9'; +} +.zmdi-volume-mute:before { + content: '\f3ba'; +} +.zmdi-volume-off:before { + content: '\f3bb'; +} +.zmdi-volume-up:before { + content: '\f3bc'; +} +.zmdi-n-1-square:before { + content: '\f3bd'; +} +.zmdi-n-2-square:before { + content: '\f3be'; +} +.zmdi-n-3-square:before { + content: '\f3bf'; +} +.zmdi-n-4-square:before { + content: '\f3c0'; +} +.zmdi-n-5-square:before { + content: '\f3c1'; +} +.zmdi-n-6-square:before { + content: '\f3c2'; +} +.zmdi-neg-1:before { + content: '\f3c3'; +} +.zmdi-neg-2:before { + content: '\f3c4'; +} +.zmdi-plus-1:before { + content: '\f3c5'; +} +.zmdi-plus-2:before { + content: '\f3c6'; +} +.zmdi-sec-10:before { + content: '\f3c7'; +} +.zmdi-sec-3:before { + content: '\f3c8'; +} +.zmdi-zero:before { + content: '\f3c9'; +} +.zmdi-airline-seat-flat-angled:before { + content: '\f3ca'; +} +.zmdi-airline-seat-flat:before { + content: '\f3cb'; +} +.zmdi-airline-seat-individual-suite:before { + content: '\f3cc'; +} +.zmdi-airline-seat-legroom-extra:before { + content: '\f3cd'; +} +.zmdi-airline-seat-legroom-normal:before { + content: '\f3ce'; +} +.zmdi-airline-seat-legroom-reduced:before { + content: '\f3cf'; +} +.zmdi-airline-seat-recline-extra:before { + content: '\f3d0'; +} +.zmdi-airline-seat-recline-normal:before { + content: '\f3d1'; +} +.zmdi-airplay:before { + content: '\f3d2'; +} +.zmdi-closed-caption:before { + content: '\f3d3'; +} +.zmdi-confirmation-number:before { + content: '\f3d4'; +} +.zmdi-developer-board:before { + content: '\f3d5'; +} +.zmdi-disc-full:before { + content: '\f3d6'; +} +.zmdi-explicit:before { + content: '\f3d7'; +} +.zmdi-flight-land:before { + content: '\f3d8'; +} +.zmdi-flight-takeoff:before { + content: '\f3d9'; +} +.zmdi-flip-to-back:before { + content: '\f3da'; +} +.zmdi-flip-to-front:before { + content: '\f3db'; +} +.zmdi-group-work:before { + content: '\f3dc'; +} +.zmdi-hd:before { + content: '\f3dd'; +} +.zmdi-hq:before { + content: '\f3de'; +} +.zmdi-markunread-mailbox:before { + content: '\f3df'; +} +.zmdi-memory:before { + content: '\f3e0'; +} +.zmdi-nfc:before { + content: '\f3e1'; +} +.zmdi-play-for-work:before { + content: '\f3e2'; +} +.zmdi-power-input:before { + content: '\f3e3'; +} +.zmdi-present-to-all:before { + content: '\f3e4'; +} +.zmdi-satellite:before { + content: '\f3e5'; +} +.zmdi-tap-and-play:before { + content: '\f3e6'; +} +.zmdi-vibration:before { + content: '\f3e7'; +} +.zmdi-voicemail:before { + content: '\f3e8'; +} +.zmdi-group:before { + content: '\f3e9'; +} +.zmdi-rss:before { + content: '\f3ea'; +} +.zmdi-shape:before { + content: '\f3eb'; +} +.zmdi-spinner:before { + content: '\f3ec'; +} +.zmdi-ungroup:before { + content: '\f3ed'; +} +.zmdi-500px:before { + content: '\f3ee'; +} +.zmdi-8tracks:before { + content: '\f3ef'; +} +.zmdi-amazon:before { + content: '\f3f0'; +} +.zmdi-blogger:before { + content: '\f3f1'; +} +.zmdi-delicious:before { + content: '\f3f2'; +} +.zmdi-disqus:before { + content: '\f3f3'; +} +.zmdi-flattr:before { + content: '\f3f4'; +} +.zmdi-flickr:before { + content: '\f3f5'; +} +.zmdi-github-alt:before { + content: '\f3f6'; +} +.zmdi-google-old:before { + content: '\f3f7'; +} +.zmdi-linkedin:before { + content: '\f3f8'; +} +.zmdi-odnoklassniki:before { + content: '\f3f9'; +} +.zmdi-outlook:before { + content: '\f3fa'; +} +.zmdi-paypal-alt:before { + content: '\f3fb'; +} +.zmdi-pinterest:before { + content: '\f3fc'; +} +.zmdi-playstation:before { + content: '\f3fd'; +} +.zmdi-reddit:before { + content: '\f3fe'; +} +.zmdi-skype:before { + content: '\f3ff'; +} +.zmdi-slideshare:before { + content: '\f400'; +} +.zmdi-soundcloud:before { + content: '\f401'; +} +.zmdi-tumblr:before { + content: '\f402'; +} +.zmdi-twitch:before { + content: '\f403'; +} +.zmdi-vimeo:before { + content: '\f404'; +} +.zmdi-whatsapp:before { + content: '\f405'; +} +.zmdi-xbox:before { + content: '\f406'; +} +.zmdi-yahoo:before { + content: '\f407'; +} +.zmdi-youtube-play:before { + content: '\f408'; +} +.zmdi-youtube:before { + content: '\f409'; +} +.zmdi-import-export:before { + content: '\f30c'; +} +.zmdi-swap-vertical-:before { + content: '\f30c'; +} +.zmdi-airplanemode-inactive:before { + content: '\f102'; +} +.zmdi-airplanemode-active:before { + content: '\f103'; +} +.zmdi-rate-review:before { + content: '\f103'; +} +.zmdi-comment-sign:before { + content: '\f25a'; +} +.zmdi-network-warning:before { + content: '\f2ad'; +} +.zmdi-shopping-cart-add:before { + content: '\f1ca'; +} +.zmdi-file-add:before { + content: '\f221'; +} +.zmdi-network-wifi-scan:before { + content: '\f2e4'; +} +.zmdi-collection-add:before { + content: '\f14e'; +} +.zmdi-format-playlist-add:before { + content: '\f3ac'; +} +.zmdi-format-queue-music:before { + content: '\f3ab'; +} +.zmdi-plus-box:before { + content: '\f277'; +} +.zmdi-tag-backspace:before { + content: '\f1d9'; +} +.zmdi-alarm-add:before { + content: '\f32b'; +} +.zmdi-battery-charging:before { + content: '\f114'; +} +.zmdi-daydream-setting:before { + content: '\f217'; +} +.zmdi-more-horiz:before { + content: '\f19c'; +} +.zmdi-book-photo:before { + content: '\f11b'; +} +.zmdi-incandescent:before { + content: '\f189'; +} +.zmdi-wb-iridescent:before { + content: '\f38c'; +} +.zmdi-calendar-remove:before { + content: '\f330'; +} +.zmdi-refresh-sync-disabled:before { + content: '\f1b7'; +} +.zmdi-refresh-sync-problem:before { + content: '\f1b6'; +} +.zmdi-crop-original:before { + content: '\f17e'; +} +.zmdi-power-off:before { + content: '\f1af'; +} +.zmdi-power-off-setting:before { + content: '\f1ae'; +} +.zmdi-leak-remove:before { + content: '\f38d'; +} +.zmdi-star-border:before { + content: '\f27c'; +} +.zmdi-brightness-low:before { + content: '\f36d'; +} +.zmdi-brightness-medium:before { + content: '\f36e'; +} +.zmdi-brightness-high:before { + content: '\f36f'; +} +.zmdi-smartphone-portrait:before { + content: '\f2d4'; +} +.zmdi-live-tv:before { + content: '\f2d9'; +} +.zmdi-format-textdirection-l-to-r:before { + content: '\f249'; +} +.zmdi-format-textdirection-r-to-l:before { + content: '\f24a'; +} +.zmdi-arrow-back:before { + content: '\f2ea'; +} +.zmdi-arrow-forward:before { + content: '\f2ee'; +} +.zmdi-arrow-in:before { + content: '\f2e9'; +} +.zmdi-arrow-out:before { + content: '\f2ed'; +} +.zmdi-rotate-90-degrees-ccw:before { + content: '\f304'; +} +.zmdi-adb:before { + content: '\f33a'; +} +.zmdi-network-wifi:before { + content: '\f2e8'; +} +.zmdi-network-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-network-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-network-wifi-off:before { + content: '\f2e6'; +} +.zmdi-network-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-network-wifi-info:before { + content: '\f2e4'; +} +.zmdi-layers-clear:before { + content: '\f18b'; +} +.zmdi-colorize:before { + content: '\f15d'; +} +.zmdi-format-paint:before { + content: '\f1ba'; +} +.zmdi-format-quote:before { + content: '\f1b2'; +} +.zmdi-camera-monochrome-photos:before { + content: '\f285'; +} +.zmdi-sort-by-alpha:before { + content: '\f1cf'; +} +.zmdi-folder-shared:before { + content: '\f225'; +} +.zmdi-folder-special:before { + content: '\f226'; +} +.zmdi-comment-dots:before { + content: '\f260'; +} +.zmdi-reorder:before { + content: '\f31e'; +} +.zmdi-dehaze:before { + content: '\f197'; +} +.zmdi-sort:before { + content: '\f1ce'; +} +.zmdi-pages:before { + content: '\f34a'; +} +.zmdi-stack-overflow:before { + content: '\f35c'; +} +.zmdi-calendar-account:before { + content: '\f204'; +} +.zmdi-paste:before { + content: '\f109'; +} +.zmdi-cut:before { + content: '\f1bc'; +} +.zmdi-save:before { + content: '\f297'; +} +.zmdi-smartphone-code:before { + content: '\f139'; +} +.zmdi-directions-bike:before { + content: '\f117'; +} +.zmdi-directions-boat:before { + content: '\f11a'; +} +.zmdi-directions-bus:before { + content: '\f121'; +} +.zmdi-directions-car:before { + content: '\f125'; +} +.zmdi-directions-railway:before { + content: '\f1b3'; +} +.zmdi-directions-run:before { + content: '\f215'; +} +.zmdi-directions-subway:before { + content: '\f1d5'; +} +.zmdi-directions-walk:before { + content: '\f216'; +} +.zmdi-local-hotel:before { + content: '\f178'; +} +.zmdi-local-activity:before { + content: '\f1df'; +} +.zmdi-local-play:before { + content: '\f1df'; +} +.zmdi-local-airport:before { + content: '\f103'; +} +.zmdi-local-atm:before { + content: '\f198'; +} +.zmdi-local-bar:before { + content: '\f137'; +} +.zmdi-local-cafe:before { + content: '\f13b'; +} +.zmdi-local-car-wash:before { + content: '\f124'; +} +.zmdi-local-convenience-store:before { + content: '\f1d3'; +} +.zmdi-local-dining:before { + content: '\f153'; +} +.zmdi-local-drink:before { + content: '\f157'; +} +.zmdi-local-florist:before { + content: '\f168'; +} +.zmdi-local-gas-station:before { + content: '\f16f'; +} +.zmdi-local-grocery-store:before { + content: '\f1cb'; +} +.zmdi-local-hospital:before { + content: '\f177'; +} +.zmdi-local-laundry-service:before { + content: '\f1e9'; +} +.zmdi-local-library:before { + content: '\f18d'; +} +.zmdi-local-mall:before { + content: '\f195'; +} +.zmdi-local-movies:before { + content: '\f19d'; +} +.zmdi-local-offer:before { + content: '\f187'; +} +.zmdi-local-parking:before { + content: '\f1a5'; +} +.zmdi-local-parking:before { + content: '\f1a5'; +} +.zmdi-local-pharmacy:before { + content: '\f176'; +} +.zmdi-local-phone:before { + content: '\f2be'; +} +.zmdi-local-pizza:before { + content: '\f1ac'; +} +.zmdi-local-post-office:before { + content: '\f15a'; +} +.zmdi-local-printshop:before { + content: '\f1b0'; +} +.zmdi-local-see:before { + content: '\f28c'; +} +.zmdi-local-shipping:before { + content: '\f1e6'; +} +.zmdi-local-store:before { + content: '\f1d4'; +} +.zmdi-local-taxi:before { + content: '\f123'; +} +.zmdi-local-wc:before { + content: '\f211'; +} +.zmdi-my-location:before { + content: '\f299'; +} +.zmdi-directions:before { + content: '\f1e7'; +} diff --git a/packages/website/public/css/material-design-iconic-font.min.css b/packages/website/public/css/material-design-iconic-font.min.css new file mode 100755 index 000000000..e1a58fe2f --- /dev/null +++ b/packages/website/public/css/material-design-iconic-font.min.css @@ -0,0 +1 @@ +@font-face{font-family:Material-Design-Iconic-Font;src:url(../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0) format('woff2'),url(../fonts/Material-Design-Iconic-Font.woff?v=2.2.0) format('woff'),url(../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0) format('truetype')}.zmdi{display:inline-block;font:normal normal normal 14px/1 'Material-Design-Iconic-Font';font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.zmdi-hc-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.zmdi-hc-2x{font-size:2em}.zmdi-hc-3x{font-size:3em}.zmdi-hc-4x{font-size:4em}.zmdi-hc-5x{font-size:5em}.zmdi-hc-fw{width:1.28571429em;text-align:center}.zmdi-hc-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.zmdi-hc-ul>li{position:relative}.zmdi-hc-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.zmdi-hc-li.zmdi-hc-lg{left:-1.85714286em}.zmdi-hc-border{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:2px}.zmdi-hc-border-circle{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:50%}.zmdi.pull-left{float:left;margin-right:.15em}.zmdi.pull-right{float:right;margin-left:.15em}.zmdi-hc-spin{-webkit-animation:zmdi-spin 1.5s infinite linear;animation:zmdi-spin 1.5s infinite linear}.zmdi-hc-spin-reverse{-webkit-animation:zmdi-spin-reverse 1.5s infinite linear;animation:zmdi-spin-reverse 1.5s infinite linear}@-webkit-keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}@keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}.zmdi-hc-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.zmdi-hc-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.zmdi-hc-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.zmdi-hc-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.zmdi-hc-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}.zmdi-hc-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.zmdi-hc-stack-1x,.zmdi-hc-stack-2x{position:absolute;left:0;width:100%;text-align:center}.zmdi-hc-stack-1x{line-height:inherit}.zmdi-hc-stack-2x{font-size:2em}.zmdi-hc-inverse{color:#fff}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-import-export:before{content:'\f30c'}.zmdi-swap-vertical-:before{content:'\f30c'}.zmdi-airplanemode-inactive:before{content:'\f102'}.zmdi-airplanemode-active:before{content:'\f103'}.zmdi-rate-review:before{content:'\f103'}.zmdi-comment-sign:before{content:'\f25a'}.zmdi-network-warning:before{content:'\f2ad'}.zmdi-shopping-cart-add:before{content:'\f1ca'}.zmdi-file-add:before{content:'\f221'}.zmdi-network-wifi-scan:before{content:'\f2e4'}.zmdi-collection-add:before{content:'\f14e'}.zmdi-format-playlist-add:before{content:'\f3ac'}.zmdi-format-queue-music:before{content:'\f3ab'}.zmdi-plus-box:before{content:'\f277'}.zmdi-tag-backspace:before{content:'\f1d9'}.zmdi-alarm-add:before{content:'\f32b'}.zmdi-battery-charging:before{content:'\f114'}.zmdi-daydream-setting:before{content:'\f217'}.zmdi-more-horiz:before{content:'\f19c'}.zmdi-book-photo:before{content:'\f11b'}.zmdi-incandescent:before{content:'\f189'}.zmdi-wb-iridescent:before{content:'\f38c'}.zmdi-calendar-remove:before{content:'\f330'}.zmdi-refresh-sync-disabled:before{content:'\f1b7'}.zmdi-refresh-sync-problem:before{content:'\f1b6'}.zmdi-crop-original:before{content:'\f17e'}.zmdi-power-off:before{content:'\f1af'}.zmdi-power-off-setting:before{content:'\f1ae'}.zmdi-leak-remove:before{content:'\f38d'}.zmdi-star-border:before{content:'\f27c'}.zmdi-brightness-low:before{content:'\f36d'}.zmdi-brightness-medium:before{content:'\f36e'}.zmdi-brightness-high:before{content:'\f36f'}.zmdi-smartphone-portrait:before{content:'\f2d4'}.zmdi-live-tv:before{content:'\f2d9'}.zmdi-format-textdirection-l-to-r:before{content:'\f249'}.zmdi-format-textdirection-r-to-l:before{content:'\f24a'}.zmdi-arrow-back:before{content:'\f2ea'}.zmdi-arrow-forward:before{content:'\f2ee'}.zmdi-arrow-in:before{content:'\f2e9'}.zmdi-arrow-out:before{content:'\f2ed'}.zmdi-rotate-90-degrees-ccw:before{content:'\f304'}.zmdi-adb:before{content:'\f33a'}.zmdi-network-wifi:before{content:'\f2e8'}.zmdi-network-wifi-alt:before{content:'\f2e3'}.zmdi-network-wifi-lock:before{content:'\f2e5'}.zmdi-network-wifi-off:before{content:'\f2e6'}.zmdi-network-wifi-outline:before{content:'\f2e7'}.zmdi-network-wifi-info:before{content:'\f2e4'}.zmdi-layers-clear:before{content:'\f18b'}.zmdi-colorize:before{content:'\f15d'}.zmdi-format-paint:before{content:'\f1ba'}.zmdi-format-quote:before{content:'\f1b2'}.zmdi-camera-monochrome-photos:before{content:'\f285'}.zmdi-sort-by-alpha:before{content:'\f1cf'}.zmdi-folder-shared:before{content:'\f225'}.zmdi-folder-special:before{content:'\f226'}.zmdi-comment-dots:before{content:'\f260'}.zmdi-reorder:before{content:'\f31e'}.zmdi-dehaze:before{content:'\f197'}.zmdi-sort:before{content:'\f1ce'}.zmdi-pages:before{content:'\f34a'}.zmdi-stack-overflow:before{content:'\f35c'}.zmdi-calendar-account:before{content:'\f204'}.zmdi-paste:before{content:'\f109'}.zmdi-cut:before{content:'\f1bc'}.zmdi-save:before{content:'\f297'}.zmdi-smartphone-code:before{content:'\f139'}.zmdi-directions-bike:before{content:'\f117'}.zmdi-directions-boat:before{content:'\f11a'}.zmdi-directions-bus:before{content:'\f121'}.zmdi-directions-car:before{content:'\f125'}.zmdi-directions-railway:before{content:'\f1b3'}.zmdi-directions-run:before{content:'\f215'}.zmdi-directions-subway:before{content:'\f1d5'}.zmdi-directions-walk:before{content:'\f216'}.zmdi-local-hotel:before{content:'\f178'}.zmdi-local-activity:before{content:'\f1df'}.zmdi-local-play:before{content:'\f1df'}.zmdi-local-airport:before{content:'\f103'}.zmdi-local-atm:before{content:'\f198'}.zmdi-local-bar:before{content:'\f137'}.zmdi-local-cafe:before{content:'\f13b'}.zmdi-local-car-wash:before{content:'\f124'}.zmdi-local-convenience-store:before{content:'\f1d3'}.zmdi-local-dining:before{content:'\f153'}.zmdi-local-drink:before{content:'\f157'}.zmdi-local-florist:before{content:'\f168'}.zmdi-local-gas-station:before{content:'\f16f'}.zmdi-local-grocery-store:before{content:'\f1cb'}.zmdi-local-hospital:before{content:'\f177'}.zmdi-local-laundry-service:before{content:'\f1e9'}.zmdi-local-library:before{content:'\f18d'}.zmdi-local-mall:before{content:'\f195'}.zmdi-local-movies:before{content:'\f19d'}.zmdi-local-offer:before{content:'\f187'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-pharmacy:before{content:'\f176'}.zmdi-local-phone:before{content:'\f2be'}.zmdi-local-pizza:before{content:'\f1ac'}.zmdi-local-post-office:before{content:'\f15a'}.zmdi-local-printshop:before{content:'\f1b0'}.zmdi-local-see:before{content:'\f28c'}.zmdi-local-shipping:before{content:'\f1e6'}.zmdi-local-store:before{content:'\f1d4'}.zmdi-local-taxi:before{content:'\f123'}.zmdi-local-wc:before{content:'\f211'}.zmdi-my-location:before{content:'\f299'}.zmdi-directions:before{content:'\f1e7'} \ No newline at end of file diff --git a/packages/website/public/css/roboto.css b/packages/website/public/css/roboto.css new file mode 100644 index 000000000..7af568a74 --- /dev/null +++ b/packages/website/public/css/roboto.css @@ -0,0 +1,83 @@ +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +}*/ diff --git a/packages/website/public/css/roboto_mono.css b/packages/website/public/css/roboto_mono.css new file mode 100644 index 000000000..f8159d35f --- /dev/null +++ b/packages/website/public/css/roboto_mono.css @@ -0,0 +1,69 @@ +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +}*/ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.eot b/packages/website/public/fonts/Material-Design-Iconic-Font.eot new file mode 100755 index 000000000..5e2519150 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.eot differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.svg b/packages/website/public/fonts/Material-Design-Iconic-Font.svg new file mode 100755 index 000000000..1d3d2eaa2 --- /dev/null +++ b/packages/website/public/fonts/Material-Design-Iconic-Font.svg @@ -0,0 +1,787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.ttf b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf new file mode 100755 index 000000000..5d489fdd1 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff b/packages/website/public/fonts/Material-Design-Iconic-Font.woff new file mode 100755 index 000000000..933b2bf85 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.woff differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 new file mode 100755 index 000000000..35970e277 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 differ diff --git a/packages/website/public/fonts/Roboto-Black.ttf b/packages/website/public/fonts/Roboto-Black.ttf new file mode 100755 index 000000000..689fe5cb3 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Black.ttf differ diff --git a/packages/website/public/fonts/Roboto-BlackItalic.ttf b/packages/website/public/fonts/Roboto-BlackItalic.ttf new file mode 100755 index 000000000..0b4e0ee10 Binary files /dev/null and b/packages/website/public/fonts/Roboto-BlackItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Bold.ttf b/packages/website/public/fonts/Roboto-Bold.ttf new file mode 100755 index 000000000..d3f01ad24 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Bold.ttf differ diff --git a/packages/website/public/fonts/Roboto-BoldItalic.ttf b/packages/website/public/fonts/Roboto-BoldItalic.ttf new file mode 100755 index 000000000..41cc1e753 Binary files /dev/null and b/packages/website/public/fonts/Roboto-BoldItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Italic.ttf b/packages/website/public/fonts/Roboto-Italic.ttf new file mode 100755 index 000000000..6a1cee5b2 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Italic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Light.ttf b/packages/website/public/fonts/Roboto-Light.ttf new file mode 100755 index 000000000..219063a57 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Light.ttf differ diff --git a/packages/website/public/fonts/Roboto-LightItalic.ttf b/packages/website/public/fonts/Roboto-LightItalic.ttf new file mode 100755 index 000000000..0e81e876f Binary files /dev/null and b/packages/website/public/fonts/Roboto-LightItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Medium.ttf b/packages/website/public/fonts/Roboto-Medium.ttf new file mode 100755 index 000000000..1a7f3b0bb Binary files /dev/null and b/packages/website/public/fonts/Roboto-Medium.ttf differ diff --git a/packages/website/public/fonts/Roboto-MediumItalic.ttf b/packages/website/public/fonts/Roboto-MediumItalic.ttf new file mode 100755 index 000000000..003029527 Binary files /dev/null and b/packages/website/public/fonts/Roboto-MediumItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Regular.ttf b/packages/website/public/fonts/Roboto-Regular.ttf new file mode 100755 index 000000000..2c97eeadf Binary files /dev/null and b/packages/website/public/fonts/Roboto-Regular.ttf differ diff --git a/packages/website/public/fonts/Roboto-Thin.ttf b/packages/website/public/fonts/Roboto-Thin.ttf new file mode 100755 index 000000000..b74a4fd1a Binary files /dev/null and b/packages/website/public/fonts/Roboto-Thin.ttf differ diff --git a/packages/website/public/fonts/Roboto-ThinItalic.ttf b/packages/website/public/fonts/Roboto-ThinItalic.ttf new file mode 100755 index 000000000..dd0ddb852 Binary files /dev/null and b/packages/website/public/fonts/Roboto-ThinItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Bold.ttf b/packages/website/public/fonts/RobotoMono-Bold.ttf new file mode 100755 index 000000000..07ef607d5 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Bold.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-BoldItalic.ttf b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf new file mode 100755 index 000000000..1cca0bf45 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Italic.ttf b/packages/website/public/fonts/RobotoMono-Italic.ttf new file mode 100755 index 000000000..ef92c372c Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Italic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Light.ttf b/packages/website/public/fonts/RobotoMono-Light.ttf new file mode 100755 index 000000000..63229b280 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Light.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-LightItalic.ttf b/packages/website/public/fonts/RobotoMono-LightItalic.ttf new file mode 100755 index 000000000..f25bed56a Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-LightItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Medium.ttf b/packages/website/public/fonts/RobotoMono-Medium.ttf new file mode 100755 index 000000000..88ff0c15a Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Medium.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-MediumItalic.ttf b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf new file mode 100755 index 000000000..307efad8f Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Regular.ttf b/packages/website/public/fonts/RobotoMono-Regular.ttf new file mode 100755 index 000000000..b158a334e Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Regular.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Thin.ttf b/packages/website/public/fonts/RobotoMono-Thin.ttf new file mode 100755 index 000000000..309484d32 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Thin.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-ThinItalic.ttf b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf new file mode 100755 index 000000000..e1bb9121e Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf differ diff --git a/packages/website/public/gifs/0xAnimation.gif b/packages/website/public/gifs/0xAnimation.gif new file mode 100644 index 000000000..b3e32a6ad Binary files /dev/null and b/packages/website/public/gifs/0xAnimation.gif differ diff --git a/packages/website/public/gifs/genesis.gif b/packages/website/public/gifs/genesis.gif new file mode 100644 index 000000000..009ecf2f8 Binary files /dev/null and b/packages/website/public/gifs/genesis.gif differ diff --git a/packages/website/public/images/0x_logo.png b/packages/website/public/images/0x_logo.png new file mode 100644 index 000000000..7b7eafe7d Binary files /dev/null and b/packages/website/public/images/0x_logo.png differ diff --git a/packages/website/public/images/advisors/fred.jpg b/packages/website/public/images/advisors/fred.jpg new file mode 100644 index 000000000..f3b37e2d9 Binary files /dev/null and b/packages/website/public/images/advisors/fred.jpg differ diff --git a/packages/website/public/images/advisors/joey.jpg b/packages/website/public/images/advisors/joey.jpg new file mode 100644 index 000000000..daccc9b55 Binary files /dev/null and b/packages/website/public/images/advisors/joey.jpg differ diff --git a/packages/website/public/images/advisors/linda.jpg b/packages/website/public/images/advisors/linda.jpg new file mode 100644 index 000000000..1ee59d301 Binary files /dev/null and b/packages/website/public/images/advisors/linda.jpg differ diff --git a/packages/website/public/images/advisors/olaf.png b/packages/website/public/images/advisors/olaf.png new file mode 100644 index 000000000..d1715081f Binary files /dev/null and b/packages/website/public/images/advisors/olaf.png differ diff --git a/packages/website/public/images/ether.png b/packages/website/public/images/ether.png new file mode 100644 index 000000000..6a40a976d Binary files /dev/null and b/packages/website/public/images/ether.png differ diff --git a/packages/website/public/images/favicon/favicon-2-16x16.png b/packages/website/public/images/favicon/favicon-2-16x16.png new file mode 100755 index 000000000..68c493c4f Binary files /dev/null and b/packages/website/public/images/favicon/favicon-2-16x16.png differ diff --git a/packages/website/public/images/favicon/favicon-2-32x32.png b/packages/website/public/images/favicon/favicon-2-32x32.png new file mode 100755 index 000000000..a5abb0eb3 Binary files /dev/null and b/packages/website/public/images/favicon/favicon-2-32x32.png differ diff --git a/packages/website/public/images/favicon/favicon.ico b/packages/website/public/images/favicon/favicon.ico new file mode 100755 index 000000000..b7ada2a1c Binary files /dev/null and b/packages/website/public/images/favicon/favicon.ico differ diff --git a/packages/website/public/images/landing/0x_chips.png b/packages/website/public/images/landing/0x_chips.png new file mode 100644 index 000000000..2e79637b0 Binary files /dev/null and b/packages/website/public/images/landing/0x_chips.png differ diff --git a/packages/website/public/images/landing/aragon.png b/packages/website/public/images/landing/aragon.png new file mode 100644 index 000000000..360b03077 Binary files /dev/null and b/packages/website/public/images/landing/aragon.png differ diff --git a/packages/website/public/images/landing/augur.png b/packages/website/public/images/landing/augur.png new file mode 100644 index 000000000..60a2ee86b Binary files /dev/null and b/packages/website/public/images/landing/augur.png differ diff --git a/packages/website/public/images/landing/currency.png b/packages/website/public/images/landing/currency.png new file mode 100644 index 000000000..280995861 Binary files /dev/null and b/packages/website/public/images/landing/currency.png differ diff --git a/packages/website/public/images/landing/dharma.png b/packages/website/public/images/landing/dharma.png new file mode 100644 index 000000000..5aa464c20 Binary files /dev/null and b/packages/website/public/images/landing/dharma.png differ diff --git a/packages/website/public/images/landing/digital_goods.png b/packages/website/public/images/landing/digital_goods.png new file mode 100644 index 000000000..bbe848441 Binary files /dev/null and b/packages/website/public/images/landing/digital_goods.png differ diff --git a/packages/website/public/images/landing/distributed_network.png b/packages/website/public/images/landing/distributed_network.png new file mode 100644 index 000000000..8dbb23083 Binary files /dev/null and b/packages/website/public/images/landing/distributed_network.png differ diff --git a/packages/website/public/images/landing/ethfinex.png b/packages/website/public/images/landing/ethfinex.png new file mode 100644 index 000000000..53e7913d6 Binary files /dev/null and b/packages/website/public/images/landing/ethfinex.png differ diff --git a/packages/website/public/images/landing/fund_management_icon.png b/packages/website/public/images/landing/fund_management_icon.png new file mode 100644 index 000000000..c45061469 Binary files /dev/null and b/packages/website/public/images/landing/fund_management_icon.png differ diff --git a/packages/website/public/images/landing/gnosis.png b/packages/website/public/images/landing/gnosis.png new file mode 100644 index 000000000..b9dd94f52 Binary files /dev/null and b/packages/website/public/images/landing/gnosis.png differ diff --git a/packages/website/public/images/landing/governance_icon.png b/packages/website/public/images/landing/governance_icon.png new file mode 100644 index 000000000..d21dc313d Binary files /dev/null and b/packages/website/public/images/landing/governance_icon.png differ diff --git a/packages/website/public/images/landing/hero_chip_image.png b/packages/website/public/images/landing/hero_chip_image.png new file mode 100644 index 000000000..447489aa7 Binary files /dev/null and b/packages/website/public/images/landing/hero_chip_image.png differ diff --git a/packages/website/public/images/landing/lendroid.png b/packages/website/public/images/landing/lendroid.png new file mode 100644 index 000000000..3b8d0c86c Binary files /dev/null and b/packages/website/public/images/landing/lendroid.png differ diff --git a/packages/website/public/images/landing/liquidity.png b/packages/website/public/images/landing/liquidity.png new file mode 100644 index 000000000..dc005dd13 Binary files /dev/null and b/packages/website/public/images/landing/liquidity.png differ diff --git a/packages/website/public/images/landing/loans_icon.png b/packages/website/public/images/landing/loans_icon.png new file mode 100644 index 000000000..b10bd83e9 Binary files /dev/null and b/packages/website/public/images/landing/loans_icon.png differ diff --git a/packages/website/public/images/landing/maker.png b/packages/website/public/images/landing/maker.png new file mode 100644 index 000000000..f7a04191e Binary files /dev/null and b/packages/website/public/images/landing/maker.png differ diff --git a/packages/website/public/images/landing/melonport.png b/packages/website/public/images/landing/melonport.png new file mode 100644 index 000000000..bdb95707c Binary files /dev/null and b/packages/website/public/images/landing/melonport.png differ diff --git a/packages/website/public/images/landing/open_source.png b/packages/website/public/images/landing/open_source.png new file mode 100644 index 000000000..2c280742a Binary files /dev/null and b/packages/website/public/images/landing/open_source.png differ diff --git a/packages/website/public/images/landing/paradex.png b/packages/website/public/images/landing/paradex.png new file mode 100644 index 000000000..f6a1e479f Binary files /dev/null and b/packages/website/public/images/landing/paradex.png differ diff --git a/packages/website/public/images/landing/prediction_market_icon.png b/packages/website/public/images/landing/prediction_market_icon.png new file mode 100644 index 000000000..063595c49 Binary files /dev/null and b/packages/website/public/images/landing/prediction_market_icon.png differ diff --git a/packages/website/public/images/landing/project_logos/anx.png b/packages/website/public/images/landing/project_logos/anx.png new file mode 100644 index 000000000..fa04ce327 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/anx.png differ diff --git a/packages/website/public/images/landing/project_logos/aragon.png b/packages/website/public/images/landing/project_logos/aragon.png new file mode 100644 index 000000000..b0cf805d3 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/aragon.png differ diff --git a/packages/website/public/images/landing/project_logos/auctus.png b/packages/website/public/images/landing/project_logos/auctus.png new file mode 100644 index 000000000..2bdc9a42c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/auctus.png differ diff --git a/packages/website/public/images/landing/project_logos/augur.png b/packages/website/public/images/landing/project_logos/augur.png new file mode 100644 index 000000000..47d4f8465 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/augur.png differ diff --git a/packages/website/public/images/landing/project_logos/blocknet.png b/packages/website/public/images/landing/project_logos/blocknet.png new file mode 100644 index 000000000..96aa97953 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/blocknet.png differ diff --git a/packages/website/public/images/landing/project_logos/chronobank.png b/packages/website/public/images/landing/project_logos/chronobank.png new file mode 100644 index 000000000..ad5ff0e5b Binary files /dev/null and b/packages/website/public/images/landing/project_logos/chronobank.png differ diff --git a/packages/website/public/images/landing/project_logos/dharma.png b/packages/website/public/images/landing/project_logos/dharma.png new file mode 100644 index 000000000..a74736ca2 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/dharma.png differ diff --git a/packages/website/public/images/landing/project_logos/district0x.png b/packages/website/public/images/landing/project_logos/district0x.png new file mode 100644 index 000000000..12aa310f0 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/district0x.png differ diff --git a/packages/website/public/images/landing/project_logos/dydx.png b/packages/website/public/images/landing/project_logos/dydx.png new file mode 100644 index 000000000..f2525292c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/dydx.png differ diff --git a/packages/website/public/images/landing/project_logos/ethfinex-top.png b/packages/website/public/images/landing/project_logos/ethfinex-top.png new file mode 100644 index 000000000..5eda914ca Binary files /dev/null and b/packages/website/public/images/landing/project_logos/ethfinex-top.png differ diff --git a/packages/website/public/images/landing/project_logos/ethix.png b/packages/website/public/images/landing/project_logos/ethix.png new file mode 100644 index 000000000..9b202502c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/ethix.png differ diff --git a/packages/website/public/images/landing/project_logos/lendroid.png b/packages/website/public/images/landing/project_logos/lendroid.png new file mode 100644 index 000000000..a5e1eb51e Binary files /dev/null and b/packages/website/public/images/landing/project_logos/lendroid.png differ diff --git a/packages/website/public/images/landing/project_logos/maker.png b/packages/website/public/images/landing/project_logos/maker.png new file mode 100644 index 000000000..d3bb927a5 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/maker.png differ diff --git a/packages/website/public/images/landing/project_logos/melonport.png b/packages/website/public/images/landing/project_logos/melonport.png new file mode 100644 index 000000000..9533faee2 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/melonport.png differ diff --git a/packages/website/public/images/landing/project_logos/paradex_top.png b/packages/website/public/images/landing/project_logos/paradex_top.png new file mode 100644 index 000000000..3fe9472b9 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/paradex_top.png differ diff --git a/packages/website/public/images/landing/project_logos/radar_relay_top.png b/packages/website/public/images/landing/project_logos/radar_relay_top.png new file mode 100644 index 000000000..737159959 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/radar_relay_top.png differ diff --git a/packages/website/public/images/landing/project_logos/status.png b/packages/website/public/images/landing/project_logos/status.png new file mode 100644 index 000000000..24c7e177a Binary files /dev/null and b/packages/website/public/images/landing/project_logos/status.png differ diff --git a/packages/website/public/images/landing/project_logos/the_ocean.png b/packages/website/public/images/landing/project_logos/the_ocean.png new file mode 100644 index 000000000..74678b5d4 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/the_ocean.png differ diff --git a/packages/website/public/images/landing/radar_relay.png b/packages/website/public/images/landing/radar_relay.png new file mode 100644 index 000000000..297f55cfb Binary files /dev/null and b/packages/website/public/images/landing/radar_relay.png differ diff --git a/packages/website/public/images/landing/relayer_diagram.png b/packages/website/public/images/landing/relayer_diagram.png new file mode 100644 index 000000000..44c4ef707 Binary files /dev/null and b/packages/website/public/images/landing/relayer_diagram.png differ diff --git a/packages/website/public/images/landing/stable_tokens_icon.png b/packages/website/public/images/landing/stable_tokens_icon.png new file mode 100644 index 000000000..40e372606 Binary files /dev/null and b/packages/website/public/images/landing/stable_tokens_icon.png differ diff --git a/packages/website/public/images/landing/stocks.png b/packages/website/public/images/landing/stocks.png new file mode 100644 index 000000000..e244cd0c5 Binary files /dev/null and b/packages/website/public/images/landing/stocks.png differ diff --git a/packages/website/public/images/landing/tokenized_world.png b/packages/website/public/images/landing/tokenized_world.png new file mode 100644 index 000000000..b284eaa88 Binary files /dev/null and b/packages/website/public/images/landing/tokenized_world.png differ diff --git a/packages/website/public/images/loading_poster.png b/packages/website/public/images/loading_poster.png new file mode 100644 index 000000000..e5618f260 Binary files /dev/null and b/packages/website/public/images/loading_poster.png differ diff --git a/packages/website/public/images/logos/FBG.png b/packages/website/public/images/logos/FBG.png new file mode 100644 index 000000000..0a91bcabc Binary files /dev/null and b/packages/website/public/images/logos/FBG.png differ diff --git a/packages/website/public/images/logos/aragon.png b/packages/website/public/images/logos/aragon.png new file mode 100644 index 000000000..db4d81905 Binary files /dev/null and b/packages/website/public/images/logos/aragon.png differ diff --git a/packages/website/public/images/logos/augur.png b/packages/website/public/images/logos/augur.png new file mode 100644 index 000000000..709da2f1a Binary files /dev/null and b/packages/website/public/images/logos/augur.png differ diff --git a/packages/website/public/images/logos/blockchain_capital.png b/packages/website/public/images/logos/blockchain_capital.png new file mode 100644 index 000000000..42fdcbfa1 Binary files /dev/null and b/packages/website/public/images/logos/blockchain_capital.png differ diff --git a/packages/website/public/images/logos/chronobank.png b/packages/website/public/images/logos/chronobank.png new file mode 100644 index 000000000..f94aa3fee Binary files /dev/null and b/packages/website/public/images/logos/chronobank.png differ diff --git a/packages/website/public/images/logos/dharma.png b/packages/website/public/images/logos/dharma.png new file mode 100644 index 000000000..65d902a1e Binary files /dev/null and b/packages/website/public/images/logos/dharma.png differ diff --git a/packages/website/public/images/logos/district0x.png b/packages/website/public/images/logos/district0x.png new file mode 100644 index 000000000..e2b4c7e4a Binary files /dev/null and b/packages/website/public/images/logos/district0x.png differ diff --git a/packages/website/public/images/logos/jen_advisors.png b/packages/website/public/images/logos/jen_advisors.png new file mode 100644 index 000000000..de0395d3d Binary files /dev/null and b/packages/website/public/images/logos/jen_advisors.png differ diff --git a/packages/website/public/images/logos/maker.png b/packages/website/public/images/logos/maker.png new file mode 100644 index 000000000..48c08d15d Binary files /dev/null and b/packages/website/public/images/logos/maker.png differ diff --git a/packages/website/public/images/logos/melonport.png b/packages/website/public/images/logos/melonport.png new file mode 100644 index 000000000..b973e081f Binary files /dev/null and b/packages/website/public/images/logos/melonport.png differ diff --git a/packages/website/public/images/logos/openANX.png b/packages/website/public/images/logos/openANX.png new file mode 100644 index 000000000..e0167257f Binary files /dev/null and b/packages/website/public/images/logos/openANX.png differ diff --git a/packages/website/public/images/logos/pantera_capital.png b/packages/website/public/images/logos/pantera_capital.png new file mode 100644 index 000000000..9cffdf39f Binary files /dev/null and b/packages/website/public/images/logos/pantera_capital.png differ diff --git a/packages/website/public/images/logos/polychain_capital.png b/packages/website/public/images/logos/polychain_capital.png new file mode 100644 index 000000000..2b7782134 Binary files /dev/null and b/packages/website/public/images/logos/polychain_capital.png differ diff --git a/packages/website/public/images/og_image.png b/packages/website/public/images/og_image.png new file mode 100644 index 000000000..74265bdf8 Binary files /dev/null and b/packages/website/public/images/og_image.png differ diff --git a/packages/website/public/images/protocol_logo_black.png b/packages/website/public/images/protocol_logo_black.png new file mode 100644 index 000000000..36a905d5a Binary files /dev/null and b/packages/website/public/images/protocol_logo_black.png differ diff --git a/packages/website/public/images/protocol_logo_white.png b/packages/website/public/images/protocol_logo_white.png new file mode 100644 index 000000000..a405ad6d5 Binary files /dev/null and b/packages/website/public/images/protocol_logo_white.png differ diff --git a/packages/website/public/images/social/github.png b/packages/website/public/images/social/github.png new file mode 100644 index 000000000..2c2a3e918 Binary files /dev/null and b/packages/website/public/images/social/github.png differ diff --git a/packages/website/public/images/social/medium.png b/packages/website/public/images/social/medium.png new file mode 100644 index 000000000..11e8f2c44 Binary files /dev/null and b/packages/website/public/images/social/medium.png differ diff --git a/packages/website/public/images/social/reddit.png b/packages/website/public/images/social/reddit.png new file mode 100644 index 000000000..3fbe6229a Binary files /dev/null and b/packages/website/public/images/social/reddit.png differ diff --git a/packages/website/public/images/social/rocketchat.png b/packages/website/public/images/social/rocketchat.png new file mode 100644 index 000000000..58ff8d293 Binary files /dev/null and b/packages/website/public/images/social/rocketchat.png differ diff --git a/packages/website/public/images/social/slack.png b/packages/website/public/images/social/slack.png new file mode 100644 index 000000000..c4b2d7b81 Binary files /dev/null and b/packages/website/public/images/social/slack.png differ diff --git a/packages/website/public/images/social/twitter.png b/packages/website/public/images/social/twitter.png new file mode 100644 index 000000000..fe0d691a9 Binary files /dev/null and b/packages/website/public/images/social/twitter.png differ diff --git a/packages/website/public/images/team/alex.jpg b/packages/website/public/images/team/alex.jpg new file mode 100644 index 000000000..ae6888804 Binary files /dev/null and b/packages/website/public/images/team/alex.jpg differ diff --git a/packages/website/public/images/team/amir.jpeg b/packages/website/public/images/team/amir.jpeg new file mode 100644 index 000000000..7ee16263a Binary files /dev/null and b/packages/website/public/images/team/amir.jpeg differ diff --git a/packages/website/public/images/team/anyone.png b/packages/website/public/images/team/anyone.png new file mode 100644 index 000000000..4de26b0ce Binary files /dev/null and b/packages/website/public/images/team/anyone.png differ diff --git a/packages/website/public/images/team/ben.jpg b/packages/website/public/images/team/ben.jpg new file mode 100644 index 000000000..b42d0a42a Binary files /dev/null and b/packages/website/public/images/team/ben.jpg differ diff --git a/packages/website/public/images/team/brandon.png b/packages/website/public/images/team/brandon.png new file mode 100644 index 000000000..ebd3cf101 Binary files /dev/null and b/packages/website/public/images/team/brandon.png differ diff --git a/packages/website/public/images/team/fabio.jpg b/packages/website/public/images/team/fabio.jpg new file mode 100644 index 000000000..da87a9e95 Binary files /dev/null and b/packages/website/public/images/team/fabio.jpg differ diff --git a/packages/website/public/images/team/leonid.png b/packages/website/public/images/team/leonid.png new file mode 100644 index 000000000..4acbf87c8 Binary files /dev/null and b/packages/website/public/images/team/leonid.png differ diff --git a/packages/website/public/images/team/philippe.png b/packages/website/public/images/team/philippe.png new file mode 100644 index 000000000..cd43c2754 Binary files /dev/null and b/packages/website/public/images/team/philippe.png differ diff --git a/packages/website/public/images/team/will.jpg b/packages/website/public/images/team/will.jpg new file mode 100644 index 000000000..7de028032 Binary files /dev/null and b/packages/website/public/images/team/will.jpg differ diff --git a/packages/website/public/images/token_icons/adtoken.png b/packages/website/public/images/token_icons/adtoken.png new file mode 100644 index 000000000..59290af6b Binary files /dev/null and b/packages/website/public/images/token_icons/adtoken.png differ diff --git a/packages/website/public/images/token_icons/aragon.png b/packages/website/public/images/token_icons/aragon.png new file mode 100644 index 000000000..d162aab24 Binary files /dev/null and b/packages/website/public/images/token_icons/aragon.png differ diff --git a/packages/website/public/images/token_icons/augur.png b/packages/website/public/images/token_icons/augur.png new file mode 100644 index 000000000..31c257ba9 Binary files /dev/null and b/packages/website/public/images/token_icons/augur.png differ diff --git a/packages/website/public/images/token_icons/bancor.png b/packages/website/public/images/token_icons/bancor.png new file mode 100644 index 000000000..d2b2fa472 Binary files /dev/null and b/packages/website/public/images/token_icons/bancor.png differ diff --git a/packages/website/public/images/token_icons/basicattentiontoken.png b/packages/website/public/images/token_icons/basicattentiontoken.png new file mode 100644 index 000000000..77e7dfb1f Binary files /dev/null and b/packages/website/public/images/token_icons/basicattentiontoken.png differ diff --git a/packages/website/public/images/token_icons/bitquence.png b/packages/website/public/images/token_icons/bitquence.png new file mode 100644 index 000000000..d8a2c6960 Binary files /dev/null and b/packages/website/public/images/token_icons/bitquence.png differ diff --git a/packages/website/public/images/token_icons/btc.png b/packages/website/public/images/token_icons/btc.png new file mode 100644 index 000000000..1d9fc8347 Binary files /dev/null and b/packages/website/public/images/token_icons/btc.png differ diff --git a/packages/website/public/images/token_icons/civic.png b/packages/website/public/images/token_icons/civic.png new file mode 100644 index 000000000..1daf28d00 Binary files /dev/null and b/packages/website/public/images/token_icons/civic.png differ diff --git a/packages/website/public/images/token_icons/clams.png b/packages/website/public/images/token_icons/clams.png new file mode 100644 index 000000000..04c2ba7d3 Binary files /dev/null and b/packages/website/public/images/token_icons/clams.png differ diff --git a/packages/website/public/images/token_icons/cofound-it.png b/packages/website/public/images/token_icons/cofound-it.png new file mode 100644 index 000000000..7bccd6248 Binary files /dev/null and b/packages/website/public/images/token_icons/cofound-it.png differ diff --git a/packages/website/public/images/token_icons/default.png b/packages/website/public/images/token_icons/default.png new file mode 100644 index 000000000..7ce754030 Binary files /dev/null and b/packages/website/public/images/token_icons/default.png differ diff --git a/packages/website/public/images/token_icons/digixdao.png b/packages/website/public/images/token_icons/digixdao.png new file mode 100644 index 000000000..f292db716 Binary files /dev/null and b/packages/website/public/images/token_icons/digixdao.png differ diff --git a/packages/website/public/images/token_icons/district0x.png b/packages/website/public/images/token_icons/district0x.png new file mode 100644 index 000000000..edb8f2238 Binary files /dev/null and b/packages/website/public/images/token_icons/district0x.png differ diff --git a/packages/website/public/images/token_icons/edgeless.png b/packages/website/public/images/token_icons/edgeless.png new file mode 100644 index 000000000..606784154 Binary files /dev/null and b/packages/website/public/images/token_icons/edgeless.png differ diff --git a/packages/website/public/images/token_icons/eos.png b/packages/website/public/images/token_icons/eos.png new file mode 100644 index 000000000..a08f3c042 Binary files /dev/null and b/packages/website/public/images/token_icons/eos.png differ diff --git a/packages/website/public/images/token_icons/ether_erc20.png b/packages/website/public/images/token_icons/ether_erc20.png new file mode 100644 index 000000000..f4154db7b Binary files /dev/null and b/packages/website/public/images/token_icons/ether_erc20.png differ diff --git a/packages/website/public/images/token_icons/etheroll.png b/packages/website/public/images/token_icons/etheroll.png new file mode 100644 index 000000000..89dd5e04b Binary files /dev/null and b/packages/website/public/images/token_icons/etheroll.png differ diff --git a/packages/website/public/images/token_icons/firstblood.jpg b/packages/website/public/images/token_icons/firstblood.jpg new file mode 100644 index 000000000..64bb65605 Binary files /dev/null and b/packages/website/public/images/token_icons/firstblood.jpg differ diff --git a/packages/website/public/images/token_icons/funfair.png b/packages/website/public/images/token_icons/funfair.png new file mode 100644 index 000000000..1b7c67ec6 Binary files /dev/null and b/packages/website/public/images/token_icons/funfair.png differ diff --git a/packages/website/public/images/token_icons/gnosis.png b/packages/website/public/images/token_icons/gnosis.png new file mode 100644 index 000000000..0111846d0 Binary files /dev/null and b/packages/website/public/images/token_icons/gnosis.png differ diff --git a/packages/website/public/images/token_icons/golem.png b/packages/website/public/images/token_icons/golem.png new file mode 100644 index 000000000..e61a4367d Binary files /dev/null and b/packages/website/public/images/token_icons/golem.png differ diff --git a/packages/website/public/images/token_icons/iconomi.png b/packages/website/public/images/token_icons/iconomi.png new file mode 100644 index 000000000..3499e4765 Binary files /dev/null and b/packages/website/public/images/token_icons/iconomi.png differ diff --git a/packages/website/public/images/token_icons/iexec.png b/packages/website/public/images/token_icons/iexec.png new file mode 100644 index 000000000..ef4860457 Binary files /dev/null and b/packages/website/public/images/token_icons/iexec.png differ diff --git a/packages/website/public/images/token_icons/lunyr.png b/packages/website/public/images/token_icons/lunyr.png new file mode 100644 index 000000000..f77094ba5 Binary files /dev/null and b/packages/website/public/images/token_icons/lunyr.png differ diff --git a/packages/website/public/images/token_icons/makerdao.png b/packages/website/public/images/token_icons/makerdao.png new file mode 100644 index 000000000..adbc9f38c Binary files /dev/null and b/packages/website/public/images/token_icons/makerdao.png differ diff --git a/packages/website/public/images/token_icons/melon.png b/packages/website/public/images/token_icons/melon.png new file mode 100644 index 000000000..29f58e631 Binary files /dev/null and b/packages/website/public/images/token_icons/melon.png differ diff --git a/packages/website/public/images/token_icons/metal.png b/packages/website/public/images/token_icons/metal.png new file mode 100644 index 000000000..d8a8c33ec Binary files /dev/null and b/packages/website/public/images/token_icons/metal.png differ diff --git a/packages/website/public/images/token_icons/monaco.png b/packages/website/public/images/token_icons/monaco.png new file mode 100644 index 000000000..865341fd3 Binary files /dev/null and b/packages/website/public/images/token_icons/monaco.png differ diff --git a/packages/website/public/images/token_icons/numeraire.png b/packages/website/public/images/token_icons/numeraire.png new file mode 100644 index 000000000..698f7cfdd Binary files /dev/null and b/packages/website/public/images/token_icons/numeraire.png differ diff --git a/packages/website/public/images/token_icons/omisego.png b/packages/website/public/images/token_icons/omisego.png new file mode 100644 index 000000000..40a86b9d7 Binary files /dev/null and b/packages/website/public/images/token_icons/omisego.png differ diff --git a/packages/website/public/images/token_icons/qtum.png b/packages/website/public/images/token_icons/qtum.png new file mode 100644 index 000000000..97e227c5c Binary files /dev/null and b/packages/website/public/images/token_icons/qtum.png differ diff --git a/packages/website/public/images/token_icons/santiment.png b/packages/website/public/images/token_icons/santiment.png new file mode 100644 index 000000000..05ce98c1d Binary files /dev/null and b/packages/website/public/images/token_icons/santiment.png differ diff --git a/packages/website/public/images/token_icons/singularity.png b/packages/website/public/images/token_icons/singularity.png new file mode 100644 index 000000000..9db788935 Binary files /dev/null and b/packages/website/public/images/token_icons/singularity.png differ diff --git a/packages/website/public/images/token_icons/status.png b/packages/website/public/images/token_icons/status.png new file mode 100644 index 000000000..a73ba23ba Binary files /dev/null and b/packages/website/public/images/token_icons/status.png differ diff --git a/packages/website/public/images/token_icons/storjcoinx.png b/packages/website/public/images/token_icons/storjcoinx.png new file mode 100644 index 000000000..87c4d4292 Binary files /dev/null and b/packages/website/public/images/token_icons/storjcoinx.png differ diff --git a/packages/website/public/images/token_icons/taas.png b/packages/website/public/images/token_icons/taas.png new file mode 100644 index 000000000..4cca722f7 Binary files /dev/null and b/packages/website/public/images/token_icons/taas.png differ diff --git a/packages/website/public/images/token_icons/tenx.png b/packages/website/public/images/token_icons/tenx.png new file mode 100644 index 000000000..d9ffca043 Binary files /dev/null and b/packages/website/public/images/token_icons/tenx.png differ diff --git a/packages/website/public/images/token_icons/tokencard.png b/packages/website/public/images/token_icons/tokencard.png new file mode 100644 index 000000000..490c1be69 Binary files /dev/null and b/packages/website/public/images/token_icons/tokencard.png differ diff --git a/packages/website/public/images/token_icons/trust.png b/packages/website/public/images/token_icons/trust.png new file mode 100644 index 000000000..62b412b41 Binary files /dev/null and b/packages/website/public/images/token_icons/trust.png differ diff --git a/packages/website/public/images/token_icons/wings.png b/packages/website/public/images/token_icons/wings.png new file mode 100644 index 000000000..cd0eb4213 Binary files /dev/null and b/packages/website/public/images/token_icons/wings.png differ diff --git a/packages/website/public/images/token_icons/zero_ex.png b/packages/website/public/images/token_icons/zero_ex.png new file mode 100644 index 000000000..6c6deef11 Binary files /dev/null and b/packages/website/public/images/token_icons/zero_ex.png differ diff --git a/packages/website/public/images/trade_arrows.png b/packages/website/public/images/trade_arrows.png new file mode 100644 index 000000000..21a8e1881 Binary files /dev/null and b/packages/website/public/images/trade_arrows.png differ diff --git a/packages/website/public/images/zrx_pie_chart.png b/packages/website/public/images/zrx_pie_chart.png new file mode 100644 index 000000000..16f5126b9 Binary files /dev/null and b/packages/website/public/images/zrx_pie_chart.png differ diff --git a/packages/website/public/images/zrx_token.png b/packages/website/public/images/zrx_token.png new file mode 100644 index 000000000..8c71798de Binary files /dev/null and b/packages/website/public/images/zrx_token.png differ diff --git a/packages/website/public/index.html b/packages/website/public/index.html new file mode 100644 index 000000000..c6f2f666c --- /dev/null +++ b/packages/website/public/index.html @@ -0,0 +1,78 @@ + + + + + + + + + + + 0x: The Protocol for Trading Tokens + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + diff --git a/packages/website/public/js/rollbar.umd.nojson.min.js b/packages/website/public/js/rollbar.umd.nojson.min.js new file mode 100644 index 000000000..f2b05d00a --- /dev/null +++ b/packages/website/public/js/rollbar.umd.nojson.min.js @@ -0,0 +1 @@ +!function(e,r){if("object"==typeof exports&&"object"==typeof module)module.exports=r();else if("function"==typeof define&&define.amd)define([],r);else{var t=r();for(var n in t)("object"==typeof exports?exports:e)[n]=t[n]}}(this,function(){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){e.exports=t(1)},function(e,r,t){"use strict";function n(){var e="undefined"==typeof JSON?{}:JSON;o.setupJSON(e)}var o=t(2),i=t(3);n();var a=window._rollbarConfig,s=a&&a.globalAlias||"Rollbar",u=window[s]&&"undefined"!=typeof window[s].shimId;!u&&a?o.wrapper.init(a):(window.Rollbar=o.wrapper,window.RollbarNotifier=i.Notifier),e.exports=o.wrapper},function(e,r,t){"use strict";function n(e,r,t){!t[4]&&window._rollbarWrappedError&&(t[4]=window._rollbarWrappedError,window._rollbarWrappedError=null),e.uncaughtError.apply(e,t),r&&r.apply(window,t)}function o(e,r){if(r.hasOwnProperty&&r.hasOwnProperty("addEventListener")){var t=r.addEventListener;r.addEventListener=function(r,n,o){t.call(this,r,e.wrap(n),o)};var n=r.removeEventListener;r.removeEventListener=function(e,r,t){n.call(this,e,r&&r._wrapped||r,t)}}}var i=t(3),a=t(8),s=i.Notifier;window._rollbarWrappedError=null;var u={};u.init=function(e,r){var t=new s(r);if(t.configure(e),e.captureUncaught){var i;r&&a.isType(r._rollbarOldOnError,"function")?i=r._rollbarOldOnError:window.onerror&&!window.onerror.belongsToShim&&(i=window.onerror),window.onerror=function(){var e=Array.prototype.slice.call(arguments,0);n(t,i,e)};var u,c,l=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"];for(u=0;u=6e4&&(L=i,R=0);var a=window._globalRollbarOptions.maxItems,c=window._globalRollbarOptions.itemsPerMinute,l=function(){return!n.ignoreRateLimit&&a>=1&&T>=a},p=function(){return!n.ignoreRateLimit&&c>=1&&R>=c};return l()?void o(new Error(a+" max items reached")):p()?void o(new Error(c+" items per minute reached")):(T++,R++,l()&&_._log(_.options.uncaughtErrorLevel,"maxItems has been hit. Ignoring errors for the remainder of the current page load.",null,{maxItems:a},null,!1,!0),n.ignoreRateLimit&&delete n.ignoreRateLimit,void y.post(r,t,n,function(r,t){return r?(r instanceof b&&(e.callback=function(){},setTimeout(function(){u(e)},s.RETRY_DELAY)),o(r)):o(null,t)}))}var h,g=t(4),m=t(5),v=t(8),w=t(11),y=w.XHR,b=w.ConnectionError,E=null;s.NOTIFIER_VERSION="1.9.4",s.DEFAULT_ENDPOINT="api.rollbar.com/api/1/",s.DEFAULT_SCRUB_FIELDS=["pw","pass","passwd","password","secret","confirm_password","confirmPassword","password_confirmation","passwordConfirmation","access_token","accessToken","secret_key","secretKey","secretToken"],s.DEFAULT_LOG_LEVEL="debug",s.DEFAULT_REPORT_LEVEL="debug",s.DEFAULT_UNCAUGHT_ERROR_LEVEL="error",s.DEFAULT_ITEMS_PER_MIN=60,s.DEFAULT_MAX_ITEMS=0,s.LEVELS={debug:0,info:1,warning:2,error:3,critical:4},s.RETRY_DELAY=1e4,window._rollbarPayloadQueue=window._rollbarPayloadQueue||[],window._globalRollbarOptions={startTime:(new Date).getTime(),maxItems:s.DEFAULT_MAX_ITEMS,itemsPerMinute:s.DEFAULT_ITEMS_PER_MIN};var _,x=s.prototype;x._getLogArgs=function(e){for(var r,t,n,i,a,u,c=this.options.logLevel||s.DEFAULT_LOG_LEVEL,l=[],p=0;p-1&&(r=r.replace(/eval code/g,"eval").replace(/(\(eval at [^\()]*)|(\)\,.*$)/g,""));var t=r.replace(/^\s+/,"").replace(/\(eval code/g,"(").split(/\s+/).slice(1),n=this.extractLocation(t.pop()),o=t.join(" ")||void 0,i="eval"===n[0]?void 0:n[0];return new e(o,void 0,i,n[1],n[2],r)},this)},parseFFOrSafari:function(n){var o=t(n.stack.split("\n"),function(e){return!e.match(i)},this);return r(o,function(r){if(r.indexOf(" > eval")>-1&&(r=r.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g,":$1")),r.indexOf("@")===-1&&r.indexOf(":")===-1)return new e(r);var t=r.split("@"),n=this.extractLocation(t.pop()),o=t.shift()||void 0;return new e(o,void 0,n[0],n[1],n[2],r)},this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(r){for(var t=/Line (\d+).*script (?:in )?(\S+)/i,n=r.message.split("\n"),o=[],i=2,a=n.length;i/,"$2").replace(/\([^\)]*\)/g,"")||void 0;i.match(/\(([^\)]*)\)/)&&(t=i.replace(/^[^\(]+\(([^\)]*)\)$/,"$1"));var s=void 0===t||"[arguments not available]"===t?void 0:t.split(",");return new e(a,s,o[0],o[1],o[2],r)},this)}}})},function(e,r,t){var n,o,i;!function(t,a){"use strict";o=[],n=a,i="function"==typeof n?n.apply(r,o):n,!(void 0!==i&&(e.exports=i))}(this,function(){"use strict";function e(e){return!isNaN(parseFloat(e))&&isFinite(e)}function r(e,r,t,n,o,i){void 0!==e&&this.setFunctionName(e),void 0!==r&&this.setArgs(r),void 0!==t&&this.setFileName(t),void 0!==n&&this.setLineNumber(n),void 0!==o&&this.setColumnNumber(o),void 0!==i&&this.setSource(i)}return r.prototype={getFunctionName:function(){return this.functionName},setFunctionName:function(e){this.functionName=String(e)},getArgs:function(){return this.args},setArgs:function(e){if("[object Array]"!==Object.prototype.toString.call(e))throw new TypeError("Args must be an Array");this.args=e},getFileName:function(){return this.fileName},setFileName:function(e){this.fileName=String(e)},getLineNumber:function(){return this.lineNumber},setLineNumber:function(r){if(!e(r))throw new TypeError("Line Number must be a Number");this.lineNumber=Number(r)},getColumnNumber:function(){return this.columnNumber},setColumnNumber:function(r){if(!e(r))throw new TypeError("Column Number must be a Number");this.columnNumber=Number(r)},getSource:function(){return this.source},setSource:function(e){this.source=String(e)},toString:function(){var r=this.getFunctionName()||"{anonymous}",t="("+(this.getArgs()||[]).join(",")+")",n=this.getFileName()?"@"+this.getFileName():"",o=e(this.getLineNumber())?":"+this.getLineNumber():"",i=e(this.getColumnNumber())?":"+this.getColumnNumber():"";return r+t+n+o+i}},r})},function(e,r,t){"use strict";function n(e){v=e}function o(e){return{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function i(e,r){return o(e)===r}function a(e){if(!i(e,"string"))throw new Error("received invalid input");for(var r=w,t=r.parser[r.strictMode?"strict":"loose"].exec(e),n={},o=14;o--;)n[r.key[o]]=t[o]||"";return n[r.q.name]={},n[r.key[12]].replace(r.q.parser,function(e,t,o){t&&(n[r.q.name][t]=o)}),n}function s(e){var r=a(e);return""===r.anchor&&(r.source=r.source.replace("#","")),e=r.source.replace("?"+r.query,"")}function u(e,r){var t,n,o,a=i(e,"object"),s=i(e,"array"),c=[];if(a)for(t in e)e.hasOwnProperty(t)&&c.push(t);else if(s)for(o=0;o500&&(t=t.substr(0,500)+"...")):"undefined"==typeof t&&(t="undefined"),e.push(t)}return e.join(" ")}function d(){m.ieVersion()<=8?console.error(f.apply(null,arguments)):console.error.apply(null,arguments)}function h(){m.ieVersion()<=8?console.info(f.apply(null,arguments)):console.info.apply(null,arguments)}function g(){m.ieVersion()<=8?console.log(f.apply(null,arguments)):console.log.apply(null,arguments)}t(9);var m=t(10),v=null,w={strictMode:!1,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},y={setupJSON:n,isType:i,parseUri:a,parseUriOptions:w,redact:c,sanitizeUrl:s,traverse:u,typeName:o,uuid4:l,objectCreate:p,consoleError:d,consoleInfo:h,consoleLog:g};e.exports=y},function(e,r){!function(e){"use strict";e.console||(e.console={});for(var r,t,n=e.console,o=function(){},i=["memory"],a="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");r=i.pop();)n[r]||(n[r]={});for(;t=a.pop();)n[t]||(n[t]=o)}("undefined"==typeof window?this:window)},function(e,r){"use strict";function t(){for(var e,r=3,t=document.createElement("div"),n=t.getElementsByTagName("i");t.innerHTML="",n[0];);return r>4?r:e}var n={ieVersion:t};e.exports=n},function(e,r,t){"use strict";function n(e){a=e}function o(e){this.name="Connection Error",this.message=e,this.stack=(new Error).stack}var i=t(8),a=null;o.prototype=i.objectCreate(Error.prototype),o.prototype.constructor=o;var s={XMLHttpFactories:[function(){return new XMLHttpRequest},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")}],createXMLHTTPObject:function(){var e,r=!1,t=s.XMLHttpFactories,n=t.length;for(e=0;e=400&&u.status<600?(403==u.status&&i.consoleError("[Rollbar]:"+e.message),n(new Error(String(u.status)))):n(new o("XHR response had no status code (likely connection failure)"))}}catch(e){var r;r=e&&e.stack?e:new Error(e),n(r)}};u.open("POST",e,!0),u.setRequestHeader&&(u.setRequestHeader("Content-Type","application/json"),u.setRequestHeader("X-Rollbar-Access-Token",r)),u.onreadystatechange=c,u.send(t)}catch(r){if("undefined"!=typeof XDomainRequest){"http:"===window.location.href.substring(0,5)&&"https"===e.substring(0,5)&&(e="http"+e.substring(5));var l=function(){n(new o("Request timed out"))},p=function(){n(new Error("Error during request"))},f=function(){n(null,a.parse(u.responseText))};u=new XDomainRequest,u.onprogress=function(){},u.ontimeout=l,u.onerror=p,u.onload=f,u.open("POST",e,!0),u.send(t)}}}catch(e){n(e)}}};e.exports={XHR:s,setupJSON:n,ConnectionError:o}}])}); diff --git a/packages/website/public/pdfs/0x_white_paper.pdf b/packages/website/public/pdfs/0x_white_paper.pdf new file mode 100644 index 000000000..340e8a73a Binary files /dev/null and b/packages/website/public/pdfs/0x_white_paper.pdf differ diff --git a/packages/website/public/videos/0xAnimation.mp4 b/packages/website/public/videos/0xAnimation.mp4 new file mode 100644 index 000000000..d78c07d4f Binary files /dev/null and b/packages/website/public/videos/0xAnimation.mp4 differ diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts new file mode 100644 index 000000000..d891acdb6 --- /dev/null +++ b/packages/website/ts/blockchain.ts @@ -0,0 +1,770 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + ZeroEx, + ZeroExError, + ExchangeContractErrs, + ExchangeContractEventArgs, + ExchangeEvents, + SubscriptionOpts, + IndexedFilterValues, + DecodedLogEvent, + BlockParam, + LogFillContractEventArgs, + LogCancelContractEventArgs, + Token as ZeroExToken, + LogWithDecodedArgs, + TransactionReceiptWithDecodedLogs, + SignedOrder, + Order, +} from '0x.js'; +import BigNumber from 'bignumber.js'; +import Web3 = require('web3'); +import promisify = require('es6-promisify'); +import findVersions = require('find-versions'); +import compareVersions = require('compare-versions'); +import contract = require('truffle-contract'); +import ethUtil = require('ethereumjs-util'); +import ProviderEngine = require('web3-provider-engine'); +import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); +import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted'; +import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed'; +import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; +import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider'; +import {ledgerWalletSubproviderFactory} from 'ts/subproviders/ledger_wallet_subprovider_factory'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import { + BlockchainErrs, + Token, + SignatureData, + Side, + ContractResponse, + BlockchainCallErrs, + ContractInstance, + ProviderType, + LedgerWalletSubprovider, + EtherscanLinkSuffixes, + TokenByAddress, + TokenStateByAddress, +} from 'ts/types'; +import {Web3Wrapper} from 'ts/web3_wrapper'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import * as MintableArtifacts from '../contracts/Mintable.json'; + +const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730; +const BLOCK_NUMBER_BACK_TRACK = 50; + +export class Blockchain { + public networkId: number; + public nodeVersion: string; + private zeroEx: ZeroEx; + private dispatcher: Dispatcher; + private web3Wrapper: Web3Wrapper; + private exchangeAddress: string; + private tokenTransferProxy: ContractInstance; + private tokenRegistry: ContractInstance; + private userAddress: string; + private cachedProvider: Web3.Provider; + private ledgerSubProvider: LedgerWalletSubprovider; + private zrxPollIntervalId: number; + constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { + this.dispatcher = dispatcher; + this.userAddress = ''; + this.onPageLoadInitFireAndForgetAsync(); + } + public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { + const isConnected = !_.isUndefined(newNetworkId); + if (!isConnected) { + this.networkId = newNetworkId; + this.dispatcher.encounteredBlockchainError(BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE); + this.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (this.networkId !== newNetworkId) { + this.networkId = newNetworkId; + this.dispatcher.encounteredBlockchainError(''); + await this.fetchTokenInformationAsync(); + await this.rehydrateStoreWithContractEvents(); + } + } + public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { + if (this.userAddress !== newUserAddress) { + this.userAddress = newUserAddress; + await this.fetchTokenInformationAsync(); + await this.rehydrateStoreWithContractEvents(); + } + } + public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { + if (this.nodeVersion !== nodeVersion) { + this.nodeVersion = nodeVersion; + } + } + public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const tokenIfExists = await this.zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); + return !_.isUndefined(tokenIfExists); + } + public getLedgerDerivationPathIfExists(): string { + if (_.isUndefined(this.ledgerSubProvider)) { + return undefined; + } + const path = this.ledgerSubProvider.getPath(); + return path; + } + public updateLedgerDerivationPathIfExists(path: string) { + if (_.isUndefined(this.ledgerSubProvider)) { + return; // noop + } + this.ledgerSubProvider.setPath(path); + } + public updateLedgerDerivationIndex(pathIndex: number) { + if (_.isUndefined(this.ledgerSubProvider)) { + return; // noop + } + this.ledgerSubProvider.setPathIndex(pathIndex); + } + public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + // Should actually be Web3.Provider|ProviderEngine union type but it causes issues + // later on in the logic. + let provider; + switch (providerType) { + case ProviderType.LEDGER: { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } + + // Cache injected provider so that we can switch the user back to it easily + this.cachedProvider = this.web3Wrapper.getProviderObj(); + + this.dispatcher.updateUserAddress(''); // Clear old userAddress + + provider = new ProviderEngine(); + this.ledgerSubProvider = ledgerWalletSubproviderFactory(this.getBlockchainNetworkId.bind(this)); + provider.addProvider(this.ledgerSubProvider); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.isMainnetEnabled ? + constants.MAINNET_NETWORK_ID : + constants.TESTNET_NETWORK_ID; + provider.addProvider(new RedundantRPCSubprovider( + constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], + )); + provider.start(); + this.web3Wrapper.destroy(); + const shouldPollUserAddress = false; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); + await this.zeroEx.setProviderAsync(provider); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + break; + } + + case ProviderType.INJECTED: { + if (_.isUndefined(this.cachedProvider)) { + return; // Going from injected to injected, so we noop + } + provider = this.cachedProvider; + const shouldPollUserAddress = true; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); + await this.zeroEx.setProviderAsync(provider); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + delete this.ledgerSubProvider; + delete this.cachedProvider; + break; + } + + default: + throw utils.spawnSwitchErr('providerType', providerType); + } + + await this.fetchTokenInformationAsync(); + } + public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise { + utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TOKEN_ADDRESS_IS_INVALID); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + + const txHash = await this.zeroEx.token.setProxyAllowanceAsync( + token.address, this.userAddress, amountInBaseUnits, + ); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const allowance = amountInBaseUnits; + this.dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); + } + public async transferAsync(token: Token, toAddress: string, + amountInBaseUnits: BigNumber): Promise { + const txHash = await this.zeroEx.token.transferAsync( + token.address, this.userAddress, toAddress, amountInBaseUnits, + ); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx); + this.dispatcher.showFlashMessage(React.createElement(TokenSendCompleted, { + etherScanLinkIfExists, + token, + toAddress, + amountInBaseUnits, + })); + } + public portalOrderToSignedOrder(maker: string, taker: string, makerTokenAddress: string, + takerTokenAddress: string, makerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, makerFee: BigNumber, + takerFee: BigNumber, expirationUnixTimestampSec: BigNumber, + feeRecipient: string, + signatureData: SignatureData, salt: BigNumber): SignedOrder { + const ecSignature = signatureData; + const exchangeContractAddress = this.getExchangeContractAddressIfExists(); + taker = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; + const signedOrder = { + ecSignature, + exchangeContractAddress, + expirationUnixTimestampSec, + feeRecipient, + maker, + makerFee, + makerTokenAddress, + makerTokenAmount, + salt, + taker, + takerFee, + takerTokenAddress, + takerTokenAmount, + }; + return signedOrder; + } + public async fillOrderAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber): Promise { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const shouldThrowOnInsufficientBalanceOrAllowance = true; + + const txHash = await this.zeroEx.exchange.fillOrderAsync( + signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, this.userAddress, + ); + const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array> = receipt.logs as any; + this.zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logFill = _.find(logs, {event: 'LogFill'}); + const args = logFill.args as any as LogFillContractEventArgs; + const filledTakerTokenAmount = args.filledTakerTokenAmount; + return filledTakerTokenAmount; + } + public async cancelOrderAsync(signedOrder: SignedOrder, + cancelTakerTokenAmount: BigNumber): Promise { + const txHash = await this.zeroEx.exchange.cancelOrderAsync( + signedOrder, cancelTakerTokenAmount, + ); + const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array> = receipt.logs as any; + this.zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logCancel = _.find(logs, {event: ExchangeEvents.LogCancel}); + const args = logCancel.args as any as LogCancelContractEventArgs; + const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; + return cancelledTakerTokenAmount; + } + public async getUnavailableTakerAmountAsync(orderHash: string): Promise { + utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const unavailableTakerAmount = await this.zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + return unavailableTakerAmount; + } + public getExchangeContractAddressIfExists() { + return this.exchangeAddress; + } + public toHumanReadableErrorMsg(error: ZeroExError|ExchangeContractErrs, takerAddress: string): string { + const ZeroExErrorToHumanReadableError: {[error: string]: string} = { + [ZeroExError.ContractDoesNotExist]: 'Contract does not exist', + [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ZeroExError.UnhandledError]: ' Unhandled error occured', + [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', + [ZeroExError.InvalidSignature]: 'Order signature is not valid', + [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ZeroExError.OutOfGas]: 'Transaction ran out of gas', + [ZeroExError.NoNetworkId]: 'No network id detected', + }; + const exchangeContractErrorToHumanReadableError: {[error: string]: string} = { + [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelAmountZero]: 'Order cancel amount can\'t be 0', + [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillAmountZero]: 'Order fill amount can\'t be 0', + [ExchangeContractErrs.OrderRemainingFillAmountZero]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', + [ExchangeContractErrs.InsufficientTakerBalance]: + 'Taker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientTakerAllowance]: + 'Taker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientMakerBalance]: + 'Maker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientMakerAllowance]: + 'Maker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientTakerFeeAllowance]: + 'Taker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeAllowance]: + 'Maker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: + `This order can only be filled by ${takerAddress}`, + [ExchangeContractErrs.InsufficientRemainingFillAmount]: + 'Insufficient remaining fill amount', + }; + const humanReadableErrorMsg = exchangeContractErrorToHumanReadableError[error] || + ZeroExErrorToHumanReadableError[error]; + return humanReadableErrorMsg; + } + public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string): Promise { + await this.zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress); + } + public async validateCancelOrderThrowIfInvalidAsync(order: Order, + cancelTakerTokenAmount: BigNumber): Promise { + await this.zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); + } + public isValidAddress(address: string): boolean { + const lowercaseAddress = address.toLowerCase(); + return this.web3Wrapper.isAddress(lowercaseAddress); + } + public async pollTokenBalanceAsync(token: Token) { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + + this.zrxPollIntervalId = window.setInterval(async () => { + const [balance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + if (!balance.eq(currBalance)) { + this.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + clearInterval(this.zrxPollIntervalId); + delete this.zrxPollIntervalId; + } + }, 5000); + } + public async signOrderHashAsync(orderHash: string): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const makerAddress = this.userAddress; + // If makerAddress is undefined, this means they have a web3 instance injected into their browser + // but no account addresses associated with it. + if (_.isUndefined(makerAddress)) { + throw new Error('Tried to send a sign request but user has no associated addresses'); + } + const ecSignature = await this.zeroEx.signOrderHashAsync(orderHash, makerAddress); + const signatureData = _.extend({}, ecSignature, { + hash: orderHash, + }); + this.dispatcher.updateSignatureData(signatureData); + return signatureData; + } + public async mintTestTokensAsync(token: Token) { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const mintableContract = await this.instantiateContractIfExistsAsync(MintableArtifacts, token.address); + await mintableContract.mint(constants.MINT_AMOUNT, { + from: this.userAddress, + }); + const balanceDelta = constants.MINT_AMOUNT; + this.dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); + } + public async getBalanceInEthAsync(owner: string): Promise { + const balance = await this.web3Wrapper.getBalanceInEthAsync(owner); + return balance; + } + public async convertEthToWrappedEthTokensAsync(amount: BigNumber): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const txHash = await this.zeroEx.etherToken.depositAsync(amount, this.userAddress); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async convertWrappedEthTokensToEthAsync(amount: BigNumber): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const txHash = await this.zeroEx.etherToken.withdrawAsync(amount, this.userAddress); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async doesContractExistAtAddressAsync(address: string) { + const doesContractExist = await this.web3Wrapper.doesContractExistAtAddressAsync(address); + return doesContractExist; + } + public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise { + const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, tokenAddress); + return tokenBalanceAndAllowance; + } + public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): + Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + + if (_.isEmpty(ownerAddress)) { + const zero = new BigNumber(0); + return [zero, zero]; + } + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this.doesUserAddressExist()) { + balance = await this.zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); + allowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); + } + return [balance, allowance]; + } + public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { + const tokenStateByAddress: TokenStateByAddress = {}; + for (const token of tokens) { + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this.doesUserAddressExist()) { + [ + balance, + allowance, + ] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + } + const tokenState = { + balance, + allowance, + }; + tokenStateByAddress[token.address] = tokenState; + } + this.dispatcher.updateTokenStateByAddress(tokenStateByAddress); + } + public async getUserAccountsAsync() { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const userAccountsIfExists = await this.zeroEx.getAvailableAddressesAsync(); + return userAccountsIfExists; + } + // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which + // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to + // manually update it. This should only be called by the LedgerConfigDialog. + public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { + this.web3Wrapper.updatePrevUserAddress(newUserAddress); + } + public destroy() { + clearInterval(this.zrxPollIntervalId); + this.web3Wrapper.destroy(); + this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget + } + private async showEtherScanLinkAndAwaitTransactionMinedAsync( + txHash: string): Promise { + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx); + this.dispatcher.showFlashMessage(React.createElement(TransactionSubmitted, { + etherScanLinkIfExists, + })); + const receipt = await this.zeroEx.awaitTransactionMinedAsync(txHash); + return receipt; + } + private doesUserAddressExist(): boolean { + return this.userAddress !== ''; + } + private async rehydrateStoreWithContractEvents() { + // Ensure we are only ever listening to one set of events + await this.stopWatchingExchangeLogFillEventsAsync(); + + if (!this.doesUserAddressExist()) { + return; // short-circuit + } + + if (!_.isUndefined(this.zeroEx)) { + // Since we do not have an index on the `taker` address and want to show + // transactions where an account is either the `maker` or `taker`, we loop + // through all fill events, and filter/cache them client-side. + const filterIndexObj = {}; + await this.startListeningForExchangeLogFillEventsAsync(filterIndexObj); + } + } + private async startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + // Fetch historical logs + await this.fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); + + // Start a subscription for new logs + const exchangeAddress = this.getExchangeContractAddressIfExists(); + const subscriptionId = await this.zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, + async (err: Error, decodedLogEvent: DecodedLogEvent) => { + if (err) { + // Note: it's not entirely clear from the documentation which + // errors will be thrown by `watch`. For now, let's log the error + // to rollbar and stop watching when one occurs + errorReporter.reportAsync(err); // fire and forget + this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget + return; + } else { + await this.addFillEventToTradeHistoryAsync(decodedLogEvent); + } + }); + } + private async fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { + const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this.userAddress, this.networkId); + const subscriptionOpts: SubscriptionOpts = { + fromBlock, + toBlock: 'latest' as BlockParam, + }; + const decodedLogs = await this.zeroEx.exchange.getLogsAsync( + ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, + ); + for (const decodedLog of decodedLogs) { + console.log('decodedLog', decodedLog); + await this.addFillEventToTradeHistoryAsync(decodedLog); + } + } + private async addFillEventToTradeHistoryAsync(decodedLog: LogWithDecodedArgs) { + const args = decodedLog.args as LogFillContractEventArgs; + const isUserMakerOrTaker = args.maker === this.userAddress || + args.taker === this.userAddress; + if (!isUserMakerOrTaker) { + return; // We aren't interested in the fill event + } + const isBlockPending = _.isNull(decodedLog.blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? + 0 : + decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); + } + const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); + const fill = { + filledTakerTokenAmount: args.filledTakerTokenAmount, + filledMakerTokenAmount: args.filledMakerTokenAmount, + logIndex: decodedLog.logIndex, + maker: args.maker, + orderHash: args.orderHash, + taker: args.taker, + makerToken: args.makerToken, + takerToken: args.takerToken, + paidMakerFee: args.paidMakerFee, + paidTakerFee: args.paidTakerFee, + transactionHash: decodedLog.transactionHash, + blockTimestamp, + }; + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + } + private async stopWatchingExchangeLogFillEventsAsync() { + this.zeroEx.exchange.unsubscribeAll(); + } + private async getTokenRegistryTokensByAddressAsync(): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const tokenRegistryTokens = await this.zeroEx.tokenRegistry.getTokensAsync(); + + const tokenByAddress: TokenByAddress = {}; + _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { + // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens + // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry + const iconUrl = constants.iconUrlBySymbol[t.symbol]; + const token: Token = { + iconUrl, + address: t.address, + name: t.name, + symbol: t.symbol, + decimals: t.decimals, + isTracked: false, + isRegistered: true, + }; + tokenByAddress[token.address] = token; + }); + return tokenByAddress; + } + private async onPageLoadInitFireAndForgetAsync() { + await this.onPageLoadAsync(); // wait for page to load + + // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in + // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot + // retrieve it from within the web3Wrapper constructor. This is and should remain the only + // call to a web3 instance outside of web3Wrapper in the entire dapp. + // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing + // Ethereum node, this call will throw. We need to handle this case gracefully + const injectedWeb3 = (window as any).web3; + let networkId: number; + if (!_.isUndefined(injectedWeb3)) { + try { + networkId = _.parseInt(await promisify(injectedWeb3.version.getNetwork)()); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + + const provider = await this.getProviderAsync(injectedWeb3, networkId); + this.zeroEx = new ZeroEx(provider); + await this.updateProviderName(injectedWeb3); + const shouldPollUserAddress = true; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, networkId, shouldPollUserAddress); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + } + // This method should always be run after instantiating or updating the provider + // of the ZeroEx instance. + private async postInstantiationOrUpdatingProviderZeroExAsync() { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + this.exchangeAddress = await this.zeroEx.exchange.getContractAddressAsync(); + } + private updateProviderName(injectedWeb3: Web3) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const providerName = doesInjectedWeb3Exist ? + this.getNameGivenProvider(injectedWeb3.currentProvider) : + constants.PUBLIC_PROVIDER_NAME; + this.dispatcher.updateInjectedProviderName(providerName); + } + // This is only ever called by the LedgerWallet subprovider in order to retrieve + // the current networkId without this value going stale. + private getBlockchainNetworkId() { + return this.networkId; + } + private async getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const publicNodeUrlsIfExistsForNetworkId = constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; + const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); + + let provider; + if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + // We catch all requests involving a users account and send it to the injectedWeb3 + // instance. All other requests go to the public hosted node. + provider = new ProviderEngine(); + provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3)); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider( + publicNodeUrlsIfExistsForNetworkId, + )); + provider.start(); + } else if (doesInjectedWeb3Exist) { + // Since no public node for this network, all requests go to injectedWeb3 instance + provider = injectedWeb3.currentProvider; + } else { + // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node + // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 + // injected into their browser. + provider = new ProviderEngine(); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.isMainnetEnabled ? + constants.MAINNET_NETWORK_ID : + constants.TESTNET_NETWORK_ID; + provider.addProvider(new RedundantRPCSubprovider( + constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], + )); + provider.start(); + } + + return provider; + } + private getNameGivenProvider(provider: Web3.Provider): string { + if (!_.isUndefined((provider as any).isMetaMask)) { + return constants.METAMASK_PROVIDER_NAME; + } + + // HACK: We use the fact that Parity Signer's provider is an instance of their + // internal `Web3FrameProvider` class. + const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); + if (isParitySigner) { + return constants.PARITY_SIGNER_PROVIDER_NAME; + } + + return constants.GENERIC_PROVIDER_NAME; + } + private async fetchTokenInformationAsync() { + utils.assert(!_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node'); + + this.dispatcher.updateBlockchainIsLoaded(false); + this.dispatcher.clearTokenByAddress(); + + const tokenRegistryTokensByAddress = await this.getTokenRegistryTokensByAddressAsync(); + + // HACK: We need to fetch the userAddress here because otherwise we cannot save the + // tracked tokens in localStorage under the users address nor fetch the token + // balances and allowances and we need to do this in order not to trigger the blockchain + // loading dialog to show up twice. First to load the contracts, and second to load the + // balances and allowances. + this.userAddress = await this.web3Wrapper.getFirstAccountIfExistsAsync(); + if (!_.isEmpty(this.userAddress)) { + this.dispatcher.updateUserAddress(this.userAddress); + } + + let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this.userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isUndefined(trackedTokensIfExists)) { + trackedTokensIfExists = _.map(configs.defaultTrackedTokenSymbols, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + return token; + }); + _.each(trackedTokensIfExists, token => { + trackedTokenStorage.addTrackedTokenToUser(this.userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensIfExists, trackedToken => { + if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { + tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; + } + }); + } + const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); + this.dispatcher.updateTokenByAddress(allTokens); + + // Get balance/allowance for tracked tokens + await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); + + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[0]}), + _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[1]}), + ]; + this.dispatcher.updateChosenAssetTokenAddress(Side.deposit, mostPopularTradingPairTokens[0].address); + this.dispatcher.updateChosenAssetTokenAddress(Side.receive, mostPopularTradingPairTokens[1].address); + this.dispatcher.updateBlockchainIsLoaded(true); + } + private async instantiateContractIfExistsAsync(artifact: any, address?: string): Promise { + const c = await contract(artifact); + const providerObj = this.web3Wrapper.getProviderObj(); + c.setProvider(providerObj); + + const artifactNetworkConfigs = artifact.networks[this.networkId]; + let contractAddress; + if (!_.isUndefined(address)) { + contractAddress = address; + } else if (!_.isUndefined(artifactNetworkConfigs)) { + contractAddress = artifactNetworkConfigs.address; + } + + if (!_.isUndefined(contractAddress)) { + const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); + throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST); + } + } + + try { + const contractInstance = _.isUndefined(address) ? + await c.deployed() : + await c.at(address); + return contractInstance; + } catch (err) { + const errMsg = `${err}`; + utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); + if (_.includes(errMsg, 'not been deployed to detected network')) { + throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST); + } else { + await errorReporter.reportAsync(err); + throw new Error(BlockchainCallErrs.UNHANDLED_ERROR); + } + } + } + private async onPageLoadAsync() { + if (document.readyState === 'complete') { + return; // Already loaded + } + return new Promise((resolve, reject) => { + window.onload = resolve; + }); + } +} diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx new file mode 100644 index 000000000..2e12fc889 --- /dev/null +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -0,0 +1,158 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import {colors} from 'material-ui/styles'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {Blockchain} from 'ts/blockchain'; +import {BlockchainErrs} from 'ts/types'; + +interface BlockchainErrDialogProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + isOpen: boolean; + userAddress: string; + toggleDialogFn: (isOpen: boolean) => void; + networkId: number; +} + +export class BlockchainErrDialog extends React.Component { + public render() { + const dialogActions = [ + , + ]; + + const hasWalletAddress = this.props.userAddress !== ''; + return ( + +
+ {this.renderExplanation(hasWalletAddress)} +
+
+ ); + } + private getTitle(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { + return '0x smart contracts not found'; + } else if (!hasWalletAddress) { + return 'Enable wallet communication'; + } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { + return 'Disconnected from Ethereum network'; + } else { + return 'Unexpected error'; + } + } + private renderExplanation(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { + return this.renderContractsNotDeployedExplanation(); + } else if (!hasWalletAddress) { + return this.renderNoWalletFoundExplanation(); + } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { + return this.renderDisconnectedFromNode(); + } else { + return this.renderUnexpectedErrorExplanation(); + } + } + private renderDisconnectedFromNode() { + return ( +
+ You were disconnected from the backing Ethereum node. + {' '}If using + Metamask + or Mist try refreshing + {' '}the page. If using a locally hosted Ethereum node, make sure it's still running. +
+ ); + } + private renderUnexpectedErrorExplanation() { + return ( +
+ We encountered an unexpected error. Please try refreshing the page. +
+ ); + } + private renderNoWalletFoundExplanation() { + return ( +
+
+ We were unable to access an Ethereum wallet you control. In order to interact + {' '}with the 0x portal dApp, + we need a way to interact with one of your Ethereum wallets. + {' '}There are two easy ways you can enable us to do that: +
+

1. Metamask chrome extension

+
+ You can install the{' '} + + Metamask + Chrome extension Ethereum wallet. Once installed and set up, refresh this page. +
+ Note: + {' '}If you already have Metamask installed, make sure it is unlocked. +
+
+

Parity Signer

+
+ The Parity Signer + Chrome extension{' '}lets you connect to a locally running Parity node. + Make sure you have started your local Parity node with{' '} + {configs.isMainnetEnabled && '`parity ui` or'} `parity --chain kovan ui`{' '} + in order to connect to {configs.isMainnetEnabled ? 'mainnet or Kovan respectively.' : 'Kovan.'} +
+
+ Note: + {' '}If you have done one of the above steps and are still seeing this message, + {' '}we might still be unable to retrieve an Ethereum address by calling `web3.eth.accounts`. + {' '}Make sure you have created at least one Ethereum address. +
+
+ ); + } + private renderContractsNotDeployedExplanation() { + return ( +
+
+ The 0x smart contracts are not deployed on the Ethereum network you are + {' '}currently connected to (network Id: {this.props.networkId}). + {' '}In order to use the 0x portal dApp, + {' '}please connect to the + {' '}{constants.TESTNET_NAME} testnet (network Id: {constants.TESTNET_NETWORK_ID}) + {configs.isMainnetEnabled ? + ` or ${constants.MAINNET_NAME} (network Id: ${constants.MAINNET_NETWORK_ID}).` : + `.` + } +
+

Metamask

+
+ If you are using{' '} + + Metamask + , you can switch networks in the top left corner of the extension popover. +
+

Parity Signer

+
+ If using the Parity Signer + Chrome extension, make sure to start your local Parity node with{' '} + {configs.isMainnetEnabled ? + '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ + or Kovan respectively.' : + '`parity --chain kovan ui` in order to connect to Kovan.' + } +
+
+ ); + } +} diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx new file mode 100644 index 000000000..1db85e375 --- /dev/null +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -0,0 +1,139 @@ +import * as React from 'react'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; +import RadioButton from 'material-ui/RadioButton'; +import {Side, Token, TokenState} from 'ts/types'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {EthAmountInput} from 'ts/components/inputs/eth_amount_input'; +import BigNumber from 'bignumber.js'; + +interface EthWethConversionDialogProps { + onComplete: (direction: Side, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; + etherBalance: BigNumber; +} + +interface EthWethConversionDialogState { + value?: BigNumber; + direction: Side; + shouldShowIncompleteErrs: boolean; + hasErrors: boolean; +} + +export class EthWethConversionDialog extends + React.Component { + constructor() { + super(); + this.state = { + direction: Side.deposit, + shouldShowIncompleteErrs: false, + hasErrors: true, + }; + } + public render() { + const convertDialogActions = [ + , + , + ]; + return ( + + {this.renderConversionDialogBody()} + + ); + } + private renderConversionDialogBody() { + return ( +
+ + + + + {this.state.direction === Side.receive ? + : + + } +
+ ); + } + private onConversionDirectionChange(e: any, direction: Side) { + this.setState({ + value: undefined, + shouldShowIncompleteErrs: false, + direction, + hasErrors: true, + }); + } + private onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + value: amount, + hasErrors: !isValid, + }); + } + private onConvertClick() { + if (this.state.hasErrors) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + value: undefined, + }); + this.props.onComplete(this.state.direction, value); + } + } + private onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } +} diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx new file mode 100644 index 000000000..f89935500 --- /dev/null +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -0,0 +1,288 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import TextField from 'material-ui/TextField'; +import { + Table, + TableBody, + TableHeader, + TableRow, + TableHeaderColumn, + TableRowColumn, +} from 'material-ui/Table'; +import ReactTooltip = require('react-tooltip'); +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; + +const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; + +enum LedgerSteps { + CONNECT, + SELECT_ADDRESS, +} + +interface LedgerConfigDialogProps { + isOpen: boolean; + toggleDialogFn: (isOpen: boolean) => void; + dispatcher: Dispatcher; + blockchain: Blockchain; + networkId: number; +} + +interface LedgerConfigDialogState { + didConnectFail: boolean; + stepIndex: LedgerSteps; + userAddresses: string[]; + addressBalances: BigNumber[]; + derivationPath: string; + derivationErrMsg: string; +} + +export class LedgerConfigDialog extends React.Component { + constructor(props: LedgerConfigDialogProps) { + super(props); + this.state = { + didConnectFail: false, + stepIndex: LedgerSteps.CONNECT, + userAddresses: [], + addressBalances: [], + derivationPath: constants.DEFAULT_DERIVATION_PATH, + derivationErrMsg: '', + }; + } + public render() { + const dialogActions = [ + , + ]; + const dialogTitle = this.state.stepIndex === LedgerSteps.CONNECT ? + 'Connect to your Ledger' : + 'Select desired address'; + return ( + +
+ {this.state.stepIndex === LedgerSteps.CONNECT && + this.renderConnectStep() + } + {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && + this.renderSelectAddressStep() + } +
+
+ ); + } + private renderConnectStep() { + return ( +
+
+ Follow these instructions before proceeding: +
+
    +
  1. + Connect your Ledger Nano S & Open the Ethereum application +
  2. +
  3. + Verify that Browser Support is enabled in Settings +
  4. +
  5. + If no Browser Support is found in settings, verify that you have{' '} + Firmware >1.2 +
  6. +
+
+ + {this.state.didConnectFail && +
+ Failed to connect. Follow the instructions and try again. +
+ } +
+
+ ); + } + private renderSelectAddressStep() { + return ( +
+
+ + + + Address + Balance + + + + {this.renderAddressTableRows()} + +
+
+
+
+ +
+
+ +
+
+
+ ); + } + private renderAddressTableRows() { + const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { + const balance = this.state.addressBalances[i]; + const addressTooltipId = `address-${userAddress}`; + const balanceTooltipId = `balance-${userAddress}`; + const networkName = constants.networkNameById[this.props.networkId]; + // We specifically prefix kovan ETH. + // TODO: We should probably add prefixes for all networks + const isKovanNetwork = networkName === 'Kovan'; + const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; + return ( + + +
+ {userAddress} +
+ {userAddress} +
+ +
+ {balanceString} +
+ {balanceString} +
+
+ ); + }); + return rows; + } + private onClose() { + this.setState({ + didConnectFail: false, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private onAddressSelected(selectedRowIndexes: number[]) { + const selectedRowIndex = selectedRowIndexes[0]; + this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); + const selectedAddress = this.state.userAddresses[selectedRowIndex]; + const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; + this.props.dispatcher.updateUserAddress(selectedAddress); + this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); + this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); + this.setState({ + stepIndex: LedgerSteps.CONNECT, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private async onFetchAddressesForDerivationPathAsync() { + const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); + if (currentlySetPath === this.state.derivationPath) { + return; + } + this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); + const didSucceed = await this.fetchAddressesAndBalancesAsync(); + if (!didSucceed) { + this.setState({ + derivationErrMsg: 'Failed to connect to Ledger.', + }); + } + return didSucceed; + } + private async fetchAddressesAndBalancesAsync() { + let userAddresses: string[]; + const addressBalances: BigNumber[] = []; + try { + userAddresses = await this.getUserAddressesAsync(); + for (const address of userAddresses) { + const balance = await this.props.blockchain.getBalanceInEthAsync(address); + addressBalances.push(balance); + } + } catch (err) { + utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); + this.setState({ + didConnectFail: true, + }); + return false; + } + this.setState({ + userAddresses, + addressBalances, + }); + return true; + } + private onDerivationPathChanged(e: any, derivationPath: string) { + let derivationErrMsg = ''; + if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { + derivationErrMsg = 'Must be valid Ethereum path.'; + } + + this.setState({ + derivationPath, + derivationErrMsg, + }); + } + private async onConnectLedgerClickAsync() { + const didSucceed = await this.fetchAddressesAndBalancesAsync(); + if (didSucceed) { + this.setState({ + stepIndex: LedgerSteps.SELECT_ADDRESS, + }); + } + return didSucceed; + } + private async getUserAddressesAsync(): Promise { + let userAddresses: string[]; + userAddresses = await this.props.blockchain.getUserAccountsAsync(); + + if (_.isEmpty(userAddresses)) { + throw new Error('No addresses retrieved.'); + } + return userAddresses; + } +} diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx new file mode 100644 index 000000000..8f870b42f --- /dev/null +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; + +interface PortalDisclaimerDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { + return ( + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > +
+
+ 0x Portal is a free software-based tool intended to help users to + buy and sell ERC20-compatible blockchain tokens through the 0x protocol + on a purely peer-to-peer basis. 0x portal is not a regulated marketplace, + exchange or intermediary of any kind, and therefore, you should only use + 0x portal to exchange tokens that are not securities, commodity interests, + or any other form of regulated instrument. 0x has not attempted to screen + or otherwise limit the tokens that you may enter in 0x Portal. By clicking + “I Agree” below, you understand that you are solely responsible for using 0x + Portal and buying and selling tokens using 0x Portal in compliance with all + applicable laws and regulations. +
+
+
+ ); +} diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx new file mode 100644 index 000000000..10417a326 --- /dev/null +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; +import RadioButton from 'material-ui/RadioButton'; +import {Side, Token, TokenState} from 'ts/types'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {EthAmountInput} from 'ts/components/inputs/eth_amount_input'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import BigNumber from 'bignumber.js'; + +interface SendDialogProps { + onComplete: (recipient: string, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; +} + +interface SendDialogState { + value?: BigNumber; + recipient: string; + shouldShowIncompleteErrs: boolean; + isAmountValid: boolean; +} + +export class SendDialog extends React.Component { + constructor() { + super(); + this.state = { + recipient: '', + shouldShowIncompleteErrs: false, + isAmountValid: false, + }; + } + public render() { + const transferDialogActions = [ + , + , + ]; + return ( + + {this.renderSendDialogBody()} + + ); + } + private renderSendDialogBody() { + return ( +
+
+ +
+ +
+ ); + } + private onRecipientChange(recipient?: string) { + this.setState({ + shouldShowIncompleteErrs: false, + recipient, + }); + } + private onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + isAmountValid: isValid, + value: amount, + }); + } + private onSendClick() { + if (this.hasErrors()) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + recipient: undefined, + value: undefined, + }); + this.props.onComplete(this.state.recipient, value); + } + } + private onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } + private hasErrors() { + return _.isUndefined(this.state.recipient) || + _.isUndefined(this.state.value) || + !this.state.isAmountValid; + } +} diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx new file mode 100644 index 000000000..97c654656 --- /dev/null +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -0,0 +1,99 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {Token, TokenByAddress} from 'ts/types'; + +interface TrackTokenConfirmationDialogProps { + tokens: Token[]; + tokenByAddress: TokenByAddress; + isOpen: boolean; + onToggleDialog: (didConfirmTokenTracking: boolean) => void; + dispatcher: Dispatcher; + networkId: number; + blockchain: Blockchain; + userAddress: string; +} + +interface TrackTokenConfirmationDialogState { + isAddingTokenToTracked: boolean; +} + +export class TrackTokenConfirmationDialog extends + React.Component { + constructor(props: TrackTokenConfirmationDialogProps) { + super(props); + this.state = { + isAddingTokenToTracked: false, + }; + } + public render() { + const tokens = this.props.tokens; + return ( + , + , + ]} + open={this.props.isOpen} + onRequestClose={this.props.onToggleDialog.bind(this, false)} + autoScrollBodyContent={true} + > +
+ +
+
+ ); + } + private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.props.onToggleDialog(didUserAcceptTracking); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + for (const token of this.props.tokens) { + const newTokenEntry = _.assign({}, token); + + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + + const [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + } + + this.setState({ + isAddingTokenToTracked: false, + }); + this.props.onToggleDialog(didUserAcceptTracking); + } +} diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx new file mode 100644 index 000000000..28c24cdbe --- /dev/null +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; + +interface U2fNotSupportedDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { + return ( + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + > +
+
+ It looks like your browser does not support U2F connections + required for us to communicate with your hardware wallet. + Please use a browser that supports U2F connections and try + again. +
+
+
    +
  • Chrome version 38 or later
  • +
  • Opera version 40 of later
  • +
  • + Firefox with{' '} + + this extension + . +
  • +
+
+
+
+ ); +} diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx new file mode 100644 index 000000000..fd8b713f4 --- /dev/null +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -0,0 +1,101 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import RaisedButton from 'material-ui/RaisedButton'; +import {BlockchainCallErrs, TokenState} from 'ts/types'; +import {EthWethConversionDialog} from 'ts/components/dialogs/eth_weth_conversion_dialog'; +import {Side, Token} from 'ts/types'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {Blockchain} from 'ts/blockchain'; + +interface EthWethConversionButtonProps { + ethToken: Token; + ethTokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + userEtherBalance: BigNumber; + onError: () => void; +} + +interface EthWethConversionButtonState { + isEthConversionDialogVisible: boolean; + isEthConversionHappening: boolean; +} + +export class EthWethConversionButton extends + React.Component { + public constructor(props: EthWethConversionButtonProps) { + super(props); + this.state = { + isEthConversionDialogVisible: false, + isEthConversionHappening: false, + }; + } + public render() { + const labelStyle = this.state.isEthConversionHappening ? {fontSize: 10} : {}; + return ( +
+ + +
+ ); + } + private toggleConversionDialog() { + this.setState({ + isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, + }); + } + private async onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + this.setState({ + isEthConversionHappening: true, + }); + this.toggleConversionDialog(); + const token = this.props.ethToken; + const tokenState = this.props.ethTokenState; + let balance = tokenState.balance; + try { + if (direction === Side.deposit) { + await this.props.blockchain.convertEthToWrappedEthTokensAsync(value); + const ethAmount = ZeroEx.toUnitAmount(value, constants.ETH_DECIMAL_PLACES); + this.props.dispatcher.showFlashMessage(`Successfully converted ${ethAmount.toString()} ETH to WETH`); + balance = balance.plus(value); + } else { + await this.props.blockchain.convertWrappedEthTokensToEthAsync(value); + const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully converted ${tokenAmount.toString()} WETH to ETH`); + balance = balance.minus(value); + } + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = '' + err; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onError(); + } + } + this.setState({ + isEthConversionHappening: false, + }); + } +} diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx new file mode 100644 index 000000000..dc965283e --- /dev/null +++ b/packages/website/ts/components/fill_order.tsx @@ -0,0 +1,714 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as accounting from 'accounting'; +import {Link} from 'react-router-dom'; +import {ZeroEx, Order as ZeroExOrder} from '0x.js'; +import * as moment from 'moment'; +import BigNumber from 'bignumber.js'; +import Paper from 'material-ui/Paper'; +import {Card, CardText, CardHeader} from 'material-ui/Card'; +import Divider from 'material-ui/Divider'; +import TextField from 'material-ui/TextField'; +import RaisedButton from 'material-ui/RaisedButton'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import { + Side, + TokenByAddress, + TokenStateByAddress, + Order, + BlockchainErrs, + OrderToken, + Token, + ExchangeContractErrs, + AlertTypes, + ContractResponse, + WebsitePaths, +} from 'ts/types'; +import {Alert} from 'ts/components/ui/alert'; +import {Identicon} from 'ts/components/ui/identicon'; +import {EthereumAddress} from 'ts/components/ui/ethereum_address'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {FillWarningDialog} from 'ts/components/fill_warning_dialog'; +import {FillOrderJSON} from 'ts/components/fill_order_json'; +import {VisualOrder} from 'ts/components/visual_order'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {Blockchain} from 'ts/blockchain'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {TrackTokenConfirmationDialog} from 'ts/components/dialogs/track_token_confirmation_dialog'; + +const CUSTOM_LIGHT_GRAY = '#BBBBBB'; + +interface FillOrderProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + orderFillAmount: BigNumber; + isOrderInUrl: boolean; + networkId: number; + userAddress: string; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + initialOrder: Order; + dispatcher: Dispatcher; +} + +interface FillOrderState { + didOrderValidationRun: boolean; + areAllInvolvedTokensTracked: boolean; + globalErrMsg: string; + orderJSON: string; + orderJSONErrMsg: string; + parsedOrder: Order; + didFillOrderSucceed: boolean; + didCancelOrderSucceed: boolean; + unavailableTakerAmount: BigNumber; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; + isFillWarningDialogOpen: boolean; + isFilling: boolean; + isCancelling: boolean; + isConfirmingTokenTracking: boolean; + tokensToTrack: Token[]; +} + +export class FillOrder extends React.Component { + private validator: SchemaValidator; + constructor(props: FillOrderProps) { + super(props); + this.state = { + globalErrMsg: '', + didOrderValidationRun: false, + areAllInvolvedTokensTracked: false, + didFillOrderSucceed: false, + didCancelOrderSucceed: false, + orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), + orderJSONErrMsg: '', + parsedOrder: this.props.initialOrder, + unavailableTakerAmount: new BigNumber(0), + isMakerTokenAddressInRegistry: false, + isTakerTokenAddressInRegistry: false, + isFillWarningDialogOpen: false, + isFilling: false, + isCancelling: false, + isConfirmingTokenTracking: false, + tokensToTrack: [], + }; + this.validator = new SchemaValidator(); + } + public componentWillMount() { + if (!_.isEmpty(this.state.orderJSON)) { + this.validateFillOrderFireAndForgetAsync(this.state.orderJSON); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+

Fill an order

+ +
+ {!this.props.isOrderInUrl && +
+
+ Paste an order JSON snippet below to begin +
+
Order JSON
+ + {this.renderOrderJsonNotices()} +
+ } +
+ {!_.isUndefined(this.state.parsedOrder) && this.state.didOrderValidationRun + && this.state.areAllInvolvedTokensTracked && + this.renderVisualOrder() + } +
+ {this.props.isOrderInUrl && +
+ + + + + + + {this.renderOrderJsonNotices()} +
+ } +
+ + +
+ ); + } + private renderOrderJsonNotices() { + return ( +
+ {!_.isUndefined(this.props.initialOrder) && !this.state.didOrderValidationRun && +
+ + + + Validating order... +
+ } + {!_.isEmpty(this.state.orderJSONErrMsg) && + + } +
+ ); + } + private renderVisualOrder() { + const takerTokenAddress = this.state.parsedOrder.taker.token.address; + const takerToken = this.props.tokenByAddress[takerTokenAddress]; + const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); + const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); + const takerAssetToken = { + amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), + symbol: takerToken.symbol, + }; + const fillToken = this.props.tokenByAddress[takerToken.address]; + const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; + const makerTokenAddress = this.state.parsedOrder.maker.token.address; + const makerToken = this.props.tokenByAddress[makerTokenAddress]; + const makerAssetToken = { + amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), + symbol: makerToken.symbol, + }; + const fillAssetToken = { + amount: this.props.orderFillAmount, + symbol: takerToken.symbol, + }; + const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) ? this.state.parsedOrder.taker.address : + this.props.userAddress; + const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); + const exchangeRate = orderMakerAmount.div(orderTakerAmount); + + let orderReceiveAmount = 0; + if (!_.isUndefined(this.props.orderFillAmount)) { + const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); + orderReceiveAmount = this.formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); + } + const isUserMaker = !_.isUndefined(this.state.parsedOrder) && + this.state.parsedOrder.maker.address === this.props.userAddress; + const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); + return ( +
+
+
Order details
+
+
+ Maker: +
+
+ +
+
+ +
+
+
+
+
+ +
+ Expires: {expiryDate} UTC +
+
+
+ {!isUserMaker && +
+
+ +
+
+ = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} +
+
+ } +
+ {isUserMaker ? +
+ + {this.state.didCancelOrderSucceed && + + } +
: +
+ + {!_.isEmpty(this.state.globalErrMsg) && + + } + {this.state.didFillOrderSucceed && + + } +
+ } +
+
+ ); + } + private renderFillSuccessMsg() { + return ( +
+ Order successfully filled. See the trade details in your{' '} + + trade history + +
+ ); + } + private renderCancelSuccessMsg() { + return ( +
+ Order successfully cancelled. +
+ ); + } + private onFillOrderClick() { + if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { + this.setState({ + isFillWarningDialogOpen: true, + }); + } else { + this.onFillOrderClickFireAndForgetAsync(); + } + } + private onFillWarningClosed(didUserCancel: boolean) { + this.setState({ + isFillWarningDialogOpen: false, + }); + if (!didUserCancel) { + this.onFillOrderClickFireAndForgetAsync(); + } + } + private onFillAmountChange(isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateOrderFillAmount(amount); + } + private onFillOrderJSONChanged(event: any) { + const orderJSON = event.target.value; + this.setState({ + didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), + didFillOrderSucceed: false, + }); + this.validateFillOrderFireAndForgetAsync(orderJSON); + } + private async checkForUntrackedTokensAndAskToAdd() { + if (!_.isEmpty(this.state.orderJSONErrMsg)) { + return; + } + + const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; + const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; + + const tokensToTrack = []; + const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); + const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; + if (isUnseenMakerToken) { + tokensToTrack.push(_.assign({}, this.state.parsedOrder.maker.token, { + iconUrl: undefined, + isTracked: false, + isRegistered: false, + })); + } else if (!isMakerTokenTracked) { + tokensToTrack.push(makerTokenIfExists); + } + const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); + const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; + if (isUnseenTakerToken) { + tokensToTrack.push(_.assign({}, this.state.parsedOrder.taker.token, { + iconUrl: undefined, + isTracked: false, + isRegistered: false, + })); + } else if (!isTakerTokenTracked) { + tokensToTrack.push(takerTokenIfExists); + } + if (!_.isEmpty(tokensToTrack)) { + this.setState({ + isConfirmingTokenTracking: true, + tokensToTrack, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + } + private async validateFillOrderFireAndForgetAsync(orderJSON: string) { + let orderJSONErrMsg = ''; + let parsedOrder: Order; + try { + const order = JSON.parse(orderJSON); + const validationResult = this.validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + orderJSONErrMsg = 'Submitted order JSON is not a valid order'; + utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); + return; + } + parsedOrder = order; + + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + const makerAmount = new BigNumber(parsedOrder.maker.amount); + const takerAmount = new BigNumber(parsedOrder.taker.amount); + const expiration = new BigNumber(parsedOrder.expiration); + const salt = new BigNumber(parsedOrder.salt); + const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); + const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); + + const zeroExOrder: ZeroExOrder = { + exchangeContractAddress: parsedOrder.exchangeContract, + expirationUnixTimestampSec: expiration, + feeRecipient: parsedOrder.feeRecipient, + maker: parsedOrder.maker.address, + makerFee: parsedMakerFee, + makerTokenAddress: parsedOrder.maker.token.address, + makerTokenAmount: makerAmount, + salt, + taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, + takerFee: parsedTakerFee, + takerTokenAddress: parsedOrder.taker.token.address, + takerTokenAmount: takerAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + + const signature = parsedOrder.signature; + const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); + if (this.props.networkId !== parsedOrder.networkId) { + orderJSONErrMsg = `This order was made on another Ethereum network + (id: ${parsedOrder.networkId}). Connect to this network to fill.`; + parsedOrder = undefined; + } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { + orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; + parsedOrder = undefined; + } else if (orderHash !== signature.hash) { + orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; + parsedOrder = undefined; + } else if (!isValidSignature) { + orderJSONErrMsg = 'Order signature is invalid'; + parsedOrder = undefined; + } else { + // Update user supplied order cache so that if they navigate away from fill view + // e.g to set a token allowance, when they come back, the fill order persists + this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); + } + } catch (err) { + utils.consoleLog(`Validate order err: ${err}`); + if (!_.isEmpty(orderJSON)) { + orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; + } + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + return; + } + + let unavailableTakerAmount = new BigNumber(0); + if (!_.isEmpty(orderJSONErrMsg)) { + // Clear cache entry if user updates orderJSON to invalid entry + this.props.dispatcher.updateUserSuppliedOrderCache(undefined); + } else { + const orderHash = parsedOrder.signature.hash; + unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.maker.token.address, + ); + const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.taker.token.address, + ); + this.setState({ + isMakerTokenAddressInRegistry, + isTakerTokenAddressInRegistry, + }); + } + + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + unavailableTakerAmount, + }); + + await this.checkForUntrackedTokensAndAskToAdd(); + } + private async onFillOrderClickFireAndForgetAsync(): Promise { + if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isFilling: true, + didFillOrderSucceed: false, + }); + + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const takerFillAmount = this.props.orderFillAmount; + + if (_.isUndefined(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; + + if (_.isUndefined(takerFillAmount)) { + globalErrMsg = 'You must specify a fill amount'; + } + + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + new BigNumber(parsedOrder.taker.amount), + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + if (_.isEmpty(globalErrMsg)) { + try { + await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( + signedOrder, takerFillAmount, this.props.userAddress); + } catch (err) { + globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address); + } + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isFilling: false, + globalErrMsg, + }); + return; + } + try { + const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( + signedOrder, this.props.orderFillAmount, + ); + // After fill completes, let's update the token balances + const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; + const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; + const tokens = [makerToken, takerToken]; + await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.setState({ + isFilling: false, + didFillOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), + }); + return; + } catch (err) { + this.setState({ + isFilling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to fill order, please refresh and try again'; + utils.consoleLog(`${err}`); + await errorReporter.reportAsync(err); + this.setState({ + globalErrMsg, + }); + return; + } + } + private async onCancelOrderClickFireAndForgetAsync(): Promise { + if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isCancelling: true, + didCancelOrderSucceed: false, + }); + + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const takerAddress = this.props.userAddress; + + if (_.isUndefined(takerAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; + + const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); + + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + takerTokenAmount, + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); + try { + await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync( + signedOrder, availableTakerTokenAmount); + } catch (err) { + globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address); + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isCancelling: false, + globalErrMsg, + }); + return; + } + try { + await this.props.blockchain.cancelOrderAsync( + signedOrder, availableTakerTokenAmount, + ); + this.setState({ + isCancelling: false, + didCancelOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: takerTokenAmount, + }); + return; + } catch (err) { + this.setState({ + isCancelling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to cancel order, please refresh and try again'; + utils.consoleLog(`${err}`); + await errorReporter.reportAsync(err); + this.setState({ + globalErrMsg, + }); + return; + } + } + private formatCurrencyAmount(amount: BigNumber, decimals: number): number { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; + return roundedUnitAmount; + } + private onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + if (!didConfirmTokenTracking) { + this.setState({ + orderJSON: '', + orderJSONErrMsg: '', + parsedOrder: undefined, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + this.setState({ + isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, + tokensToTrack: [], + }); + } +} diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx new file mode 100644 index 000000000..b355d910b --- /dev/null +++ b/packages/website/ts/components/fill_order_json.tsx @@ -0,0 +1,69 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import Paper from 'material-ui/Paper'; +import TextField from 'material-ui/TextField'; +import {Side, TokenByAddress} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Blockchain} from 'ts/blockchain'; +import {constants} from 'ts/utils/constants'; + +interface FillOrderJSONProps { + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + networkId: number; + orderJSON: string; + onFillOrderJSONChanged: (event: any) => void; +} + +interface FillOrderJSONState {} + +export class FillOrderJSON extends React.Component { + public render() { + const tokenAddresses = _.keys(this.props.tokenByAddress); + const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); + const hintSideToAssetToken = { + [Side.deposit]: { + amount: new BigNumber(35), + address: tokenAddresses[0], + }, + [Side.receive]: { + amount: new BigNumber(89), + address: tokenAddresses[1], + }, + }; + const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const hintSignatureData = { + hash: '0xf965a9978a0381ab58f5a2408ad967c...', + r: '0xf01103f759e2289a28593eaf22e5820032...', + s: '937862111edcba395f8a9e0cc1b2c5e12320...', + v: 27, + }; + const hintSalt = ZeroEx.generatePseudoRandomSalt(); + const hintOrder = utils.generateOrder(this.props.networkId, exchangeContract, hintSideToAssetToken, + hintOrderExpiryTimestamp, '', '', constants.MAKER_FEE, + constants.TAKER_FEE, constants.FEE_RECIPIENT_ADDRESS, + hintSignatureData, this.props.tokenByAddress, hintSalt); + const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; + return ( +
+ + + +
+ ); + } +} diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx new file mode 100644 index 000000000..029fa8b0c --- /dev/null +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; + +interface FillWarningDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function FillWarningDialog(props: FillWarningDialogProps) { + const didCancel = true; + return ( + , + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > +
+
+ At least one of the tokens in this order was not found in the + token registry smart contract and may be counterfeit. It is your + responsibility to verify the token addresses on Etherscan ( + + See this how-to guide + ) before filling an order. This action may result in the loss of funds. +
+
+
+ ); +} diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx new file mode 100644 index 000000000..c4977d70b --- /dev/null +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import {Token} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface TokenSendCompletedProps { + etherScanLinkIfExists?: string; + token: Token; + toAddress: string; + amountInBaseUnits: BigNumber; +} + +interface TokenSendCompletedState {} + +export class TokenSendCompleted extends React.Component { + public render() { + const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && + ( + + Verify on Etherscan + + ); + const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); + const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); + return ( +
+ {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} + {etherScanLink} +
+ ); + } +} diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx new file mode 100644 index 000000000..7a3cc6e86 --- /dev/null +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import * as _ from 'lodash'; + +interface TransactionSubmittedProps { + etherScanLinkIfExists?: string; +} + +interface TransactionSubmittedState {} + +export class TransactionSubmitted extends React.Component { + public render() { + if (_.isUndefined(this.props.etherScanLinkIfExists)) { + return
Transaction submitted to the network
; + } else { + return ( +
+ Transaction submitted to the network:{' '} + + Verify on Etherscan + +
+ ); + } + } +} diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx new file mode 100644 index 000000000..99f97292e --- /dev/null +++ b/packages/website/ts/components/footer.tsx @@ -0,0 +1,255 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {HashLink} from 'react-router-hash-link'; +import {Styles, WebsitePaths} from 'ts/types'; +import { + Link, +} from 'react-router-dom'; +import { + Link as ScrollLink, +} from 'react-scroll'; +import {constants} from 'ts/utils/constants'; + +interface MenuItemsBySection { + [sectionName: string]: FooterMenuItem[]; +} + +interface FooterMenuItem { + title: string; + path?: string; + isExternal?: boolean; + fileName?: string; +} + +enum Sections { + Documentation = 'Documentation', + Community = 'Community', + Organization = 'Organization', +} + +const ICON_DIMENSION = 16; +const CUSTOM_DARK_GRAY = '#393939'; +const CUSTOM_LIGHT_GRAY = '#CACACA'; +const CUSTOM_LIGHTEST_GRAY = '#9E9E9E'; +const menuItemsBySection: MenuItemsBySection = { + Documentation: [ + { + title: '0x.js', + path: WebsitePaths.ZeroExJs, + }, + { + title: '0x Smart Contracts', + path: WebsitePaths.SmartContracts, + }, + { + title: 'Whitepaper', + path: WebsitePaths.Whitepaper, + isExternal: true, + }, + { + title: 'Wiki', + path: WebsitePaths.Wiki, + }, + { + title: 'FAQ', + path: WebsitePaths.FAQ, + }, + ], + Community: [ + { + title: 'Rocket.chat', + isExternal: true, + path: constants.ZEROEX_CHAT_URL, + fileName: 'rocketchat.png', + }, + { + title: 'Blog', + isExternal: true, + path: constants.BLOG_URL, + fileName: 'medium.png', + }, + { + title: 'Twitter', + isExternal: true, + path: constants.TWITTER_URL, + fileName: 'twitter.png', + }, + { + title: 'Reddit', + isExternal: true, + path: constants.REDDIT_URL, + fileName: 'reddit.png', + }, + ], + Organization: [ + { + title: 'About', + isExternal: false, + path: WebsitePaths.About, + }, + { + title: 'Careers', + isExternal: true, + path: constants.ANGELLIST_URL, + }, + { + title: 'Contact', + isExternal: true, + path: 'mailto:team@0xproject.com', + }, + ], +}; +const linkStyle = { + color: 'white', + cursor: 'pointer', +}; + +const titleToIcon: {[title: string]: string} = { + 'Rocket.chat': 'rocketchat.png', + 'Blog': 'medium.png', + 'Twitter': 'twitter.png', + 'Reddit': 'reddit.png', +}; + +export interface FooterProps { + location: Location; +} + +interface FooterState {} + +export class Footer extends React.Component { + public render() { + return ( +
+
+
+
+
+ +
+
+ © ZeroEx, Intl. +
+
+
+
+
+
+ {this.renderHeader(Sections.Documentation)} + {_.map(menuItemsBySection[Sections.Documentation], this.renderMenuItem.bind(this))} +
+
+
+
+ {this.renderHeader(Sections.Community)} + {_.map(menuItemsBySection[Sections.Community], this.renderMenuItem.bind(this))} +
+
+
+
+ {this.renderHeader(Sections.Organization)} + {_.map(menuItemsBySection[Sections.Organization], this.renderMenuItem.bind(this))} +
+
+
+
+
+ ); + } + private renderIcon(fileName: string) { + return ( +
+ +
+ ); + } + private renderMenuItem(item: FooterMenuItem) { + const iconIfExists = titleToIcon[item.title]; + return ( +
+ {item.isExternal ? + + {!_.isUndefined(iconIfExists) ? +
+
+
+ {this.renderIcon(iconIfExists)} +
+
{item.title}
+
+
: + item.title + } +
: + +
+ {!_.isUndefined(iconIfExists) && +
+ {this.renderIcon(iconIfExists)} +
+ } + {item.title} +
+ + } +
+ ); + } + private renderHeader(title: string) { + const headerStyle = { + textTransform: 'uppercase', + color: CUSTOM_LIGHT_GRAY, + letterSpacing: 2, + fontFamily: 'Roboto Mono', + fontSize: 13, + }; + return ( +
+ {title} +
+ ); + } + private renderHomepageLink(title: string) { + const hash = title.toLowerCase(); + if (this.props.location.pathname === WebsitePaths.Home) { + return ( + + {title} + + ); + } else { + return ( + + {title} + + ); + } + } +} diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx new file mode 100644 index 000000000..59826d06e --- /dev/null +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -0,0 +1,291 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import GridList from 'material-ui/GridList/GridList'; +import GridTile from 'material-ui/GridList/GridTile'; +import FlatButton from 'material-ui/FlatButton'; +import {utils} from 'ts/utils/utils'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + Token, + AssetToken, + TokenByAddress, + Styles, + TokenState, + DialogConfigs, + TokenVisibility, +} from 'ts/types'; +import {NewTokenForm} from 'ts/components/generate_order/new_token_form'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; +import {TokenIcon} from 'ts/components/ui/token_icon'; + +const TOKEN_ICON_DIMENSION = 100; +const TILE_DIMENSION = 146; +enum AssetViews { + ASSET_PICKER = 'ASSET_PICKER', + NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', + CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', +} + +interface AssetPickerProps { + userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + networkId: number; + isOpen: boolean; + currentTokenAddress: string; + onTokenChosen: (tokenAddress: string) => void; + tokenByAddress: TokenByAddress; + tokenVisibility?: TokenVisibility; +} + +interface AssetPickerState { + assetView: AssetViews; + hoveredAddress: string | undefined; + chosenTrackTokenAddress: string; + isAddingTokenToTracked: boolean; +} + +export class AssetPicker extends React.Component { + public static defaultProps: Partial = { + tokenVisibility: TokenVisibility.ALL, + }; + private dialogConfigsByAssetView: {[assetView: string]: DialogConfigs}; + constructor(props: AssetPickerProps) { + super(props); + this.state = { + assetView: AssetViews.ASSET_PICKER, + hoveredAddress: undefined, + chosenTrackTokenAddress: undefined, + isAddingTokenToTracked: false, + }; + this.dialogConfigsByAssetView = { + [AssetViews.ASSET_PICKER]: { + title: 'Select token', + isModal: false, + actions: [], + }, + [AssetViews.NEW_TOKEN_FORM]: { + title: 'Add an ERC20 token', + isModal: false, + actions: [], + }, + [AssetViews.CONFIRM_TRACK_TOKEN]: { + title: 'Tracking confirmation', + isModal: true, + actions: [ + , + , + ], + }, + }; + } + public render() { + const dialogConfigs: DialogConfigs = this.dialogConfigsByAssetView[this.state.assetView]; + return ( + + {this.state.assetView === AssetViews.ASSET_PICKER && + this.renderAssetPicker() + } + {this.state.assetView === AssetViews.NEW_TOKEN_FORM && + + } + {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && + this.renderConfirmTrackToken() + } + + ); + } + private renderConfirmTrackToken() { + const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; + return ( + + ); + } + private renderAssetPicker() { + return ( +
+ {this.renderGridTiles()} +
+ ); + } + private renderGridTiles() { + let isHovered; + let tileStyles; + const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { + if ((this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)) { + return null; // Skip + } + isHovered = this.state.hoveredAddress === address; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + return ( +
+
+ +
+
{token.name}
+
+ ); + }); + const otherTokenKey = 'otherToken'; + isHovered = this.state.hoveredAddress === otherTokenKey; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { + gridTiles.push(( +
+
+ +
+
Other ERC20 Token
+
+ )); + } + return gridTiles; + } + private onToggleHover(address: string, isHovered: boolean) { + const hoveredAddress = isHovered ? address : undefined; + this.setState({ + hoveredAddress, + }); + } + private onCloseDialog() { + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(this.props.currentTokenAddress); + } + private onChooseToken(tokenAddress: string) { + const token = this.props.tokenByAddress[tokenAddress]; + if (token.isTracked) { + this.props.onTokenChosen(tokenAddress); + } else { + this.setState({ + assetView: AssetViews.CONFIRM_TRACK_TOKEN, + chosenTrackTokenAddress: tokenAddress, + }); + } + } + private getTitle() { + switch (this.state.assetView) { + case AssetViews.ASSET_PICKER: + return 'Select token'; + + case AssetViews.NEW_TOKEN_FORM: + return 'Add an ERC20 token'; + + case AssetViews.CONFIRM_TRACK_TOKEN: + return 'Tracking confirmation'; + + default: + throw utils.spawnSwitchErr('assetView', this.state.assetView); + } + } + private onCustomAssetChosen() { + this.setState({ + assetView: AssetViews.NEW_TOKEN_FORM, + }); + } + private onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { + this.props.dispatcher.updateTokenStateByAddress({ + [newToken.address]: newTokenState, + }); + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); + this.props.dispatcher.addTokenToTokenByAddress(newToken); + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(newToken.address); + } + private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.onCloseDialog(); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + const tokenAddress = this.state.chosenTrackTokenAddress; + const token = this.props.tokenByAddress[tokenAddress]; + const newTokenEntry = _.assign({}, token); + + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + + const [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.props.onTokenChosen(tokenAddress); + } +} diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx new file mode 100644 index 000000000..e9026d9bc --- /dev/null +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -0,0 +1,348 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx, Order} from '0x.js'; +import BigNumber from 'bignumber.js'; +import {Blockchain} from 'ts/blockchain'; +import Divider from 'material-ui/Divider'; +import Dialog from 'material-ui/Dialog'; +import {colors} from 'material-ui/styles'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {utils} from 'ts/utils/utils'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {Alert} from 'ts/components/ui/alert'; +import {OrderJSON} from 'ts/components/order_json'; +import {IdenticonAddressInput} from 'ts/components/inputs/identicon_address_input'; +import {TokenInput} from 'ts/components/inputs/token_input'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {HashInput} from 'ts/components/inputs/hash_input'; +import {ExpirationInput} from 'ts/components/inputs/expiration_input'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {HelpTooltip} from 'ts/components/ui/help_tooltip'; +import {SwapIcon} from 'ts/components/ui/swap_icon'; +import { + Side, + SideToAssetToken, + SignatureData, + HashData, + TokenByAddress, + TokenStateByAddress, + BlockchainErrs, + Token, + AlertTypes, +} from 'ts/types'; + +enum SigningState { + UNSIGNED, + SIGNING, + SIGNED, +} + +interface GenerateOrderFormProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + orderExpiryTimestamp: BigNumber; + networkId: number; + userAddress: string; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderSalt: BigNumber; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; +} + +interface GenerateOrderFormState { + globalErrMsg: string; + shouldShowIncompleteErrs: boolean; + signingState: SigningState; +} + +const style = { + paper: { + display: 'inline-block', + position: 'relative', + textAlign: 'center', + width: '100%', + }, +}; + +export class GenerateOrderForm extends React.Component { + private validator: SchemaValidator; + constructor(props: GenerateOrderFormProps) { + super(props); + this.state = { + globalErrMsg: '', + shouldShowIncompleteErrs: false, + signingState: SigningState.UNSIGNED, + }; + this.validator = new SchemaValidator(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const dispatcher = this.props.dispatcher; + const depositTokenAddress = this.props.sideToAssetToken[Side.deposit].address; + const depositToken = this.props.tokenByAddress[depositTokenAddress]; + const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; + const receiveTokenAddress = this.props.sideToAssetToken[Side.receive].address; + const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; + const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; + const takerExplanation = 'If a taker is specified, only they are
\ + allowed to fill this order. If no taker is
\ + specified, anyone is able to fill it.'; + const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); + return ( +
+

Generate an order

+ +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+
+
Expiration
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ {this.state.globalErrMsg !== '' && + + } +
+
+ + + +
+ ); + } + private onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateChosenAssetToken(side, {address: token.address, amount}); + } + private onCloseOrderJSONDialog() { + // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store + // with a new value so that if a user signs the identical order again, the newly signed + // orderHash will not collide with the previously generated orderHash. + this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + } + private async onSignClickedAsync(): Promise { + if (this.props.blockchainErr !== '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + // Check if all required inputs were supplied + const debitToken = this.props.sideToAssetToken[Side.deposit]; + const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; + const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; + const receiveAmount = this.props.sideToAssetToken[Side.receive].amount; + if (!_.isUndefined(debitToken.amount) && !_.isUndefined(receiveAmount) && + debitToken.amount.gt(0) && receiveAmount.gt(0) && + this.props.userAddress !== '' && + debitBalance.gte(debitToken.amount) && debitAllowance.gte(debitToken.amount)) { + const didSignSuccessfully = await this.signTransactionAsync(); + if (didSignSuccessfully) { + this.setState({ + globalErrMsg: '', + shouldShowIncompleteErrs: false, + }); + } + return didSignSuccessfully; + } else { + let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; + if (this.props.userAddress === '') { + globalErrMsg = 'You must enable wallet communication'; + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } + this.setState({ + globalErrMsg, + shouldShowIncompleteErrs: true, + }); + return false; + } + } + private async signTransactionAsync(): Promise { + this.setState({ + signingState: SigningState.SIGNING, + }); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + if (_.isUndefined(exchangeContractAddr)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isSigning: false, + }); + return false; + } + const hashData = this.props.hashData; + + const zeroExOrder: Order = { + exchangeContractAddress: exchangeContractAddr, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + + let globalErrMsg = ''; + try { + const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); + const order = utils.generateOrder(this.props.networkId, exchangeContractAddr, this.props.sideToAssetToken, + hashData.orderExpiryTimestamp, this.props.orderTakerAddress, + this.props.userAddress, hashData.makerFee, hashData.takerFee, + hashData.feeRecipientAddress, signatureData, this.props.tokenByAddress, + hashData.orderSalt); + const validationResult = this.validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + globalErrMsg = 'Order signing failed. Please refresh and try again'; + utils.consoleLog(`Unexpected error occured: Order validation failed: + ${validationResult.errors}`); + } + } catch (err) { + const errMsg = '' + err; + if (utils.didUserDenyWeb3Request(errMsg)) { + globalErrMsg = 'User denied sign request'; + } else { + globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; + utils.consoleLog(`Unexpected error occured: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + } + } + this.setState({ + signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, + globalErrMsg, + }); + return globalErrMsg === ''; + } + private updateOrderAddress(address?: string): void { + if (!_.isUndefined(address)) { + this.props.dispatcher.updateOrderTakerAddress(address); + } + } +} diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx new file mode 100644 index 000000000..95c05f5bb --- /dev/null +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -0,0 +1,237 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import TextField from 'material-ui/TextField'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Token, TokenState, TokenByAddress, AlertTypes} from 'ts/types'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import {Alert} from 'ts/components/ui/alert'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import BigNumber from 'bignumber.js'; + +interface NewTokenFormProps { + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; +} + +interface NewTokenFormState { + globalErrMsg: string; + name: string; + nameErrText: string; + symbol: string; + symbolErrText: string; + address: string; + shouldShowAddressIncompleteErr: boolean; + decimals: string; + decimalsErrText: string; +} + +export class NewTokenForm extends React.Component { + constructor(props: NewTokenFormProps) { + super(props); + this.state = { + address: '', + globalErrMsg: '', + name: '', + nameErrText: '', + shouldShowAddressIncompleteErr: false, + symbol: '', + symbolErrText: '', + decimals: '18', + decimalsErrText: '', + }; + } + public render() { + return ( +
+
+ } + value={this.state.name} + errorText={this.state.nameErrText} + onChange={this.onTokenNameChanged.bind(this)} + /> +
+
+ } + value={this.state.symbol} + errorText={this.state.symbolErrText} + onChange={this.onTokenSymbolChanged.bind(this)} + /> +
+
+ +
+
+ } + value={this.state.decimals} + errorText={this.state.decimalsErrText} + onChange={this.onTokenDecimalsChanged.bind(this)} + /> +
+
+ +
+ {this.state.globalErrMsg !== '' && + + } +
+ ); + } + private async onAddNewTokenClickAsync() { + // Trigger validation of name and symbol + this.onTokenNameChanged(undefined, this.state.name); + this.onTokenSymbolChanged(undefined, this.state.symbol); + this.onTokenDecimalsChanged(undefined, this.state.decimals); + + const isAddressIncomplete = this.state.address === ''; + let doesContractExist = false; + if (!isAddressIncomplete) { + doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); + } + + let hasBalanceAllowanceErr = false; + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (doesContractExist) { + try { + [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address); + } catch (err) { + hasBalanceAllowanceErr = true; + } + } + + let globalErrMsg = ''; + if (this.state.nameErrText !== '' || this.state.symbolErrText !== '' || + this.state.decimalsErrText !== '' || isAddressIncomplete) { + globalErrMsg = 'Please fix the above issues'; + } else if (!doesContractExist) { + globalErrMsg = 'No contract found at supplied address'; + } else if (hasBalanceAllowanceErr) { + globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; + } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { + globalErrMsg = 'A token already exists with this address'; + } + + if (globalErrMsg !== '') { + this.setState({ + globalErrMsg, + shouldShowAddressIncompleteErr: isAddressIncomplete, + }); + return; + } + + const newToken: Token = { + address: this.state.address, + decimals: _.parseInt(this.state.decimals), + iconUrl: undefined, + name: this.state.name, + symbol: this.state.symbol.toUpperCase(), + isTracked: true, + isRegistered: false, + }; + const newTokenState: TokenState = { + balance, + allowance, + }; + this.props.onNewTokenSubmitted(newToken, newTokenState); + } + private onTokenNameChanged(e: any, name: string) { + let nameErrText = ''; + const maxLength = 30; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithNameIfExists = _.find(tokens, {name}); + const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); + if (name === '') { + nameErrText = 'Name is required'; + } else if (!this.isValidName(name)) { + nameErrText = 'Name should only contain letters, digits and spaces'; + } else if (name.length > maxLength) { + nameErrText = `Max length is ${maxLength}`; + } else if (tokenWithNameExists) { + nameErrText = 'Token with this name already exists'; + } + + this.setState({ + name, + nameErrText, + }); + } + private onTokenSymbolChanged(e: any, symbol: string) { + let symbolErrText = ''; + const maxLength = 5; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, {symbol})); + if (symbol === '') { + symbolErrText = 'Symbol is required'; + } else if (!this.isLetters(symbol)) { + symbolErrText = 'Can only include letters'; + } else if (symbol.length > maxLength) { + symbolErrText = `Max length is ${maxLength}`; + } else if (tokenWithSymbolExists) { + symbolErrText = 'Token with symbol already exists'; + } + + this.setState({ + symbol, + symbolErrText, + }); + } + private onTokenDecimalsChanged(e: any, decimals: string) { + let decimalsErrText = ''; + const maxLength = 2; + if (decimals === '') { + decimalsErrText = 'Decimals is required'; + } else if (!this.isInteger(decimals)) { + decimalsErrText = 'Must be an integer'; + } else if (decimals.length > maxLength) { + decimalsErrText = `Max length is ${maxLength}`; + } + + this.setState({ + decimals, + decimalsErrText, + }); + } + private onTokenAddressChanged(address?: string) { + if (!_.isUndefined(address)) { + this.setState({ + address, + }); + } + } + private isValidName(input: string) { + return /^[a-z0-9 ]+$/i.test(input); + } + private isInteger(input: string) { + return /^[0-9]+$/i.test(input); + } + private isLetters(input: string) { + return /^[a-zA-Z]+$/i.test(input); + } +} diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx new file mode 100644 index 000000000..57ad7a5e2 --- /dev/null +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -0,0 +1,74 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {isAddress} from 'ethereum-address'; +import TextField from 'material-ui/TextField'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {RequiredLabel} from 'ts/components/ui/required_label'; + +interface AddressInputProps { + disabled?: boolean; + initialAddress: string; + isRequired?: boolean; + hintText?: string; + shouldHideLabel?: boolean; + label?: string; + shouldShowIncompleteErrs?: boolean; + updateAddress: (address?: string) => void; +} + +interface AddressInputState { + address: string; + errMsg: string; +} + +export class AddressInput extends React.Component { + constructor(props: AddressInputProps) { + super(props); + this.state = { + address: this.props.initialAddress, + errMsg: '', + }; + } + public componentWillReceiveProps(nextProps: AddressInputProps) { + if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && + this.state.address === '') { + this.setState({ + errMsg: 'Address is required', + }); + } + } + public render() { + const label = this.props.isRequired ? : + this.props.label; + const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; + const hintText = this.props.hintText ? this.props.hintText : ''; + return ( +
+ +
+ ); + } + private onOrderTakerAddressUpdated(e: any) { + const address = e.target.value.toLowerCase(); + const isValidAddress = isAddress(address) || address === ''; + const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; + this.setState({ + address, + errMsg, + }); + const addressIfValid = isValidAddress ? address : undefined; + this.props.updateAddress(addressIfValid); + } +} diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx new file mode 100644 index 000000000..f02112253 --- /dev/null +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import Toggle from 'material-ui/Toggle'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {Token, TokenState, BalanceErrs} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {errorReporter} from 'ts/utils/error_reporter'; + +const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); + +interface AllowanceToggleProps { + blockchain: Blockchain; + dispatcher: Dispatcher; + onErrorOccurred: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + userAddress: string; +} + +interface AllowanceToggleState { + isSpinnerVisible: boolean; + prevAllowance: BigNumber; +} + +export class AllowanceToggle extends React.Component { + constructor(props: AllowanceToggleProps) { + super(props); + this.state = { + isSpinnerVisible: false, + prevAllowance: props.tokenState.allowance, + }; + } + public componentWillReceiveProps(nextProps: AllowanceToggleProps) { + if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { + this.setState({ + isSpinnerVisible: false, + prevAllowance: nextProps.tokenState.allowance, + }); + } + } + public render() { + return ( +
+
+ +
+ {this.state.isSpinnerVisible && +
+ +
+ } +
+ ); + } + private async onToggleAllowanceAsync() { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + this.setState({ + isSpinnerVisible: true, + }); + + let newAllowanceAmountInBaseUnits = new BigNumber(0); + if (!this.isAllowanceSet()) { + newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; + } + try { + await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); + } catch (err) { + this.setState({ + isSpinnerVisible: false, + }); + const errMsg = '' + err; + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); + } + } + private isAllowanceSet() { + return !this.props.tokenState.allowance.eq(0); + } +} diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx new file mode 100644 index 000000000..1c8b410a4 --- /dev/null +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -0,0 +1,160 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {ValidatedBigNumberCallback, InputErrMsg, WebsitePaths} from 'ts/types'; +import TextField from 'material-ui/TextField'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {Link} from 'react-router-dom'; + +interface BalanceBoundedInputProps { + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs?: boolean; + shouldCheckBalance: boolean; + validate?: (amount: BigNumber) => InputErrMsg; + onVisitBalancesPageClick?: () => void; + shouldHideVisitBalancesLink?: boolean; +} + +interface BalanceBoundedInputState { + errMsg: InputErrMsg; + amountString: string; +} + +export class BalanceBoundedInput extends + React.Component { + public static defaultProps: Partial = { + shouldShowIncompleteErrs: false, + shouldHideVisitBalancesLink: false, + }; + constructor(props: BalanceBoundedInputProps) { + super(props); + const amountString = this.props.amount ? this.props.amount.toString() : ''; + this.state = { + errMsg: this.validate(amountString, props.balance), + amountString, + }; + } + public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { + if (nextProps === this.props) { + return; + } + const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); + if (!_.isUndefined(nextProps.amount)) { + let shouldResetState = false; + if (!isCurrentAmountNumeric) { + shouldResetState = true; + } else { + const currentAmount = new BigNumber(this.state.amountString); + if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { + shouldResetState = true; + } + } + if (shouldResetState) { + const amountString = nextProps.amount.toString(); + this.setState({ + errMsg: this.validate(amountString, nextProps.balance), + amountString, + }); + } + } else if (isCurrentAmountNumeric) { + const amountString = ''; + this.setState({ + errMsg: this.validate(amountString, nextProps.balance), + amountString, + }); + } + } + public render() { + let errorText = this.state.errMsg; + if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { + errorText = 'This field is required'; + } + let label: React.ReactNode|string = ''; + if (!_.isUndefined(this.props.label)) { + label = ; + } + return ( + amount} + onChange={this.onValueChange.bind(this)} + underlineStyle={{width: 'calc(100% + 50px)'}} + /> + ); + } + private onValueChange(e: any, amountString: string) { + const errMsg = this.validate(amountString, this.props.balance); + this.setState({ + amountString, + errMsg, + }, () => { + const isValid = _.isUndefined(errMsg); + if (utils.isNumeric(amountString)) { + this.props.onChange(isValid, new BigNumber(amountString)); + } else { + this.props.onChange(isValid); + } + }); + } + private validate(amountString: string, balance: BigNumber): InputErrMsg { + if (!utils.isNumeric(amountString)) { + return amountString !== '' ? 'Must be a number' : ''; + } + const amount = new BigNumber(amountString); + if (amount.eq(0)) { + return 'Cannot be zero'; + } + if (this.props.shouldCheckBalance && amount.gt(balance)) { + return ( + + Insufficient balance.{' '} + {this.renderIncreaseBalanceLink()} + + ); + } + const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); + return errMsg; + } + private renderIncreaseBalanceLink() { + if (this.props.shouldHideVisitBalancesLink) { + return null; + } + + const increaseBalanceText = 'Increase balance'; + const linkStyle = { + cursor: 'pointer', + color: colors.grey900, + textDecoration: 'underline', + display: 'inline', + }; + if (_.isUndefined(this.props.onVisitBalancesPageClick)) { + return ( + + {increaseBalanceText} + + ); + } else { + return ( +
+ {increaseBalanceText} +
+ ); + } + } +} diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx new file mode 100644 index 000000000..ad551e125 --- /dev/null +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -0,0 +1,51 @@ +import BigNumber from 'bignumber.js'; +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import {ValidatedBigNumberCallback} from 'ts/types'; +import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; +import {constants} from 'ts/utils/constants'; + +interface EthAmountInputProps { + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs: boolean; + onVisitBalancesPageClick?: () => void; + shouldCheckBalance: boolean; + shouldHideVisitBalancesLink?: boolean; +} + +interface EthAmountInputState {} + +export class EthAmountInput extends React.Component { + public render() { + const amount = this.props.amount ? + ZeroEx.toUnitAmount(this.props.amount, constants.ETH_DECIMAL_PLACES) : + undefined; + return ( +
+ +
+ ETH +
+
+ ); + } + private onChange(isValid: boolean, amount?: BigNumber) { + const baseUnitAmountIfExists = _.isUndefined(amount) ? + undefined : + ZeroEx.toBaseUnitAmount(amount, constants.ETH_DECIMAL_PLACES); + this.props.onChange(isValid, baseUnitAmountIfExists); + } +} diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx new file mode 100644 index 000000000..32dcad189 --- /dev/null +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -0,0 +1,108 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import DatePicker from 'material-ui/DatePicker'; +import TimePicker from 'material-ui/TimePicker'; +import {utils} from 'ts/utils/utils'; +import BigNumber from 'bignumber.js'; +import * as moment from 'moment'; + +interface ExpirationInputProps { + orderExpiryTimestamp: BigNumber; + updateOrderExpiry: (unixTimestampSec: BigNumber) => void; +} + +interface ExpirationInputState { + dateMoment: moment.Moment; + timeMoment: moment.Moment; +} + +export class ExpirationInput extends React.Component { + private earliestPickableMoment: moment.Moment; + constructor(props: ExpirationInputProps) { + super(props); + // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates + this.earliestPickableMoment = moment().startOf('day'); + const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); + const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); + this.state = { + dateMoment: didUserSetExpiry ? expirationMoment : undefined, + timeMoment: didUserSetExpiry ? expirationMoment : undefined, + }; + } + public render() { + const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; + const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; + return ( +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ ); + } + private shouldDisableDate(date: Date): boolean { + return moment(date).startOf('day').isBefore(this.earliestPickableMoment); + } + private clearDates() { + this.setState({ + dateMoment: undefined, + timeMoment: undefined, + }); + const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); + this.props.updateOrderExpiry(defaultDateTime); + } + private onDateChanged(e: any, date: Date) { + const dateMoment = moment(date); + this.setState({ + dateMoment, + }); + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); + this.props.updateOrderExpiry(timestamp); + } + private onTimeChanged(e: any, time: Date) { + const timeMoment = moment(time); + this.setState({ + timeMoment, + }); + const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); + this.props.updateOrderExpiry(timestamp); + } +} diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx new file mode 100644 index 000000000..3e42f1d5f --- /dev/null +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import {Blockchain} from 'ts/blockchain'; +import {ZeroEx, Order} from '0x.js'; +import {FakeTextField} from 'ts/components/ui/fake_text_field'; +import ReactTooltip = require('react-tooltip'); +import {HashData, Styles} from 'ts/types'; +import {constants} from 'ts/utils/constants'; + +const styles: Styles = { + textField: { + overflow: 'hidden', + paddingTop: 8, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, +}; + +interface HashInputProps { + blockchain: Blockchain; + blockchainIsLoaded: boolean; + hashData: HashData; + label: string; +} + +interface HashInputState {} + +export class HashInput extends React.Component { + public render() { + const msgHashHex = this.props.blockchainIsLoaded ? this.generateMessageHashHex() : ''; + return ( +
+ +
+ {msgHashHex} +
+
+ {msgHashHex} +
+ ); + } + private generateMessageHashHex() { + const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); + const hashData = this.props.hashData; + const order: Order = { + exchangeContractAddress, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + return orderHash; + } +} diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx new file mode 100644 index 000000000..6452f5fe9 --- /dev/null +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -0,0 +1,56 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {Identicon} from 'ts/components/ui/identicon'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import {InputLabel} from 'ts/components/ui/input_label'; + +interface IdenticonAddressInputProps { + initialAddress: string; + isRequired?: boolean; + label: string; + updateOrderAddress: (address?: string) => void; +} + +interface IdenticonAddressInputState { + address: string; +} + +export class IdenticonAddressInput extends React.Component { + constructor(props: IdenticonAddressInputProps) { + super(props); + this.state = { + address: props.initialAddress, + }; + } + public render() { + const label = this.props.isRequired ? : + this.props.label; + return ( +
+ +
+
+ +
+
+ +
+
+
+ ); + } + private updateAddress(address?: string): void { + this.setState({ + address, + }); + this.props.updateOrderAddress(address); + } +} diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx new file mode 100644 index 000000000..e19af8984 --- /dev/null +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import {Link} from 'react-router-dom'; +import {colors} from 'material-ui/styles'; +import {Token, TokenState, InputErrMsg, ValidatedBigNumberCallback, WebsitePaths} from 'ts/types'; +import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; + +interface TokenAmountInputProps { + label: string; + token: Token; + tokenState: TokenState; + amount?: BigNumber; + shouldShowIncompleteErrs: boolean; + shouldCheckBalance: boolean; + shouldCheckAllowance: boolean; + onChange: ValidatedBigNumberCallback; + onVisitBalancesPageClick?: () => void; +} + +interface TokenAmountInputState {} + +export class TokenAmountInput extends React.Component { + public render() { + const amount = this.props.amount ? + ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) : + undefined; + return ( +
+ +
+ {this.props.token.symbol} +
+
+ ); + } + private onChange(isValid: boolean, amount?: BigNumber) { + let baseUnitAmount; + if (!_.isUndefined(amount)) { + baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); + } + this.props.onChange(isValid, baseUnitAmount); + } + private validate(amount: BigNumber): InputErrMsg { + if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { + return ( + + Insufficient allowance.{' '} + + Set allowance + + + ); + } + } +} diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx new file mode 100644 index 000000000..2be74d4fd --- /dev/null +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -0,0 +1,107 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {AssetToken, Side, TokenByAddress, BlockchainErrs, Token, TokenState} from 'ts/types'; +import {AssetPicker} from 'ts/components/generate_order/asset_picker'; +import {InputLabel} from 'ts/components/ui/input_label'; +import {TokenIcon} from 'ts/components/ui/token_icon'; + +const TOKEN_ICON_DIMENSION = 80; + +interface TokenInputProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + dispatcher: Dispatcher; + label: string; + side: Side; + networkId: number; + assetToken: AssetToken; + updateChosenAssetToken: (side: Side, token: AssetToken) => void; + tokenByAddress: TokenByAddress; + userAddress: string; +} + +interface TokenInputState { + isHoveringIcon: boolean; + isPickerOpen: boolean; + trackCandidateTokenIfExists?: Token; +} + +export class TokenInput extends React.Component { + constructor(props: TokenInputProps) { + super(props); + this.state = { + isHoveringIcon: false, + isPickerOpen: false, + }; + } + public render() { + const token = this.props.tokenByAddress[this.props.assetToken.address]; + const iconStyles = { + cursor: 'pointer', + opacity: this.state.isHoveringIcon ? 0.5 : 1, + }; + return ( +
+
+ +
+ +
+ +
+
+ {token.name} +
+
+ +
+ ); + } + private onTokenChosen(tokenAddress: string) { + const assetToken: AssetToken = { + address: tokenAddress, + amount: this.props.assetToken.amount, + }; + this.props.updateChosenAssetToken(this.props.side, assetToken); + this.setState({ + isPickerOpen: false, + }); + } + private onToggleHover(isHoveringIcon: boolean) { + this.setState({ + isHoveringIcon, + }); + } + private onAssetClicked() { + if (this.props.blockchainErr !== '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isPickerOpen: true, + }); + } +} diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx new file mode 100644 index 000000000..90e3543dd --- /dev/null +++ b/packages/website/ts/components/order_json.tsx @@ -0,0 +1,164 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {utils} from 'ts/utils/utils'; +import {colors} from 'material-ui/styles'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import TextField from 'material-ui/TextField'; +import Paper from 'material-ui/Paper'; +import {CopyIcon} from 'ts/components/ui/copy_icon'; +import {SideToAssetToken, SignatureData, Order, TokenByAddress, WebsitePaths} from 'ts/types'; +import {errorReporter} from 'ts/utils/error_reporter'; + +interface OrderJSONProps { + exchangeContractIfExists: string; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderMakerAddress: string; + orderSalt: BigNumber; + orderMakerFee: BigNumber; + orderTakerFee: BigNumber; + orderFeeRecipient: string; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; +} + +interface OrderJSONState { + shareLink: string; +} + +export class OrderJSON extends React.Component { + constructor(props: OrderJSONProps) { + super(props); + this.state = { + shareLink: '', + }; + this.setShareLinkAsync(); + } + public render() { + const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, + this.props.sideToAssetToken, this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, this.props.orderMakerAddress, + this.props.orderMakerFee, this.props.orderTakerFee, + this.props.orderFeeRecipient, this.props.orderSignatureData, + this.props.tokenByAddress, this.props.orderSalt); + const orderJSON = JSON.stringify(order); + return ( +
+
+ You have successfully generated and cryptographically signed an order! The{' '} + following JSON contains the order parameters and cryptographic signature that{' '} + your counterparty will need to execute a trade with you. +
+
+
+ +
+
+ + + +
+
+ Share your signed order! +
+
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ ); + } + private async shareViaTwitterAsync() { + const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); + window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); + } + private async shareViaFacebook() { + (window as any).FB.ui({ + display: 'popup', + href: this.state.shareLink, + method: 'share', + }, _.noop); + } + private async shareViaEmailAsync() { + const encodedSubject = encodeURIComponent('Let\'s trade using the 0x protocol'); + const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. +You can see and fill it here: ${this.state.shareLink}`); + const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; + window.open(mailToLink, '_blank'); + } + private async setShareLinkAsync() { + const shareLink = await this.generateShareLinkAsync(); + this.setState({ + shareLink, + }); + } + private async generateShareLinkAsync(): Promise { + const longUrl = encodeURIComponent(this.getOrderUrl()); + const bitlyRequestUrl = constants.BITLY_ENDPOINT + '/v3/shorten?' + + 'access_token=' + constants.BITLY_ACCESS_TOKEN + + '&longUrl=' + longUrl; + const response = await fetch(bitlyRequestUrl); + const responseBody = await response.text(); + const bodyObj = JSON.parse(responseBody); + if (response.status !== 200 || bodyObj.status_code !== 200) { + // TODO: Show error message in UI + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); + return ''; + } + return (bodyObj as any).data.url; + } + private getOrderUrl() { + const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, + this.props.sideToAssetToken, this.props.orderExpiryTimestamp, this.props.orderTakerAddress, + this.props.orderMakerAddress, this.props.orderMakerFee, this.props.orderTakerFee, + this.props.orderFeeRecipient, this.props.orderSignatureData, this.props.tokenByAddress, + this.props.orderSalt); + const orderJSONString = JSON.stringify(order); + const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; + return orderUrl; + } +} diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx new file mode 100644 index 000000000..3591a347b --- /dev/null +++ b/packages/website/ts/components/portal.tsx @@ -0,0 +1,344 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import {Switch, Route} from 'react-router-dom'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {utils} from 'ts/utils/utils'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import Paper from 'material-ui/Paper'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {GenerateOrderForm} from 'ts/containers/generate_order_form'; +import {TokenBalances} from 'ts/components/token_balances'; +import {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog'; +import {FillOrder} from 'ts/components/fill_order'; +import {Blockchain} from 'ts/blockchain'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {localStorage} from 'ts/local_storage/local_storage'; +import {TradeHistory} from 'ts/components/trade_history/trade_history'; +import { + HashData, + TokenByAddress, + BlockchainErrs, + Order, + Fill, + Side, + Styles, + ScreenWidths, + Token, + TokenStateByAddress, + WebsitePaths, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {Footer} from 'ts/components/footer'; +import {Loading} from 'ts/components/ui/loading'; +import {PortalMenu} from 'ts/components/portal_menu'; +import {BlockchainErrDialog} from 'ts/components/dialogs/blockchain_err_dialog'; +import BigNumber from 'bignumber.js'; +import {FlashMessage} from 'ts/components/ui/flash_message'; + +const THROTTLE_TIMEOUT = 100; + +export interface PortalPassedProps {} + +export interface PortalAllProps { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + userAddress: string; + shouldBlockchainErrDialogBeOpen: boolean; + userSuppliedOrderCache: Order; + location: Location; + flashMessage?: string|React.ReactNode; +} + +interface PortalAllState { + prevNetworkId: number; + prevNodeVersion: string; + prevUserAddress: string; + hasAcceptedDisclaimer: boolean; +} + +const styles: Styles = { + button: { + color: 'white', + }, + headline: { + fontSize: 20, + fontWeight: 400, + marginBottom: 12, + paddingTop: 16, + }, + inkBar: { + background: colors.amber600, + }, + menuItem: { + padding: '0px 16px 0px 48px', + }, + tabItemContainer: { + background: colors.blueGrey500, + borderRadius: '4px 4px 0 0', + }, +}; + +export class Portal extends React.Component { + private blockchain: Blockchain; + private sharedOrderIfExists: Order; + private throttledScreenWidthUpdate: () => void; + constructor(props: PortalAllProps) { + super(props); + this.sharedOrderIfExists = this.getSharedOrderIfExists(); + this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + this.state = { + prevNetworkId: this.props.networkId, + prevNodeVersion: this.props.nodeVersion, + prevUserAddress: this.props.userAddress, + hasAcceptedDisclaimer: false, + }; + } + public componentDidMount() { + window.addEventListener('resize', this.throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillMount() { + this.blockchain = new Blockchain(this.props.dispatcher); + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY); + const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && + !_.isEmpty(didAcceptPortalDisclaimer); + this.setState({ + hasAcceptedDisclaimer, + }); + } + public componentWillUnmount() { + this.blockchain.destroy(); + window.removeEventListener('resize', this.throttledScreenWidthUpdate); + // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered + // the initialization process always occurs from the same base state. This helps avoid + // initialization inconsistencies (i.e While the portal was unrendered, the user might have + // become disconnected from their backing Ethereum node, changes user accounts, etc...) + this.props.dispatcher.resetState(); + } + public componentWillReceiveProps(nextProps: PortalAllProps) { + if (nextProps.networkId !== this.state.prevNetworkId) { + this.blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this.setState({ + prevNetworkId: nextProps.networkId, + }); + } + if (nextProps.userAddress !== this.state.prevUserAddress) { + this.blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + if (!_.isEmpty(nextProps.userAddress) && + nextProps.blockchainIsLoaded) { + const tokens = _.values(nextProps.tokenByAddress); + this.updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + } + this.setState({ + prevUserAddress: nextProps.userAddress, + }); + } + if (nextProps.nodeVersion !== this.state.prevNodeVersion) { + this.blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + } + public render() { + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher + .updateShouldBlockchainErrDialogBeOpen.bind(this.props.dispatcher); + const portalStyle: React.CSSProperties = { + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }; + return ( +
+ + +
+ + {!configs.isMainnetEnabled && this.props.networkId === constants.MAINNET_NETWORK_ID ? +
+
Mainnet unavailable
+
+ +
+
+ 0x portal is currently unavailable on the Ethereum mainnet. +
+ To try it out, switch to the Kovan test network + (networkId: 42). +
+
+ Check back soon! +
+
+
: +
+
+ +
+
+
+ {this.props.blockchainIsLoaded ? + + + + + + : + + } +
+
+
+ } +
+ + + +
+
+
+ ); + } + private renderTradeHistory() { + return ( + + ); + } + private renderTokenBalances() { + return ( + + ); + } + private renderFillOrder(match: any, location: Location, history: History) { + const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? + this.props.userSuppliedOrderCache : + this.sharedOrderIfExists; + return ( + + ); + } + private renderGenerateOrderForm(match: any, location: Location, history: History) { + return ( + + ); + } + private onPortalDisclaimerAccepted() { + localStorage.setItem(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY, 'set'); + this.setState({ + hasAcceptedDisclaimer: true, + }); + } + private getSharedOrderIfExists(): Order { + const queryString = window.location.search; + if (queryString.length === 0) { + return; + } + const queryParams = queryString.substring(1).split('&'); + const orderQueryParam = _.find(queryParams, queryParam => { + const queryPair = queryParam.split('='); + return queryPair[0] === 'order'; + }); + if (_.isUndefined(orderQueryParam)) { + return; + } + const orderPair = orderQueryParam.split('='); + if (orderPair.length !== 2) { + return; + } + + const validator = new SchemaValidator(); + const order = JSON.parse(decodeURIComponent(orderPair[1])); + const validationResult = validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); + return; + } + return order; + } + private updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private async updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { + this.props.dispatcher.updateBlockchainIsLoaded(false); + await this.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.props.dispatcher.updateBlockchainIsLoaded(true); + } +} diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx new file mode 100644 index 000000000..3b3641729 --- /dev/null +++ b/packages/website/ts/components/portal_menu.tsx @@ -0,0 +1,68 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {MenuItem} from 'ts/components/ui/menu_item'; +import {Link} from 'react-router-dom'; +import {WebsitePaths} from 'ts/types'; + +export interface PortalMenuProps { + menuItemStyle: React.CSSProperties; + onClick?: () => void; +} + +interface PortalMenuState {} + +export class PortalMenu extends React.Component { + public static defaultProps: Partial = { + onClick: _.noop, + }; + public render() { + return ( +
+ + {this.renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} + + + {this.renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} + + + {this.renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} + + + {this.renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + +
+ ); + } + private renderMenuItemWithIcon(title: string, iconName: string) { + return ( +
+
+ +
+
+ {title} +
+
+ ); + } +} diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx new file mode 100644 index 000000000..274ba96a7 --- /dev/null +++ b/packages/website/ts/components/send_button.tsx @@ -0,0 +1,89 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import RaisedButton from 'material-ui/RaisedButton'; +import {BlockchainCallErrs, Token, TokenState} from 'ts/types'; +import {SendDialog} from 'ts/components/dialogs/send_dialog'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {Blockchain} from 'ts/blockchain'; + +interface SendButtonProps { + token: Token; + tokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + onError: () => void; +} + +interface SendButtonState { + isSendDialogVisible: boolean; + isSending: boolean; +} + +export class SendButton extends React.Component { + public constructor(props: SendButtonProps) { + super(props); + this.state = { + isSendDialogVisible: false, + isSending: false, + }; + } + public render() { + const labelStyle = this.state.isSending ? {fontSize: 10} : {}; + return ( +
+ + +
+ ); + } + private toggleSendDialog() { + this.setState({ + isSendDialogVisible: !this.state.isSendDialogVisible, + }); + } + private async onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + this.setState({ + isSending: true, + }); + this.toggleSendDialog(); + const token = this.props.token; + const tokenState = this.props.tokenState; + let balance = tokenState.balance; + try { + await this.props.blockchain.transferAsync(token, recipient, value); + balance = balance.minus(value); + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onError(); + } + } + this.setState({ + isSending: false, + }); + } +} diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx new file mode 100644 index 000000000..c552d19dc --- /dev/null +++ b/packages/website/ts/components/token_balances.tsx @@ -0,0 +1,697 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import DharmaLoanFrame from 'dharma-loan-frame'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import Divider from 'material-ui/Divider'; +import FlatButton from 'material-ui/FlatButton'; +import RaisedButton from 'material-ui/RaisedButton'; +import FloatingActionButton from 'material-ui/FloatingActionButton'; +import ContentAdd from 'material-ui/svg-icons/content/add'; +import ContentRemove from 'material-ui/svg-icons/content/remove'; +import { + Table, + TableBody, + TableHeader, + TableRow, + TableHeaderColumn, + TableRowColumn, +} from 'material-ui/Table'; +import ReactTooltip = require('react-tooltip'); +import BigNumber from 'bignumber.js'; +import firstBy = require('thenby'); +import QueryString = require('query-string'); +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + TokenByAddress, + TokenStateByAddress, + Token, + BlockchainErrs, + BalanceErrs, + Styles, + ScreenWidths, + EtherscanLinkSuffixes, + BlockchainCallErrs, + TokenVisibility, +} from 'ts/types'; +import {Blockchain} from 'ts/blockchain'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {HelpTooltip} from 'ts/components/ui/help_tooltip'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {AllowanceToggle} from 'ts/components/inputs/allowance_toggle'; +import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button'; +import {SendButton} from 'ts/components/send_button'; +import {AssetPicker} from 'ts/components/generate_order/asset_picker'; +import {TokenIcon} from 'ts/components/ui/token_icon'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; + +const ETHER_ICON_PATH = '/images/ether.png'; +const ETHER_TOKEN_SYMBOL = 'WETH'; +const ZRX_TOKEN_SYMBOL = 'ZRX'; + +const PRECISION = 5; +const ICON_DIMENSION = 40; +const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000; +const TOKEN_TABLE_ROW_HEIGHT = 60; +const MAX_TOKEN_TABLE_HEIGHT = 420; +const TOKEN_COL_SPAN_LG = 2; +const TOKEN_COL_SPAN_SM = 1; + +const styles: Styles = { + bgColor: { + backgroundColor: colors.grey50, + }, +}; + +interface TokenBalancesProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + networkId: number; +} + +interface TokenBalancesState { + errorType: BalanceErrs; + isBalanceSpinnerVisible: boolean; + isDharmaDialogVisible: boolean; + isZRXSpinnerVisible: boolean; + currentZrxBalance?: BigNumber; + isTokenPickerOpen: boolean; + isAddingToken: boolean; +} + +export class TokenBalances extends React.Component { + public constructor(props: TokenBalancesProps) { + super(props); + this.state = { + errorType: undefined, + isBalanceSpinnerVisible: false, + isZRXSpinnerVisible: false, + isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), + isTokenPickerOpen: false, + isAddingToken: false, + }; + } + public componentWillReceiveProps(nextProps: TokenBalancesProps) { + if (nextProps.userEtherBalance !== this.props.userEtherBalance) { + if (this.state.isBalanceSpinnerVisible) { + const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); + this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); + } + this.setState({ + isBalanceSpinnerVisible: false, + }); + } + const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); + const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; + if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { + if (this.state.isZRXSpinnerVisible) { + const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); + const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, 18); + this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); + } + this.setState({ + isZRXSpinnerVisible: false, + currentZrxBalance: undefined, + }); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const errorDialogActions = [ + , + ]; + const dharmaDialogActions = [ + , + ]; + const isTestNetwork = this.props.networkId === constants.TESTNET_NETWORK_ID; + const dharmaButtonColumnStyle = { + paddingLeft: 3, + display: isTestNetwork ? 'table-cell' : 'none', + }; + const stubColumnStyle = { + display: isTestNetwork ? 'none' : 'table-cell', + }; + const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; + const tokenTableHeight = allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? + allTokenRowHeight : + MAX_TOKEN_TABLE_HEIGHT; + const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const dharmaLoanExplanation = 'If you need access to larger amounts of ether,
\ + you can request a loan from the Dharma Loan
\ + network. Your loan should be funded in 5
\ + minutes or less.'; + const allowanceExplanation = '0x smart contracts require access to your
\ + token balances in order to execute trades.
\ + Toggling permissions sets an allowance for the
\ + smart contract so you can start trading that token.'; + return ( +
+

{isTestNetwork ? 'Test ether' : 'Ether'}

+ +
+ {isTestNetwork ? + 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ + gas costs. It might take a bit of time for the test ether to show up.' : + 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ + You can convert between Ether and Ether Tokens by clicking the "convert" button below.' + } +
+ + + + Currency + Balance + + { + isTestNetwork && + + {isSmallScreen ? 'Faucet' : 'Request from faucet'} + + } + { + isTestNetwork && + + {isSmallScreen ? 'Loan' : 'Request Dharma loan'} + + + } + + + + + + + + + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {this.state.isBalanceSpinnerVisible && + + + + } + + + { + isTestNetwork && + + + + } + { + isTestNetwork && + + + + } + + +
+
+
+

+ {isTestNetwork ? 'Test tokens' : 'Tokens'} +

+
+
+ + + +
+
+ + + +
+
+ +
+ {isTestNetwork ? + 'Mint some test tokens you\'d like to use to generate or fill an order using 0x.' : + 'Set trading permissions for a token you\'d like to start trading.' + } +
+ + + + + Token + + Balance + +
{!isSmallScreen && 'Trade '}Permissions
+ +
+ + Action + + {this.props.screenWidth !== ScreenWidths.SM && + + Send + + } +
+
+ + {this.renderTokenTableRows()} + +
+ + {this.renderErrorDialogBody()} + + + {this.renderDharmaLoanFrame()} + + +
+ ); + } + private renderTokenTableRows() { + if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== '') { + return ''; + } + const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const actionPaddingX = isSmallScreen ? 2 : 24; + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + const trackedTokensStartingWithEtherToken = trackedTokens.sort( + firstBy((t: Token) => (t.symbol !== ETHER_TOKEN_SYMBOL)) + .thenBy((t: Token) => (t.symbol !== ZRX_TOKEN_SYMBOL)) + .thenBy('address'), + ); + const tableRows = _.map( + trackedTokensStartingWithEtherToken, + this.renderTokenRow.bind(this, tokenColSpan, actionPaddingX), + ); + return tableRows; + } + private renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + const tokenState = this.props.tokenStateByAddress[token.address]; + const tokenLink = utils.getEtherScanLinkIfExists(token.address, this.props.networkId, + EtherscanLinkSuffixes.address); + const isMintable = _.includes(configs.symbolsOfMintableTokens, token.symbol) && + this.props.networkId !== constants.MAINNET_NETWORK_ID; + return ( + + + {_.isUndefined(tokenLink) ? + this.renderTokenName(token) : + + {this.renderTokenName(token)} + + } + + + {this.renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && token.symbol === ZRX_TOKEN_SYMBOL && + + + + } + + + + + + {isMintable && + Minting...} + labelComplete="Minted!" + onClickAsyncFn={this.onMintTestTokensAsync.bind(this, token)} + /> + } + {token.symbol === ETHER_TOKEN_SYMBOL && + + } + {token.symbol === ZRX_TOKEN_SYMBOL && this.props.networkId === constants.TESTNET_NETWORK_ID && + + } + + {this.props.screenWidth !== ScreenWidths.SM && + + + + } + + ); + } + private onAssetTokenPicked(tokenAddress: string) { + if (_.isEmpty(tokenAddress)) { + this.setState({ + isTokenPickerOpen: false, + }); + return; + } + const token = this.props.tokenByAddress[tokenAddress]; + const isDefaultTrackedToken = _.includes(configs.defaultTrackedTokenSymbols, token.symbol); + if (!this.state.isAddingToken && !isDefaultTrackedToken) { + if (token.isRegistered) { + // Remove the token from tracked tokens + const newToken = _.assign({}, token, { + isTracked: false, + }); + this.props.dispatcher.updateTokenByAddress([newToken]); + } else { + this.props.dispatcher.removeTokenToTokenByAddress(token); + } + this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); + trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); + } else if (isDefaultTrackedToken) { + this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); + } + this.setState({ + isTokenPickerOpen: false, + }); + } + private onEthWethConversionFailed() { + this.setState({ + errorType: BalanceErrs.wethConversionFailed, + }); + } + private onSendFailed() { + this.setState({ + errorType: BalanceErrs.sendFailed, + }); + } + private renderAmount(amount: BigNumber, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return unitAmount.toNumber().toFixed(PRECISION); + } + private renderTokenName(token: Token) { + const tooltipId = `tooltip-${token.address}`; + return ( +
+ +
+ {token.name} +
+ {token.address} +
+ ); + } + private renderErrorDialogBody() { + switch (this.state.errorType) { + case BalanceErrs.incorrectNetworkForFaucet: + return ( +
+ Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} + {' '}testnet (networkId {constants.TESTNET_NETWORK_ID}). Please make sure you are + {' '}connected to the {constants.TESTNET_NAME} testnet and try requesting ether again. +
+ ); + + case BalanceErrs.faucetRequestFailed: + return ( +
+ An unexpected error occurred while trying to request test Ether from our faucet. + {' '}Please refresh the page and try again. +
+ ); + + case BalanceErrs.faucetQueueIsFull: + return ( +
+ Our test Ether faucet queue is full. Please try requesting test Ether again later. +
+ ); + + case BalanceErrs.mintingFailed: + return ( +
+ Minting your test tokens failed unexpectedly. Please refresh the page and try again. +
+ ); + + case BalanceErrs.wethConversionFailed: + return ( +
+ Converting between Ether and Ether Tokens failed unexpectedly. + Please refresh the page and try again. +
+ ); + + case BalanceErrs.allowanceSettingFailed: + return ( +
+ An unexpected error occurred while trying to set your test token allowance. + {' '}Please refresh the page and try again. +
+ ); + + case undefined: + return null; // No error to show + + default: + throw utils.spawnSwitchErr('errorType', this.state.errorType); + } + } + private renderDharmaLoanFrame() { + if (utils.isUserOnMobile()) { + return ( +

+ We apologize -- Dharma loan requests are not available on + mobile yet. Please try again through your desktop browser. +

+ ); + } else { + return ( + + ); + } + } + private onErrorOccurred(errorType: BalanceErrs) { + this.setState({ + errorType, + }); + } + private async onMintTestTokensAsync(token: Token): Promise { + try { + await this.props.blockchain.mintTestTokensAsync(token); + const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); + return true; + } catch (err) { + const errMsg = '' + err; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.setState({ + errorType: BalanceErrs.mintingFailed, + }); + return false; + } + } + private async faucetRequestAsync(isEtherRequest: boolean): Promise { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + // If on another network other then the testnet our faucet serves test ether + // from, we must show user an error message + if (this.props.blockchain.networkId !== constants.TESTNET_NETWORK_ID) { + this.setState({ + errorType: BalanceErrs.incorrectNetworkForFaucet, + }); + return false; + } + + await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); + + const segment = isEtherRequest ? 'ether' : 'zrx'; + const response = await fetch(`${constants.ETHER_FAUCET_ENDPOINT}/${segment}/${this.props.userAddress}`); + const responseBody = await response.text(); + if (response.status !== constants.SUCCESS_STATUS) { + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); + const errorType = response.status === constants.UNAVAILABLE_STATUS ? + BalanceErrs.faucetQueueIsFull : + BalanceErrs.faucetRequestFailed; + this.setState({ + errorType, + }); + return false; + } + + if (isEtherRequest) { + this.setState({ + isBalanceSpinnerVisible: true, + }); + } else { + const tokens = _.values(this.props.tokenByAddress); + const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); + const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; + this.setState({ + isZRXSpinnerVisible: true, + currentZrxBalance: zrxTokenState.balance, + }); + this.props.blockchain.pollTokenBalanceAsync(zrxToken); + } + return true; + } + private onErrorDialogToggle(isOpen: boolean) { + this.setState({ + errorType: undefined, + }); + } + private onDharmaDialogToggle() { + this.setState({ + isDharmaDialogVisible: !this.state.isDharmaDialogVisible, + }); + } + private getWrappedEthToken() { + const tokens = _.values(this.props.tokenByAddress); + const wrappedEthToken = _.find(tokens, {symbol: ETHER_TOKEN_SYMBOL}); + return wrappedEthToken; + } + private onAddTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: true, + }); + } + private onRemoveTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: false, + }); + } +} diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx new file mode 100644 index 000000000..6248095b3 --- /dev/null +++ b/packages/website/ts/components/top_bar.tsx @@ -0,0 +1,370 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + Link as ScrollLink, + animateScroll, +} from 'react-scroll'; +import {Link} from 'react-router-dom'; +import {HashLink} from 'react-router-hash-link'; +import AppBar from 'material-ui/AppBar'; +import Drawer from 'material-ui/Drawer'; +import MenuItem from 'material-ui/MenuItem'; +import {colors} from 'material-ui/styles'; +import ReactTooltip = require('react-tooltip'); +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Identicon} from 'ts/components/ui/identicon'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {PortalMenu} from 'ts/components/portal_menu'; +import {Styles, TypeDocNode, MenuSubsectionsBySection, WebsitePaths, Docs} from 'ts/types'; +import {TopBarMenuItem} from 'ts/components/top_bar_menu_item'; +import {DropDownMenuItem} from 'ts/components/ui/drop_down_menu_item'; + +const CUSTOM_DARK_GRAY = '#231F20'; +const SECTION_HEADER_COLOR = 'rgb(234, 234, 234)'; + +interface TopBarProps { + userAddress?: string; + blockchainIsLoaded: boolean; + location: Location; + docsVersion?: string; + availableDocVersions?: string[]; + menuSubsectionsBySection?: MenuSubsectionsBySection; + shouldFullWidth?: boolean; + doc?: Docs; + style?: React.CSSProperties; + isNightVersion?: boolean; +} + +interface TopBarState { + isDrawerOpen: boolean; +} + +const styles: Styles = { + address: { + marginRight: 12, + overflow: 'hidden', + paddingTop: 4, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 70, + }, + addressPopover: { + backgroundColor: colors.blueGrey500, + color: 'white', + padding: 3, + }, + topBar: { + backgroundColor: 'white', + height: 59, + width: '100%', + position: 'fixed', + top: 0, + zIndex: 1100, + paddingBottom: 1, + }, + bottomBar: { + boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', + }, + menuItem: { + fontSize: 14, + color: CUSTOM_DARK_GRAY, + paddingTop: 6, + paddingBottom: 6, + marginTop: 17, + cursor: 'pointer', + fontWeight: 400, + }, +}; + +export class TopBar extends React.Component { + public static defaultProps: Partial = { + shouldFullWidth: false, + style: {}, + isNightVersion: false, + }; + constructor(props: TopBarProps) { + super(props); + this.state = { + isDrawerOpen: false, + }; + } + public render() { + const isNightVersion = this.props.isNightVersion; + const isFullWidthPage = this.props.shouldFullWidth; + const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const developerSectionMenuItems = [ + + + , + + + , + + + , + + + , + + + , + ]; + const bottomBorderStyle = this.shouldDisplayBottomBar() ? styles.bottomBar : {}; + const fullWithClassNames = isFullWidthPage ? 'pr4' : ''; + const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; + return ( +
+
+
+ + + +
+
+
+ {!this.isViewingPortal() && +
+
+ + + + +
+
+ } + {this.props.blockchainIsLoaded && !_.isEmpty(this.props.userAddress) && +
+ {this.renderUser()} +
+ } + {!this.isViewingPortal() && +
+
+ +
+
+ } +
+ {this.renderDrawer()} +
+ ); + } + private renderDrawer() { + return ( + + {this.renderPortalMenu()} + {this.renderDocsMenu()} + {this.renderWiki()} +
Website
+ + Home + + + Wiki + + {!this.isViewing0xjsDocs() && + + 0x.js Docs + + } + {!this.isViewingSmartContractsDocs() && + + Smart Contract Docs + + } + {!this.isViewingPortal() && + + Portal DApp + + } + + Whitepaper + + + About + + + Blog + + + + FAQ + + +
+ ); + } + private renderDocsMenu() { + if (!this.isViewing0xjsDocs() && !this.isViewingSmartContractsDocs()) { + return; + } + + const topLevelMenu = this.isViewing0xjsDocs() ? + typeDocUtils.getFinal0xjsMenu(this.props.docsVersion) : + constants.menuSmartContracts; + + const sectionTitle = this.isViewing0xjsDocs() ? '0x.js Docs' : 'Smart contract Docs'; + return ( +
+
{sectionTitle}
+ +
+ ); + } + private renderWiki() { + if (!this.isViewingWiki()) { + return; + } + + return ( +
+
0x Protocol Wiki
+ +
+ ); + } + private renderPortalMenu() { + if (!this.isViewingPortal()) { + return; + } + + return ( +
+
Portal DApp
+ +
+ ); + } + private renderUser() { + const userAddress = this.props.userAddress; + const identiconDiameter = 26; + return ( +
+
+ {!_.isEmpty(userAddress) ? userAddress : ''} +
+ {userAddress} +
+ +
+
+ ); + } + private onMenuButtonClick() { + this.setState({ + isDrawerOpen: !this.state.isDrawerOpen, + }); + } + private isViewingPortal() { + return _.includes(this.props.location.pathname, WebsitePaths.Portal); + } + private isViewingFAQ() { + return _.includes(this.props.location.pathname, WebsitePaths.FAQ); + } + private isViewing0xjsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); + } + private isViewingSmartContractsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); + } + private isViewingWiki() { + return _.includes(this.props.location.pathname, WebsitePaths.Wiki); + } + private shouldDisplayBottomBar() { + return this.isViewingWiki() || this.isViewing0xjsDocs() || this.isViewingFAQ() || + this.isViewingSmartContractsDocs(); + } +} diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx new file mode 100644 index 000000000..de429fba6 --- /dev/null +++ b/packages/website/ts/components/top_bar_menu_item.tsx @@ -0,0 +1,53 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link} from 'react-router-dom'; +import {Styles} from 'ts/types'; + +const CUSTOM_DARK_GRAY = '#231F20'; +const DEFAULT_STYLE = { + color: CUSTOM_DARK_GRAY, +}; + +interface TopBarMenuItemProps { + title: string; + path?: string; + isPrimary?: boolean; + style?: React.CSSProperties; + className?: string; + isNightVersion?: boolean; +} + +interface TopBarMenuItemState {} + +export class TopBarMenuItem extends React.Component { + public static defaultProps: Partial = { + isPrimary: false, + style: DEFAULT_STYLE, + className: '', + isNightVersion: false, + }; + public render() { + const primaryStyles = this.props.isPrimary ? { + borderRadius: 4, + border: `1px solid ${this.props.isNightVersion ? '#979797' : 'rgb(230, 229, 229)'}`, + marginTop: 15, + paddingLeft: 9, + paddingRight: 9, + width: 77, + } : {}; + const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; + const linkColor = _.isUndefined(menuItemColor) ? + CUSTOM_DARK_GRAY : + menuItemColor; + return ( +
+ + {this.props.title} + +
+ ); + } +} diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx new file mode 100644 index 000000000..bc036eae3 --- /dev/null +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -0,0 +1,65 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {utils} from 'ts/utils/utils'; +import {Party} from 'ts/components/ui/party'; +import {Token, TokenByAddress} from 'ts/types'; + +interface TrackTokenConfirmationProps { + tokens: Token[]; + tokenByAddress: TokenByAddress; + networkId: number; + isAddingTokenToTracked: boolean; +} + +interface TrackTokenConfirmationState {} + +export class TrackTokenConfirmation extends + React.Component { + public render() { + const isMultipleTokens = this.props.tokens.length > 1; + const allTokens = _.values(this.props.tokenByAddress); + return ( +
+ {this.props.isAddingTokenToTracked ? +
+ + + + Adding token{isMultipleTokens && 's'}... +
: +
+
+ You do not currently track the following token{isMultipleTokens && 's'}: +
+
+ {_.map(this.props.tokens, (token: Token) => ( +
+ +
+ ))} +
+
+ Tracking a token adds it to the balances section of 0x Portal and + allows you to generate/fill orders involving the token + {isMultipleTokens && 's'}. Would you like to start tracking{' '} + {isMultipleTokens ? 'these' : 'this'}{' '}token? +
+
+ } +
+ ); + } +} diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx new file mode 100644 index 000000000..9deaf8fd8 --- /dev/null +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -0,0 +1,115 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import Divider from 'material-ui/Divider'; +import {utils} from 'ts/utils/utils'; +import {Fill, TokenByAddress} from 'ts/types'; +import {TradeHistoryItem} from 'ts/components/trade_history/trade_history_item'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; + +const FILL_POLLING_INTERVAL = 1000; + +interface TradeHistoryProps { + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; +} + +interface TradeHistoryState { + sortedFills: Fill[]; +} + +export class TradeHistory extends React.Component { + private fillPollingIntervalId: number; + public constructor(props: TradeHistoryProps) { + super(props); + const sortedFills = this.getSortedFills(); + this.state = { + sortedFills, + }; + } + public componentWillMount() { + this.startPollingForFills(); + } + public componentWillUnmount() { + this.stopPollingForFills(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+

Trade history

+ +
+ {this.renderTrades()} +
+
+ ); + } + private renderTrades() { + const numNonCustomFills = this.numFillsWithoutCustomERC20Tokens(); + if (numNonCustomFills === 0) { + return this.renderEmptyNotice(); + } + + return _.map(this.state.sortedFills, (fill, index) => { + return ( + + ); + }); + } + private renderEmptyNotice() { + return ( + + No filled orders yet. + + ); + } + private numFillsWithoutCustomERC20Tokens() { + let numNonCustomFills = 0; + const tokens = _.values(this.props.tokenByAddress); + _.each(this.state.sortedFills, fill => { + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history_item.tsx + if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { + numNonCustomFills += 1; + } + }); + return numNonCustomFills; + } + private startPollingForFills() { + this.fillPollingIntervalId = window.setInterval(() => { + const sortedFills = this.getSortedFills(); + if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { + this.setState({ + sortedFills, + }); + } + }, FILL_POLLING_INTERVAL); + } + private stopPollingForFills() { + clearInterval(this.fillPollingIntervalId); + } + private getSortedFills() { + const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); + const fills = _.values(fillsByHash); + const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); + return sortedFills; + } +} diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx new file mode 100644 index 000000000..96b755e3c --- /dev/null +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -0,0 +1,178 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import * as ReactTooltip from 'react-tooltip'; +import * as moment from 'moment'; +import Paper from 'material-ui/Paper'; +import {colors} from 'material-ui/styles'; +import {ZeroEx} from '0x.js'; +import {TokenByAddress, Fill, Token, EtherscanLinkSuffixes} from 'ts/types'; +import {Party} from 'ts/components/ui/party'; +import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; + +const PRECISION = 5; +const IDENTICON_DIAMETER = 40; + +interface TradeHistoryItemProps { + fill: Fill; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; +} + +interface TradeHistoryItemState {} + +export class TradeHistoryItem extends React.Component { + public render() { + const fill = this.props.fill; + const tokens = _.values(this.props.tokenByAddress); + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history.tsx + if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { + return null; + } + + const amountColStyle: React.CSSProperties = { + fontWeight: 100, + display: 'inline-block', + }; + const amountColClassNames = 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ + lg-right-align md-right-align sm-center'; + + return ( + +
+
+ {this.renderDate()} +
+
+
+ + + +
+
+
+ {this.renderAmounts(makerToken, takerToken)} +
+
+
+ +
+
+
+
+ ); + } + private renderAmounts(makerToken: Token, takerToken: Token) { + const fill = this.props.fill; + const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); + const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); + let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); + const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); + + let receiveAmount; + let receiveToken; + let givenAmount; + let givenToken; + if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { + receiveAmount = new BigNumber(0); + givenAmount = new BigNumber(0); + receiveToken = makerToken; + givenToken = takerToken; + } else if (this.props.userAddress === fill.maker) { + receiveAmount = fill.filledTakerTokenAmount; + givenAmount = fillMakerTokenAmount; + receiveToken = takerToken; + givenToken = makerToken; + exchangeRate = new BigNumber(1).div(exchangeRate); + } else if (this.props.userAddress === fill.taker) { + receiveAmount = fillMakerTokenAmount; + givenAmount = fill.filledTakerTokenAmount; + receiveToken = makerToken; + givenToken = takerToken; + } + + return ( +
+
+ +{' '} + {this.renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} +
+
+ -{' '} + {this.renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} +
+
+ {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} +
+
+ ); + } + private renderDate() { + const blockMoment = moment.unix(this.props.fill.blockTimestamp); + if (!blockMoment.isValid()) { + return null; + } + + const dayOfMonth = blockMoment.format('D'); + const monthAbreviation = blockMoment.format('MMM'); + const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); + const dateTooltipId = `${this.props.fill.transactionHash}-date`; + + return ( +
+
{monthAbreviation}
+
{dayOfMonth}
+ {formattedBlockDate} +
+ ); + } + private renderAmount(amount: BigNumber, symbol: string, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return ( + + {unitAmount.toFixed(PRECISION)} {symbol} + + ); + } +} diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx new file mode 100644 index 000000000..bf2f0baf5 --- /dev/null +++ b/packages/website/ts/components/ui/alert.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {AlertTypes} from 'ts/types'; + +const CUSTOM_GREEN = 'rgb(137, 199, 116)'; + +interface AlertProps { + type: AlertTypes; + message: string|React.ReactNode; +} + +export function Alert(props: AlertProps) { + const isAlert = props.type === AlertTypes.ERROR; + const errMsgStyles = { + background: isAlert ? colors.red200 : CUSTOM_GREEN, + color: 'white', + marginTop: 10, + padding: 4, + paddingLeft: 8, + }; + + return ( +
+ {props.message} +
+ ); +} diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx new file mode 100644 index 000000000..1e3bbdb99 --- /dev/null +++ b/packages/website/ts/components/ui/badge.tsx @@ -0,0 +1,58 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Styles} from 'ts/types'; + +const styles: Styles = { + badge: { + width: 50, + fontSize: 11, + height: 10, + borderRadius: 5, + marginTop: 25, + lineHeight: 0.9, + fontFamily: 'Roboto Mono', + marginLeft: 3, + marginRight: 3, + }, +}; + +interface BadgeProps { + title: string; + backgroundColor: string; +} + +interface BadgeState { + isHovering: boolean; +} + +export class Badge extends React.Component { + constructor(props: BadgeProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const badgeStyle = { + ...styles.badge, + backgroundColor: this.props.backgroundColor, + opacity: this.state.isHovering ? 0.7 : 1, + }; + return ( +
+ {this.props.title} +
+ ); + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx new file mode 100644 index 000000000..f8abaa59e --- /dev/null +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -0,0 +1,81 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import {colors} from 'material-ui/styles'; +import ReactTooltip = require('react-tooltip'); + +interface CopyIconProps { + data: string; + callToAction?: string; +} + +interface CopyIconState { + isHovering: boolean; +} + +export class CopyIcon extends React.Component { + private copyTooltipTimeoutId: number; + private copyable: HTMLInputElement; + constructor(props: CopyIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public componentDidUpdate() { + // Remove tooltip if hover away + if (!this.state.isHovering && this.copyTooltipTimeoutId) { + clearInterval(this.copyTooltipTimeoutId); + this.hideTooltip(); + } + } + public render() { + return ( +
+ +
+
+ +
+ {this.props.callToAction && +
{this.props.callToAction}
+ } +
+
+ Copied! +
+ ); + } + private setRefToProperty(el: HTMLInputElement) { + this.copyable = el; + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } + private onCopy() { + if (this.copyTooltipTimeoutId) { + clearInterval(this.copyTooltipTimeoutId); + } + + const tooltipLifespanMs = 1000; + this.copyTooltipTimeoutId = window.setTimeout(() => { + this.hideTooltip(); + }, tooltipLifespanMs); + } + private hideTooltip() { + ReactTooltip.hide(ReactDOM.findDOMNode(this.copyable)); + } +} diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx new file mode 100644 index 000000000..b8b7eb167 --- /dev/null +++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx @@ -0,0 +1,117 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + Link as ScrollLink, +} from 'react-scroll'; +import {Link} from 'react-router-dom'; +import Popover from 'material-ui/Popover'; +import Menu from 'material-ui/Menu'; +import MenuItem from 'material-ui/MenuItem'; +import {Styles, WebsitePaths} from 'ts/types'; + +const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; +const CUSTOM_LIGHT_GRAY = '#848484'; +const DEFAULT_STYLE = { + fontSize: 14, +}; + +interface DropDownMenuItemProps { + title: string; + subMenuItems: React.ReactNode[]; + style?: React.CSSProperties; + menuItemStyle?: React.CSSProperties; + isNightVersion?: boolean; +} + +interface DropDownMenuItemState { + isDropDownOpen: boolean; + anchorEl?: HTMLInputElement; +} + +export class DropDownMenuItem extends React.Component { + public static defaultProps: Partial = { + style: DEFAULT_STYLE, + menuItemStyle: DEFAULT_STYLE, + isNightVersion: false, + }; + private isHovering: boolean; + private popoverCloseCheckIntervalId: number; + constructor(props: DropDownMenuItemProps) { + super(props); + this.state = { + isDropDownOpen: false, + }; + } + public componentDidMount() { + this.popoverCloseCheckIntervalId = window.setInterval(() => { + this.checkIfShouldClosePopover(); + }, CHECK_CLOSE_POPOVER_INTERVAL_MS); + } + public componentWillUnmount() { + window.clearInterval(this.popoverCloseCheckIntervalId); + } + public render() { + const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; + return ( +
+
+
+ {this.props.title} +
+
+ +
+
+ +
+ + {this.props.subMenuItems} + +
+
+
+ ); + } + private onHover(event: React.FormEvent) { + this.isHovering = true; + this.checkIfShouldOpenPopover(event); + } + private checkIfShouldOpenPopover(event: React.FormEvent) { + if (this.state.isDropDownOpen) { + return; // noop + } + + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + private onHoverOff(event: React.FormEvent) { + this.isHovering = false; + } + private checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this.isHovering) { + return; // noop + } + this.closePopover(); + } + private closePopover() { + this.setState({ + isDropDownOpen: false, + }); + } +} diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx new file mode 100644 index 000000000..c3d03b78c --- /dev/null +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; +import ReactTooltip = require('react-tooltip'); +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface EthereumAddressProps { + address: string; + networkId: number; +} + +export const EthereumAddress = (props: EthereumAddressProps) => { + const tooltipId = `${props.address}-ethereum-address`; + const truncatedAddress = utils.getAddressBeginAndEnd(props.address); + return ( +
+
+ {truncatedAddress} +
+
+ +
+ {props.address} +
+ ); +}; diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx new file mode 100644 index 000000000..12044f44b --- /dev/null +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -0,0 +1,50 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import {colors} from 'material-ui/styles'; +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface EtherScanIconProps { + addressOrTxHash: string; + etherscanLinkSuffixes: EtherscanLinkSuffixes; + networkId: number; +} + +export const EtherScanIcon = (props: EtherScanIconProps) => { + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + props.addressOrTxHash, props.networkId, EtherscanLinkSuffixes.address, + ); + const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; + return ( +
+ {!_.isUndefined(etherscanLinkIfExists) ? + + {renderIcon()} + : +
+ {renderIcon()} + + Your network (id: {props.networkId}) is not supported by Etherscan + +
+ } +
+ ); +}; + +function renderIcon() { + return ( + + ); +} diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx new file mode 100644 index 000000000..372785c2f --- /dev/null +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {InputLabel} from 'ts/components/ui/input_label'; +import {Styles} from 'ts/types'; + +const styles: Styles = { + hr: { + borderBottom: '1px solid rgb(224, 224, 224)', + borderLeft: 'none rgb(224, 224, 224)', + borderRight: 'none rgb(224, 224, 224)', + borderTop: 'none rgb(224, 224, 224)', + bottom: 6, + boxSizing: 'content-box', + margin: 0, + position: 'absolute', + width: '100%', + }, +}; + +interface FakeTextFieldProps { + label?: React.ReactNode | string; + children?: any; +} + +export function FakeTextField(props: FakeTextFieldProps) { + return ( +
+ {props.label !== '' && } +
+ {props.children} +
+
+
+ ); +} diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx new file mode 100644 index 000000000..684aeef68 --- /dev/null +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -0,0 +1,40 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Snackbar from 'material-ui/Snackbar'; +import {Dispatcher} from 'ts/redux/dispatcher'; + +const SHOW_DURATION_MS = 4000; + +interface FlashMessageProps { + dispatcher: Dispatcher; + flashMessage?: string|React.ReactNode; + showDurationMs?: number; + bodyStyle?: React.CSSProperties; +} + +interface FlashMessageState {} + +export class FlashMessage extends React.Component { + public static defaultProps: Partial = { + showDurationMs: SHOW_DURATION_MS, + bodyStyle: {}, + }; + public render() { + if (!_.isUndefined(this.props.flashMessage)) { + return ( + + ); + } else { + return null; + } + } + private onClose() { + this.props.dispatcher.hideFlashMessage(); + } +} diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx new file mode 100644 index 000000000..003b795ef --- /dev/null +++ b/packages/website/ts/components/ui/help_tooltip.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); + +interface HelpTooltipProps { + style?: React.CSSProperties; + explanation: React.ReactNode; +} + +export const HelpTooltip = (props: HelpTooltipProps) => { + return ( +
+ + +
+ ); +}; diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx new file mode 100644 index 000000000..814548fb4 --- /dev/null +++ b/packages/website/ts/components/ui/identicon.tsx @@ -0,0 +1,36 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import blockies = require('blockies'); + +interface IdenticonProps { + address: string; + diameter: number; + style?: React.CSSProperties; +} + +interface IdenticonState {} + +export class Identicon extends React.Component { + public static defaultProps: Partial = { + style: {}, + }; + public render() { + let address = this.props.address; + if (_.isEmpty(address)) { + address = constants.NULL_ADDRESS; + } + const diameter = this.props.diameter; + const icon = blockies({ + seed: address.toLowerCase(), + }); + return ( +
+ +
+ ); + } +} diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx new file mode 100644 index 000000000..5866c70b6 --- /dev/null +++ b/packages/website/ts/components/ui/input_label.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +export interface InputLabelProps { + text: string | Element | React.ReactNode; +} + +const styles = { + label: { + color: colors.grey500, + fontSize: 12, + pointerEvents: 'none', + textAlign: 'left', + transform: 'scale(0.75) translate(0px, -28px)', + transformOrigin: 'left top 0px', + transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', + userSelect: 'none', + width: 240, + zIndex: 1, + }, +}; + +export const InputLabel = (props: InputLabelProps) => { + return ( + + ); +}; diff --git a/packages/website/ts/components/ui/labeled_switcher.tsx b/packages/website/ts/components/ui/labeled_switcher.tsx new file mode 100644 index 000000000..3ed8ba0a4 --- /dev/null +++ b/packages/website/ts/components/ui/labeled_switcher.tsx @@ -0,0 +1,76 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +const CUSTOM_BLUE = '#63A6F1'; + +interface LabeledSwitcherProps { + labelLeft: string; + labelRight: string; + isLeftInitiallySelected: boolean; + onLeftLabelClickAsync: () => Promise; + onRightLabelClickAsync: () => Promise; +} + +interface LabeledSwitcherState { + isLeftSelected: boolean; +} + +export class LabeledSwitcher extends React.Component { + constructor(props: LabeledSwitcherProps) { + super(props); + this.state = { + isLeftSelected: props.isLeftInitiallySelected, + }; + } + public render() { + const isLeft = true; + return ( +
+ {this.renderLabel(this.props.labelLeft, isLeft, this.state.isLeftSelected)} + {this.renderLabel(this.props.labelRight, !isLeft, !this.state.isLeftSelected)} +
+ ); + } + private renderLabel(title: string, isLeft: boolean, isSelected: boolean) { + const borderStyle = `2px solid ${isSelected ? '#4F8BCF' : '#DADADA'}`; + const style = { + cursor: 'pointer', + backgroundColor: isSelected ? CUSTOM_BLUE : colors.grey200, + color: isSelected ? 'white' : '#A5A5A5', + boxShadow: isSelected ? `inset 0px 0px 4px #4083CE` : 'inset 0px 0px 4px #F7F6F6', + borderTop: borderStyle, + borderBottom: borderStyle, + [isLeft ? 'borderLeft' : 'borderRight']: borderStyle, + paddingTop: 12, + paddingBottom: 12, + }; + return ( +
+ {title} +
+ ); + } + private async onLabelClickAsync(isLeft: boolean): Promise { + this.setState({ + isLeftSelected: isLeft, + }); + let didSucceed; + if (isLeft) { + didSucceed = await this.props.onLeftLabelClickAsync(); + } else { + didSucceed = await this.props.onRightLabelClickAsync(); + } + if (!didSucceed) { + this.setState({ + isLeftSelected: !isLeft, + }); + } + } +} diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx new file mode 100644 index 000000000..e93c80ba4 --- /dev/null +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -0,0 +1,105 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {Token} from 'ts/types'; +import {Blockchain} from 'ts/blockchain'; +import RaisedButton from 'material-ui/RaisedButton'; + +const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; + +enum ButtonState { + READY, + LOADING, + COMPLETE, +}; + +interface LifeCycleRaisedButtonProps { + isHidden?: boolean; + isDisabled?: boolean; + isPrimary?: boolean; + labelReady: React.ReactNode|string; + labelLoading: React.ReactNode|string; + labelComplete: React.ReactNode|string; + onClickAsyncFn: () => boolean; + backgroundColor?: string; + labelColor?: string; +} + +interface LifeCycleRaisedButtonState { + buttonState: ButtonState; +} + +export class LifeCycleRaisedButton extends + React.Component { + public static defaultProps: Partial = { + isDisabled: false, + backgroundColor: 'white', + labelColor: 'rgb(97, 97, 97)', + }; + private buttonTimeoutId: number; + private didUnmount: boolean; + constructor(props: LifeCycleRaisedButtonProps) { + super(props); + this.state = { + buttonState: ButtonState.READY, + }; + } + public componentWillUnmount() { + clearTimeout(this.buttonTimeoutId); + this.didUnmount = true; + } + public render() { + if (this.props.isHidden === true) { + return ; + } + + let label; + switch (this.state.buttonState) { + case ButtonState.READY: + label = this.props.labelReady; + break; + case ButtonState.LOADING: + label = this.props.labelLoading; + break; + case ButtonState.COMPLETE: + label = this.props.labelComplete; + break; + default: + throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); + } + return ( + + ); + } + public async onClickAsync() { + this.setState({ + buttonState: ButtonState.LOADING, + }); + const didSucceed = await this.props.onClickAsyncFn(); + if (this.didUnmount) { + return; // noop since unmount called before async callback returned. + } + if (didSucceed) { + this.setState({ + buttonState: ButtonState.COMPLETE, + }); + this.buttonTimeoutId = window.setTimeout(() => { + this.setState({ + buttonState: ButtonState.READY, + }); + }, COMPLETE_STATE_SHOW_LENGTH_MS); + } else { + this.setState({ + buttonState: ButtonState.READY, + }); + } + } +} diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx new file mode 100644 index 000000000..39c119d8f --- /dev/null +++ b/packages/website/ts/components/ui/loading.tsx @@ -0,0 +1,36 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import {utils} from 'ts/utils/utils'; +import {DefaultPlayer as Video} from 'react-html5video'; +import 'react-html5video/dist/styles.css'; + +interface LoadingProps {} + +interface LoadingState {} + +export class Loading extends React.Component { + public render() { + return ( +
+ + {utils.isUserOnMobile() ? + : +
+ +
+ } +
Connecting to the blockchain...
+
+
+ ); + } +} diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx new file mode 100644 index 000000000..b9caa91fb --- /dev/null +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link} from 'react-router-dom'; +import {Styles} from 'ts/types'; +import {constants} from 'ts/utils/constants'; +import {colors} from 'material-ui/styles'; + +interface MenuItemProps { + to: string; + style?: React.CSSProperties; + onClick?: () => void; + className?: string; +} + +interface MenuItemState { + isHovering: boolean; +} + +export class MenuItem extends React.Component { + public static defaultProps: Partial = { + onClick: _.noop, + className: '', + }; + public constructor(props: MenuItemProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const menuItemStyles = { + cursor: 'pointer', + opacity: this.state.isHovering ? 0.5 : 1, + }; + return ( + +
+ {this.props.children} +
+ + ); + } + private onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx new file mode 100644 index 000000000..b72e75181 --- /dev/null +++ b/packages/website/ts/components/ui/party.tsx @@ -0,0 +1,150 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import {colors} from 'material-ui/styles'; +import {Identicon} from 'ts/components/ui/identicon'; +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {EthereumAddress} from 'ts/components/ui/ethereum_address'; + +const MIN_ADDRESS_WIDTH = 60; +const IMAGE_DIMENSION = 100; +const IDENTICON_DIAMETER = 95; +const CHECK_MARK_GREEN = 'rgb(0, 195, 62)'; + +interface PartyProps { + label: string; + address: string; + networkId: number; + alternativeImage?: string; + identiconDiameter?: number; + identiconStyle?: React.CSSProperties; + isInTokenRegistry?: boolean; + hasUniqueNameAndSymbol?: boolean; +} + +interface PartyState {} + +export class Party extends React.Component { + public static defaultProps: Partial = { + identiconStyle: {}, + identiconDiameter: IDENTICON_DIAMETER, + }; + public render() { + const label = this.props.label; + const address = this.props.address; + const tooltipId = `${label}-${address}-tooltip`; + const identiconDiameter = this.props.identiconDiameter; + const addressWidth = identiconDiameter > MIN_ADDRESS_WIDTH ? + identiconDiameter : MIN_ADDRESS_WIDTH; + const emptyIdenticonStyles = { + width: identiconDiameter, + height: identiconDiameter, + backgroundColor: 'lightgray', + marginTop: 13, + marginBottom: 10, + }; + const tokenImageStyle = { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + }; + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + this.props.address, this.props.networkId, EtherscanLinkSuffixes.address, + ); + const isRegistered = this.props.isInTokenRegistry; + const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; + const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; + return ( +
+
{label}
+ {_.isEmpty(address) ? +
: + + {isRegistered && !_.isUndefined(this.props.alternativeImage) ? + : +
+ +
+ } +
+ } +
+
+ +
+ {!_.isUndefined(this.props.isInTokenRegistry) && +
+
+ + + {' '} + {isRegistered ? 'Registered' : 'Unregistered'} token + + {isRegistered ? +
+ This token address was found in the token registry
+ smart contract and is therefore believed to be a
+ legitimate token. +
: +
+ This token is not included in the token registry
+ smart contract. We cannot guarantee the legitimacy
+ of this token. Make sure to verify its address on Etherscan. +
+ } +
+
+
+ } + {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && !this.props.hasUniqueNameAndSymbol && +
+
+ + + {' '} + Suspicious token + + This token shares it's name, symbol or both with
+ a token in the 0x Token Registry but it has a different
+ smart contract address. This is most likely a scam token! +
+
+
+ } +
+
+ ); + } +} diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx new file mode 100644 index 000000000..f9c73157a --- /dev/null +++ b/packages/website/ts/components/ui/required_label.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +export interface RequiredLabelProps { + label: string|React.ReactNode; +} + +export const RequiredLabel = (props: RequiredLabelProps) => { + return ( + + {props.label} + * + + ); +}; diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx new file mode 100644 index 000000000..12d09ecc4 --- /dev/null +++ b/packages/website/ts/components/ui/simple_loading.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; + +export interface SimpleLoadingProps { + message: string; +} + +export const SimpleLoading = (props: SimpleLoadingProps) => { + return ( +
+
+ +
+ {props.message} +
+
+
+ ); +}; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx new file mode 100644 index 000000000..89bb33d55 --- /dev/null +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {colors} from 'material-ui/styles'; + +interface SwapIconProps { + swapTokensFn: () => void; +} + +interface SwapIconState { + isHovering: boolean; +} + +export class SwapIcon extends React.Component { + public constructor(props: SwapIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const swapStyles = { + color: this.state.isHovering ? colors.amber600 : colors.amber800, + fontSize: 50, + }; + return ( +
+ +
+ ); + } + private onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx new file mode 100644 index 000000000..168c09bd4 --- /dev/null +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -0,0 +1,29 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Token} from 'ts/types'; +import {Identicon} from 'ts/components/ui/identicon'; + +interface TokenIconProps { + token: Token; + diameter: number; +} + +interface TokenIconState {} + +export class TokenIcon extends React.Component { + public render() { + const token = this.props.token; + const diameter = this.props.diameter; + return ( +
+ {(token.isRegistered && !_.isUndefined(token.iconUrl)) ? + : + + } +
+ ); + } +} diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx new file mode 100644 index 000000000..a7d6d1a9e --- /dev/null +++ b/packages/website/ts/components/visual_order.tsx @@ -0,0 +1,77 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import {AssetToken, Token, TokenByAddress} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Party} from 'ts/components/ui/party'; +import {constants} from 'ts/utils/constants'; + +const PRECISION = 5; + +interface VisualOrderProps { + orderTakerAddress: string; + orderMakerAddress: string; + makerAssetToken: AssetToken; + takerAssetToken: AssetToken; + makerToken: Token; + takerToken: Token; + networkId: number; + tokenByAddress: TokenByAddress; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; +} + +interface VisualOrderState {} + +export class VisualOrder extends React.Component { + public render() { + const allTokens = _.values(this.props.tokenByAddress); + const makerImage = this.props.makerToken.iconUrl; + const takerImage = this.props.takerToken.iconUrl; + return ( +
+
+
+ +
+
+
+ {this.renderAmount(this.props.takerAssetToken, this.props.takerToken)} +
+
+ +
+
+ {this.renderAmount(this.props.makerAssetToken, this.props.makerToken)} +
+
+
+ +
+
+
+ ); + } + private renderAmount(assetToken: AssetToken, token: Token) { + const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); + return ( +
+ {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} +
+ ); + } +} diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx new file mode 100644 index 000000000..97b5172e7 --- /dev/null +++ b/packages/website/ts/containers/generate_order_form.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {Blockchain} from 'ts/blockchain'; +import {GenerateOrderForm as GenerateOrderFormComponent} from 'ts/components/generate_order/generate_order_form'; +import { + SideToAssetToken, + SignatureData, + HashData, + TokenByAddress, + TokenStateByAddress, + BlockchainErrs, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +interface GenerateOrderFormProps { + blockchain: Blockchain; + hashData: HashData; + dispatcher: Dispatcher; +} + +interface ConnectedState { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + userAddress: string; + orderTakerAddress: string; + orderSalt: BigNumber; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; +} + +const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderSignatureData: state.orderSignatureData, + orderTakerAddress: state.orderTakerAddress, + orderSalt: state.orderSalt, + networkId: state.networkId, + sideToAssetToken: state.sideToAssetToken, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, +}); + +export const GenerateOrderForm: React.ComponentClass = + connect(mapStateToProps)(GenerateOrderFormComponent); diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx new file mode 100644 index 000000000..805058aa3 --- /dev/null +++ b/packages/website/ts/containers/portal.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {State} from 'ts/redux/reducer'; +import {constants} from 'ts/utils/constants'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + Side, + HashData, + TokenByAddress, + BlockchainErrs, + Fill, + Order, + ScreenWidths, + TokenStateByAddress, +} from 'ts/types'; +import { + Portal as PortalComponent, + PortalAllProps as PortalComponentAllProps, + PortalPassedProps as PortalComponentPassedProps, +} from 'ts/components/portal'; +import BigNumber from 'bignumber.js'; + +interface MapStateToProps { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: number; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: number; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + userAddress: string; + userSuppliedOrderCache: Order; +} + +interface ConnectedState {} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => { + const receiveAssetToken = state.sideToAssetToken[Side.receive]; + const depositAssetToken = state.sideToAssetToken[Side.deposit]; + const receiveAddress = !_.isUndefined(receiveAssetToken.address) ? + receiveAssetToken.address : constants.NULL_ADDRESS; + const depositAddress = !_.isUndefined(depositAssetToken.address) ? + depositAssetToken.address : constants.NULL_ADDRESS; + const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? + receiveAssetToken.amount : new BigNumber(0); + const depositAmount = !_.isUndefined(depositAssetToken.amount) ? + depositAssetToken.amount : new BigNumber(0); + const hashData = { + depositAmount, + depositTokenContractAddr: depositAddress, + feeRecipientAddress: constants.FEE_RECIPIENT_ADDRESS, + makerFee: constants.MAKER_FEE, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderMakerAddress: state.userAddress, + orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, + receiveAmount, + receiveTokenContractAddr: receiveAddress, + takerFee: constants.TAKER_FEE, + orderSalt: state.orderSalt, + }; + return { + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + networkId: state.networkId, + nodeVersion: state.nodeVersion, + orderFillAmount: state.orderFillAmount, + hashData, + screenWidth: state.screenWidth, + shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, + userEtherBalance: state.userEtherBalance, + userSuppliedOrderCache: state.userSuppliedOrderCache, + flashMessage: state.flashMessage, + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Portal: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(PortalComponent); diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx new file mode 100644 index 000000000..5d05bdd2f --- /dev/null +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -0,0 +1,31 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import { + SmartContractsDocumentation as SmartContractsDocumentationComponent, + SmartContractsDocumentationAllProps, +} from 'ts/pages/documentation/smart_contracts_documentation'; + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: SmartContractsDocumentationAllProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, +}); + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const SmartContractsDocumentation: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(SmartContractsDocumentationComponent); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx new file mode 100644 index 000000000..a5b8298b5 --- /dev/null +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -0,0 +1,33 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {Blockchain} from 'ts/blockchain'; +import { + ZeroExJSDocumentation as ZeroExJSDocumentationComponent, + ZeroExJSDocumentationAllProps, +} from 'ts/pages/documentation/zero_ex_js_documentation'; +import BigNumber from 'bignumber.js'; + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: ZeroExJSDocumentationAllProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, +}); + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const ZeroExJSDocumentation: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(ZeroExJSDocumentationComponent); diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts new file mode 100644 index 000000000..ee449ecfd --- /dev/null +++ b/packages/website/ts/globals.d.ts @@ -0,0 +1,154 @@ +declare module 'react-tooltip'; +declare module 'react-router-hash-link'; +declare module 'es6-promisify'; +declare module 'truffle-contract'; +declare module 'ethereumjs-util'; +declare module 'keccak'; +declare module 'web3-provider-engine'; +declare module 'whatwg-fetch'; +declare module 'react-html5video'; +declare module 'web3-provider-engine/subproviders/filters'; +declare module 'thenby'; +declare module 'react-highlight'; +declare module 'react-recaptcha'; +declare module 'react-document-title'; +declare module 'ledgerco'; +declare module 'ethereumjs-tx'; + +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} + +// find-version declarations +declare function findVersions(version: string): string[]; +declare module 'find-versions' { + export = findVersions; +} + +// compare-version declarations +declare function compareVersions(firstVersion: string, secondVersion: string): number; +declare module 'compare-versions' { + export = compareVersions; +} + +// semver-sort declarations +declare module 'semver-sort' { + const desc: (versions: string[]) => string[]; +} + +// xml-js declarations +declare interface XML2JSONOpts { + compact?: boolean; + spaces?: number; +} +declare module 'xml-js' { + const xml2json: (xml: string, opts: XML2JSONOpts) => string; +} + +// This will be defined by default in TS 2.4 +// Source: https://github.com/Microsoft/TypeScript/issues/12364 +interface System { + import(module: string): Promise; +} +declare var System: System; + +// ethereum-address declarations +declare module 'ethereum-address' { + export const isAddress: (address: string) => boolean; +} + +// jsonschema declarations +// Source: https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts +declare interface Schema { + id?: string; + $schema?: string; + title?: string; + description?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalItems?: boolean | Schema; + items?: Schema | Schema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + additionalProperties?: boolean | Schema; + definitions?: { + [name: string]: Schema; + }; + properties?: { + [name: string]: Schema; + }; + patternProperties?: { + [name: string]: Schema; + }; + dependencies?: { + [name: string]: Schema | string[]; + }; + 'enum'?: any[]; + type?: string | string[]; + allOf?: Schema[]; + anyOf?: Schema[]; + oneOf?: Schema[]; + not?: Schema; + // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts + // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 + // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files + // As soon as it gets merged we should be good to use types from 'jsonschema' package + $ref?: string; +} + +// blockies declarations +declare interface BlockiesIcon { + toDataURL(): string; +} +declare interface BlockiesConfig { + seed: string; +} +declare function blockies(config: BlockiesConfig): BlockiesIcon; +declare module 'blockies' { + export = blockies; +} + +// is-mobile declarations +declare function isMobile(): boolean; +declare module 'is-mobile' { + export = isMobile; +} + +// web3-provider-engine declarations +declare class Subprovider {} +declare module 'web3-provider-engine/subproviders/subprovider' { + export = Subprovider; +} +declare class RpcSubprovider { + constructor(options: {rpcUrl: string}); + public handleRequest(payload: any, next: any, end: (err?: Error, data?: any) => void): void; +} +declare module 'web3-provider-engine/subproviders/rpc' { + export = RpcSubprovider; +} +declare class HookedWalletSubprovider { + constructor(wallet: any); +} +declare module 'web3-provider-engine/subproviders/hooked-wallet' { + export = HookedWalletSubprovider; +} + +declare interface Artifact { + abi: any; + networks: {[networkId: number]: { + address: string; + }}; +} diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx new file mode 100644 index 000000000..ed4d09956 --- /dev/null +++ b/packages/website/ts/index.tsx @@ -0,0 +1,116 @@ +// Polyfills +import 'whatwg-fetch'; + +import * as React from 'react'; +import {render} from 'react-dom'; +import {Provider} from 'react-redux'; +import {createStore, Store as ReduxStore} from 'redux'; +import BigNumber from 'bignumber.js'; +import {constants} from 'ts/utils/constants'; +import {Landing} from 'ts/pages/landing/landing'; +import {FAQ} from 'ts/pages/faq/faq'; +import {About} from 'ts/pages/about/about'; +import {Wiki} from 'ts/pages/wiki/wiki'; +import {NotFound} from 'ts/pages/not_found'; +import {createLazyComponent} from 'ts/lazy_component'; +import {State, reducer} from 'ts/redux/reducer'; +import {colors, getMuiTheme, MuiThemeProvider} from 'material-ui/styles'; +import {Switch, BrowserRouter as Router, Route, Link, Redirect} from 'react-router-dom'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; +import * as injectTapEventPlugin from 'react-tap-event-plugin'; +import {WebsitePaths} from 'ts/types'; +injectTapEventPlugin(); + +// By default BigNumber's `toString` method converts to exponential notation if the value has +// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number +BigNumber.config({ + EXPONENTIAL_AT: 1000, +}); + +// Check if we've introduced an update that requires us to clear the tradeHistory local storage entries +tradeHistoryStorage.clearIfRequired(); + +const CUSTOM_GREY = 'rgb(39, 39, 39)'; +const CUSTOM_GREEN = 'rgb(102, 222, 117)'; +const CUSTOM_DARKER_GREEN = 'rgb(77, 197, 92)'; + +import 'basscss/css/basscss.css'; +import 'less/all.less'; + +const muiTheme = getMuiTheme({ + appBar: { + height: 45, + color: 'white', + textColor: 'black', + }, + palette: { + pickerHeaderColor: constants.CUSTOM_BLUE, + primary1Color: constants.CUSTOM_BLUE, + primary2Color: constants.CUSTOM_BLUE, + textColor: colors.grey700, + }, + datePicker: { + color: colors.grey700, + textColor: 'white', + calendarTextColor: 'white', + selectColor: CUSTOM_GREY, + selectTextColor: 'white', + }, + timePicker: { + color: colors.grey700, + textColor: 'white', + accentColor: 'white', + headerColor: CUSTOM_GREY, + selectColor: CUSTOM_GREY, + selectTextColor: CUSTOM_GREY, + }, + toggle: { + thumbOnColor: CUSTOM_GREEN, + trackOnColor: CUSTOM_DARKER_GREEN, + }, +}); + +// We pass modulePromise returning lambda instead of module promise, +// cause we only want to import the module when the user navigates to the page. +// At the same time webpack statically parses for System.import() to determine bundle chunk split points +// so each lazy import needs it's own `System.import()` declaration. +const LazyPortal = createLazyComponent( + 'Portal', () => System.import(/* webpackChunkName: "portal" */'ts/containers/portal'), +); +const LazyZeroExJSDocumentation = createLazyComponent( + 'ZeroExJSDocumentation', + () => System.import(/* webpackChunkName: "zeroExDocs" */'ts/containers/zero_ex_js_documentation'), +); +const LazySmartContractsDocumentation = createLazyComponent( + 'SmartContractsDocumentation', + () => System.import(/* webpackChunkName: "smartContractDocs" */'ts/containers/smart_contracts_documentation'), +); + +const store: ReduxStore = createStore(reducer); +render( + +
+ + +
+ + + + + + + + + + + +
+
+
+
+
, + document.getElementById('app'), +); diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx new file mode 100644 index 000000000..7052b7be6 --- /dev/null +++ b/packages/website/ts/lazy_component.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import * as _ from 'lodash'; + +interface LazyComponentProps { + reactComponentPromise: Promise>; + reactComponentProps: any; +} + +interface LazyComponentState { + component?: React.ComponentClass; +} + +/** + * This component is used for rendering components that are lazily loaded from other chunks. + * Source: https://reacttraining.com/react-router/web/guides/code-splitting + */ +export class LazyComponent extends React.Component { + constructor(props: LazyComponentProps) { + super(props); + this.state = { + component: undefined, + }; + } + public componentWillMount() { + this.loadComponentFireAndForgetAsync(this.props); + } + public componentWillReceiveProps(nextProps: LazyComponentProps) { + if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { + this.loadComponentFireAndForgetAsync(nextProps); + } + } + public render() { + return _.isUndefined(this.state.component) ? + null : + React.createElement(this.state.component, this.props.reactComponentProps); + } + private async loadComponentFireAndForgetAsync(props: LazyComponentProps) { + const component = await props.reactComponentPromise; + this.setState({ + component, + }); + } +} + +/** + * [createLazyComponent description] + * @param componentName name of exported component + * @param lazyImport lambda returning module promise + * we pass a lambda because we only want to require a module if it's used + * @example `const LazyPortal = createLazyComponent('Portal', () => System.import('ts/containers/portal'));`` + */ +export const createLazyComponent = (componentName: string, lazyImport: () => Promise) => { + return (props: any) => { + const reactComponentPromise = (async (): Promise> => { + const mod = await lazyImport(); + const component = mod[componentName]; + return component; + })(); + return ( + + ); + }; +}; diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts new file mode 100644 index 000000000..d94e6877b --- /dev/null +++ b/packages/website/ts/local_storage/local_storage.ts @@ -0,0 +1,35 @@ +import * as _ from 'lodash'; + +export const localStorage = { + doesExist() { + return !!window.localStorage; + }, + getItemIfExists(key: string): string { + if (!this.doesExist) { + return undefined; + } + const item = window.localStorage.getItem(key); + if (_.isNull(item) || item === 'undefined') { + return ''; + } + return item; + }, + setItem(key: string, value: string) { + if (!this.doesExist || _.isUndefined(value)) { + return; + } + window.localStorage.setItem(key, value); + }, + removeItem(key: string) { + if (!this.doesExist) { + return; + } + window.localStorage.removeItem(key); + }, + getAllKeys(): string[] { + if (!this.doesExist) { + return []; + } + return _.keys(window.localStorage); + }, +}; diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts new file mode 100644 index 000000000..0b54a66e0 --- /dev/null +++ b/packages/website/ts/local_storage/tracked_token_storage.ts @@ -0,0 +1,56 @@ +import * as _ from 'lodash'; +import {Token, TrackedTokensByNetworkId} from 'ts/types'; +import {localStorage} from 'ts/local_storage/local_storage'; + +const TRACKED_TOKENS_KEY = 'trackedTokens'; + +export const trackedTokenStorage = { + addTrackedTokenToUser(userAddress: string, networkId: number, token: Token) { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + trackedTokensByNetworkId = {}; + } + const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) ? + trackedTokensByNetworkId[networkId] : + []; + trackedTokens.push(token); + trackedTokensByNetworkId[networkId] = trackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, + getTrackedTokensByUserAddress(): TrackedTokensByNetworkId { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return {}; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + return trackedTokensByUserAddress; + }, + getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return undefined; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + return undefined; + } + const trackedTokens = trackedTokensByNetworkId[networkId]; + return trackedTokens; + }, + removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string) { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + const trackedTokens = trackedTokensByNetworkId[networkId]; + const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { + return token.address !== tokenAddress; + }); + trackedTokensByNetworkId[networkId] = remainingTrackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, +}; diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx new file mode 100644 index 000000000..415b380b7 --- /dev/null +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -0,0 +1,82 @@ +import * as _ from 'lodash'; +import {Fill} from 'ts/types'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {localStorage} from 'ts/local_storage/local_storage'; +import ethUtil = require('ethereumjs-util'); +import BigNumber from 'bignumber.js'; + +const FILLS_KEY = 'fills'; +const FILLS_LATEST_BLOCK = 'fillsLatestBlock'; +const FILL_CLEAR_KEY = 'lastClearFillDate'; + +export const tradeHistoryStorage = { + // Clear all fill related localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the user to re-fetch the fills from + // the blockchain + clearIfRequired() { + const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); + if (lastClearFillDate !== configs.lastLocalStorageFillClearanceDate) { + const localStorageKeys = localStorage.getAllKeys(); + _.each(localStorageKeys, key => { + if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { + localStorage.removeItem(key); + } + }); + } + localStorage.setItem(FILL_CLEAR_KEY, configs.lastLocalStorageFillClearanceDate); + }, + addFillToUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (doesFillExist) { + return; + } + fillsByHash[fillHash] = fill; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + getUserFillsByHash(userAddress: string, networkId: number): {[fillHash: string]: Fill} { + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); + if (_.isEmpty(userFillsJSONString)) { + return {}; + } + const userFillsByHash = JSON.parse(userFillsJSONString); + _.each(userFillsByHash, (fill, hash) => { + fill.paidMakerFee = new BigNumber(fill.paidMakerFee); + fill.paidTakerFee = new BigNumber(fill.paidTakerFee); + fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); + fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); + }); + return userFillsByHash; + }, + getFillsLatestBlock(userAddress: string, networkId: number): number { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); + if (_.isEmpty(blockNumberStr)) { + return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; + } + const blockNumber = _.parseInt(blockNumberStr); + return blockNumber; + }, + setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); + }, + _getUserFillsKey(userAddress: string, networkId: number) { + const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; + return userFillsKey; + }, + _getFillsLatestBlockKey(userAddress: string, networkId: number) { + const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; + return userFillsLatestBlockKey; + }, + _getFillHash(fill: Fill): string { + const fillJSON = JSON.stringify(fill); + const fillHash = ethUtil.sha256(fillJSON); + return fillHash.toString('hex'); + }, +}; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx new file mode 100644 index 000000000..8859fb00a --- /dev/null +++ b/packages/website/ts/pages/about/about.tsx @@ -0,0 +1,253 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {Styles, ProfileInfo} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; +import {Question} from 'ts/pages/faq/question'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Profile} from 'ts/pages/about/profile'; + +const CUSTOM_BACKGROUND_COLOR = '#F0F0F0'; +const CUSTOM_GRAY = '#4C4C4C'; +const CUSTOM_LIGHT_GRAY = '#A2A2A2'; + +const teamRow1: ProfileInfo[] = [ + { + name: 'Will Warren', + title: 'Co-founder & CEO', + description: `Smart contract R&D. Previously applied physics at Los Alamos \ + Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`, + image: '/images/team/will.jpg', + linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', + github: 'https://github.com/willwarren89', + medium: 'https://medium.com/@willwarren89', + }, + { + name: 'Amir Bandeali', + title: 'Co-founder & CTO', + description: `Smart contract R&D. Previously fixed income trader at DRW. \ + Finance at University of Illinois, Urbana-Champaign.`, + image: '/images/team/amir.jpeg', + linkedIn: 'https://www.linkedin.com/in/abandeali1/', + github: 'https://github.com/abandeali1', + medium: 'https://medium.com/@abandeali1', + }, + { + name: 'Fabio Berger', + title: 'Senior Engineer', + description: `Full-stack blockchain engineer. Previously software engineer \ + at Airtable and founder of WealthLift. Computer science at Duke.`, + image: '/images/team/fabio.jpg', + linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', + github: 'https://github.com/fabioberger', + medium: 'https://medium.com/@fabioberger', + }, + { + name: 'Alex Xu', + title: 'Director of Operations', + description: `Strategy and operations. Previously digital marketing at Google \ + and vendor management at Amazon. Economics at UC San Diego.`, + image: '/images/team/alex.jpg', + linkedIn: 'https://www.linkedin.com/in/alex-xu/', + github: '', + medium: '', + }, +]; + +const teamRow2: ProfileInfo[] = [ + { + name: 'Leonid Logvinov', + title: 'Engineer', + description: `Full-stack blockchain engineer. Previously blockchain engineer \ + at Neufund. Computer science at University of Warsaw.`, + image: '/images/team/leonid.png', + linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', + github: 'https://github.com/LogvinovLeon', + medium: '', + }, + { + name: 'Ben Burns', + title: 'Designer', + description: `Product, motion, and graphic designer. Previously designer \ + at Airtable and Apple. Digital Design at University of Cincinnati.`, + image: '/images/team/ben.jpg', + linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', + github: '', + medium: '', + }, + { + name: 'Philippe Castonguay', + title: 'Dev Relations Manager', + description: `Developer relations. Previously computational neuroscience \ + research at Janelia. Statistics at Western University. MA Dropout.`, + image: '/images/team/philippe.png', + linkedIn: '', + github: 'https://github.com/PhABC', + medium: '', + }, + { + name: 'Brandon Millman', + title: 'Senior Engineer', + description: `Full-stack engineer. Previously senior software engineer at \ + Twitter. Electrical and Computer Engineering at Duke.`, + image: '/images/team/brandon.png', + linkedIn: 'https://www.linkedin.com/company-beta/17942619/', + }, +]; + +const advisors: ProfileInfo[] = [ + { + name: 'Fred Ehrsam', + description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', + image: '/images/advisors/fred.jpg', + linkedIn: 'https://www.linkedin.com/in/fredehrsam/', + medium: 'https://medium.com/@FEhrsam', + twitter: 'https://twitter.com/FEhrsam', + }, + { + name: 'Olaf Carlson-Wee', + image: '/images/advisors/olaf.png', + description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', + linkedIn: 'https://www.linkedin.com/in/olafcw/', + angellist: 'https://angel.co/olafcw', + }, + { + name: 'Joey Krug', + description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, + image: '/images/advisors/joey.jpg', + linkedIn: 'https://www.linkedin.com/in/joeykrug/', + github: 'https://github.com/joeykrug', + angellist: 'https://angel.co/joeykrug', + }, + { + name: 'Linda Xie', + description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', + image: '/images/advisors/linda.jpg', + linkedIn: 'https://www.linkedin.com/in/lindaxie/', + medium: 'https://medium.com/@linda.xie', + twitter: 'https://twitter.com/ljxie', + }, +]; + +export interface AboutProps { + source: string; + location: Location; +} + +interface AboutState {} + +const styles: Styles = { + header: { + fontFamily: 'Roboto Mono', + fontSize: 36, + color: 'black', + paddingTop: 110, + }, +}; + +export class About extends React.Component { + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+ + +
+
+
+ About us: +
+
+ Our team is a diverse and globally distributed group with backgrounds + in engineering, research, business and design. We are passionate about + decentralized technology and its potential to act as an equalizing force + in the world. +
+
+
+
+ {this.renderProfiles(teamRow1)} +
+
+ {this.renderProfiles(teamRow2)} +
+
+
+
+ Advisors: +
+
+ {this.renderProfiles(advisors)} +
+
+
+
+ WE'RE HIRING +
+
+ We are seeking outstanding candidates to{' '} + + join our team + + . We value passion, diversity and unique perspectives. +
+
+
+
+
+ ); + } + private renderProfiles(profiles: ProfileInfo[]) { + const numIndiv = profiles.length; + const colSize = utils.getColSize(profiles.length); + return _.map(profiles, profile => { + return ( +
+ +
+ ); + }); + } +} diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx new file mode 100644 index 000000000..6c48a8553 --- /dev/null +++ b/packages/website/ts/pages/about/profile.tsx @@ -0,0 +1,99 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {Element as ScrollElement} from 'react-scroll'; +import {Styles, ProfileInfo} from 'ts/types'; + +const IMAGE_DIMENSION = 149; +const styles: Styles = { + subheader: { + textTransform: 'uppercase', + fontSize: 32, + margin: 0, + }, + imageContainer: { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', + }, +}; + +interface ProfileProps { + colSize: number; + profileInfo: ProfileInfo; +} + +export function Profile(props: ProfileProps) { + return ( +
+
+
+ +
+
+ {props.profileInfo.name} +
+ {!_.isUndefined(props.profileInfo.title) && +
+ {props.profileInfo.title.toUpperCase()} +
+ } +
+ {props.profileInfo.description} +
+
+ {renderSocialMediaIcons(props.profileInfo)} +
+
+
+ ); +} + +function renderSocialMediaIcons(profileInfo: ProfileInfo) { + const icons = [ + renderSocialMediaIcon('zmdi-github-box', profileInfo.github), + renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), + renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), + ]; + return icons; +} + +function renderSocialMediaIcon(iconName: string, url: string) { + if (_.isEmpty(url)) { + return null; + } + + return ( +
+ + + +
+ ); +} diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx new file mode 100644 index 000000000..78bbdc069 --- /dev/null +++ b/packages/website/ts/pages/documentation/comment.tsx @@ -0,0 +1,24 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block'; + +interface CommentProps { + comment: string; + className?: string; +} + +const defaultProps = { + className: '', +}; + +export const Comment: React.SFC = (props: CommentProps) => { + return ( +
+ +
+ ); +}; diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx new file mode 100644 index 000000000..aca8af832 --- /dev/null +++ b/packages/website/ts/pages/documentation/custom_enum.tsx @@ -0,0 +1,31 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {CustomType} from 'ts/types'; + +const STRING_ENUM_CODE_PREFIX = ' strEnum('; + +interface CustomEnumProps { + type: CustomType; +} + +// This component renders custom string enums that was a work-around for versions of +// TypeScript <2.4.0 that did not support them natively. We keep it around to support +// older versions of 0x.js <0.9.0 +export function CustomEnum(props: CustomEnumProps) { + const type = props.type; + if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { + utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); + return null; + } + // Remove the prefix and postfix, leaving only the strEnum values without quotes. + const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); + return ( + + {`{`} + {'\t'}{enumValues} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx new file mode 100644 index 000000000..9364a5d31 --- /dev/null +++ b/packages/website/ts/pages/documentation/enum.tsx @@ -0,0 +1,26 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {TypeDocNode, EnumValue} from 'ts/types'; + +const STRING_ENUM_CODE_PREFIX = ' strEnum('; + +interface EnumProps { + values: EnumValue[]; +} + +export function Enum(props: EnumProps) { + const values = _.map(props.values, (value, i) => { + const isLast = i === props.values.length - 1; + const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; + return `\n\t${value.name}${defaultValueIfAny},`; + }); + return ( + + {`{`} + {values} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx new file mode 100644 index 000000000..58271e98f --- /dev/null +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -0,0 +1,80 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Event, EventArg, HeaderSizes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; + +const KEYWORD_COLOR = '#a81ca6'; +const CUSTOM_GREEN = 'rgb(77, 162, 75)'; + +interface EventDefinitionProps { + event: Event; +} + +interface EventDefinitionState { + shouldShowAnchor: boolean; +} + +export class EventDefinition extends React.Component { + constructor(props: EventDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const event = this.props.event; + return ( +
+ +
+
+                        
+                            {this.renderEventCode()}
+                        
+                    
+
+
+ ); + } + private renderEventCode() { + const indexed = indexed; + const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { + return ( + + {eventArg.name}{eventArg.isIndexed ? indexed : ''}: , + + ); + }); + const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + + {`{`} +
+ {'\t'}{argList} +
+ {`}`} +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx new file mode 100644 index 000000000..9e40b8901 --- /dev/null +++ b/packages/website/ts/pages/documentation/interface.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {CustomType, TypeDocTypes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; + +interface InterfaceProps { + type: CustomType; +} + +export function Interface(props: InterfaceProps) { + const type = props.type; + const properties = _.map(type.children, property => { + return ( + + {property.name}:{' '} + {property.type.typeDocType !== TypeDocTypes.Reflection ? + : + + }, + + ); + }); + const hasIndexSignature = !_.isUndefined(type.indexSignature); + if (hasIndexSignature) { + const is = type.indexSignature; + const param = ( + + {is.keyName}: + + ); + properties.push(( + + [{param}]: {is.valueName}, + + )); + } + const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + + {`{`} +
+ {'\t'}{propertyList} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx new file mode 100644 index 000000000..6fead2f47 --- /dev/null +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -0,0 +1,174 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {Chip} from 'material-ui/Chip'; +import {colors} from 'material-ui/styles'; +import { + TypeDocNode, + Styles, + TypeDefinitionByName, + TypescriptMethod, + SolidityMethod, + Parameter, + HeaderSizes, +} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {Comment} from 'ts/pages/documentation/comment'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; + +interface MethodBlockProps { + method: SolidityMethod|TypescriptMethod; + libraryVersion: string; + typeDefinitionByName: TypeDefinitionByName; +} + +interface MethodBlockState { + shouldShowAnchor: boolean; +} + +const styles: Styles = { + chip: { + fontSize: 13, + backgroundColor: colors.lightBlueA700, + color: 'white', + height: 11, + borderRadius: 14, + marginTop: 19, + lineHeight: 0.8, + }, +}; + +export class MethodBlock extends React.Component { + constructor(props: MethodBlockProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const method = this.props.method; + if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { + return null; + } + + return ( +
+ {!method.isConstructor && +
+ {(method as TypescriptMethod).isStatic && + this.renderChip('Static') + } + {(method as SolidityMethod).isConstant && + this.renderChip('Constant') + } + {(method as SolidityMethod).isPayable && + this.renderChip('Payable') + } + +
+ } + + + + {(method as TypescriptMethod).source && + + } + {method.comment && + + } + {method.parameters && !_.isEmpty(method.parameters) && +
+

+ ARGUMENTS +

+ {this.renderParameterDescriptions(method.parameters)} +
+ } + {method.returnComment && +
+

+ RETURNS +

+ +
+ } +
+ ); + } + private renderChip(text: string) { + return ( +
+ {text} +
+ ); + } + private renderParameterDescriptions(parameters: Parameter[]) { + const descriptions = _.map(parameters, parameter => { + const isOptional = parameter.isOptional; + return ( +
+
+
+
+ {parameter.name} +
+
+ {isOptional && 'optional'} +
+
+
+ {parameter.comment && + + } +
+
+ ); + }); + return descriptions; + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx new file mode 100644 index 000000000..3b5d2ce78 --- /dev/null +++ b/packages/website/ts/pages/documentation/method_signature.tsx @@ -0,0 +1,62 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {TypescriptMethod, SolidityMethod, TypeDefinitionByName, Parameter} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; + +interface MethodSignatureProps { + method: TypescriptMethod|SolidityMethod; + shouldHideMethodName?: boolean; + shouldUseArrowSyntax?: boolean; + typeDefinitionByName?: TypeDefinitionByName; +} + +const defaultProps = { + shouldHideMethodName: false, + shouldUseArrowSyntax: false, +}; + +export const MethodSignature: React.SFC = (props: MethodSignatureProps) => { + const parameters = renderParameters(props.method, props.typeDefinitionByName); + const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + const methodName = props.shouldHideMethodName ? '' : props.method.name; + const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) ? + undefined : + renderTypeParameter(props.method, props.typeDefinitionByName); + return ( + + {props.method.callPath}{methodName}{typeParameterIfExists}({paramString}) + {props.shouldUseArrowSyntax ? ' => ' : ': '} + {' '} + {props.method.returnType && + + } + + ); +}; + +function renderParameters(method: TypescriptMethod|SolidityMethod, typeDefinitionByName?: TypeDefinitionByName) { + const parameters = method.parameters; + const params = _.map(parameters, (p: Parameter) => { + const isOptional = p.isOptional; + return ( + + {p.name}{isOptional && '?'}: + + ); + }); + return params; +} + +function renderTypeParameter(method: TypescriptMethod, typeDefinitionByName?: TypeDefinitionByName) { + const typeParameter = method.typeParameter; + const typeParam = ( + + {`<${typeParameter.name} extends `} + + {`>`} + + ); + return typeParam; +} diff --git a/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx new file mode 100644 index 000000000..3e97829c4 --- /dev/null +++ b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx @@ -0,0 +1,401 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import findVersions = require('find-versions'); +import semverSort = require('semver-sort'); +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; +import { + scroller, +} from 'react-scroll'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + SmartContractsDocSections, + Styles, + DoxityDocObj, + TypeDefinitionByName, + DocAgnosticFormat, + SolidityMethod, + Property, + CustomType, + MenuSubsectionsBySection, + Event, + Docs, + AddressByContractName, + Networks, + EtherscanLinkSuffixes, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {utils} from 'ts/utils/utils'; +import {docUtils} from 'ts/utils/doc_utils'; +import {constants} from 'ts/utils/constants'; +import {MethodBlock} from 'ts/pages/documentation/method_block'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {Type} from 'ts/pages/documentation/type'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; +import {Comment} from 'ts/pages/documentation/comment'; +import {Badge} from 'ts/components/ui/badge'; +import {EventDefinition} from 'ts/pages/documentation/event_definition'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {doxityUtils} from 'ts/utils/doxity_utils'; +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/smart_contracts/introduction'); +/* tslint:enable:no-var-requires */ + +const SCROLL_TO_TIMEOUT = 500; +const CUSTOM_PURPLE = '#690596'; +const CUSTOM_RED = '#e91751'; +const CUSTOM_TURQUOIS = '#058789'; +const DOC_JSON_ROOT = constants.S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT; + +const sectionNameToMarkdown = { + [SmartContractsDocSections.Introduction]: IntroMarkdown, +}; +const networkNameToColor: {[network: string]: string} = { + [Networks.kovan]: CUSTOM_PURPLE, + [Networks.ropsten]: CUSTOM_RED, + [Networks.mainnet]: CUSTOM_TURQUOIS, +}; + +export interface SmartContractsDocumentationAllProps { + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; +} + +interface SmartContractsDocumentationState { + docAgnosticFormat?: DocAgnosticFormat; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class SmartContractsDocumentation extends + React.Component { + constructor(props: SmartContractsDocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : this.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( +
+ + + {_.isUndefined(this.state.docAgnosticFormat) ? +
+
+
+ +
+
Loading documentation...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x Smart Contracts + +

+ {this.renderDocumentation()} +
+
+
+ } +
+ ); + } + private renderDocumentation(): React.ReactNode { + const subMenus = _.values(constants.menuSmartContracts); + const orderedSectionNames = _.flatten(subMenus); + // Since smart contract method params are all base types, no need to pass + // down the typeDefinitionByName + const typeDefinitionByName = {}; + const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName)); + + return sections; + } + private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const docSection = this.state.docAgnosticFormat[sectionName]; + + const markdownFileIfExists = sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + + ); + } + + if (_.isUndefined(docSection)) { + return null; + } + + const sortedProperties = _.sortBy(docSection.properties, 'name'); + const propertyDefs = _.map(sortedProperties, this.renderProperty.bind(this)); + + const sortedMethods = _.sortBy(docSection.methods, 'name'); + const methodDefs = _.map(sortedMethods, method => { + const isConstructor = false; + return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); + + const sortedEvents = _.sortBy(docSection.events, 'name'); + const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { + return ( + + ); + }); + return ( +
+
+
+ +
+ {this.renderNetworkBadges(sectionName)} +
+ {docSection.comment && + + } + {docSection.constructors.length > 0 && +
+

Constructor

+ {this.renderConstructors(docSection.constructors, typeDefinitionByName)} +
+ } + {docSection.properties.length > 0 && +
+

Properties

+
{propertyDefs}
+
+ } + {docSection.methods.length > 0 && +
+

Methods

+
{methodDefs}
+
+ } + {docSection.events.length > 0 && +
+

Events

+
{eventDefs}
+
+ } +
+ ); + } + private renderNetworkBadges(sectionName: string) { + const networkToAddressByContractName = constants.contractAddresses[this.props.docsVersion]; + const badges = _.map(networkToAddressByContractName, + (addressByContractName: AddressByContractName, networkName: string) => { + const contractAddress = addressByContractName[sectionName]; + const linkIfExists = utils.getEtherScanLinkIfExists( + contractAddress, constants.networkIdByName[networkName], EtherscanLinkSuffixes.address, + ); + return ( + + + + ); + }); + return badges; + } + private renderConstructors(constructors: SolidityMethod[], + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this.renderMethodBlocks( + constructor, SmartContractsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName, + ); + }); + return ( +
+ {constructorDefs} +
+ ); + } + private renderProperty(property: Property): React.ReactNode { + return ( +
+ + {property.name}: + + {property.source && + + } + {property.comment && + + } +
+ ); + } + private renderMethodBlocks(method: SolidityMethod, sectionName: string, isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + return ( + + ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = 'smartContractsDocs'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } + + const docSections = _.keys(SmartContractsDocSections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } + + if (sectionName === SmartContractsDocSections.types) { + const sortedTypesNames = _.sortBy(docSection.types, 'name'); + const typeNames = _.map(sortedTypesNames, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + const sortedEventNames = _.sortBy(docSection.events, 'name'); + const eventNames = _.map(sortedEventNames, m => m.name); + const sortedMethodNames = _.sortBy(docSection.methods, 'name'); + const methodNames = _.map(sortedMethodNames, m => m.name); + menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; + } + }); + return menuSubsectionsBySection; + } + private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise { + const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; + + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT); + const docAgnosticFormat = doxityUtils.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); + + this.setState({ + docAgnosticFormat, + }, () => { + this.scrollToHash(); + }); + } +} diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx new file mode 100644 index 000000000..2fb69e2f0 --- /dev/null +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Source} from 'ts/types'; +import {constants} from 'ts/utils/constants'; + +interface SourceLinkProps { + source: Source; + version: string; +} + +export function SourceLink(props: SourceLinkProps) { + const source = props.source; + const githubUrl = constants.GITHUB_0X_JS_URL; + const sourceCodeUrl = `${githubUrl}/blob/v${props.version}/${source.fileName}#L${source.line}`; + return ( + + ); +} diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx new file mode 100644 index 000000000..af18f97c2 --- /dev/null +++ b/packages/website/ts/pages/documentation/type.tsx @@ -0,0 +1,187 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link as ScrollLink} from 'react-scroll'; +import * as ReactTooltip from 'react-tooltip'; +import {colors} from 'material-ui/styles'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {constants} from 'ts/utils/constants'; +import {Type as TypeDef, TypeDocTypes, TypeDefinitionByName} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; + +const BUILT_IN_TYPE_COLOR = '#e69d00'; +const STRING_LITERAL_COLOR = '#4da24b'; + +// Some types reference other libraries. For these types, we want to link the user to the relevant documentation. +const typeToUrl: {[typeName: string]: string} = { + Web3: constants.WEB3_DOCS_URL, + Provider: constants.WEB3_PROVIDER_DOCS_URL, + BigNumber: constants.BIGNUMBERJS_GITHUB_URL, +}; + +const typeToSection: {[typeName: string]: string} = { + ExchangeWrapper: 'exchange', + TokenWrapper: 'token', + TokenRegistryWrapper: 'tokenRegistry', + EtherTokenWrapper: 'etherToken', + ProxyWrapper: 'proxy', + TokenTransferProxyWrapper: 'proxy', +}; + +interface TypeProps { + type: TypeDef; + typeDefinitionByName?: TypeDefinitionByName; +} + +// The return type needs to be `any` here so that we can recursively define components within +// components (e.g when rendering the union type). +export function Type(props: TypeProps): any { + const type = props.type; + const isIntrinsic = type.typeDocType === TypeDocTypes.Intrinsic; + const isReference = type.typeDocType === TypeDocTypes.Reference; + const isArray = type.typeDocType === TypeDocTypes.Array; + const isStringLiteral = type.typeDocType === TypeDocTypes.StringLiteral; + let typeNameColor = 'inherit'; + let typeName: string|React.ReactNode; + let typeArgs: React.ReactNode[] = []; + switch (type.typeDocType) { + case TypeDocTypes.Intrinsic: + case TypeDocTypes.Unknown: + typeName = type.name; + typeNameColor = BUILT_IN_TYPE_COLOR; + break; + + case TypeDocTypes.Reference: + typeName = type.name; + typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { + if (arg.typeDocType === TypeDocTypes.Array) { + const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; + return ( + + [] + + ); + } else { + const subType = ( + + ); + return subType; + } + }); + break; + + case TypeDocTypes.StringLiteral: + typeName = `'${type.value}'`; + typeNameColor = STRING_LITERAL_COLOR; + break; + + case TypeDocTypes.Array: + typeName = type.elementType.name; + break; + + case TypeDocTypes.Union: + const unionTypes = _.map(type.types, t => { + return ( + + ); + }); + typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '|', curr]; + }); + break; + + case TypeDocTypes.TypeParameter: + typeName = type.name; + break; + + default: + throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); + } + // HACK: Normalize BigNumber to simply BigNumber. For some reason the type + // name is unpredictably one or the other. + if (typeName === 'BigNumber') { + typeName = 'BigNumber'; + } + const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + + const typeNameUrlIfExists = typeToUrl[(typeName as string)]; + const sectionNameIfExists = typeToSection[(typeName as string)]; + if (!_.isUndefined(typeNameUrlIfExists)) { + typeName = ( + + {typeName} + + ); + } else if ((isReference || isArray) && + (typeDocUtils.isPublicType(typeName as string) || + !_.isUndefined(sectionNameIfExists))) { + const id = Math.random().toString(); + const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) ? typeName : sectionNameIfExists; + let typeDefinition; + if (props.typeDefinitionByName) { + typeDefinition = props.typeDefinitionByName[typeName as string]; + } + typeName = ( + + {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? + + {typeName} + : + + {typeName} + + + + + } + + ); + } + return ( + + {typeName} + {isArray && '[]'}{!_.isEmpty(typeArgs) && + + {'<'}{commaSeparatedTypeArgs}{'>'} + + } + + ); +} diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx new file mode 100644 index 000000000..bcb07be8e --- /dev/null +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -0,0 +1,135 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {KindString, CustomType, TypeDocTypes, CustomTypeChild, HeaderSizes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {Interface} from 'ts/pages/documentation/interface'; +import {CustomEnum} from 'ts/pages/documentation/custom_enum'; +import {Enum} from 'ts/pages/documentation/enum'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {Comment} from 'ts/pages/documentation/comment'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; + +const KEYWORD_COLOR = '#a81ca6'; + +interface TypeDefinitionProps { + customType: CustomType; + shouldAddId?: boolean; +} + +interface TypeDefinitionState { + shouldShowAnchor: boolean; +} + +export class TypeDefinition extends React.Component { + public static defaultProps: Partial = { + shouldAddId: true, + }; + constructor(props: TypeDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const customType = this.props.customType; + if (!typeDocUtils.isPublicType(customType.name)) { + return null; // no-op + } + + let typePrefix: string; + let codeSnippet: React.ReactNode; + switch (customType.kindString) { + case KindString.Interface: + typePrefix = 'Interface'; + codeSnippet = ( + + ); + break; + + case KindString.Variable: + typePrefix = 'Enum'; + codeSnippet = ( + + ); + break; + + case KindString.Enumeration: + typePrefix = 'Enum'; + const enumValues = _.map(customType.children, (c: CustomTypeChild) => { + return { + name: c.name, + defaultValue: c.defaultValue, + }; + }); + codeSnippet = ( + + ); + break; + + case KindString['Type alias']: + typePrefix = 'Type Alias'; + codeSnippet = ( + + type {customType.name} ={' '} + {customType.type.typeDocType !== TypeDocTypes.Reflection ? + : + + } + + ); + break; + + default: + throw utils.spawnSwitchErr('type.kindString', customType.kindString); + } + + const typeDefinitionAnchorId = customType.name; + return ( +
+ +
+
+                        
+                            {codeSnippet}
+                        
+                    
+
+ {customType.comment && + + } +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx new file mode 100644 index 000000000..c26fb7d0a --- /dev/null +++ b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx @@ -0,0 +1,340 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import DocumentTitle = require('react-document-title'); +import findVersions = require('find-versions'); +import semverSort = require('semver-sort'); +import {colors} from 'material-ui/styles'; +import MenuItem from 'material-ui/MenuItem'; +import CircularProgress from 'material-ui/CircularProgress'; +import Paper from 'material-ui/Paper'; +import { + Link as ScrollLink, + Element as ScrollElement, + scroller, +} from 'react-scroll'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + KindString, + TypeDocNode, + ZeroExJsDocSections, + Styles, + ScreenWidths, + TypeDefinitionByName, + DocAgnosticFormat, + TypescriptMethod, + Property, + CustomType, + Docs, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {utils} from 'ts/utils/utils'; +import {docUtils} from 'ts/utils/doc_utils'; +import {constants} from 'ts/utils/constants'; +import {Loading} from 'ts/components/ui/loading'; +import {MethodBlock} from 'ts/pages/documentation/method_block'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {Type} from 'ts/pages/documentation/type'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; +import {Comment} from 'ts/pages/documentation/comment'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/0xjs/introduction'); +const InstallationMarkdown = require('md/docs/0xjs/installation'); +const AsyncMarkdown = require('md/docs/0xjs/async'); +const ErrorsMarkdown = require('md/docs/0xjs/errors'); +const versioningMarkdown = require('md/docs/0xjs/versioning'); +/* tslint:enable:no-var-requires */ + +const SCROLL_TO_TIMEOUT = 500; +const DOC_JSON_ROOT = constants.S3_0XJS_DOCUMENTATION_JSON_ROOT; + +const sectionNameToMarkdown = { + [ZeroExJsDocSections.introduction]: IntroMarkdown, + [ZeroExJsDocSections.installation]: InstallationMarkdown, + [ZeroExJsDocSections.async]: AsyncMarkdown, + [ZeroExJsDocSections.errors]: ErrorsMarkdown, + [ZeroExJsDocSections.versioning]: versioningMarkdown, +}; + +export interface ZeroExJSDocumentationPassedProps { + source: string; + location: Location; +} + +export interface ZeroExJSDocumentationAllProps { + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; +} + +interface ZeroExJSDocumentationState { + docAgnosticFormat?: DocAgnosticFormat; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class ZeroExJSDocumentation extends React.Component { + constructor(props: ZeroExJSDocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : typeDocUtils.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( +
+ + + {_.isUndefined(this.state.docAgnosticFormat) ? +
+
+
+ +
+
Loading documentation...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x.js + +

+ {this.renderDocumentation()} +
+
+
+ } +
+ ); + } + private renderDocumentation(): React.ReactNode { + const typeDocSection = this.state.docAgnosticFormat[ZeroExJsDocSections.types]; + const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); + + const subMenus = _.values(constants.menu0xjs); + const orderedSectionNames = _.flatten(subMenus); + const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName)); + + return sections; + } + private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const docSection = this.state.docAgnosticFormat[sectionName]; + + const markdownFileIfExists = sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + + ); + } + + if (_.isUndefined(docSection)) { + return null; + } + + const typeDefs = _.map(docSection.types, customType => { + return ( + + ); + }); + const propertyDefs = _.map(docSection.properties, this.renderProperty.bind(this)); + const methodDefs = _.map(docSection.methods, method => { + const isConstructor = false; + return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); + return ( +
+ + + {sectionName === ZeroExJsDocSections.zeroEx && docSection.constructors.length > 0 && +
+

Constructor

+ {this.renderZeroExConstructors(docSection.constructors, typeDefinitionByName)} +
+ } + {docSection.properties.length > 0 && +
+

Properties

+
{propertyDefs}
+
+ } + {docSection.methods.length > 0 && +
+

Methods

+
{methodDefs}
+
+ } + {typeDefs.length > 0 && +
+
{typeDefs}
+
+ } +
+ ); + } + private renderZeroExConstructors(constructors: TypescriptMethod[], + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this.renderMethodBlocks( + constructor, ZeroExJsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName, + ); + }); + return ( +
+ {constructorDefs} +
+ ); + } + private renderProperty(property: Property): React.ReactNode { + return ( +
+ + {property.name}: + + + {property.comment && + + } +
+ ); + } + private renderMethodBlocks(method: TypescriptMethod, sectionName: string, isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + return ( + + ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = 'zeroExJSDocs'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise { + const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; + + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT); + const docAgnosticFormat = typeDocUtils.convertToDocAgnosticFormat((versionDocObj as TypeDocNode)); + + this.setState({ + docAgnosticFormat, + }, () => { + this.scrollToHash(); + }); + } +} diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx new file mode 100644 index 000000000..3c65d1042 --- /dev/null +++ b/packages/website/ts/pages/faq/faq.tsx @@ -0,0 +1,497 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {Styles, FAQSection, FAQQuestion, WebsitePaths} from 'ts/types'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; +import {Question} from 'ts/pages/faq/question'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; + +export interface FAQProps { + source: string; + location: Location; +} + +interface FAQState {} + +const styles: Styles = { + thin: { + fontWeight: 100, + }, +}; + +const sections: FAQSection[] = [ + { + name: '0x Protocol', + questions: [ + { + prompt: 'What is 0x?', + answer: ( +
+ At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, + low friction exchange of Ethereum-based assets. Developers can use 0x as a platform + to build exchange applications on top of{' '} + (0x.js + {' '}is a Javascript library for interacting with the 0x protocol). For end users, 0x will be + the infrastructure of a wide variety of user-facing applications i.e.{' '} + 0x Portal, + a decentralized application that facilitates trustless trading of Ethereum-based tokens + between known counterparties. +
+ ), + }, + { + prompt: 'What problem does 0x solve?', + answer: ( +
+ In the two years since the Ethereum blockchain’s genesis block, numerous decentralized + applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. + Rapid iteration and a lack of best practices have left the blockchain scattered with + proprietary and application-specific implementations. As a result, end users are + exposed to numerous smart contracts of varying quality and security, with unique + configuration processes and learning curves, all of which implement the same + functionality. This approach imposes unnecessary costs on the network by fragmenting + end users according to the particular dApp each user happens to be using, eliminating + valuable network effects around liquidity. 0x is the solution to this problem by + acting as modular, unopinionated building blocks that may be assembled and reconfigured. +
+ ), + }, + { + prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', + answer: ( +
+
    +
  • + 0x is a protocol for exchange, not a user-facing exchange application. +
  • +
  • + 0x is decentralized and trustless; there is no central party which can be + hacked, run away with customer funds or be subjected to government regulations. + Hacks of Mt. Gox, Shapeshift and Bitfinex have demonstrated that these types of + systemic risks are palpable. +
  • +
  • + Rather than a proprietary system that exists to extract rent for its owners, + 0x is public infrastructure that is funded by a globally distributed community + of stakeholders. While the protocol is free to use, it enables for-profit + user-facing exchange applications to be built on top of the protocol. +
  • +
+
+ ), + }, + { + prompt: 'If 0x protocol is free to use, where do transaction fees come in?', + answer: ( +
+ 0x protocol uses off-chain order books to massively reduce friction costs for + market makers and ensure that the blockchain is only used for trade settlement. + Hosting and maintaining an off-chain order book is a service; to incent “Relayers” + to provide this service they must be able to charge transaction fees on trading + activity. Relayers are free to set their transaction fees to any value they desire. + We expect Relayers to be highly competitive and transaction fees to approach an + efficient economic equilibrium over time. +
+ ), + }, + { + prompt: 'What are the differences between 0x protocol and state channels?', + answer: ( +
+
+ Participants in a state channel pass cryptographically signed messages back and + forth, accumulating intermediate state changes without publishing them to the + canonical chain until the channel is closed. State channels are ideal for “bar tab” + applications where numerous intermediate state changes may be accumulated off-chain + before being settled by a final on-chain transaction (i.e. day trading, poker, + turn-based games). +
+
    +
  • + While state channels drastically reduce the number of on-chain transactions + for specific use cases, numerous on-chain transactions and a security deposit + are required to open and safely close a state channel making them less efficient + than 0x for executing one-time trades. +
  • +
  • + State channels are isolated from the Ethereum blockchain meaning that + they cannot interact with smart contracts. 0x is designed to be integrated + directly into smart contracts so trades can be executed programmatically + in a single line of Solidity code. +
  • +
+
+ ), + }, + { + prompt: 'What types of digital assets are supported by 0x?', + answer: ( +
+ 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. + There are many ERC20 tokens, worth a combined $2.2B, and more tokens are created + each month. We believe that, by 2020, thousands of assets will be tokenized and + moved onto the Ethereum blockchain including traditional securities such as equities, + bonds and derivatives, fiat currencies and scarce digital goods such as video game + items. In the future, cross-blockchain solutions such as{' '} + Cosmos and{' '} + Polkadot will allow cryptocurrencies + to freely move between blockchains and, naturally, currencies such as Bitcoin will + end up being represented as ERC20 tokens on the Ethereum blockchain. +
+ ), + }, + { + prompt: '0x is open source: what prevents someone from forking the protocol?', + answer: ( +
+ Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, + but the resulting clone networks have seen little adoption (as measured by transaction + count or market cap). This is because users have little to no incentive to switch + over to a clone network if the original has initial network effects and a talented + developer team behind it. + An exception is in the case that a protocol includes a controversial feature such as + a method of rent extraction or a monetary policy that favors one group of users over + another (Zcash developer subsidy - for better or worse - resulted in Zclassic). + Perceived inequality can provide a strong enough incentive that users will fork the + original protocol’s codebase and spin up a new network that eliminates the controversial + feature. In the case of 0x, there is no rent extraction and no users are given + special permissions. + + 0x protocol is upgradable. Cutting-edge technical capabilities can be integrated + into 0x via decentralized governance (see section below), eliminating incentives + to fork off of the original protocol and sacrifice the network effects surrounding + liquidity that result from the shared protocol and settlement layer. +
+ ), + }, + ], + }, + { + name: '0x Token (ZRX)', + questions: [ + { + prompt: 'Explain how the 0x protocol token (zrx) works.', + answer: ( +
+
+ 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} + + coordination problem + and drive network effects around liquidity, creating a feedback loop + where early adopters of the protocol benefit from wider adoption and 2) to + be used for decentralized governance over 0x protocol's update mechanism. + In more detail: +
+
    +
  • + ZRX tokens are used by Makers and Takers (market participants that generate + and consume orders, respectively) to pay transaction fees to Relayers + (entities that host and maintain public order books). +
  • +
  • + ZRX tokens are used for decentralized governance over 0x protocol’s update + mechanism which allows its underlying smart contracts to be replaced and + improved over time. An update mechanism is needed because 0x is built upon + Ethereum’s rapidly evolving technology stack, decentralized governance is + needed because 0x protocol’s smart contracts will have access to user funds + and numerous dApps will need to plug into 0x smart contracts. Decentralized + governance ensures that this update process is secure and minimizes disruption + to the network. +
  • +
+
+ ), + }, + { + prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', + answer: ( +
+ 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. + To maximize the alignment of stakeholder and end user incentives, the staked + asset must provide utility within the protocol. +
+ ), + }, + { + prompt: 'How will decentralized governance work?', + answer: ( +
+ Decentralized governance is an ongoing focus of research; it will involve token + voting with ZRX. Ultimately the solution will maximize security while also maximizing + the protocol’s ability to absorb new innovations. Until the governance structure is + formalized and encoded within 0x DAO, a multi-sig will be used as a placeholder. +
+ ), + }, + ], + }, + { + name: 'ZRX Token Launch and Fund Use', + questions: [ + { + prompt: 'What is the total supply of ZRX tokens?', + answer: ( +
+ 1,000,000,000 ZRX. Fixed supply. +
+ ), + }, + { + prompt: 'When is the Token Launch? will there be a pre-sale?', + answer: ( +
+ The token launch will be on August 15th, 2017. There will not be a pre-sale. +
+ ), + }, + { + prompt: 'What will the token launch proceeds be used for?', + answer: ( +
+ 100% of the proceeds raised in the token launch will be used to fund the development + of free and open source software, tools and infrastructure that support the protocol + and surrounding ecosystem. Check out our{' '} + + development roadmap + . +
+ ), + }, + { + prompt: 'What will be the initial distribution of ZRX tokens?', + answer: ( +
+
+ +
+
+
+ Token Launch (50%) +
+
+ ZRX is inherently a governance token that plays a critical role in the + process of upgrading 0x protocol. We are fully committed to formulating + a functional and theoretically sound governance model and we plan to dedicate + significant resources to R&D. +
+
+
+
+ Retained by 0x (15%) +
+
+ The 0x core development team will be able to sustain itself for approximately + five years using funds raised through the token launch. If 0x protocol + proves to be as foundational a technology as we believe it to be, the + retained ZRX tokens will allow the 0x core development team to sustain + operations beyond the first 5 years. +
+
+
+
+ Developer Fund (15%) +
+
+ The Developer Fund will be used to make targeted capital injections + into high potential projects and teams that are attempting to grow + the 0x ecosystem, strategic partnerships, hackathon prizes and community + development activities. +
+
+
+
+ Founding Team (10%) +
+
+ The founding team’s allocation of ZRX will vest over a traditional 4 + year vesting schedule with a one year cliff. We believe this should + be standard practice for any team that is committed to making their + project a long term success. +
+
+
+
+ Early Backers & Advisors (10%) +
+
+ Our backers and advisors have provided capital, resources and guidance + that have allowed us to fill out our team, setup a robust legal entity + and build a fully functional product before launching a token. As a result, + we have a proven track record and can offer a token that holds genuine utility. +
+
+
+ ), + }, + { + prompt: 'Can I mine ZRX tokens?', + answer: ( +
+ No, the total supply of ZRX tokens is fixed and there is no continuous issuance + model. Users that facilitate trading over 0x protocol by operating a Relayer + earn transaction fees denominated in ZRX; as more trading activity is generated, + more transaction fees are earned. +
+ ), + }, + { + prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', + answer: ( +
+ No, ZRX tokens sold in the token launch will immediately be liquid. +
+ ), + }, + { + prompt: 'Will there be a lockup period for tokens allocated to the founding team?', + answer: ( +
+ Yes. ZRX tokens allocated to founders, advisors and staff members will be released + over a 4 year vesting schedule with a 25% cliff upon completion of the initial + token launch and 25% released each subsequent year in monthly installments. Staff + members hired after the token launch will have a 4 year vesting schedule with a + one year cliff. +
+ ), + }, + { + prompt: 'Which cryptocurrencies will be accepted in the token launch?', + answer: ( +
ETH.
+ ), + }, + { + prompt: 'When will 0x be live?', + answer: ( +
+ An alpha version of 0x has been live on our private test network since January + 2017. Version 1.0 of 0x protocol will be deployed to the canonical Ethereum + blockchain after a round of security audits and prior to the public token launch. + 0x will be using the 0x protocol during our token launch. +
+ ), + }, + { + prompt: 'Where can I find a development roadmap?', + answer: ( +
+ Check it out{' '} + + here + . +
+ ), + }, + ], + }, + { + name: 'Team', + questions: [ + { + prompt: 'Where is 0x based?', + answer: ( +
+ 0x was founded in SF and is driven by a diverse group of contributors. +
+ ), + }, + { + prompt: 'How can I get involved?', + answer: ( +
+ Join our Rocket.chat! + As an open source project, 0x will rely on a worldwide community of passionate + developers to contribute proposals, ideas and code. +
+ ), + }, + { + prompt: 'Why the name 0x?', + answer: ( +
+ 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. + In a more abstract context, as the first open protocol for exchange 0x represents + the beginning of the end for the exchange industry’s rent seeking oligopoly: + zero exchange. +
+ ), + }, + { + prompt: 'How do you pronounce 0x?', + answer: ( +
+ We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please. +
+ ), + }, + ], + }, +]; + +export class FAQ extends React.Component { + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+ + +
+

0x FAQ

+
+ {this.renderSections()} +
+
+
+
+ ); + } + private renderSections() { + const renderedSections = _.map(sections, (section: FAQSection, i: number) => { + const isFirstSection = i === 0; + return ( +
+

{section.name}

+ {this.renderQuestions(section.questions, isFirstSection)} +
+ ); + }); + return renderedSections; + } + private renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { + const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { + const isFirstQuestion = i === 0; + return ( + + ); + }); + return renderedQuestions; + } +} diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx new file mode 100644 index 000000000..4ed198b91 --- /dev/null +++ b/packages/website/ts/pages/faq/question.tsx @@ -0,0 +1,52 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Card, CardHeader, CardText} from 'material-ui/Card'; + +export interface QuestionProps { + prompt: string; + answer: React.ReactNode; + shouldDisplayExpanded: boolean; +} + +interface QuestionState { + isExpanded: boolean; +} + +export class Question extends React.Component { + constructor(props: QuestionProps) { + super(props); + this.state = { + isExpanded: props.shouldDisplayExpanded, + }; + } + public render() { + return ( +
+ + + +
+ {this.props.answer} +
+
+
+
+ ); + } + private onExchangeChange() { + this.setState({ + isExpanded: !this.state.isExpanded, + }); + } +} diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx new file mode 100644 index 000000000..32ea86736 --- /dev/null +++ b/packages/website/ts/pages/landing/landing.tsx @@ -0,0 +1,843 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import {Link} from 'react-router-dom'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Styles, WebsitePaths, ScreenWidths} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {TopBar} from 'ts/components/top_bar'; +import {Footer} from 'ts/components/footer'; + +interface BoxContent { + title: string; + description: string; + imageUrl: string; + classNames: string; +} +interface AssetType { + title: string; + imageUrl: string; + style?: React.CSSProperties; +} +interface UseCase { + imageUrl: string; + type: string; + description: string; + classNames: string; + style?: React.CSSProperties; + projectIconUrls: string[]; +} +interface Project { + logoFileName: string; + projectUrl: string; +} + +const THROTTLE_TIMEOUT = 100; +const CUSTOM_HERO_BACKGROUND_COLOR = '#404040'; +const CUSTOM_PROJECTS_BACKGROUND_COLOR = '#343333'; +const CUSTOM_WHITE_BACKGROUND = 'rgb(245, 245, 245)'; +const CUSTOM_WHITE_TEXT = '#E4E4E4'; +const CUSTOM_GRAY_TEXT = '#919191'; + +const boxContents: BoxContent[] = [ + { + title: 'Trustless exchange', + description: 'Built on Ethereum\'s distributed network with no centralized \ + point of failure and no down time, each trade is settled atomically \ + and without counterparty risk.', + imageUrl: '/images/landing/distributed_network.png', + classNames: '', + }, + { + title: 'Shared liquidity', + description: 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ + creating network effects around liquidity that compound as more relayers come online.', + imageUrl: '/images/landing/liquidity.png', + classNames: 'mx-auto', + }, + { + title: 'Open source', + description: '0x is open source, permissionless and free to use. Trade directly with a known \ + counterparty for free or pay a relayer some ZRX tokens to access their liquidity \ + pool.', + imageUrl: '/images/landing/open_source.png', + classNames: 'right', + }, +]; + +const projects: Project[] = [ + { + logoFileName: 'ethfinex-top.png', + projectUrl: constants.ETHFINEX_URL, + }, + { + logoFileName: 'radar_relay_top.png', + projectUrl: constants.RADAR_RELAY_URL, + }, + { + logoFileName: 'paradex_top.png', + projectUrl: constants.PARADEX_URL, + }, + { + logoFileName: 'the_ocean.png', + projectUrl: constants.OCEAN_URL, + }, + { + logoFileName: 'dydx.png', + projectUrl: constants.DYDX_URL, + }, + { + logoFileName: 'melonport.png', + projectUrl: constants.MELONPORT_URL, + }, + { + logoFileName: 'maker.png', + projectUrl: constants.MAKER_URL, + }, + { + logoFileName: 'dharma.png', + projectUrl: constants.DHARMA_URL, + }, + { + logoFileName: 'lendroid.png', + projectUrl: constants.LENDROID_URL, + }, + { + logoFileName: 'district0x.png', + projectUrl: constants.DISTRICT_0X_URL, + }, + { + logoFileName: 'aragon.png', + projectUrl: constants.ARAGON_URL, + }, + { + logoFileName: 'blocknet.png', + projectUrl: constants.BLOCKNET_URL, + }, + { + logoFileName: 'status.png', + projectUrl: constants.STATUS_URL, + }, + { + logoFileName: 'augur.png', + projectUrl: constants.AUGUR_URL, + }, + { + logoFileName: 'anx.png', + projectUrl: constants.OPEN_ANX_URL, + }, + { + logoFileName: 'auctus.png', + projectUrl: constants.AUCTUS_URL, + }, +]; + +export interface LandingProps { + location: Location; +} + +interface LandingState { + screenWidth: ScreenWidths; +} + +export class Landing extends React.Component { + private throttledScreenWidthUpdate: () => void; + constructor(props: LandingProps) { + super(props); + this.state = { + screenWidth: utils.getScreenWidth(), + }; + this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount() { + window.addEventListener('resize', this.throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillUnmount() { + window.removeEventListener('resize', this.throttledScreenWidthUpdate); + } + public render() { + return ( +
+ + + {this.renderHero()} + {this.renderProjects()} + {this.renderTokenizationSection()} + {this.renderProtocolSection()} + {this.renderInfoBoxes()} + {this.renderBuildingBlocksSection()} + {this.renderUseCases()} + {this.renderCallToAction()} +
+
+ ); + } + private renderHero() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: isSmallScreen ? 12 : 14, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #D8D8D8', + lineHeight: '33px', + height: 38, + }; + const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; + return ( +
+
+
+
+ +
+
+
+
+ Powering decentralized exchange +
+
+ 0x is an open, permissionless protocol allowing for ERC20 tokens to + be traded on the Ethereum blockchain. +
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+
+ ); + } + private renderProjects() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const isMediumScreen = this.state.screenWidth === ScreenWidths.MD; + const projectList = _.map(projects, (project: Project, i: number) => { + const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - (i % 2); + return ( +
+
+ + + +
+
+ ); + }); + const titleStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + color: '#A4A4A4', + textTransform: 'uppercase', + fontWeight: 300, + letterSpacing: 3, + }; + return ( +
+
+
+ Projects building on 0x +
+
+ {projectList} +
+
+ view the{' '} + + full list + +
+
+
+ ); + } + private renderTokenizationSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+
+ {isSmallScreen && + this.renderTokenCloud() + } +
+
+
+ The world's value is becoming tokenized +
+
+ {isSmallScreen ? + + The Ethereum blockchain is an open, borderless financial system that represents + a wide variety of assets as cryptographic tokens. In the future, most digital + assets and goods will be tokenized. + : +
+
+ The Ethereum blockchain is an open, borderless + financial system that represents +
+
+ a wide variety of assets as cryptographic tokens. + In the future, most digital assets and goods will be tokenized. +
+
+ } +
+
+ {this.renderAssetTypes()} +
+
+
+ {!isSmallScreen && + this.renderTokenCloud() + } +
+
+ ); + } + private renderProtocolSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+
+
+ +
+
+
+
+ Off-chain order relay +
+
+ On-chain settlement +
+
+
+ In 0x protocol, orders are transported off-chain, massively reducing gas + costs and eliminating blockchain bloat. Relayers help broadcast orders and + collect a fee each time they facilitate a trade. Anyone can build a relayer. +
+
+
+
+ RELAYERS BUILDING ON 0X +
+
+ + view all + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ ); + } + private renderBuildingBlocksSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const underlineStyle: React.CSSProperties = { + height: isSmallScreen ? 18 : 23, + lineHeight: 'none', + borderBottom: '2px solid #979797', + }; + const descriptionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + lineHeight: isSmallScreen ? 1.5 : 2, + fontWeight: 300, + fontSize: 15, + maxWidth: isSmallScreen ? 375 : 'none', + }; + const callToActionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + fontSize: 15, + fontWeight: 300, + maxWidth: isSmallScreen ? 375 : 441, + }; + return ( +
+
+ {isSmallScreen && + this.renderBlockChipImage() + } +
+
+ A building block for dApps +
+
+ 0x protocol is a pluggable building block for dApps that require exchange functionality. + Join the many developers that are already using 0x in their web applications and smart + contracts. +
+
+ Learn how in our{' '} + + 0x.js + + {' '}and{' '} + + smart contract + + {' '}docs +
+
+ {!isSmallScreen && + this.renderBlockChipImage() + } +
+
+ ); + } + private renderBlockChipImage() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+ +
+ ); + } + private renderTokenCloud() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+ +
+ ); + } + private renderAssetTypes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const assetTypes: AssetType[] = [ + { + title: 'Currency', + imageUrl: '/images/landing/currency.png', + }, + { + title: 'Traditional assets', + imageUrl: '/images/landing/stocks.png', + style: {paddingLeft: isSmallScreen ? 41 : 56, paddingRight: isSmallScreen ? 41 : 56}, + }, + { + title: 'Digital goods', + imageUrl: '/images/landing/digital_goods.png', + }, + ]; + const assets = _.map(assetTypes, (assetType: AssetType) => { + const style = _.isUndefined(assetType.style) ? {} : assetType.style; + return ( +
+
+ +
+
+ {assetType.title} +
+
+ ); + }); + return assets; + } + private renderLink(label: string, path: string, color: string, style?: React.CSSProperties) { + return ( +
+ + {label} + +
+ ); + } + private renderInfoBoxes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const boxStyle: React.CSSProperties = { + maxWidth: 252, + height: 386, + backgroundColor: '#F9F9F9', + borderRadius: 5, + padding: '10px 24px 24px', + }; + const boxes = _.map(boxContents, (boxContent: BoxContent) => { + return ( +
+
+
+ +
+
+ {boxContent.title} +
+
+ {boxContent.description} +
+
+
+ ); + + }); + return ( +
+
+ {boxes} +
+
+ ); + } + private renderUseCases() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const isMediumScreen = this.state.screenWidth === ScreenWidths.MD; + + const useCases: UseCase[] = [ + { + imageUrl: '/images/landing/governance_icon.png', + type: 'Decentralized governance', + description: 'Decentralized organizations use tokens to represent ownership and \ + guide their governance logic. 0x allows decentralized organizations \ + to seamlessly and safely trade ownership for startup capital.', + projectIconUrls: ['/images/landing/aragon.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/prediction_market_icon.png', + type: 'Prediction markets', + description: 'Decentralized prediction market platforms generate sets of tokens that \ + represent a financial stake in the outcomes of real-world events. 0x allows \ + these tokens to be instantly tradable.', + projectIconUrls: ['/images/landing/augur.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/stable_tokens_icon.png', + type: 'Stable tokens', + description: 'Novel economic constructs such as stable coins require efficient, liquid \ + markets to succeed. 0x will facilitate the underlying economic mechanisms \ + that allow these tokens to remain stable.', + projectIconUrls: ['/images/landing/maker.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/loans_icon.png', + type: 'Decentralized loans', + description: 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ + 0x enables an ecosystem of lenders to self-organize and efficiently determine \ + market prices for all outstanding loans.', + projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], + classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', + style: {width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0}, + }, + { + imageUrl: '/images/landing/fund_management_icon.png', + type: 'Fund management', + description: 'Decentralized fund management limits fund managers to investing in pre-agreed \ + upon asset classes. Embedding 0x into fund management smart contracts enables \ + them to enforce these security constraints.', + projectIconUrls: ['/images/landing/melonport.png'], + classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', + style: {width: 291, marginTop: !isSmallScreen ? 38 : 0}, + }, + ]; + + const cases = _.map(useCases, (useCase: UseCase) => { + const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; + const useCaseBoxStyle = { + color: '#A2A2A2', + border: '1px solid #565656', + borderRadius: 4, + maxWidth: isSmallScreen ? 375 : 'none', + ...style, + }; + const typeStyle: React.CSSProperties = { + color: '#EBEBEB', + fontSize: 13, + textTransform: 'uppercase', + fontFamily: 'Roboto Mono', + fontWeight: 300, + }; + return ( +
+
+
+ +
+
+ {useCase.type} +
+
+ {useCase.description} +
+
+
+ ); + }); + return ( +
+
+ {cases} +
+
+ ); + } + private renderCallToAction() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: 15, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #a0a0a0', + lineHeight: '33px', + height: 49, + }; + const callToActionClassNames = 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ + lg-right-align md-right-align sm-center sm-px3 h4'; + return ( +
+
+
+ Get started on building the decentralized future +
+
+ + + +
+
+
+ ); + } + private updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + if (newScreenWidth !== this.state.screenWidth) { + this.setState({ + screenWidth: newScreenWidth, + }); + } + } +} diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx new file mode 100644 index 000000000..ddd720c97 --- /dev/null +++ b/packages/website/ts/pages/not_found.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Styles} from 'ts/types'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; + +export interface NotFoundProps { + location: Location; +} + +interface NotFoundState {} + +const styles: Styles = { + thin: { + fontWeight: 100, + }, +}; + +export class NotFound extends React.Component { + public render() { + return ( +
+ +
+
+
+
+

404 Not Found

+
+
+ Hm... looks like we couldn't find what you are looking for. +
+
+
+
+
+
+
+
+ ); + } +} diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx new file mode 100644 index 000000000..dfa9401ae --- /dev/null +++ b/packages/website/ts/pages/shared/anchor_title.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import {Styles, HeaderSizes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {Link as ScrollLink} from 'react-scroll'; + +const headerSizeToScrollOffset: {[headerSize: string]: number} = { + h2: -20, + h3: 0, +}; + +interface AnchorTitleProps { + title: string|React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; +} + +interface AnchorTitleState { + isHovering: boolean; +} + +const styles: Styles = { + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h2: { + fontSize: '1.5em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h3: { + fontSize: '1.17em', + WebkitMarginBefore: '1em', + WebkitMarginAfter: '1em', + }, +}; + +export class AnchorTitle extends React.Component { + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + if (this.state.isHovering) { + opacity = 0.6; + } else { + opacity = 1; + } + } + return ( +
+
+ {this.props.title} +
+ + + +
+ ); + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx new file mode 100644 index 000000000..621e5b606 --- /dev/null +++ b/packages/website/ts/pages/shared/markdown_code_block.tsx @@ -0,0 +1,20 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as HighLight from 'react-highlight'; + +interface MarkdownCodeBlockProps { + literal: string; + language: string; +} + +export function MarkdownCodeBlock(props: MarkdownCodeBlockProps) { + return ( + + + {props.literal} + + + ); +} diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx new file mode 100644 index 000000000..32b55abc8 --- /dev/null +++ b/packages/website/ts/pages/shared/markdown_section.tsx @@ -0,0 +1,77 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {Element as ScrollElement} from 'react-scroll'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {utils} from 'ts/utils/utils'; +import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block'; +import RaisedButton from 'material-ui/RaisedButton'; +import {HeaderSizes} from 'ts/types'; + +interface MarkdownSectionProps { + sectionName: string; + markdownContent: string; + headerSize?: HeaderSizes; + githubLink?: string; +} + +interface MarkdownSectionState { + shouldShowAnchor: boolean; +} + +export class MarkdownSection extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName; + const id = utils.getIdFromName(sectionName); + return ( +
+ +
+
+ + + +
+
+ {!_.isUndefined(this.props.githubLink) && + } + /> + } +
+
+ +
+
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx new file mode 100644 index 000000000..e69506bb8 --- /dev/null +++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx @@ -0,0 +1,163 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import MenuItem from 'material-ui/MenuItem'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {VersionDropDown} from 'ts/pages/shared/version_drop_down'; +import {ZeroExJsDocSections, Styles, MenuSubsectionsBySection, Docs} from 'ts/types'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {Link as ScrollLink} from 'react-scroll'; + +interface NestedSidebarMenuProps { + topLevelMenu: {[topLevel: string]: string[]}; + menuSubsectionsBySection: MenuSubsectionsBySection; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; + doc?: Docs; + isSectionHeaderClickable?: boolean; +} + +interface NestedSidebarMenuState {} + +const styles: Styles = { + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + lineHeight: 2, + }, +}; + +export class NestedSidebarMenu extends React.Component { + public static defaultProps: Partial = { + shouldDisplaySectionHeaders: true, + onMenuItemClick: _.noop, + }; + public render() { + const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { + const finalSectionName = sectionName.replace(/-/g, ' '); + if (this.props.shouldDisplaySectionHeaders) { + const id = utils.getIdFromName(sectionName); + return ( +
+ +
+ {finalSectionName.toUpperCase()} +
+
+ {this.renderMenuItems(menuItems)} +
+ ); + } else { + return ( +
+ {this.renderMenuItems(menuItems)} +
+ ); + } + }); + return ( +
+ {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && + !_.isUndefined(this.props.doc) && + + } + {navigation} +
+ ); + } + private renderMenuItems(menuItemNames: string[]): React.ReactNode[] { + const menuItemStyles = this.props.shouldDisplaySectionHeaders ? + styles.menuItemWithHeaders : + styles.menuItemWithoutHeaders; + const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? + styles.menuItemInnerDivWithHeaders : {}; + const menuItems = _.map(menuItemNames, menuItemName => { + const id = utils.getIdFromName(menuItemName); + return ( +
+ + + + {menuItemName} + + + + {this.renderMenuItemSubsections(menuItemName)} +
+ ); + }); + return menuItems; + } + private renderMenuItemSubsections(menuItemName: string): React.ReactNode { + if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + return null; + } + return this.renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + } + private renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + return ( +
    + {_.map(entityNames, entityName => { + const id = utils.getIdFromName(entityName); + return ( +
  • + + + {entityName} + + +
  • + ); + })} +
+ ); + } + private onMenuItemClick(menuItemName: string): void { + const id = utils.getIdFromName(menuItemName); + utils.setUrlHash(id); + this.props.onMenuItemClick(); + } +} diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx new file mode 100644 index 000000000..5937be13b --- /dev/null +++ b/packages/website/ts/pages/shared/section_header.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import {Element as ScrollElement} from 'react-scroll'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {utils} from 'ts/utils/utils'; +import {HeaderSizes} from 'ts/types'; + +interface SectionHeaderProps { + sectionName: string; + headerSize?: HeaderSizes; +} + +interface SectionHeaderState { + shouldShowAnchor: boolean; +} + +export class SectionHeader extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(sectionName); + return ( +
+ + {sectionName}} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx new file mode 100644 index 000000000..f29547c9c --- /dev/null +++ b/packages/website/ts/pages/shared/version_drop_down.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import MenuItem from 'material-ui/MenuItem'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import {constants} from 'ts/utils/constants'; +import {Docs} from 'ts/types'; + +interface VersionDropDownProps { + selectedVersion: string; + versions: string[]; + doc: Docs; +} + +interface VersionDropDownState {} + +export class VersionDropDown extends React.Component { + public render() { + return ( +
+ + {this.renderDropDownItems()} + +
+ ); + } + private renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return ( + + ); + }); + return items; + } + private updateSelectedVersion(e: any, index: number, value: string) { + const docPath = constants.docToPath[this.props.doc]; + window.location.href = `${docPath}/${value}${window.location.hash}`; + } +} diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx new file mode 100644 index 000000000..0e6fc98ab --- /dev/null +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -0,0 +1,210 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; +import { + scroller, +} from 'react-scroll'; +import {Styles, Article, ArticlesBySection} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {HeaderSizes, WebsitePaths} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; + +const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; + +export interface WikiProps { + source: string; + location: Location; +} + +interface WikiState { + articlesBySection: ArticlesBySection; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class Wiki extends React.Component { + private wikiBackoffTimeoutId: number; + constructor(props: WikiProps) { + super(props); + this.state = { + articlesBySection: undefined, + }; + } + public componentWillMount() { + this.fetchArticlesBySectionAsync(); + } + public componentWillUnmount() { + clearTimeout(this.wikiBackoffTimeoutId); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) + ? {} + : this.getMenuSubsectionsBySection(this.state.articlesBySection); + return ( +
+ + + {_.isUndefined(this.state.articlesBySection) ? +
+
+
+ +
+
Loading wiki...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x Protocol Wiki + +

+
+ {this.renderWikiArticles()} +
+
+
+
+ } +
+ ); + } + private renderWikiArticles(): React.ReactNode { + const sectionNames = _.keys(this.state.articlesBySection); + const sections = _.map(sectionNames, sectionName => this.renderSection(sectionName)); + return sections; + } + private renderSection(sectionName: string) { + const articles = this.state.articlesBySection[sectionName]; + const renderedArticles = _.map(articles, (article: Article) => { + const githubLink = `${constants.GITHUB_WIKI_URL}/edit/master/${sectionName}/${article.fileName}`; + return ( +
+ +
+ See a way to make this article better?{' '} + + Edit here → + +
+
+ ); + }); + return ( +
+ + {renderedArticles} +
+ ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = '0xProtocolWiki'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private async fetchArticlesBySectionAsync(): Promise { + const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; + const response = await fetch(endpoint); + if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { + // We need to backoff and try fetching again later + this.wikiBackoffTimeoutId = window.setTimeout(() => { + this.fetchArticlesBySectionAsync(); + }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); + return; + } + if (response.status !== 200) { + // TODO: Show the user an error message when the wiki fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); + return; + } + const articlesBySection = await response.json(); + this.setState({ + articlesBySection, + }, () => { + this.scrollToHash(); + }); + } + private getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { + const sectionNames = _.keys(articlesBySection); + const menuSubsectionsBySection: {[section: string]: string[]} = {}; + for (const sectionName of sectionNames) { + const articles = articlesBySection[sectionName]; + const articleNames = _.map(articles, article => article.title); + menuSubsectionsBySection[sectionName] = articleNames; + } + return menuSubsectionsBySection; + } +} diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts new file mode 100644 index 000000000..6badf95bd --- /dev/null +++ b/packages/website/ts/redux/dispatcher.ts @@ -0,0 +1,244 @@ +import {Dispatch} from 'redux'; +import {State} from 'ts/redux/reducer'; +import { + Direction, + Side, + AssetToken, + BlockchainErrs, + Token, + SignatureData, + Fill, + Order, + ActionTypes, + ScreenWidths, + ProviderType, + TokenStateByAddress, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +export class Dispatcher { + private dispatch: Dispatch; + constructor(dispatch: Dispatch) { + this.dispatch = dispatch; + } + // Portal + public resetState() { + this.dispatch({ + type: ActionTypes.RESET_STATE, + }); + } + public updateNodeVersion(nodeVersion: string) { + this.dispatch({ + data: nodeVersion, + type: ActionTypes.UPDATE_NODE_VERSION, + }); + } + public updateScreenWidth(screenWidth: ScreenWidths) { + this.dispatch({ + data: screenWidth, + type: ActionTypes.UPDATE_SCREEN_WIDTH, + }); + } + public swapAssetTokenSymbols() { + this.dispatch({ + type: ActionTypes.SWAP_ASSET_TOKENS, + }); + } + public updateGenerateOrderStep(direction: Direction) { + this.dispatch({ + data: direction, + type: ActionTypes.UPDATE_GENERATE_ORDER_STEP, + }); + } + public updateOrderSalt(salt: BigNumber) { + this.dispatch({ + data: salt, + type: ActionTypes.UPDATE_ORDER_SALT, + }); + } + public updateUserSuppliedOrderCache(order: Order) { + this.dispatch({ + data: order, + type: ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE, + }); + } + public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { + this.dispatch({ + data: shouldBeOpen, + type: ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN, + }); + } + public updateChosenAssetToken(side: Side, token: AssetToken) { + this.dispatch({ + data: { + side, + token, + }, + type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN, + }); + } + public updateChosenAssetTokenAddress(side: Side, address: string) { + this.dispatch({ + data: { + address, + side, + }, + type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS, + }); + } + public updateOrderTakerAddress(address: string) { + this.dispatch({ + data: address, + type: ActionTypes.UPDATE_ORDER_TAKER_ADDRESS, + }); + } + public updateUserAddress(address: string) { + this.dispatch({ + data: address, + type: ActionTypes.UPDATE_USER_ADDRESS, + }); + } + public updateOrderExpiry(unixTimestampSec: BigNumber) { + this.dispatch({ + data: unixTimestampSec, + type: ActionTypes.UPDATE_ORDER_EXPIRY, + }); + } + public encounteredBlockchainError(err: BlockchainErrs) { + this.dispatch({ + data: err, + type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, + }); + } + public updateBlockchainIsLoaded(isLoaded: boolean) { + this.dispatch({ + data: isLoaded, + type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, + }); + } + public addTokenToTokenByAddress(token: Token) { + this.dispatch({ + data: token, + type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, + }); + } + public removeTokenToTokenByAddress(token: Token) { + this.dispatch({ + data: token, + type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, + }); + } + public clearTokenByAddress() { + this.dispatch({ + type: ActionTypes.CLEAR_TOKEN_BY_ADDRESS, + }); + } + public updateTokenByAddress(tokens: Token[]) { + this.dispatch({ + data: tokens, + type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, + }); + } + public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { + this.dispatch({ + data: tokenStateByAddress, + type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, + }); + } + public removeFromTokenStateByAddress(tokenAddress: string) { + this.dispatch({ + data: tokenAddress, + type: ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS, + }); + } + public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { + this.dispatch({ + data: { + address, + allowance, + }, + type: ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS, + }); + } + public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { + this.dispatch({ + data: { + address, + balance, + }, + type: ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS, + }); + } + public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { + this.dispatch({ + data: { + address, + balanceDelta, + }, + type: ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS, + }); + } + public updateSignatureData(signatureData: SignatureData) { + this.dispatch({ + data: signatureData, + type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, + }); + } + public updateUserEtherBalance(balance: BigNumber) { + this.dispatch({ + data: balance, + type: ActionTypes.UPDATE_USER_ETHER_BALANCE, + }); + } + public updateNetworkId(networkId: number) { + this.dispatch({ + data: networkId, + type: ActionTypes.UPDATE_NETWORK_ID, + }); + } + public updateOrderFillAmount(amount: BigNumber) { + this.dispatch({ + data: amount, + type: ActionTypes.UPDATE_ORDER_FILL_AMOUNT, + }); + } + + // Docs + public updateCurrentDocsVersion(version: string) { + this.dispatch({ + data: version, + type: ActionTypes.UPDATE_LIBRARY_VERSION, + }); + } + public updateAvailableDocVersions(versions: string[]) { + this.dispatch({ + data: versions, + type: ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS, + }); + } + + // Shared + public showFlashMessage(msg: string|React.ReactNode) { + this.dispatch({ + data: msg, + type: ActionTypes.SHOW_FLASH_MESSAGE, + }); + } + public hideFlashMessage() { + this.dispatch({ + type: ActionTypes.HIDE_FLASH_MESSAGE, + }); + } + public updateProviderType(providerType: ProviderType) { + this.dispatch({ + type: ActionTypes.UPDATE_PROVIDER_TYPE, + data: providerType, + }); + } + public updateInjectedProviderName(injectedProviderName: string) { + this.dispatch({ + type: ActionTypes.UPDATE_INJECTED_PROVIDER_NAME, + data: injectedProviderName, + }); + } +} diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts new file mode 100644 index 000000000..7723597cd --- /dev/null +++ b/packages/website/ts/redux/reducer.ts @@ -0,0 +1,363 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import BigNumber from 'bignumber.js'; +import {utils} from 'ts/utils/utils'; +import { + GenerateOrderSteps, + Side, + SideToAssetToken, + Direction, + BlockchainErrs, + SignatureData, + TokenByAddress, + TokenStateByAddress, + Order, + Action, + ActionTypes, + ScreenWidths, + ProviderType, + TokenState, +} from 'ts/types'; + +// Instead of defaulting the docs version to an empty string, we pre-populate it with +// a valid version value. This does not need to be updated however, since onLoad, it +// is always replaced with a value retrieved from our S3 bucket. +const DEFAULT_DOCS_VERSION = '0.0.0'; + +export interface State { + // Portal + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + generateOrderStep: GenerateOrderSteps; + networkId: number; + orderExpiryTimestamp: BigNumber; + orderFillAmount: BigNumber; + orderTakerAddress: string; + orderSignatureData: SignatureData; + orderSalt: BigNumber; + nodeVersion: string; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. + userSuppliedOrderCache: Order; + + // Docs + docsVersion: string; + availableDocVersions: string[]; + + // Shared + flashMessage: string|React.ReactNode; + providerType: ProviderType; + injectedProviderName: string; +}; + +const INITIAL_STATE: State = { + // Portal + blockchainErr: '', + blockchainIsLoaded: false, + generateOrderStep: GenerateOrderSteps.ChooseAssets, + networkId: undefined, + orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), + orderFillAmount: undefined, + orderSignatureData: { + hash: '', + r: '', + s: '', + v: 27, + }, + orderTakerAddress: '', + orderSalt: ZeroEx.generatePseudoRandomSalt(), + nodeVersion: undefined, + screenWidth: utils.getScreenWidth(), + shouldBlockchainErrDialogBeOpen: false, + sideToAssetToken: { + [Side.deposit]: {}, + [Side.receive]: {}, + }, + tokenByAddress: {}, + tokenStateByAddress: {}, + userAddress: '', + userEtherBalance: new BigNumber(0), + userSuppliedOrderCache: undefined, + + // Docs + docsVersion: DEFAULT_DOCS_VERSION, + availableDocVersions: [DEFAULT_DOCS_VERSION], + + // Shared + flashMessage: undefined, + providerType: ProviderType.INJECTED, + injectedProviderName: '', +}; + +export function reducer(state: State = INITIAL_STATE, action: Action) { + switch (action.type) { + // Portal + case ActionTypes.RESET_STATE: + return INITIAL_STATE; + + case ActionTypes.UPDATE_ORDER_SALT: { + return _.assign({}, state, { + orderSalt: action.data, + }); + } + + case ActionTypes.UPDATE_NODE_VERSION: { + return _.assign({}, state, { + nodeVersion: action.data, + }); + } + + case ActionTypes.UPDATE_ORDER_FILL_AMOUNT: { + return _.assign({}, state, { + orderFillAmount: action.data, + }); + } + + case ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN: { + return _.assign({}, state, { + shouldBlockchainErrDialogBeOpen: action.data, + }); + } + + case ActionTypes.UPDATE_USER_ETHER_BALANCE: { + return _.assign({}, state, { + userEtherBalance: action.data, + }); + } + + case ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE: { + return _.assign({}, state, { + userSuppliedOrderCache: action.data, + }); + } + + case ActionTypes.CLEAR_TOKEN_BY_ADDRESS: { + return _.assign({}, state, { + tokenByAddress: {}, + }); + } + + case ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS: { + const newTokenByAddress = state.tokenByAddress; + newTokenByAddress[action.data.address] = action.data; + return _.assign({}, state, { + tokenByAddress: newTokenByAddress, + }); + } + + case ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS: { + const newTokenByAddress = state.tokenByAddress; + delete newTokenByAddress[action.data.address]; + return _.assign({}, state, { + tokenByAddress: newTokenByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_BY_ADDRESS: { + const tokenByAddress = state.tokenByAddress; + const tokens = action.data; + _.each(tokens, token => { + const updatedToken = _.assign({}, tokenByAddress[token.address], token); + tokenByAddress[token.address] = updatedToken; + }); + return _.assign({}, state, { + tokenByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const updatedTokenStateByAddress = action.data; + _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { + const updatedTokenState = _.assign({}, tokenStateByAddress[address], tokenState); + tokenStateByAddress[address] = updatedTokenState; + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const tokenAddress = action.data; + delete tokenStateByAddress[tokenAddress]; + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const allowance = action.data.allowance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + allowance, + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const balance = action.data.balance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + balance, + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const balanceDelta = action.data.balanceDelta; + const tokenAddress = action.data.address; + const currBalance = tokenStateByAddress[tokenAddress].balance; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + balance: currBalance.plus(balanceDelta), + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.UPDATE_ORDER_SIGNATURE_DATA: { + return _.assign({}, state, { + orderSignatureData: action.data, + }); + } + + case ActionTypes.UPDATE_SCREEN_WIDTH: { + return _.assign({}, state, { + screenWidth: action.data, + }); + } + + case ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED: { + return _.assign({}, state, { + blockchainIsLoaded: action.data, + }); + } + + case ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED: { + return _.assign({}, state, { + blockchainErr: action.data, + }); + } + + case ActionTypes.UPDATE_NETWORK_ID: { + return _.assign({}, state, { + networkId: action.data, + }); + } + + case ActionTypes.UPDATE_GENERATE_ORDER_STEP: { + const direction = action.data; + let nextGenerateOrderStep = state.generateOrderStep; + if (direction === Direction.forward) { + nextGenerateOrderStep += 1; + } else if (state.generateOrderStep !== 0) { + nextGenerateOrderStep -= 1; + } + return _.assign({}, state, { + generateOrderStep: nextGenerateOrderStep, + }); + } + + case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN: { + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [action.data.side]: action.data.token, + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS: { + const newAssetToken = state.sideToAssetToken[action.data.side]; + newAssetToken.address = action.data.address; + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [action.data.side]: newAssetToken, + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.SWAP_ASSET_TOKENS: { + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [Side.deposit]: state.sideToAssetToken[Side.receive], + [Side.receive]: state.sideToAssetToken[Side.deposit], + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.UPDATE_ORDER_EXPIRY: { + return _.assign({}, state, { + orderExpiryTimestamp: action.data, + }); + } + + case ActionTypes.UPDATE_ORDER_TAKER_ADDRESS: { + return _.assign({}, state, { + orderTakerAddress: action.data, + }); + } + + case ActionTypes.UPDATE_USER_ADDRESS: { + return _.assign({}, state, { + userAddress: action.data, + }); + } + + // Docs + case ActionTypes.UPDATE_LIBRARY_VERSION: { + return _.assign({}, state, { + docsVersion: action.data, + }); + } + case ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS: { + return _.assign({}, state, { + availableDocVersions: action.data, + }); + } + + // Shared + case ActionTypes.SHOW_FLASH_MESSAGE: { + return _.assign({}, state, { + flashMessage: action.data, + }); + } + + case ActionTypes.HIDE_FLASH_MESSAGE: { + return _.assign({}, state, { + flashMessage: undefined, + }); + } + + case ActionTypes.UPDATE_PROVIDER_TYPE: { + return _.assign({}, state, { + providerType: action.data, + }); + } + + case ActionTypes.UPDATE_INJECTED_PROVIDER_NAME: { + return _.assign({}, state, { + injectedProviderName: action.data, + }); + } + + default: + return state; + } +} diff --git a/packages/website/ts/schemas/order_schema.ts b/packages/website/ts/schemas/order_schema.ts new file mode 100644 index 000000000..61b93d273 --- /dev/null +++ b/packages/website/ts/schemas/order_schema.ts @@ -0,0 +1,24 @@ +export const orderSchema = { + id: '/Order', + properties: { + maker: {$ref: '/OrderTaker'}, + taker: {$ref: '/OrderTaker'}, + salt: {type: 'string'}, + signature: {$ref: '/SignatureData'}, + expiration: {type: 'string'}, + feeRecipient: {type: 'string'}, + exchangeContract: {type: 'string'}, + networkId: {type: 'number'}, + }, + required: [ + 'maker', + 'taker', + 'salt', + 'signature', + 'expiration', + 'feeRecipient', + 'exchangeContract', + 'networkId', + ], + type: 'object', +}; diff --git a/packages/website/ts/schemas/order_taker_schema.ts b/packages/website/ts/schemas/order_taker_schema.ts new file mode 100644 index 000000000..6b484a60d --- /dev/null +++ b/packages/website/ts/schemas/order_taker_schema.ts @@ -0,0 +1,11 @@ +export const orderTakerSchema = { + id: '/OrderTaker', + properties: { + address: {type: 'string'}, + token: {$ref: '/Token'}, + amount: {type: 'string'}, + feeAmount: {type: 'string'}, + }, + required: ['address', 'token', 'amount', 'feeAmount'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/signature_data_schema.ts b/packages/website/ts/schemas/signature_data_schema.ts new file mode 100644 index 000000000..d208cc438 --- /dev/null +++ b/packages/website/ts/schemas/signature_data_schema.ts @@ -0,0 +1,11 @@ +export const signatureDataSchema = { + id: '/SignatureData', + properties: { + hash: {type: 'string'}, + r: {type: 'string'}, + s: {type: 'string'}, + v: {type: 'number'}, + }, + required: ['hash', 'r', 's', 'v'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/token_schema.ts b/packages/website/ts/schemas/token_schema.ts new file mode 100644 index 000000000..c15f57429 --- /dev/null +++ b/packages/website/ts/schemas/token_schema.ts @@ -0,0 +1,11 @@ +export const tokenSchema = { + id: '/Token', + properties: { + name: {type: 'string'}, + symbol: {type: 'string'}, + decimals: {type: 'number'}, + address: {type: 'string'}, + }, + required: ['name', 'symbol', 'decimals', 'address'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts new file mode 100644 index 000000000..bf6ba4044 --- /dev/null +++ b/packages/website/ts/schemas/validator.ts @@ -0,0 +1,19 @@ +import {Validator, Schema as JSONSchema} from 'jsonschema'; +import {signatureDataSchema} from 'ts/schemas/signature_data_schema'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {tokenSchema} from 'ts/schemas/token_schema'; +import {orderTakerSchema} from 'ts/schemas/order_taker_schema'; + +export class SchemaValidator { + private validator: Validator; + constructor() { + this.validator = new Validator(); + this.validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); + this.validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); + this.validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); + this.validator.addSchema(orderSchema as JSONSchema, orderSchema.id); + } + public validate(instance: object, schema: Schema) { + return this.validator.validate(instance, schema); + } +} diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/website/ts/subproviders/injected_web3_subprovider.ts new file mode 100644 index 000000000..b9e5af3ef --- /dev/null +++ b/packages/website/ts/subproviders/injected_web3_subprovider.ts @@ -0,0 +1,44 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import {constants} from 'ts/utils/constants'; + +/* + * This class implements the web3-provider-engine subprovider interface and forwards + * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected + * web3 instance in their browser. + * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js + */ +export class InjectedWeb3SubProvider { + private injectedWeb3: Web3; + constructor(injectedWeb3: Web3) { + this.injectedWeb3 = injectedWeb3; + } + public handleRequest(payload: any, next: () => void, end: (err: Error, result: any) => void) { + switch (payload.method) { + case 'web3_clientVersion': + this.injectedWeb3.version.getNode(end); + return; + case 'eth_accounts': + this.injectedWeb3.eth.getAccounts(end); + return; + + case 'eth_sendTransaction': + const [txParams] = payload.params; + this.injectedWeb3.eth.sendTransaction(txParams, end); + return; + + case 'eth_sign': + const [address, message] = payload.params; + this.injectedWeb3.eth.sign(address, message, end); + return; + + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + public setEngine(engine: any) { + // noop + } +} diff --git a/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts new file mode 100644 index 000000000..df0c5a4db --- /dev/null +++ b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts @@ -0,0 +1,172 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import * as EthereumTx from 'ethereumjs-tx'; +import ethUtil = require('ethereumjs-util'); +import * as ledger from 'ledgerco'; +import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet'); +import {constants} from 'ts/utils/constants'; +import {LedgerEthConnection, SignPersonalMessageParams, TxParams} from 'ts/types'; + +const NUM_ADDRESSES_TO_FETCH = 10; +const ASK_FOR_ON_DEVICE_CONFIRMATION = false; +const SHOULD_GET_CHAIN_CODE = false; + +export class LedgerWallet { + public isU2FSupported: boolean; + public getAccounts: (callback: (err: Error, accounts: string[]) => void) => void; + public signMessage: (msgParams: SignPersonalMessageParams, + callback: (err: Error, result?: string) => void) => void; + public signTransaction: (txParams: TxParams, + callback: (err: Error, result?: string) => void) => void; + private getNetworkId: () => number; + private path: string; + private pathIndex: number; + private ledgerEthConnection: LedgerEthConnection; + private accounts: string[]; + constructor(getNetworkIdFn: () => number) { + this.path = constants.DEFAULT_DERIVATION_PATH; + this.pathIndex = 0; + this.isU2FSupported = false; + this.getNetworkId = getNetworkIdFn; + this.getAccounts = this.getAccountsAsync.bind(this); + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public getPath(): string { + return this.path; + } + public setPath(derivationPath: string) { + this.path = derivationPath; + // HACK: Must re-assign getAccounts, signMessage and signTransaction since they were + // previously bound to old values of this.path + this.getAccounts = this.getAccountsAsync.bind(this); + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public setPathIndex(pathIndex: number) { + this.pathIndex = pathIndex; + // HACK: Must re-assign signMessage & signTransaction since they it was previously bound to + // old values of this.path + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public async getAccountsAsync(callback: (err: Error, accounts: string[]) => void) { + if (!_.isUndefined(this.ledgerEthConnection)) { + callback(null, []); + return; + } + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + const accounts = []; + for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { + try { + const derivationPath = `${this.path}/${i}`; + const result = await this.ledgerEthConnection.getAddress_async( + derivationPath, ASK_FOR_ON_DEVICE_CONFIRMATION, SHOULD_GET_CHAIN_CODE, + ); + accounts.push(result.address.toLowerCase()); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + return; + } + } + + await this.closeLedgerConnectionAsync(); + callback(null, accounts); + } + public async signTransactionAsync(txParams: TxParams, callback: (err: Error, result?: string) => void) { + const tx = new EthereumTx(txParams); + + const networkId = this.getNetworkId(); + const chainId = networkId; // Same thing + + // Set the EIP155 bits + tx.raw[6] = Buffer.from([chainId]); // v + tx.raw[7] = Buffer.from([]); // r + tx.raw[8] = Buffer.from([]); // s + + const txHex = tx.serialize().toString('hex'); + + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + try { + const derivationPath = this.getDerivationPath(); + const result = await this.ledgerEthConnection.signTransaction_async(derivationPath, txHex); + + // Store signature in transaction + tx.v = new Buffer(result.v, 'hex'); + tx.r = new Buffer(result.r, 'hex'); + tx.s = new Buffer(result.s, 'hex'); + + // EIP155: v should be chain_id * 2 + {35, 36} + const signedChainId = Math.floor((tx.v[0] - 35) / 2); + if (signedChainId !== chainId) { + const err = new Error('TOO_OLD_LEDGER_FIRMWARE'); + callback(err, null); + return; + } + + const signedTxHex = `0x${tx.serialize().toString('hex')}`; + await this.closeLedgerConnectionAsync(); + callback(null, signedTxHex); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + } + } + public async signPersonalMessageAsync(msgParams: SignPersonalMessageParams, + callback: (err: Error, result?: string) => void) { + if (!_.isUndefined(this.ledgerEthConnection)) { + callback(new Error('Another request is in progress.')); + return; + } + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + try { + const derivationPath = this.getDerivationPath(); + const result = await this.ledgerEthConnection.signPersonalMessage_async( + derivationPath, ethUtil.stripHexPrefix(msgParams.data), + ); + const v = _.parseInt(result.v) - 27; + let vHex = v.toString(16); + if (vHex.length < 2) { + vHex = `0${v}`; + } + const signature = `0x${result.r}${result.s}${vHex}`; + await this.closeLedgerConnectionAsync(); + callback(null, signature); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + } + } + private async createLedgerConnectionAsync() { + if (!_.isUndefined(this.ledgerEthConnection)) { + throw new Error('Multiple open connections to the Ledger disallowed.'); + } + const ledgerConnection = await ledger.comm_u2f.create_async(); + const ledgerEthConnection = new ledger.eth(ledgerConnection); + return ledgerEthConnection; + } + private async closeLedgerConnectionAsync() { + if (_.isUndefined(this.ledgerEthConnection)) { + return; + } + await this.ledgerEthConnection.comm.close_async(); + this.ledgerEthConnection = undefined; + } + private getDerivationPath() { + const derivationPath = `${this.path}/${this.pathIndex}`; + return derivationPath; + } +} + +export const ledgerWalletSubproviderFactory = (getNetworkIdFn: () => number): LedgerWallet => { + const ledgerWallet = new LedgerWallet(getNetworkIdFn); + const ledgerWalletSubprovider = new HookedWalletSubprovider(ledgerWallet) as LedgerWallet; + ledgerWalletSubprovider.getPath = ledgerWallet.getPath.bind(ledgerWallet); + ledgerWalletSubprovider.setPath = ledgerWallet.setPath.bind(ledgerWallet); + ledgerWalletSubprovider.setPathIndex = ledgerWallet.setPathIndex.bind(ledgerWallet); + return ledgerWalletSubprovider; +}; diff --git a/packages/website/ts/subproviders/redundant_rpc_subprovider.ts b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts new file mode 100644 index 000000000..a6c53ebd1 --- /dev/null +++ b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts @@ -0,0 +1,41 @@ +import * as _ from 'lodash'; +import {JSONRPCPayload} from 'ts/types'; +import promisify = require('es6-promisify'); +import Subprovider = require('web3-provider-engine/subproviders/subprovider'); +import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); + +export class RedundantRPCSubprovider extends Subprovider { + private rpcs: RpcSubprovider[]; + constructor(endpoints: string[]) { + super(); + this.rpcs = _.map(endpoints, endpoint => { + return new RpcSubprovider({ + rpcUrl: endpoint, + }); + }); + } + public async handleRequest(payload: JSONRPCPayload, next: () => void, + end: (err?: Error, data?: any) => void): Promise { + const rpcsCopy = this.rpcs.slice(); + try { + const data = await this.firstSuccessAsync(rpcsCopy, payload, next); + end(null, data); + } catch (err) { + end(err); + } + + } + private async firstSuccessAsync(rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void): Promise { + let lastErr; + for (const rpc of rpcs) { + try { + const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); + return data; + } catch (err) { + lastErr = err; + continue; + } + } + throw Error(lastErr); + } +} diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts new file mode 100644 index 000000000..2d0103499 --- /dev/null +++ b/packages/website/ts/types.ts @@ -0,0 +1,692 @@ +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; + +// Utility function to create a K:V from a list of strings +// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html +function strEnum(values: string[]): {[key: string]: string} { + return _.reduce(values, (result, key) => { + result[key] = key; + return result; + }, Object.create(null)); +} + +export enum GenerateOrderSteps { + ChooseAssets, + GrantAllowance, + RemainingConfigs, + SignTransaction, + CopyAndShare, +}; + +export const Side = strEnum([ + 'receive', + 'deposit', +]); +export type Side = keyof typeof Side; + +export const BlockchainErrs = strEnum([ + 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', + 'DISCONNECTED_FROM_ETHEREUM_NODE', + 'UNHANDLED_ERROR', +]); +export type BlockchainErrs = keyof typeof BlockchainErrs; + +export const Direction = strEnum([ + 'forward', + 'backward', +]); +export type Direction = keyof typeof Direction; + +export interface Token { + iconUrl?: string; + name: string; + address: string; + symbol: string; + decimals: number; + isTracked: boolean; + isRegistered: boolean; +}; + +export interface TokenByAddress { + [address: string]: Token; +}; + +export interface TokenState { + allowance: BigNumber; + balance: BigNumber; +} + +export interface TokenStateByAddress { + [address: string]: TokenState; +}; + +export interface AssetToken { + address?: string; + amount?: BigNumber; +} + +export interface SideToAssetToken { + [side: string]: AssetToken; +}; + +export interface SignatureData { + hash: string; + r: string; + s: string; + v: number; +}; + +export interface HashData { + depositAmount: BigNumber; + depositTokenContractAddr: string; + feeRecipientAddress: string; + makerFee: BigNumber; + orderExpiryTimestamp: BigNumber; + orderMakerAddress: string; + orderTakerAddress: string; + receiveAmount: BigNumber; + receiveTokenContractAddr: string; + takerFee: BigNumber; + orderSalt: BigNumber; +} + +export interface OrderToken { + name: string; + symbol: string; + decimals: number; + address: string; +} + +export interface OrderParty { + address: string; + token: OrderToken; + amount: string; + feeAmount: string; +} + +export interface Order { + maker: OrderParty; + taker: OrderParty; + expiration: string; + feeRecipient: string; + salt: string; + signature: SignatureData; + exchangeContract: string; + networkId: number; +} + +export interface Fill { + logIndex: number; + maker: string; + taker: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + orderHash: string; + transactionHash: string; + blockTimestamp: number; +} + +export enum BalanceErrs { + incorrectNetworkForFaucet, + faucetRequestFailed, + faucetQueueIsFull, + mintingFailed, + wethConversionFailed, + sendFailed, + allowanceSettingFailed, +}; + +export const ActionTypes = strEnum([ + // Portal + 'UPDATE_SCREEN_WIDTH', + 'UPDATE_NODE_VERSION', + 'RESET_STATE', + 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', + 'BLOCKCHAIN_ERR_ENCOUNTERED', + 'CLEAR_TOKEN_BY_ADDRESS', + 'UPDATE_BLOCKCHAIN_IS_LOADED', + 'UPDATE_NETWORK_ID', + 'UPDATE_GENERATE_ORDER_STEP', + 'UPDATE_CHOSEN_ASSET_TOKEN', + 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', + 'UPDATE_ORDER_TAKER_ADDRESS', + 'UPDATE_ORDER_SALT', + 'UPDATE_ORDER_SIGNATURE_DATA', + 'UPDATE_TOKEN_BY_ADDRESS', + 'REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS', + 'UPDATE_TOKEN_STATE_BY_ADDRESS', + 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', + 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', + 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', + 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', + 'UPDATE_ORDER_EXPIRY', + 'SWAP_ASSET_TOKENS', + 'UPDATE_USER_ADDRESS', + 'UPDATE_USER_ETHER_BALANCE', + 'UPDATE_USER_SUPPLIED_ORDER_CACHE', + 'UPDATE_ORDER_FILL_AMOUNT', + 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', + + // Docs + 'UPDATE_LIBRARY_VERSION', + 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', + + // Shared + 'SHOW_FLASH_MESSAGE', + 'HIDE_FLASH_MESSAGE', + 'UPDATE_PROVIDER_TYPE', + 'UPDATE_INJECTED_PROVIDER_NAME', +]); +export type ActionTypes = keyof typeof ActionTypes; + +export interface Action { + type: ActionTypes; + data?: any; +} + +export interface TrackedTokensByNetworkId { + [networkId: number]: Token; +} + +export interface Styles { + [name: string]: React.CSSProperties; +} + +export interface ProfileInfo { + name: string; + title?: string; + description: string; + image: string; + linkedIn?: string; + github?: string; + angellist?: string; + medium?: string; + twitter?: string; +} + +export interface Partner { + name: string; + logo: string; + url: string; +} + +export interface Statistic { + title: string; + figure: string; +} + +export interface StatisticByKey { + [key: string]: Statistic; +} + +export interface ERC20MarketInfo { + etherMarketCapUsd: number; + numLiquidERC20Tokens: number; + marketCapERC20TokensUsd: number; +} + +export enum ExchangeContractErrs { + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', +} + +export interface ContractResponse { + logs: ContractEvent[]; +} + +export interface ContractEvent { + event: string; + args: any; +} + +export type InputErrMsg = React.ReactNode | string | undefined; +export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; +export const ScreenWidths = strEnum([ + 'SM', + 'MD', + 'LG', +]); +export type ScreenWidths = keyof typeof ScreenWidths; + +export enum AlertTypes { + ERROR, + SUCCESS, +} + +export const EtherscanLinkSuffixes = strEnum([ + 'address', + 'tx', +]); +export type EtherscanLinkSuffixes = keyof typeof EtherscanLinkSuffixes; + +export const BlockchainCallErrs = strEnum([ + 'CONTRACT_DOES_NOT_EXIST', + 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + 'UNHANDLED_ERROR', + 'TOKEN_ADDRESS_IS_INVALID', + 'INVALID_SIGNATURE', +]); +export type BlockchainCallErrs = keyof typeof BlockchainCallErrs; + +export const KindString = strEnum([ + 'Constructor', + 'Property', + 'Method', + 'Interface', + 'Type alias', + 'Variable', + 'Function', + 'Enumeration', +]); +export type KindString = keyof typeof KindString; + +export interface EnumValue { + name: string; + defaultValue?: string; +} + +export enum Environments { + DEVELOPMENT, + PRODUCTION, +} + +export type ContractInstance = any; // TODO: add type definition for Contract + +export interface TypeDocType { + type: TypeDocTypes; + value: string; + name: string; + types: TypeDocType[]; + typeArguments?: TypeDocType[]; + declaration: TypeDocNode; + elementType?: TypeDocType; +} + +export interface TypeDocFlags { + isStatic?: boolean; + isOptional?: boolean; + isPublic?: boolean; +} + +export interface TypeDocGroup { + title: string; + children: number[]; +} + +export interface TypeDocNode { + id?: number; + name?: string; + kind?: string; + defaultValue?: string; + kindString?: string; + type?: TypeDocType; + fileName?: string; + line?: number; + comment?: TypeDocNode; + text?: string; + shortText?: string; + returns?: string; + declaration: TypeDocNode; + flags?: TypeDocFlags; + indexSignature?: TypeDocNode[]; + signatures?: TypeDocNode[]; + parameters?: TypeDocNode[]; + typeParameter?: TypeDocNode[]; + sources?: TypeDocNode[]; + children?: TypeDocNode[]; + groups?: TypeDocGroup[]; +} + +export enum TypeDocTypes { + Intrinsic = 'intrinsic', + Reference = 'reference', + Array = 'array', + StringLiteral = 'stringLiteral', + Reflection = 'reflection', + Union = 'union', + TypeParameter = 'typeParameter', + Unknown = 'unknown', +} + +export interface DocAgnosticFormat { + [sectionName: string]: DocSection; +} + +export interface DocSection { + comment: string; + constructors: Array; + methods: Array; + properties: Property[]; + types: CustomType[]; + events?: Event[]; +} + +export interface Event { + name: string; + eventArgs: EventArg[]; +} + +export interface EventArg { + isIndexed: boolean; + name: string; + type: Type; +} + +export interface Property { + name: string; + type: Type; + source?: Source; + comment?: string; +} + +export interface BaseMethod { + isConstructor: boolean; + name: string; + returnComment?: string|undefined; + callPath: string; + parameters: Parameter[]; + returnType: Type; + comment?: string; +} + +export interface TypescriptMethod extends BaseMethod { + source?: Source; + isStatic?: boolean; + typeParameter?: TypeParameter; +} + +export interface SolidityMethod extends BaseMethod { + isConstant?: boolean; + isPayable?: boolean; +} + +export interface Source { + fileName: string; + line: number; +} + +export interface Parameter { + name: string; + comment: string; + isOptional: boolean; + type: Type; +} + +export interface TypeParameter { + name: string; + type: Type; +} + +export interface Type { + name: string; + typeDocType: TypeDocTypes; + value?: string; + typeArguments?: Type[]; + elementType?: ElementType; + types?: Type[]; + method?: TypescriptMethod; +} + +export interface ElementType { + name: string; + typeDocType: TypeDocTypes; +} + +export interface IndexSignature { + keyName: string; + keyType: Type; + valueName: string; +} + +export interface CustomType { + name: string; + kindString: string; + type?: Type; + method?: TypescriptMethod; + indexSignature?: IndexSignature; + defaultValue?: string; + comment?: string; + children?: CustomTypeChild[]; +} + +export interface CustomTypeChild { + name: string; + type?: Type; + defaultValue?: string; +} + +export const ZeroExJsDocSections = strEnum([ + 'introduction', + 'installation', + 'testrpc', + 'async', + 'errors', + 'versioning', + 'zeroEx', + 'exchange', + 'token', + 'tokenRegistry', + 'etherToken', + 'proxy', + 'types', +]); +export type ZeroExJsDocSections = keyof typeof ZeroExJsDocSections; + +export const SmartContractsDocSections = strEnum([ + 'Introduction', + 'Exchange', + 'TokenTransferProxy', + 'TokenRegistry', + 'ZRXToken', + 'EtherToken', +]); +export type SmartContractsDocSections = keyof typeof SmartContractsDocSections; + +export interface FAQQuestion { + prompt: string; + answer: React.ReactNode; +} +export interface FAQSection { + name: string; + questions: FAQQuestion[]; +} + +export interface S3FileObject { + Key: { + _text: string; + }; +} + +export interface MenuSubsectionsBySection { + [section: string]: string[]; +} + +export const ProviderType = strEnum([ + 'INJECTED', + 'LEDGER', +]); +export type ProviderType = keyof typeof ProviderType; + +export interface Fact { + title: string; + explanation: string; + image: string; +} + +interface LedgerGetAddressResult { + address: string; +} +interface LedgerSignResult { + v: string; + r: string; + s: string; +} +interface LedgerCommunication { + close_async: () => void; +} +export interface LedgerEthConnection { + getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean, + shouldGetChainCode: boolean) => Promise; + signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise; + signTransaction_async: (derivationPath: string, txHex: string) => Promise; + comm: LedgerCommunication; +} +export interface SignPersonalMessageParams { + data: string; +} + +export interface LedgerWalletSubprovider { + getPath: () => string; + setPath: (path: string) => void; + setPathIndex: (pathIndex: number) => void; +} + +export interface TxParams { + nonce: string; + gasPrice?: number; + gasLimit: string; + to: string; + value?: string; + data?: string; + chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 +} + +export interface PublicNodeUrlsByNetworkId { + [networkId: number]: string[]; +}; + +export interface JSONRPCPayload { + params: any[]; + method: string; +} + +export interface BlogPost { + image: string; + date: string; + title: string; + description: string; + url: string; +} + +export interface TypeDefinitionByName { + [typeName: string]: CustomType; +} + +export interface Article { + section: string; + title: string; + content: string; + fileName: string; +} + +export interface ArticlesBySection { + [section: string]: Article[]; +} + +export interface DialogConfigs { + title: string; + isModal: boolean; + actions: any[]; +} + +export enum TokenVisibility { + ALL = 'ALL', + UNTRACKED = 'UNTRACKED', + TRACKED = 'TRACKED', +} + +export enum HeaderSizes { + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', +} + +export interface DoxityDocObj { + [contractName: string]: DoxityContractObj; +} + +export interface DoxityContractObj { + title: string; + fileName: string; + name: string; + abiDocs: DoxityAbiDoc[]; +} + +export interface DoxityAbiDoc { + constant: boolean; + inputs: DoxityInput[]; + name: string; + outputs: DoxityOutput[]; + payable: boolean; + type: string; + details?: string; + return?: string; +} + +export interface DoxityOutput { + name: string; + type: string; +} + +export interface DoxityInput { + name: string; + type: string; + description: string; + indexed?: boolean; +} + +export interface VersionToFileName { + [version: string]: string; +} + +export enum Docs { + ZeroExJs, + SmartContracts, +} + +export interface ContractAddresses { + [version: string]: { + [network: string]: AddressByContractName; + }; +} + +export interface AddressByContractName { + [contractName: string]: string; +} + +export enum Networks { + mainnet = 'Mainnet', + kovan = 'Kovan', + ropsten = 'Ropsten', + rinkeby = 'Rinkeby', +} + +export enum AbiTypes { + Constructor = 'constructor', + Function = 'function', + Event = 'event', +} + +export enum WebsitePaths { + Portal = '/portal', + Wiki = '/wiki', + ZeroExJs = '/docs/0xjs', + Home = '/', + FAQ = '/faq', + About = '/about', + Whitepaper = '/pdfs/0x_white_paper.pdf', + SmartContracts = '/docs/contracts', +} diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts new file mode 100644 index 000000000..49ac4b5e0 --- /dev/null +++ b/packages/website/ts/utils/configs.ts @@ -0,0 +1,18 @@ +import * as _ from 'lodash'; +import {Environments} from 'ts/types'; + +const BASE_URL = window.location.origin; +const isDevelopment = _.includes(BASE_URL, 'https://0xproject.dev:3572') || + _.includes(BASE_URL, 'https://localhost:3572') || + _.includes(BASE_URL, 'https://127.0.0.1'); + +export const configs = { + BASE_URL, + ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, + BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', + symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], + // WARNING: ZRX & WETH MUST always be default trackedTokens + defaultTrackedTokenSymbols: ['WETH', 'ZRX'], + lastLocalStorageFillClearanceDate: '2017-09-09', + isMainnetEnabled: true, +}; diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts new file mode 100644 index 000000000..42b80795e --- /dev/null +++ b/packages/website/ts/utils/constants.ts @@ -0,0 +1,270 @@ +import { + ExchangeContractErrs, + PublicNodeUrlsByNetworkId, + ZeroExJsDocSections, + SmartContractsDocSections, + Docs, + ContractAddresses, + Networks, + WebsitePaths, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; + +export const constants = { + ANGELLIST_URL: 'https://angel.co/0xproject/jobs', + STAGING_DOMAIN: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', + PRODUCTION_DOMAIN: '0xproject.com', + DEVELOPMENT_DOMAIN: '0xproject.dev:3572', + BIGNUMBERJS_GITHUB_URL: 'http://mikemcl.github.io/bignumber.js', + BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', + BITLY_ENDPOINT: 'https://api-ssl.bitly.com', + BLOG_URL: 'https://blog.0xproject.com/latest', + CUSTOM_BLUE: '#60a4f4', + DEFAULT_DERIVATION_PATH: `44'/60'/0'`, + ETHER_FAUCET_ENDPOINT: 'https://faucet.0xproject.com', + FEE_RECIPIENT_ADDRESS: '0x0000000000000000000000000000000000000000', + FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', + GITHUB_URL: 'https://github.com/0xProject', + GITHUB_0X_JS_URL: 'https://github.com/0xProject/0x.js', + GITHUB_CONTRACTS_URL: 'https://github.com/0xProject/contracts', + GITHUB_WIKI_URL: 'https://github.com/0xProject/wiki', + HTTP_NO_CONTENT_STATUS_CODE: 204, + ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY: 'didAcceptPortalDisclaimer', + LINKEDIN_0X_URL: 'https://www.linkedin.com/company/0x', + LEDGER_PROVIDER_NAME: 'Ledger', + METAMASK_PROVIDER_NAME: 'Metamask', + GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { + 1: 4145578, + 42: 3117574, + 50: 0, + } as {[networkId: number]: number}, + PUBLIC_PROVIDER_NAME: '0x Public', + // The order matters. We first try first node and only then fall back to others. + PUBLIC_NODE_URLS_BY_NETWORK_ID: { + [1]: [ + `https://mainnet.infura.io/${INFURA_API_KEY}`, + ], + [42]: [ + `https://kovan.infura.io/${INFURA_API_KEY}`, + ], + } as PublicNodeUrlsByNetworkId, + PARITY_SIGNER_PROVIDER_NAME: 'Parity Signer', + GENERIC_PROVIDER_NAME: 'Injected Web3', + MAKER_FEE: new BigNumber(0), + MAINNET_NAME: 'Main network', + MAINNET_NETWORK_ID: 1, + METAMASK_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', + // tslint:disable-next-line:max-line-length + PARITY_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', + MIST_DOWNLOAD_URL: 'https://github.com/ethereum/mist/releases', + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + HOME_SCROLL_DURATION_MS: 500, + REDDIT_URL: 'https://reddit.com/r/0xproject', + STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', + SUCCESS_STATUS: 200, + S3_0XJS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/0xjs-docs-jsons', + S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/smart-contracts-docs-json', + UNAVAILABLE_STATUS: 503, + TAKER_FEE: new BigNumber(0), + TESTNET_NAME: 'Kovan', + TESTNET_NETWORK_ID: 42, + TESTRPC_NETWORK_ID: 50, + TWITTER_URL: 'https://twitter.com/0xproject', + ETH_DECIMAL_PLACES: 18, + MINT_AMOUNT: new BigNumber('100000000000000000000'), + WEB3_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + WEB3_PROVIDER_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API#example-7', + ZEROEX_CHAT_URL: 'https://chat.0xproject.com', + // Projects + ETHFINEX_URL: 'https://www.bitfinex.com/ethfinex', + RADAR_RELAY_URL: 'https://radarrelay.com', + PARADEX_URL: 'https://paradex.io', + DYDX_URL: 'https://dydx.exchange', + MELONPORT_URL: 'https://melonport.com', + DISTRICT_0X_URL: 'https://district0x.io', + DHARMA_URL: 'https://dharma.io', + LENDROID_URL: 'https://lendroid.com', + MAKER_URL: 'https://makerdao.com', + ARAGON_URL: 'https://aragon.one', + BLOCKNET_URL: 'https://blocknet.co', + OCEAN_URL: 'http://the0cean.com', + STATUS_URL: 'https://status.im', + AUGUR_URL: 'https://augur.net', + AUCTUS_URL: 'https://auctus.org', + OPEN_ANX_URL: 'https://www.openanx.org', + + iconUrlBySymbol: { + 'REP': '/images/token_icons/augur.png', + 'DGD': '/images/token_icons/digixdao.png', + 'WETH': '/images/token_icons/ether_erc20.png', + 'MLN': '/images/token_icons/melon.png', + 'GNT': '/images/token_icons/golem.png', + 'MKR': '/images/token_icons/makerdao.png', + 'ZRX': '/images/token_icons/zero_ex.png', + 'ANT': '/images/token_icons/aragon.png', + 'BNT': '/images/token_icons/bancor.png', + 'BAT': '/images/token_icons/basicattentiontoken.png', + 'CVC': '/images/token_icons/civic.png', + 'EOS': '/images/token_icons/eos.png', + 'FUN': '/images/token_icons/funfair.png', + 'GNO': '/images/token_icons/gnosis.png', + 'ICN': '/images/token_icons/iconomi.png', + 'OMG': '/images/token_icons/omisego.png', + 'SNT': '/images/token_icons/status.png', + 'STORJ': '/images/token_icons/storjcoinx.png', + 'PAY': '/images/token_icons/tenx.png', + 'QTUM': '/images/token_icons/qtum.png', + 'DNT': '/images/token_icons/district0x.png', + 'SNGLS': '/images/token_icons/singularity.png', + 'EDG': '/images/token_icons/edgeless.png', + '1ST': '/images/token_icons/firstblood.jpg', + 'WINGS': '/images/token_icons/wings.png', + 'BQX': '/images/token_icons/bitquence.png', + 'LUN': '/images/token_icons/lunyr.png', + 'RLC': '/images/token_icons/iexec.png', + 'MCO': '/images/token_icons/monaco.png', + 'ADT': '/images/token_icons/adtoken.png', + 'CFI': '/images/token_icons/cofound-it.png', + 'ROL': '/images/token_icons/etheroll.png', + 'WGNT': '/images/token_icons/golem.png', + 'MTL': '/images/token_icons/metal.png', + 'NMR': '/images/token_icons/numeraire.png', + 'SAN': '/images/token_icons/santiment.png', + 'TAAS': '/images/token_icons/taas.png', + 'TKN': '/images/token_icons/tokencard.png', + 'TRST': '/images/token_icons/trust.png', + } as {[symbol: string]: string}, + networkNameById: { + 1: Networks.mainnet, + 3: Networks.ropsten, + 4: Networks.rinkeby, + 42: Networks.kovan, + } as {[symbol: number]: string}, + networkIdByName: { + [Networks.mainnet]: 1, + [Networks.ropsten]: 3, + [Networks.rinkeby]: 4, + [Networks.kovan]: 42, + } as {[networkName: string]: number}, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + public0xjsTypes: [ + 'Order', + 'SignedOrder', + 'ECSignature', + 'ZeroExError', + 'EventCallback', + 'EventCallbackAsync', + 'EventCallbackSync', + 'ExchangeContractErrs', + 'ContractEvent', + 'Token', + 'ExchangeEvents', + 'IndexedFilterValues', + 'SubscriptionOpts', + 'BlockParam', + 'OrderFillOrKillRequest', + 'OrderCancellationRequest', + 'OrderFillRequest', + 'ContractEventEmitter', + 'Web3Provider', + 'ContractEventArgs', + 'LogCancelArgs', + 'LogFillArgs', + 'LogErrorContractEventArgs', + 'LogFillContractEventArgs', + 'LogCancelContractEventArgs', + 'TokenEvents', + 'ExchangeContractEventArgs', + 'TransferContractEventArgs', + 'ApprovalContractEventArgs', + 'TokenContractEventArgs', + 'ZeroExConfig', + 'TransactionReceiptWithDecodedLogs', + 'LogWithDecodedArgs', + 'DecodedLogArgs', + 'MethodOpts', + 'ValidateOrderFillableOpts', + 'OrderTransactionOpts', + 'ContractEventArg', + 'LogEvent', + 'LogEntry', + 'DecodedLogEvent', + ], + menuSmartContracts: { + introduction: [ + SmartContractsDocSections.Introduction, + ], + contracts: [ + SmartContractsDocSections.Exchange, + SmartContractsDocSections.TokenRegistry, + SmartContractsDocSections.ZRXToken, + SmartContractsDocSections.EtherToken, + SmartContractsDocSections.TokenTransferProxy, + ], + }, + menu0xjs: { + introduction: [ + ZeroExJsDocSections.introduction, + ], + install: [ + ZeroExJsDocSections.installation, + ], + topics: [ + ZeroExJsDocSections.async, + ZeroExJsDocSections.errors, + ZeroExJsDocSections.versioning, + ], + zeroEx: [ + ZeroExJsDocSections.zeroEx, + ], + contracts: [ + ZeroExJsDocSections.exchange, + ZeroExJsDocSections.token, + ZeroExJsDocSections.tokenRegistry, + ZeroExJsDocSections.etherToken, + ZeroExJsDocSections.proxy, + ], + types: [ + ZeroExJsDocSections.types, + ], + }, + menuSubsectionToVersionWhenIntroduced: { + [ZeroExJsDocSections.etherToken]: '0.7.1', + [ZeroExJsDocSections.proxy]: '0.8.0', + }, + docToPath: { + [Docs.ZeroExJs]: WebsitePaths.ZeroExJs, + [Docs.SmartContracts]: WebsitePaths.SmartContracts, + }, + contractAddresses: { + '1.0.0': { + [Networks.mainnet]: { + [SmartContractsDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', + [SmartContractsDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + [SmartContractsDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [SmartContractsDocSections.EtherToken]: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', + [SmartContractsDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', + }, + [Networks.ropsten]: { + [SmartContractsDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', + [SmartContractsDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + [SmartContractsDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [SmartContractsDocSections.EtherToken]: '0xc00fd9820cd2898cc4c054b7bf142de637ad129a', + [SmartContractsDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', + }, + [Networks.kovan]: { + [SmartContractsDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + [SmartContractsDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', + [SmartContractsDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + [SmartContractsDocSections.EtherToken]: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', + [SmartContractsDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', + }, + }, + } as ContractAddresses, +}; diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts new file mode 100644 index 000000000..ca6e0dc52 --- /dev/null +++ b/packages/website/ts/utils/doc_utils.ts @@ -0,0 +1,55 @@ +import * as _ from 'lodash'; +import findVersions = require('find-versions'); +import convert = require('xml-js'); +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {VersionToFileName, S3FileObject, TypeDocNode, DoxityDocObj} from 'ts/types'; + +export const docUtils = { + async getVersionToFileNameAsync(s3DocJsonRoot: string): + Promise { + const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); + const versionToFileName: VersionToFileName = {}; + _.each(versionFileNames, fileName => { + const [version] = findVersions(fileName); + versionToFileName[version] = fileName; + }); + return versionToFileName; + }, + async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise { + const response = await fetch(s3DocJsonRoot); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); + return; + } + const responseXML = await response.text(); + const responseJSONString = convert.xml2json(responseXML, { + compact: true, + }); + const responseObj = JSON.parse(responseJSONString); + let fileObjs: S3FileObject[]; + if (_.isArray(responseObj.ListBucketResult.Contents)) { + fileObjs = responseObj.ListBucketResult.Contents as S3FileObject[]; + } else { + fileObjs = [responseObj.ListBucketResult.Contents]; + } + const versionFileNames = _.map(fileObjs, fileObj => { + return fileObj.Key._text; + }); + return versionFileNames; + }, + async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise { + const endpoint = `${s3DocJsonRoot}/${fileName}`; + const response = await fetch(endpoint); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); + return; + } + const jsonDocObj = await response.json(); + return jsonDocObj; + }, +}; diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts new file mode 100644 index 000000000..3bab0a69d --- /dev/null +++ b/packages/website/ts/utils/doxity_utils.ts @@ -0,0 +1,162 @@ +import * as _ from 'lodash'; +import { + DoxityDocObj, + DoxityContractObj, + DoxityAbiDoc, + DoxityInput, + DocAgnosticFormat, + DocSection, + Parameter, + Property, + Type, + TypeDocTypes, + EventArg, + AbiTypes, + SolidityMethod, +} from 'ts/types'; + +export const doxityUtils = { + convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { + const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return abiDoc.type === AbiTypes.Constructor; + }); + const constructors = []; + if (!_.isUndefined(doxityConstructor)) { + const constructor = { + isConstructor: true, + name: doxityContractObj.name, + comment: doxityConstructor.details, + returnComment: doxityConstructor.return, + callPath: '', + parameters: this._convertParameters(doxityConstructor.inputs), + returnType: this._convertType(doxityContractObj.name), + }; + constructors.push(constructor); + } + + const doxityMethods: DoxityAbiDoc[] = _.filter + (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return this._isMethod(abiDoc); + }); + const methods: SolidityMethod[] = _.map(doxityMethods, + (doxityMethod: DoxityAbiDoc) => { + // We assume that none of our functions returns more then a single value + const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? + doxityMethod.outputs[0] : + undefined; + const returnTypeIfExists = !_.isUndefined(outputIfExists) ? + this._convertType(outputIfExists.type) : + undefined; + // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken + const callPath = contractName !== 'ZRXToken' ? + `${contractName[0].toLowerCase()}${contractName.slice(1)}.` : + `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; + const method = { + isConstructor: false, + isConstant: doxityMethod.constant, + isPayable: doxityMethod.payable, + name: doxityMethod.name, + comment: doxityMethod.details, + returnComment: doxityMethod.return, + callPath, + parameters: this._convertParameters(doxityMethod.inputs), + returnType: returnTypeIfExists, + }; + return method; + }); + + const doxityProperties: DoxityAbiDoc[] = _.filter + (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return this._isProperty(abiDoc); + }); + const properties = _.map(doxityProperties, (doxityProperty: DoxityAbiDoc) => { + // We assume that none of our functions return more then a single return value + let typeName = doxityProperty.outputs[0].type; + if (!_.isEmpty(doxityProperty.inputs)) { + // Properties never have more then a single input + typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; + } + const property = { + name: doxityProperty.name, + type: this._convertType(typeName), + comment: doxityProperty.details, + }; + return property; + }); + + const doxityEvents = _.filter( + doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, + ); + const events = _.map(doxityEvents, doxityEvent => { + const event = { + name: doxityEvent.name, + eventArgs: this._convertEventArgs(doxityEvent.inputs), + }; + return event; + }); + + const docSection: DocSection = { + comment: doxityContractObj.title, + constructors, + methods, + properties, + types: [], + events, + }; + docAgnosticFormat[contractName] = docSection; + }); + return docAgnosticFormat; + }, + _convertParameters(inputs: DoxityInput[]): Parameter[] { + const parameters = _.map(inputs, input => { + const parameter = { + name: input.name, + comment: input.description, + isOptional: false, + type: this._convertType(input.type), + }; + return parameter; + }); + return parameters; + }, + _convertType(typeName: string): Type { + const type = { + name: typeName, + typeDocType: TypeDocTypes.Intrinsic, + }; + return type; + }, + _isMethod(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isMethod = hasNamedOutputIfExists && !isNameAllCaps; + return isMethod; + }, + _isProperty(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isProperty = !hasNamedOutputIfExists || isNameAllCaps; + return isProperty; + }, + _convertEventArgs(inputs: DoxityInput[]): EventArg[] { + const eventArgs = _.map(inputs, input => { + const eventArg = { + isIndexed: input.indexed, + name: input.name, + type: this._convertType(input.type), + }; + return eventArg; + }); + return eventArgs; + }, +}; diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts new file mode 100644 index 000000000..a9731c4d4 --- /dev/null +++ b/packages/website/ts/utils/error_reporter.ts @@ -0,0 +1,52 @@ +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {Environments} from 'ts/types'; + +// Suggested way to include Rollbar with Webpack +// https://github.com/rollbar/rollbar.js/tree/master/examples/webpack +const rollbarConfig = { + accessToken: constants.ROLLBAR_ACCESS_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: configs.ENVIRONMENT, + }, + uncaughtErrorLevel: 'error', + hostWhiteList: [constants.PRODUCTION_DOMAIN, constants.STAGING_DOMAIN], + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + 'undefined is not an object (evaluating \'__gCrWeb.autofill.extractForms\')', + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], +}; +import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js'); +const rollbar = Rollbar.init(rollbarConfig); + +export const errorReporter = { + reportAsync(err: Error): Promise { + if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { + return; // Let's not log development errors to rollbar + } + + return new Promise((resolve, reject) => { + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + // We never want to reject and cause the app to throw because of rollbar + resolve(); + } else { + resolve(); + } + }); + }); + }, +}; diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts new file mode 100644 index 000000000..b3d0f7d90 --- /dev/null +++ b/packages/website/ts/utils/typedoc_utils.ts @@ -0,0 +1,356 @@ +import * as _ from 'lodash'; +import compareVersions = require('compare-versions'); +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import { + TypeDocNode, + KindString, + ZeroExJsDocSections, + MenuSubsectionsBySection, + TypeDocType, + Type, + DocAgnosticFormat, + DocSection, + TypescriptMethod, + Parameter, + Property, + CustomType, + IndexSignature, + CustomTypeChild, + TypeParameter, + TypeDocTypes, +} from 'ts/types'; + +const TYPES_MODULE_PATH = '"src/types"'; + +export const sectionNameToPossibleModulePaths: {[name: string]: string[]} = { + [ZeroExJsDocSections.zeroEx]: ['"src/0x"'], + [ZeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], + [ZeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], + [ZeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], + [ZeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], + [ZeroExJsDocSections.proxy]: [ + '"src/contract_wrappers/proxy_wrapper"', + '"src/contract_wrappers/token_transfer_proxy_wrapper"', + ], + [ZeroExJsDocSections.types]: [TYPES_MODULE_PATH], +}; + +export const typeDocUtils = { + isType(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Interface || + entity.kindString === KindString.Function || + entity.kindString === KindString['Type alias'] || + entity.kindString === KindString.Variable || + entity.kindString === KindString.Enumeration; + }, + isMethod(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Method; + }, + isConstructor(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Constructor; + }, + isProperty(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Property; + }, + isPrivateOrProtectedProperty(propertyName: string): boolean { + return _.startsWith(propertyName, '_'); + }, + isPublicType(typeName: string): boolean { + return _.includes(constants.public0xjsTypes, typeName); + }, + getModuleDefinitionBySectionNameIfExists(versionDocObj: TypeDocNode, sectionName: string): + TypeDocNode|undefined { + const possibleModulePathNames = sectionNameToPossibleModulePaths[sectionName]; + const modules = versionDocObj.children; + for (const mod of modules) { + if (_.includes(possibleModulePathNames, mod.name)) { + const moduleWithName = mod; + return moduleWithName; + } + } + return undefined; + }, + getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } + + const docSections = _.keys(ZeroExJsDocSections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } + + if (sectionName === ZeroExJsDocSections.types) { + const typeNames = _.map(docSection.types, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + const methodNames = _.map(docSection.methods, m => m.name); + menuSubsectionsBySection[sectionName] = methodNames; + } + }); + return menuSubsectionsBySection; + }, + getFinal0xjsMenu(selectedVersion: string): {[section: string]: string[]} { + const finalMenu = _.cloneDeep(constants.menu0xjs); + finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { + const versionIntroducedIfExists = constants.menuSubsectionToVersionWhenIntroduced[contractName]; + if (!_.isUndefined(versionIntroducedIfExists)) { + const existsInSelectedVersion = compareVersions(selectedVersion, + versionIntroducedIfExists) >= 0; + return existsInSelectedVersion; + } else { + return true; + } + }); + return finalMenu; + }, + convertToDocAgnosticFormat(typeDocJson: TypeDocNode): DocAgnosticFormat { + const subMenus = _.values(constants.menu0xjs); + const orderedSectionNames = _.flatten(subMenus); + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(orderedSectionNames, sectionName => { + const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( + typeDocJson, sectionName, + ); + if (_.isUndefined(packageDefinitionIfExists)) { + return; // no-op + } + + // Since the `types.ts` file is the only file that does not export a module/class but + // instead has each type export itself, we do not need to go down two levels of nesting + // for it. + let entities; + let packageComment = ''; + if (sectionName === ZeroExJsDocSections.types) { + entities = packageDefinitionIfExists.children; + } else { + entities = packageDefinitionIfExists.children[0].children; + const commentObj = packageDefinitionIfExists.children[0].comment; + packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; + } + + const docSection = typeDocUtils._convertEntitiesToDocSection(entities, sectionName); + docSection.comment = packageComment; + docAgnosticFormat[sectionName] = docSection; + }); + return docAgnosticFormat; + }, + _convertEntitiesToDocSection(entities: TypeDocNode[], sectionName: string) { + const docSection: DocSection = { + comment: '', + constructors: [], + methods: [], + properties: [], + types: [], + }; + + let isConstructor; + _.each(entities, entity => { + switch (entity.kindString) { + case KindString.Constructor: + isConstructor = true; + const constructor = typeDocUtils._convertMethod(entity, isConstructor, sectionName); + docSection.constructors.push(constructor); + break; + + case KindString.Method: + if (entity.flags.isPublic) { + isConstructor = false; + const method = typeDocUtils._convertMethod(entity, isConstructor, sectionName); + docSection.methods.push(method); + } + break; + + case KindString.Property: + if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { + const property = typeDocUtils._convertProperty(entity, sectionName); + docSection.properties.push(property); + } + break; + + case KindString.Interface: + case KindString.Function: + case KindString.Variable: + case KindString.Enumeration: + case KindString['Type alias']: + if (typeDocUtils.isPublicType(entity.name)) { + const customType = typeDocUtils._convertCustomType(entity, sectionName); + docSection.types.push(customType); + } + break; + + default: + throw utils.spawnSwitchErr('kindString', entity.kindString); + } + }); + return docSection; + }, + _convertCustomType(entity: TypeDocNode, sectionName: string): CustomType { + const typeIfExists = !_.isUndefined(entity.type) ? + typeDocUtils._convertType(entity.type, sectionName) : + undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) ? + typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) : + undefined; + const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) ? + typeDocUtils._convertIndexSignature(entity.indexSignature[0], sectionName) : + undefined; + const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ? + entity.comment.shortText : + undefined; + + const childrenIfExist = !_.isUndefined(entity.children) ? + _.map(entity.children, (child: TypeDocNode) => { + const childTypeIfExists = !_.isUndefined(child.type) ? + typeDocUtils._convertType(child.type, sectionName) : + undefined; + const c: CustomTypeChild = { + name: child.name, + type: childTypeIfExists, + defaultValue: child.defaultValue, + }; + return c; + }) : + undefined; + + const customType = { + name: entity.name, + kindString: entity.kindString, + type: typeIfExists, + method: methodIfExists, + indexSignature: indexSignatureIfExists, + defaultValue: entity.defaultValue, + comment: commentIfExists, + children: childrenIfExist, + }; + return customType; + }, + _convertIndexSignature(entity: TypeDocNode, sectionName: string): IndexSignature { + const key = entity.parameters[0]; + const indexSignature = { + keyName: key.name, + keyType: typeDocUtils._convertType(key.type, sectionName), + valueName: entity.type.name, + }; + return indexSignature; + }, + _convertProperty(entity: TypeDocNode, sectionName: string): Property { + const source = entity.sources[0]; + const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; + const property = { + name: entity.name, + type: typeDocUtils._convertType(entity.type, sectionName), + source: { + fileName: source.fileName, + line: source.line, + }, + comment: commentIfExists, + }; + return property; + }, + _convertMethod(entity: TypeDocNode, isConstructor: boolean, sectionName: string): TypescriptMethod { + const signature = entity.signatures[0]; + const source = entity.sources[0]; + const hasComment = !_.isUndefined(signature.comment); + const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; + + const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; + // HACK: we use the fact that the sectionName is the same as the property name at the top-level + // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. + let callPath = (sectionName !== ZeroExJsDocSections.zeroEx) ? + `${topLevelInterface}${sectionName}.` : + topLevelInterface; + callPath = isConstructor ? '' : callPath; + + const parameters = _.map(signature.parameters, param => { + return typeDocUtils._convertParameter(param, sectionName); + }); + const returnType = typeDocUtils._convertType(signature.type, sectionName); + const typeParameter = _.isUndefined(signature.typeParameter) ? + undefined : + typeDocUtils._convertTypeParameter(signature.typeParameter[0], sectionName); + + const method = { + isConstructor, + isStatic, + name: signature.name, + comment: hasComment ? signature.comment.shortText : undefined, + returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, + source: { + fileName: source.fileName, + line: source.line, + }, + callPath, + parameters, + returnType, + typeParameter, + }; + return method; + }, + _convertTypeParameter(entity: TypeDocNode, sectionName: string): TypeParameter { + const type = typeDocUtils._convertType(entity.type, sectionName); + const parameter = { + name: entity.name, + type, + }; + return parameter; + }, + _convertParameter(entity: TypeDocNode, sectionName: string): Parameter { + let comment = ''; + if (entity.comment && entity.comment.shortText) { + comment = entity.comment.shortText; + } else if (entity.comment && entity.comment.text) { + comment = entity.comment.text; + } + + const isOptional = !_.isUndefined(entity.flags.isOptional) ? + entity.flags.isOptional : + false; + + const type = typeDocUtils._convertType(entity.type, sectionName); + + const parameter = { + name: entity.name, + comment, + isOptional, + type, + }; + return parameter; + }, + _convertType(entity: TypeDocType, sectionName: string): Type { + const typeArguments = _.map(entity.typeArguments, typeArgument => { + return typeDocUtils._convertType(typeArgument, sectionName); + }); + const types = _.map(entity.types, t => { + return typeDocUtils._convertType(t, sectionName); + }); + + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) ? + typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) : + undefined; + + const elementTypeIfExists = !_.isUndefined(entity.elementType) ? + { + name: entity.elementType.name, + typeDocType: entity.elementType.type, + } : + undefined; + + const type = { + name: entity.name, + value: entity.value, + typeDocType: entity.type, + typeArguments, + elementType: elementTypeIfExists, + types, + method: methodIfExists, + }; + return type; + }, +}; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts new file mode 100644 index 000000000..eb4c5be3a --- /dev/null +++ b/packages/website/ts/utils/utils.ts @@ -0,0 +1,215 @@ +import * as _ from 'lodash'; +import { + SideToAssetToken, + SignatureData, + Order, + Side, + TokenByAddress, + OrderParty, + ScreenWidths, + EtherscanLinkSuffixes, + Token, + Networks, +} from 'ts/types'; +import * as moment from 'moment'; +import isMobile = require('is-mobile'); +import * as u2f from 'ts/vendor/u2f_api'; +import deepEqual = require('deep-equal'); +import ethUtil = require('ethereumjs-util'); +import BigNumber from 'bignumber.js'; +import {constants} from 'ts/utils/constants'; + +const LG_MIN_EM = 64; +const MD_MIN_EM = 52; + +export const utils = { + assert(condition: boolean, message: string) { + if (!condition) { + throw new Error(message); + } + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + isNumeric(n: string) { + return !isNaN(parseFloat(n)) && isFinite(Number(n)); + }, + // This default unix timestamp is used for orders where the user does not specify an expiry date. + // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for + // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide + // with actual values a user would select. + initialOrderExpiryUnixTimestampSec(): BigNumber { + const m = moment('2050-01-01'); + return new BigNumber(m.unix()); + }, + convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { + const finalMoment = date; + if (!_.isUndefined(time)) { + finalMoment.hours(time.hours()); + finalMoment.minutes(time.minutes()); + } + return new BigNumber(finalMoment.unix()); + }, + convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { + return moment.unix(unixTimestampSec.toNumber()); + }, + convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { + const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); + const formattedDate: string = m.format('h:MMa MMMM D YYYY'); + return formattedDate; + }, + generateOrder(networkId: number, exchangeContract: string, sideToAssetToken: SideToAssetToken, + orderExpiryTimestamp: BigNumber, orderTakerAddress: string, orderMakerAddress: string, + makerFee: BigNumber, takerFee: BigNumber, feeRecipient: string, + signatureData: SignatureData, tokenByAddress: TokenByAddress, orderSalt: BigNumber): Order { + const makerToken = tokenByAddress[sideToAssetToken[Side.deposit].address]; + const takerToken = tokenByAddress[sideToAssetToken[Side.receive].address]; + const order = { + maker: { + address: orderMakerAddress, + token: { + name: makerToken.name, + symbol: makerToken.symbol, + decimals: makerToken.decimals, + address: makerToken.address, + }, + amount: sideToAssetToken[Side.deposit].amount.toString(), + feeAmount: makerFee.toString(), + }, + taker: { + address: orderTakerAddress, + token: { + name: takerToken.name, + symbol: takerToken.symbol, + decimals: takerToken.decimals, + address: takerToken.address, + }, + amount: sideToAssetToken[Side.receive].amount.toString(), + feeAmount: takerFee.toString(), + }, + expiration: orderExpiryTimestamp.toString(), + feeRecipient, + salt: orderSalt.toString(), + signature: signatureData, + exchangeContract, + networkId, + }; + return order; + }, + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + sleepAsync(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + deepEqual(actual: any, expected: any, opts?: {strict: boolean}) { + return deepEqual(actual, expected, opts); + }, + getColSize(items: number) { + const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid + const colSize = 12 / items; + if (!_.isInteger(colSize)) { + throw new Error('Number of cols must be divisible by 12'); + } + return colSize; + }, + getScreenWidth() { + const documentEl = document.documentElement; + const body = document.getElementsByTagName('body')[0]; + const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; + const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); + const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); + + // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS + // class prefixes. Do not edit these. + if (widthInEm > LG_MIN_EM) { + return ScreenWidths.LG; + } else if (widthInEm > MD_MIN_EM) { + return ScreenWidths.MD; + } else { + return ScreenWidths.SM; + } + }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, + getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { + const networkName = constants.networkNameById[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + async isU2FSupportedAsync(): Promise { + const w = (window as any); + return new Promise((resolve: (isSupported: boolean) => void) => { + if (w.u2f && !w.u2f.getApiVersion) { + // u2f object was found (Firefox with extension) + resolve(true); + } else { + // u2f object was not found. Using Google polyfill + // HACK: u2f.getApiVersion will simply not return a version if the + // U2F call fails for any reason. Because of this, we set a hard 3sec + // timeout to the request on our end. + const getApiVersionTimeoutMs = 3000; + const intervalId = setTimeout(() => { + resolve(false); + }, getApiVersionTimeoutMs); + u2f.getApiVersion((version: number) => { + clearTimeout(intervalId); + resolve(true); + }); + } + }); + }, + // This checks the error message returned from an injected Web3 instance on the page + // after a user was prompted to sign a message or send a transaction and decided to + // reject the request. + didUserDenyWeb3Request(errMsg: string) { + const metamaskDenialErrMsg = 'User denied message'; + const paritySignerDenialErrMsg = 'Request has been rejected'; + const ledgerDenialErrMsg = 'Invalid status 6985'; + const isUserDeniedErrMsg = _.includes(errMsg, metamaskDenialErrMsg) || + _.includes(errMsg, paritySignerDenialErrMsg) || + _.includes(errMsg, ledgerDenialErrMsg); + return isUserDeniedErrMsg; + }, + getCurrentEnvironment() { + switch (location.host) { + case constants.DEVELOPMENT_DOMAIN: + return 'development'; + case constants.STAGING_DOMAIN: + return 'staging'; + case constants.PRODUCTION_DOMAIN: + return 'production'; + default: + return 'production'; + } + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getAddressBeginAndEnd(address: string): string { + const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 + return truncatedAddress; + }, + hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + if (token.isRegistered) { + return true; // Since it's registered, it is the canonical token + } + const registeredTokens = _.filter(tokens, t => t.isRegistered); + const tokenWithSameNameIfExists = _.find(registeredTokens, {name: token.name}); + const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); + const tokenWithSameSymbolIfExists = _.find(registeredTokens, {name: token.symbol}); + const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); + return isUniqueName && isUniqueSymbol; + }, +}; diff --git a/packages/website/ts/vendor/u2f_api.js b/packages/website/ts/vendor/u2f_api.js new file mode 100644 index 000000000..3b538d817 --- /dev/null +++ b/packages/website/ts/vendor/u2f_api.js @@ -0,0 +1,760 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * Require integration + */ +if (typeof module != "undefined") { + module.exports = u2f; +} + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +/* +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; +*/ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.ledger.android.u2f.bridge.AUTHENTICATE'; + + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + //console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + //console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts new file mode 100644 index 000000000..24279f5d2 --- /dev/null +++ b/packages/website/ts/web3_wrapper.ts @@ -0,0 +1,146 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import BigNumber from 'bignumber.js'; +import promisify = require('es6-promisify'); +import {Dispatcher} from 'ts/redux/dispatcher'; + +export class Web3Wrapper { + private dispatcher: Dispatcher; + private web3: Web3; + private prevNetworkId: number; + private shouldPollUserAddress: boolean; + private watchNetworkAndBalanceIntervalId: number; + private prevUserEtherBalanceInEth: BigNumber; + private prevUserAddress: string; + constructor(dispatcher: Dispatcher, provider: Web3.Provider, networkIdIfExists: number, + shouldPollUserAddress: boolean) { + this.dispatcher = dispatcher; + this.prevNetworkId = networkIdIfExists; + this.shouldPollUserAddress = shouldPollUserAddress; + + this.web3 = new Web3(); + this.web3.setProvider(provider); + + this.startEmittingNetworkConnectionAndUserBalanceStateAsync(); + } + public isAddress(address: string) { + return this.web3.isAddress(address); + } + public async getAccountsAsync(): Promise { + const addresses = await promisify(this.web3.eth.getAccounts)(); + return addresses; + } + public async getFirstAccountIfExistsAsync() { + const addresses = await this.getAccountsAsync(); + if (_.isEmpty(addresses)) { + return ''; + } + return (addresses as string[])[0]; + } + public async getNodeVersionAsync() { + const nodeVersion = await promisify(this.web3.version.getNode)(); + return nodeVersion; + } + public getProviderObj() { + return this.web3.currentProvider; + } + public async getNetworkIdIfExists() { + try { + const networkId = await this.getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise { + const balanceInWei: BigNumber = await promisify(this.web3.eth.getBalance)(owner); + const balanceEthOldBigNumber = this.web3.fromWei(balanceInWei, 'ether'); + const balanceEth = new BigNumber(balanceEthOldBigNumber); + return balanceEth; + } + public async doesContractExistAtAddressAsync(address: string): Promise { + const code = await promisify(this.web3.eth.getCode)(address); + // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients + const zeroHexAddressRegex = /^0[xX][0]*$/; + const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); + return didFindCode; + } + public async signTransactionAsync(address: string, message: string): Promise { + const signData = await promisify(this.web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise { + const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); + return timestamp; + } + public destroy() { + this.stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + // HACK: stop() is only available on providerEngine instances + const provider = this.web3.currentProvider; + if (!_.isUndefined((provider as any).stop)) { + (provider as any).stop(); + } + } + // This should only be called from the LedgerConfigDialog + public updatePrevUserAddress(userAddress: string) { + this.prevUserAddress = userAddress; + } + private async getNetworkAsync() { + const networkId = await promisify(this.web3.version.getNetwork)(); + return networkId; + } + private async startEmittingNetworkConnectionAndUserBalanceStateAsync() { + if (!_.isUndefined(this.watchNetworkAndBalanceIntervalId)) { + return; // we are already emitting the state + } + + let prevNodeVersion: string; + this.prevUserEtherBalanceInEth = new BigNumber(0); + this.dispatcher.updateNetworkId(this.prevNetworkId); + this.watchNetworkAndBalanceIntervalId = window.setInterval(async () => { + // Check for network state changes + const currentNetworkId = await this.getNetworkIdIfExists(); + if (currentNetworkId !== this.prevNetworkId) { + this.prevNetworkId = currentNetworkId; + this.dispatcher.updateNetworkId(currentNetworkId); + } + + // Check for node version changes + const currentNodeVersion = await this.getNodeVersionAsync(); + if (currentNodeVersion !== prevNodeVersion) { + prevNodeVersion = currentNodeVersion; + this.dispatcher.updateNodeVersion(currentNodeVersion); + } + + if (this.shouldPollUserAddress) { + const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); + // Update makerAddress on network change + if (this.prevUserAddress !== userAddressIfExists) { + this.prevUserAddress = userAddressIfExists; + this.dispatcher.updateUserAddress(userAddressIfExists); + } + + // Check for user ether balance changes + if (userAddressIfExists !== '') { + await this.updateUserEtherBalanceAsync(userAddressIfExists); + } + } else { + // This logic is primarily for the Ledger, since we don't regularly poll for the address + // we simply update the balance for the last fetched address. + if (!_.isEmpty(this.prevUserAddress)) { + await this.updateUserEtherBalanceAsync(this.prevUserAddress); + } + } + }, 5000); + } + private async updateUserEtherBalanceAsync(userAddress: string) { + const balance = await this.getBalanceInEthAsync(userAddress); + if (!balance.eq(this.prevUserEtherBalanceInEth)) { + this.prevUserEtherBalanceInEth = balance; + this.dispatcher.updateUserEtherBalance(balance); + } + } + private stopEmittingNetworkConnectionAndUserBalanceStateAsync() { + clearInterval(this.watchNetworkAndBalanceIntervalId); + } +} diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json new file mode 100644 index 000000000..15e764c02 --- /dev/null +++ b/packages/website/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "outDir": "./transpiled/", + "sourceMap": true, + "noImplicitAny": true, + "module": "commonjs", + "target": "es5", + "jsx": "react", + "baseUrl": "./", + "allowJs": true, + "paths": { + "*": [ "node_modules/@types/*", "*"] + } + }, + "include": [ + "./ts/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts" + ] +} diff --git a/packages/website/tslint.json b/packages/website/tslint.json new file mode 100644 index 000000000..6ac3ed760 --- /dev/null +++ b/packages/website/tslint.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "@0xproject/tslint-config" + ], + "rules": { + "no-implicit-dependencies": false, + "no-object-literal-type-assertion": false + } +} diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js new file mode 100644 index 000000000..c436888bd --- /dev/null +++ b/packages/website/webpack.config.js @@ -0,0 +1,86 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + entry: ['./ts/index.tsx'], + output: { + path: path.join(__dirname, '/public'), + filename: 'bundle.js', + chunkFilename: 'bundle-[name].js', + publicPath: '/', + }, + devtool: 'source-map', + resolve: { + modules: [ + path.join(__dirname, '/ts'), + 'node_modules', + ], + extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md'], + alias: { + ts: path.join(__dirname, '/ts'), + less: path.join(__dirname, '/less'), + md: path.join(__dirname, '/md'), + }, + }, + module: { + rules: [ + { + test: /\.js$/, + loader: 'source-map-loader', + }, + { + test: /\.tsx?$/, + loader: 'awesome-typescript-loader', + }, + { + test: /\.md$/, + use: 'raw-loader', + }, + { + test: /\.less$/, + loader: 'style-loader!css-loader!less-loader', + exclude: /node_modules/, + }, + { + test: /\.css$/, + loaders: ['style-loader', 'css-loader'], + }, + { + test: /\.json$/, + loader: 'json-loader', + }, + ], + }, + devServer: { + port: 3572, + historyApiFallback: { + // Fixes issue where having dots in URL path that aren't part of fileNames causes webpack-dev-server + // to fail. Doc versions have dots in them, therefore we special case these urls to also load index.html. + // Source: https://github.com/cvut/fittable/issues/171 + rewrites: [ + { + from: /^\/docs\/.*$/, + to: function() { + return 'index.html'; + } + } + ] + }, + disableHostCheck: true, + }, + plugins: process.env.NODE_ENV === 'production' ? [ + // Since we do not use moment's locale feature, we exclude them from the bundle. + // This reduces the bundle size by 0.4MB. + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': JSON.stringify(process.env.NODE_ENV) + } + }), + new webpack.optimize.UglifyJsPlugin({ + mangle: { + except: ['BigNumber'] + } + }) + ] : [], +}; diff --git a/yarn.lock b/yarn.lock index 6bc4ccf1b..71fa75f7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@types/accounting@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/accounting/-/accounting-0.4.1.tgz#865d9f5694fd7c438fba34eb4bc82eec6f34cdd5" + +"@types/dateformat@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-1.0.1.tgz#2e5b235c8c55652c4fec284506d2a36fe65fe87e" + +"@types/deep-equal@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" + +"@types/es6-promise@0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@types/es6-promise/-/es6-promise-0.0.32.tgz#3bcf44fb1e429f3df76188c8c6d874463ba371fd" + "@types/fetch-mock@^5.12.1": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" @@ -27,6 +43,10 @@ version "9.12.1" resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.1.tgz#990f5dc63774852beab55e6173e5851d35da52f2" +"@types/history@*": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" + "@types/jsonschema@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/jsonschema/-/jsonschema-1.1.1.tgz#08703dfe074010e8e829123111594af731f57b1a" @@ -45,7 +65,7 @@ dependencies: "@types/lodash" "*" -"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.77", "@types/lodash@^4.14.78": +"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.55", "@types/lodash@^4.14.64", "@types/lodash@^4.14.77", "@types/lodash@^4.14.78": version "4.14.85" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b" @@ -53,6 +73,13 @@ version "0.0.28" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" +"@types/material-ui@0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@types/material-ui/-/material-ui-0.18.0.tgz#f3abc5431df8faa4592233c6c5377f2843eb807f" + dependencies: + "@types/react" "*" + "@types/react-addons-linked-state-mixin" "*" + "@types/minimatch@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.1.tgz#b683eb60be358304ef146f5775db4c0e3696a550" @@ -65,14 +92,88 @@ version "2.2.44" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e" +"@types/moment@^2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@types/moment/-/moment-2.13.0.tgz#604ebd189bc3bc34a1548689404e61a2a4aac896" + dependencies: + moment "*" + "@types/node@*", "@types/node@^8.0.1": version "8.0.51" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" -"@types/query-string@^5.0.1": +"@types/node@^7.0.8": + version "7.0.48" + resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb" + +"@types/query-string@^5.0.0", "@types/query-string@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-5.0.1.tgz#6cb41c724cb1644d56c2d1dae7c7b204e706b39e" +"@types/react-addons-linked-state-mixin@*": + version "0.14.18" + resolved "https://registry.yarnpkg.com/@types/react-addons-linked-state-mixin/-/react-addons-linked-state-mixin-0.14.18.tgz#6977ae59e19aa0ce3a5d7a9e057961aaf1afe959" + dependencies: + "@types/react" "*" + +"@types/react-copy-to-clipboard@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.2.3.tgz#9da09521fc0f7768f9ad9781d9768f1cde81e5bd" + dependencies: + "@types/react" "*" + +"@types/react-dom@^0.14.23": + version "0.14.23" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-0.14.23.tgz#cecfcfad754b4c2765fe5d29b81b301889ad6c2e" + dependencies: + "@types/react" "*" + +"@types/react-redux@^4.4.37": + version "4.4.47" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-4.4.47.tgz#12af1677116e08d413fe2620d0a85560c8a0536e" + dependencies: + "@types/react" "*" + redux "^3.6.0" + +"@types/react-router-dom@^4.0.4": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.1.tgz#c5583550d7246dea60612cc55a523cf980d56df1" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.17.tgz#1032c800ed0da188d8a3bb38fe6afa8c917865d5" + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-scroll@0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-0.0.31.tgz#1bb26bfd9f595da6403c2f13c2f9a3ed4d2929fa" + dependencies: + "@types/react" "*" + +"@types/react-tap-event-plugin@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/react-tap-event-plugin/-/react-tap-event-plugin-0.0.30.tgz#123f35080412f489b6770c5a65c159ff96654cb5" + +"@types/react@*": + version "16.0.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed" + +"@types/react@^15.0.15": + version "15.6.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-15.6.7.tgz#e910b6aace59d8d0b48dd679c2c03cffedafeec6" + +"@types/redux@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a" + dependencies: + redux "*" + "@types/shelljs@^0.7.0": version "0.7.6" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.6.tgz#4ac7ca01c191ba65b8e2bf50543c5560084d8d27" @@ -123,6 +224,17 @@ abstract-leveldown@~2.7.1: dependencies: xtend "~4.0.0" +accepts@~1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + +accounting@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/accounting/-/accounting-0.4.1.tgz#87dd4103eff7f4460f1e186f5c677ed6cf566883" + acorn-dynamic-import@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" @@ -173,6 +285,10 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -181,6 +297,10 @@ ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + ansi-regex@^1.0.0, ansi-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d" @@ -263,10 +383,25 @@ array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -297,6 +432,10 @@ arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + asn1.js@^4.0.0: version "4.9.2" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" @@ -349,11 +488,17 @@ async-eventemitter@ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a dependencies: async "^2.4.0" +async@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" + dependencies: + lodash "^4.14.0" + async@^0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@^1.4.0, async@^1.4.2, async@^1.5.0: +async@^1.4.0, async@^1.4.2, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -371,6 +516,17 @@ atob@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + awesome-typescript-loader@^3.1.3: version "3.3.0" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.3.0.tgz#24bed5650ca0d6e95457904d9969127ba4ff3575" @@ -396,7 +552,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0, babel-code-frame@^6.8.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -428,7 +584,33 @@ babel-core@^6.0.14, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.6" -babel-generator@^6.18.0, babel-generator@^6.26.0: +babel-core@~6.13.2: + version "6.13.2" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.13.2.tgz#f761e1199361d5a6ed16f93ce801ad50acadb338" + dependencies: + babel-code-frame "^6.8.0" + babel-generator "^6.11.4" + babel-helpers "^6.8.0" + babel-messages "^6.8.0" + babel-register "^6.9.0" + babel-runtime "^6.9.1" + babel-template "^6.9.0" + babel-traverse "^6.13.0" + babel-types "^6.13.0" + babylon "^6.7.0" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.4.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-exists "^1.0.0" + path-is-absolute "^1.0.0" + private "^0.1.6" + shebang-regex "^1.0.0" + slash "^1.0.0" + source-map "^0.5.0" + +babel-generator@^6.11.4, babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: @@ -441,6 +623,14 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: source-map "^0.5.6" trim-right "^1.0.1" +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + babel-helper-call-delegate@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" @@ -509,38 +699,50 @@ babel-helper-replace-supers@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-helpers@^6.24.1: +babel-helpers@^6.24.1, babel-helpers@^6.8.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-messages@^6.23.0: +babel-messages@^6.23.0, babel-messages@^6.8.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" dependencies: babel-runtime "^6.22.0" -babel-plugin-check-es2015-constants@^6.22.0: +babel-plugin-check-es2015-constants@^6.22.0, babel-plugin-check-es2015-constants@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-arrow-functions@^6.22.0: +babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.3.13: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + +babel-plugin-syntax-jsx@~6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.13.0.tgz#e741ff3992c578310be45c571bcd90a2f9c5586e" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0, babel-plugin-transform-es2015-arrow-functions@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0, babel-plugin-transform-es2015-block-scoped-functions@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-block-scoping@^6.24.1: +babel-plugin-transform-es2015-block-scoping@^6.24.1, babel-plugin-transform-es2015-block-scoping@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" dependencies: @@ -550,7 +752,7 @@ babel-plugin-transform-es2015-block-scoping@^6.24.1: babel-types "^6.26.0" lodash "^4.17.4" -babel-plugin-transform-es2015-classes@^6.24.1: +babel-plugin-transform-es2015-classes@^6.24.1, babel-plugin-transform-es2015-classes@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" dependencies: @@ -564,33 +766,33 @@ babel-plugin-transform-es2015-classes@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-computed-properties@^6.24.1: +babel-plugin-transform-es2015-computed-properties@^6.24.1, babel-plugin-transform-es2015-computed-properties@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-destructuring@^6.22.0: +babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.9.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-duplicate-keys@^6.24.1: +babel-plugin-transform-es2015-duplicate-keys@^6.24.1, babel-plugin-transform-es2015-duplicate-keys@^6.6.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-for-of@^6.22.0: +babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.6.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-function-name@^6.24.1: +babel-plugin-transform-es2015-function-name@^6.24.1, babel-plugin-transform-es2015-function-name@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" dependencies: @@ -598,13 +800,13 @@ babel-plugin-transform-es2015-function-name@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-literals@^6.22.0: +babel-plugin-transform-es2015-literals@^6.22.0, babel-plugin-transform-es2015-literals@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-modules-amd@^6.24.1: +babel-plugin-transform-es2015-modules-amd@^6.24.1, babel-plugin-transform-es2015-modules-amd@^6.8.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" dependencies: @@ -612,7 +814,7 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.6.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" dependencies: @@ -621,7 +823,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.24.1: babel-template "^6.26.0" babel-types "^6.26.0" -babel-plugin-transform-es2015-modules-systemjs@^6.24.1: +babel-plugin-transform-es2015-modules-systemjs@^6.12.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" dependencies: @@ -629,7 +831,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-umd@^6.24.1: +babel-plugin-transform-es2015-modules-umd@^6.12.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" dependencies: @@ -637,14 +839,14 @@ babel-plugin-transform-es2015-modules-umd@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-object-super@^6.24.1: +babel-plugin-transform-es2015-object-super@^6.24.1, babel-plugin-transform-es2015-object-super@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" dependencies: babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" -babel-plugin-transform-es2015-parameters@^6.24.1: +babel-plugin-transform-es2015-parameters@^6.24.1, babel-plugin-transform-es2015-parameters@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" dependencies: @@ -655,20 +857,20 @@ babel-plugin-transform-es2015-parameters@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-shorthand-properties@^6.24.1: +babel-plugin-transform-es2015-shorthand-properties@^6.24.1, babel-plugin-transform-es2015-shorthand-properties@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-spread@^6.22.0: +babel-plugin-transform-es2015-spread@^6.22.0, babel-plugin-transform-es2015-spread@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-sticky-regex@^6.24.1: +babel-plugin-transform-es2015-sticky-regex@^6.24.1, babel-plugin-transform-es2015-sticky-regex@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" dependencies: @@ -676,19 +878,19 @@ babel-plugin-transform-es2015-sticky-regex@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-template-literals@^6.22.0: +babel-plugin-transform-es2015-template-literals@^6.22.0, babel-plugin-transform-es2015-template-literals@^6.6.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-typeof-symbol@^6.22.0: +babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.6.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-unicode-regex@^6.24.1: +babel-plugin-transform-es2015-unicode-regex@^6.24.1, babel-plugin-transform-es2015-unicode-regex@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" dependencies: @@ -696,7 +898,42 @@ babel-plugin-transform-es2015-unicode-regex@^6.24.1: babel-runtime "^6.22.0" regexpu-core "^2.0.0" -babel-plugin-transform-regenerator@^6.24.1: +babel-plugin-transform-flow-strip-types@^6.3.13: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-display-name@^6.3.13: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@^6.11.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-source@^6.3.13: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@^6.3.13: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.24.1, babel-plugin-transform-regenerator@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" dependencies: @@ -709,6 +946,12 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" +babel-preset-es2015-loose@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-es2015-loose/-/babel-preset-es2015-loose-7.0.0.tgz#fd80c85d3b20cbf309bd0ce30a36380c5784bf06" + dependencies: + modify-babel-preset "^1.0.0" + babel-preset-es2015@^6.24.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" @@ -738,7 +981,48 @@ babel-preset-es2015@^6.24.0: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" -babel-register@^6.26.0: +babel-preset-es2015@~6.13.2: + version "6.13.2" + resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.13.2.tgz#006c469a7528bd066f2917c8b4955309dcd53cfb" + dependencies: + babel-plugin-check-es2015-constants "^6.3.13" + babel-plugin-transform-es2015-arrow-functions "^6.3.13" + babel-plugin-transform-es2015-block-scoped-functions "^6.3.13" + babel-plugin-transform-es2015-block-scoping "^6.9.0" + babel-plugin-transform-es2015-classes "^6.9.0" + babel-plugin-transform-es2015-computed-properties "^6.3.13" + babel-plugin-transform-es2015-destructuring "^6.9.0" + babel-plugin-transform-es2015-duplicate-keys "^6.6.0" + babel-plugin-transform-es2015-for-of "^6.6.0" + babel-plugin-transform-es2015-function-name "^6.9.0" + babel-plugin-transform-es2015-literals "^6.3.13" + babel-plugin-transform-es2015-modules-amd "^6.8.0" + babel-plugin-transform-es2015-modules-commonjs "^6.6.0" + babel-plugin-transform-es2015-modules-systemjs "^6.12.0" + babel-plugin-transform-es2015-modules-umd "^6.12.0" + babel-plugin-transform-es2015-object-super "^6.3.13" + babel-plugin-transform-es2015-parameters "^6.9.0" + babel-plugin-transform-es2015-shorthand-properties "^6.3.13" + babel-plugin-transform-es2015-spread "^6.3.13" + babel-plugin-transform-es2015-sticky-regex "^6.3.13" + babel-plugin-transform-es2015-template-literals "^6.6.0" + babel-plugin-transform-es2015-typeof-symbol "^6.6.0" + babel-plugin-transform-es2015-unicode-regex "^6.3.13" + babel-plugin-transform-regenerator "^6.9.0" + +babel-preset-react@~6.11.0: + version "6.11.1" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.11.1.tgz#98ac2bd3c1b76f3062ae082580eade154a19b590" + dependencies: + babel-plugin-syntax-flow "^6.3.13" + babel-plugin-syntax-jsx "^6.3.13" + babel-plugin-transform-flow-strip-types "^6.3.13" + babel-plugin-transform-react-display-name "^6.3.13" + babel-plugin-transform-react-jsx "^6.3.13" + babel-plugin-transform-react-jsx-self "^6.11.0" + babel-plugin-transform-react-jsx-source "^6.3.13" + +babel-register@^6.26.0, babel-register@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -750,14 +1034,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.9.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -767,7 +1051,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.13.0, babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -781,7 +1065,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: +babel-types@^6.13.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -797,10 +1081,14 @@ babelify@^7.3.0: babel-core "^6.0.14" object-assign "^4.0.0" -babylon@^6.18.0: +babylon@^6.18.0, babylon@^6.7.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -825,6 +1113,70 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +basscss-align@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/basscss-align/-/basscss-align-1.0.2.tgz#294aa689d6f99da86e4af4c5c2892870855c1c37" + +basscss-border@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/basscss-border/-/basscss-border-4.0.2.tgz#14b4506329b90cb14abe5f4d3473e9fe9202df2e" + +basscss-flexbox@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/basscss-flexbox/-/basscss-flexbox-1.0.1.tgz#f64bc7607f97a7dfab74c1e011e1a77ad734f83a" + +basscss-grid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/basscss-grid/-/basscss-grid-2.0.0.tgz#6f4c3198e786a38529f8362bc3b3bce5254c1369" + +basscss-hide@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/basscss-hide/-/basscss-hide-1.0.1.tgz#34bc138bba867c6c49ab8682a610ef495e47d750" + +basscss-layout@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/basscss-layout/-/basscss-layout-3.1.0.tgz#f9f392e480da66657d9fe5de9ca4c07c579c3a4e" + +basscss-margin@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/basscss-margin/-/basscss-margin-1.0.7.tgz#5a92d8cda98ef391c73a15ede97b34b48886417c" + +basscss-padding@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/basscss-padding/-/basscss-padding-1.1.3.tgz#69db799414e6dd58bed83776952cc299e2e6874e" + +basscss-position@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/basscss-position/-/basscss-position-2.0.3.tgz#467180a1f8f386e9072ed8d08294d2a6e0ba4305" + +basscss-type-scale@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/basscss-type-scale/-/basscss-type-scale-1.0.5.tgz#23bf5e41c9d142c8061cf9829ccf23e9b3258ec7" + +basscss-typography@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/basscss-typography/-/basscss-typography-3.0.3.tgz#182cf43df7c4ebed02750dc748041cbadff60d43" + +basscss@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/basscss/-/basscss-8.0.3.tgz#720607254e89d363cd8d46b3aefa01d3c2b91363" + dependencies: + basscss-align "^1.0.2" + basscss-border "^4.0.2" + basscss-flexbox "^1.0.1" + basscss-grid "^2.0.0" + basscss-hide "^1.0.1" + basscss-layout "^3.1.0" + basscss-margin "^1.0.7" + basscss-padding "^1.1.3" + basscss-position "^2.0.3" + basscss-type-scale "^1.0.5" + basscss-typography "^3.0.3" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + bcrypt-pbkdf@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" @@ -893,6 +1245,18 @@ block-stream@*: dependencies: inherits "~2.0.0" +blockies@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/blockies/-/blockies-0.0.2.tgz#22ad58da4f6b382bc79bf4386c5820c70047e4ed" + +bluebird@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + bn.js@4.11.7: version "4.11.7" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" @@ -901,6 +1265,32 @@ bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4. version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -919,6 +1309,10 @@ boom@5.x.x: dependencies: hoek "4.x.x" +bowser@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.8.1.tgz#49785777e7302febadb1a5b71d9a646520ed310d" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -1016,6 +1410,13 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + bs58@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" @@ -1033,6 +1434,14 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1064,6 +1473,10 @@ byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -1109,6 +1522,19 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000769" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000769.tgz#c230b9c1b9e8db3e1c0d858c96e685741b96cc10" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -1159,6 +1585,10 @@ chai@^4.0.1, chai@^4.1.1: pathval "^1.0.0" type-detect "^4.0.0" +chain-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1169,7 +1599,7 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" dependencies: @@ -1177,6 +1607,10 @@ chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +change-emitter@^0.1.2: + version "0.1.6" + resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + check-error@^1.0.1, check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -1187,7 +1621,7 @@ checkpoint-store@^1.1.0: dependencies: functional-red-black-tree "^1.0.1" -chokidar@^1.7.0: +chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1217,6 +1651,12 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +clap@^1.0.9: + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + dependencies: + chalk "^1.1.3" + class-utils@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" @@ -1227,6 +1667,10 @@ class-utils@^0.3.5: lazy-cache "^2.0.2" static-extend "^0.1.1" +classnames@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -1276,6 +1720,12 @@ co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +coa@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + dependencies: + q "^1.1.2" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1294,17 +1744,39 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" dependencies: color-name "^1.1.1" -color-name@^1.1.1: +color-name@^1.0.0, color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" -colors@^1.1.2: +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + +colors@^1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1333,6 +1805,23 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +commonmark-react-renderer@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/commonmark-react-renderer/-/commonmark-react-renderer-4.3.4.tgz#29f345357951ab36eb386d45ea6bc08006f3ff9b" + dependencies: + lodash.assign "^4.2.0" + lodash.isplainobject "^4.0.6" + pascalcase "^0.1.1" + xss-filters "^1.2.6" + +commonmark@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.24.0.tgz#b80de0182c546355643aa15db12bfb282368278f" + dependencies: + entities "~ 1.1.1" + mdurl "~ 1.0.1" + string.prototype.repeat "^0.2.0" + compare-func@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" @@ -1344,10 +1833,59 @@ compare-versions@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" +component-clone@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/component-clone/-/component-clone-0.2.2.tgz#c7f5979822880fad8cfb0962ba29186d061ee04f" + dependencies: + component-type "*" + +component-emitter@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.0.tgz#ccd113a86388d06482d03de3fc7df98526ba8efe" + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +component-raf@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-raf/-/component-raf-1.2.0.tgz#b2bc72d43f1b014fde7a4b3c447c764bc73ccbaa" + +component-tween@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-tween/-/component-tween-1.2.0.tgz#cc39ce5dbab05b52825f41d1947638a0b01b2b8a" + dependencies: + component-clone "0.2.2" + component-emitter "1.2.0" + component-type "1.1.0" + ease-component "1.0.0" + +component-type@*: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9" + +component-type@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.1.0.tgz#95b666aad53e5c8d1f2be135c45b5d499197c0c5" + +compressible@~2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" + dependencies: + mime-db ">= 1.30.0 < 2" + +compression@^1.5.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" + dependencies: + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.11" + debug "2.6.9" + on-headers "~1.0.1" + safe-buffer "5.1.1" + vary "~1.1.2" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1360,6 +1898,10 @@ concat-stream@^1.4.10: readable-stream "^2.2.2" typedarray "^0.0.6" +connect-history-api-fallback@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" + console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -1370,10 +1912,22 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +consolidated-events@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-1.1.1.tgz#25395465b35e531395418b7bbecb5ecaf198d179" + constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + conventional-changelog-angular@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.5.2.tgz#2b38f665fe9c5920af1a2f82f547f4babe6de57c" @@ -1519,14 +2073,41 @@ conventional-recommended-bump@^1.0.1: meow "^3.3.0" object-assign "^4.0.1" -convert-source-map@^1.3.0, convert-source-map@^1.5.0: +convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +copy-to-clipboard@^3: + version "3.0.8" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" + dependencies: + toggle-selection "^1.0.3" + +copy-webpack-plugin@^4.0.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.2.1.tgz#4017366b2d815bd2997e7d6b2b2446f719f486a1" + dependencies: + bluebird "^3.5.1" + fs-extra "^4.0.2" + glob "^7.1.2" + is-glob "^4.0.0" + loader-utils "^0.2.15" + lodash "^4.3.0" + minimatch "^3.0.4" + node-dir "^0.1.10" + copyfiles@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" @@ -1538,6 +2119,10 @@ copyfiles@^1.2.0: noms "0.0.0" through2 "^2.0.1" +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -1589,6 +2174,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.5.2, create-react-class@^15.6.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@^4: version "4.0.2" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" @@ -1636,6 +2229,106 @@ crypto-js@^3.1.4, crypto-js@^3.1.6: version "3.1.8" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" +crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-in-js-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.0.tgz#5af1dd70f4b06b331f48d22a3d86e0786c0b9435" + dependencies: + hyphenate-style-name "^1.0.2" + +css-loader@0.23.x: + version "0.23.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.23.1.tgz#9fa23f2b5c0965235910ad5ecef3b8a36390fe50" + dependencies: + css-selector-tokenizer "^0.5.1" + cssnano ">=2.6.1 <4" + loader-utils "~0.2.2" + lodash.camelcase "^3.0.1" + object-assign "^4.0.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.0.0" + postcss-modules-local-by-default "^1.0.1" + postcss-modules-scope "^1.0.0" + postcss-modules-values "^1.1.0" + source-list-map "^0.1.4" + +css-selector-tokenizer@^0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.5.4.tgz#139bafd34a35fd0c1428487049e0699e6f6a2c21" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +"cssnano@>=2.6.1 <4": + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.0.0.tgz#178b43a44621221c27756086f531e02f42900ee8" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1671,21 +2364,31 @@ dateformat@^1.0.11, dateformat@^1.0.12: get-stdin "^4.0.1" meow "^3.3.0" +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: - ms "2.0.0" + ms "0.7.1" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" @@ -1705,7 +2408,7 @@ deep-eql@^3.0.0: dependencies: type-detect "^4.0.0" -deep-equal@~1.0.1: +deep-equal@^1.0.1, deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -1750,10 +2453,21 @@ define-property@^1.0.0: dependencies: is-descriptor "^1.0.0" -defined@~1.0.0: +defined@^1.0.0, defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1762,6 +2476,10 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +depd@1.1.1, depd@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -1769,7 +2487,11 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -detect-indent@^4.0.0: +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" dependencies: @@ -1783,6 +2505,18 @@ detect-libc@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" +detect-node@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + +dharma-loan-frame@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/dharma-loan-frame/-/dharma-loan-frame-0.0.12.tgz#5765883feb92aec0481471f663f5dcb71e88b5e7" + dependencies: + progress-checkmark "^0.0.1" + react-svg-loader "^1.1.1" + urlencode "^1.1.0" + diff@3.3.1, diff@^3.1.0, diff@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" @@ -1799,6 +2533,27 @@ dirty-chai@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + +dom-helpers@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -1835,6 +2590,10 @@ duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" +ease-component@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ease-component/-/ease-component-1.0.0.tgz#b375726db0b5b04595b77440396fec7daa5d77c9" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -1845,6 +2604,14 @@ editor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.2.7: + version "1.3.27" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" + elliptic@^6.0.0, elliptic@^6.2.3: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -1861,6 +2628,10 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -1882,7 +2653,7 @@ enhanced-resolve@3.3.0: object-assign "^4.0.1" tapable "^0.2.5" -enhanced-resolve@^3.4.0: +enhanced-resolve@^3.3.0, enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: @@ -1891,7 +2662,11 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" -errno@^0.1.3, errno@~0.1.1: +"entities@~ 1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +errno@^0.1.1, errno@^0.1.3, errno@~0.1.1: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" dependencies: @@ -1903,7 +2678,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.4.3, es-abstract@^1.5.0: +es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" dependencies: @@ -1987,6 +2762,10 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2000,6 +2779,10 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + esprima@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" @@ -2019,6 +2802,10 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + eth-block-tracker@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-2.2.2.tgz#b3d72cd82ba5ee37471d22bac4f56387ee4137cf" @@ -2037,7 +2824,7 @@ eth-query@^2.1.0: json-rpc-random-id "^1.0.0" xtend "^4.0.1" -eth-sig-util@^1.3.0: +eth-sig-util@^1.2.1, eth-sig-util@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-1.4.0.tgz#ad42fd1d9c60fff19bdef7377b42fb38e92ee7e1" dependencies: @@ -2103,7 +2890,7 @@ ethereumjs-testrpc@4.0.1: dependencies: webpack "^3.0.0" -ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2: +ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.3.tgz#ece051d3efdbe771ad2a518d61632ca2ab75ecbb" dependencies: @@ -2161,6 +2948,14 @@ ethereumjs-wallet@^0.6.0: utf8 "^2.1.1" uuid "^2.0.1" +ethjs-abi@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18" + dependencies: + bn.js "4.11.6" + js-sha3 "0.5.5" + number-to-bn "1.7.0" + ethjs-util@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.4.tgz#1c8b6879257444ef4d3f3fbbac2ded12cd997d93" @@ -2187,10 +2982,20 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +eventsource@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + dependencies: + original ">=0.0.5" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -2222,6 +3027,10 @@ execa@^0.8.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exenv@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -2250,6 +3059,48 @@ expand-template@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.0.tgz#e09efba977bf98f9ee0ed25abd0c692e02aec3fc" +exports-loader@0.6.x: + version "0.6.4" + resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886" + dependencies: + loader-utils "^1.0.2" + source-map "0.5.x" + +express@^4.13.3: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" + dependencies: + accepts "~1.3.4" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.0" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.2" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.1" + serve-static "1.13.1" + setprototypeof "1.1.0" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.1" + vary "~1.1.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -2305,6 +3156,34 @@ fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + dependencies: + websocket-driver ">=0.5.1" + +fbjs@^0.8, fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.6, fbjs@^0.8.9: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + fetch-mock@^5.13.1: version "5.13.1" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7" @@ -2359,6 +3238,18 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + find-cache-dir@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" @@ -2387,6 +3278,10 @@ find-versions@^2.0.0: array-uniq "^1.0.0" semver-regex "^1.0.0" +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + for-each@^0.3.2, for-each@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" @@ -2440,12 +3335,20 @@ formatio@1.2.0, formatio@^1.2.0: dependencies: samsam "1.x" +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" dependencies: map-cache "^0.2.2" +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" @@ -2460,7 +3363,7 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^4.0.0, fs-extra@^4.0.1: +fs-extra@^4.0.0, fs-extra@^4.0.1, fs-extra@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" dependencies: @@ -2479,7 +3382,7 @@ fsevents@^1.0.0: nan "^2.3.0" node-pre-gyp "^0.6.39" -fstream-ignore@^1.0.5: +fstream-ignore@^1.0.5, fstream-ignore@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" dependencies: @@ -2487,7 +3390,7 @@ fstream-ignore@^1.0.5: inherits "2" minimatch "^3.0.0" -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2, fstream@~1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: @@ -2691,6 +3594,10 @@ growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" +handle-thing@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" + handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -2823,10 +3730,20 @@ he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" -highlight.js@^9.0.0: +highlight.js@^9.0.0, highlight.js@^9.11.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" +history@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + resolve-pathname "^2.2.0" + value-equal "^0.4.0" + warning "^3.0.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -2843,6 +3760,14 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" +hoist-non-react-statics@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" + +hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -2854,6 +3779,56 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + +html-entities@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + +http-errors@1.6.2, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-parser-js@>=0.4.0: + version "0.4.9" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + +http-proxy-middleware@~0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" + dependencies: + http-proxy "^1.16.2" + is-glob "^3.1.0" + lodash "^4.17.2" + micromatch "^2.3.11" + +http-proxy@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + dependencies: + eventemitter3 "1.x.x" + requires-port "1.x.x" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -2870,6 +3845,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpplease@^0.16: + version "0.16.4" + resolved "https://registry.yarnpkg.com/httpplease/-/httpplease-0.16.4.tgz#d382ebe230ef5079080b4e9ffebf316a9e75c0da" + dependencies: + urllite "~0.5.0" + xmlhttprequest "*" + xtend "~3.0.0" + https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" @@ -2881,14 +3864,26 @@ hyperquest@~1.2.0: duplexer2 "~0.0.2" through2 "~0.6.3" -iconv-lite@^0.4.17, iconv-lite@~0.4.13: +hyphenate-style-name@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b" + +iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.11, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" @@ -2897,6 +3892,20 @@ immutable@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" +import-local@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + +imports-loader@0.6.x: + version "0.6.5" + resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.6.5.tgz#ae74653031d59e37b3c2fb2544ac61aeae3530a6" + dependencies: + loader-utils "0.2.x" + source-map "0.1.x" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2907,6 +3916,10 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -2918,7 +3931,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -2930,6 +3943,13 @@ ini@^1.3.2, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" +inline-style-prefixer@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz#8551b8e5b4d573244e66a34b04f7d32076a2b534" + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inquirer@^0.8.2: version "0.8.5" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.5.tgz#dbd740cf6ca3b731296a63ce6f6d961851f336df" @@ -2962,11 +3982,17 @@ inquirer@^3.2.2: strip-ansi "^4.0.0" through "^2.3.6" +internal-ip@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" -invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2976,6 +4002,18 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3056,7 +4094,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3096,10 +4134,20 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-hex-prefixed@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" +is-mobile@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/is-mobile/-/is-mobile-0.2.2.tgz#0e2e006d99ed2c2155b761df80f2a3619ae2ad9f" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -3122,6 +4170,22 @@ is-odd@^1.0.0: dependencies: is-number "^3.0.0" +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -3166,6 +4230,12 @@ is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -3188,6 +4258,10 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3210,7 +4284,7 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" -isomorphic-fetch@^2.2.0, isomorphic-fetch@^2.2.1: +isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.0, isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" dependencies: @@ -3268,6 +4342,14 @@ istanbul-reports@^1.1.3: dependencies: handlebars "^4.0.3" +js-base64@^2.1.9: + version "2.3.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" + +js-sha3@0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" + js-sha3@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" @@ -3287,6 +4369,20 @@ js-yaml@^3.6.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@~3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -3339,6 +4435,14 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d" + json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -3363,7 +4467,7 @@ jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" -jsonschema@*, jsonschema@^1.2.0: +jsonschema@*, jsonschema@^1.1.1, jsonschema@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.0.tgz#d6ebaf70798db7b3a20c544f6c9ef9319b077de2" @@ -3397,6 +4501,14 @@ keccakjs@^0.2.0: browserify-sha3 "^0.0.1" sha3 "^1.1.0" +keycode@^2.1.8: + version "2.1.9" + resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" + +killable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3443,6 +4555,14 @@ lcov-parse@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" +ledgerco@0xProject/ledger-node-js-api: + version "1.1.3" + resolved "https://codeload.github.com/0xProject/ledger-node-js-api/tar.gz/dc2024bac997bf023f12203f118d10ba84d15ded" + dependencies: + async "2.1.4" + node-hid "0.5.4" + q "1.4.1" + lerna@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/lerna/-/lerna-2.5.1.tgz#d07099bd3051ee799f98c753328bd69e96c6fab8" @@ -3486,6 +4606,25 @@ lerna@^2.5.1: write-pkg "^3.1.0" yargs "^8.0.2" +less-loader@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-2.2.3.tgz#b6d8f8139c8493df09d992a93a00734b08f84528" + dependencies: + loader-utils "^0.2.5" + +less@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + mime "^1.2.11" + mkdirp "^0.5.0" + promise "^7.1.1" + request "2.81.0" + source-map "^0.5.3" + level-codec@~7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.1.tgz#341f22f907ce0f16763f24bddd681e395a0fb8a7" @@ -3565,7 +4704,16 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^1.1.0: +loader-utils@0.2.x, loader-utils@^0.2.15, loader-utils@^0.2.5, loader-utils@~0.2.13, loader-utils@~0.2.2: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + +loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -3580,14 +4728,41 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash-es@^4.2.0, lodash-es@^4.2.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + +lodash._createcompounder@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz#5dd2cb55372d6e70e0e2392fb2304d6631091075" + dependencies: + lodash.deburr "^3.0.0" + lodash.words "^3.0.0" + lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" -lodash.assign@^4.0.3, lodash.assign@^4.0.6: +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + +lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.camelcase@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-3.0.1.tgz#932c8b87f8a4377897c67197533282f97aeac298" + dependencies: + lodash._createcompounder "^3.0.0" + +lodash.deburr@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5" + dependencies: + lodash._root "^3.0.0" + lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" @@ -3596,6 +4771,18 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.isplainobject@^4.0.6, lodash.isplainobject@~4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + lodash.template@^4.0.2: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" @@ -3609,15 +4796,29 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" +lodash.words@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-3.2.0.tgz#4e2a8649bc08745b17c695b1a3ce8fee596623b3" + dependencies: + lodash._root "^3.0.0" + lodash@^3.3.1, lodash@^3.6.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0: +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3625,6 +4826,10 @@ log-driver@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" +loglevel@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.0.tgz#ae0caa561111498c5ba13723d6fb631d24003934" + lolex@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" @@ -3637,7 +4842,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -3669,6 +4874,10 @@ ltgt@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.0.tgz#b65ba5fcb349a29924c8e333f7c6a5562f2e4842" +macaddress@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" + make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -3697,6 +4906,27 @@ marked@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +material-ui@^0.17.1: + version "0.17.4" + resolved "https://registry.yarnpkg.com/material-ui/-/material-ui-0.17.4.tgz#193999ecb49c3ec15ae0abb4e90fdf9a7bd343e0" + dependencies: + babel-runtime "^6.23.0" + inline-style-prefixer "^3.0.2" + keycode "^2.1.8" + lodash.merge "^4.6.0" + lodash.throttle "^4.1.1" + prop-types "^15.5.7" + react-addons-create-fragment "^15.4.0" + react-addons-transition-group "^15.4.0" + react-event-listener "^0.4.5" + recompose "^0.23.0" + simple-assign "^0.1.0" + warning "^3.0.0" + +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + md5-hex@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4" @@ -3714,6 +4944,14 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +"mdurl@~ 1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" @@ -3757,6 +4995,10 @@ meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + merge-source-map@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" @@ -3776,6 +5018,10 @@ merkle-patricia-tree@^2.1.2: rlp "^2.0.0" semaphore ">=1.0.1" +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -3819,17 +5065,21 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" +"mime-db@>= 1.30.0 < 2": + version "1.31.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb" + mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" -mime@^1.3.4: +mime@1.4.1, mime@^1.2.11, mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -3876,7 +5126,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^0.1.1" -mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3897,18 +5147,39 @@ mocha@^4.0.0, mocha@^4.0.1: mkdirp "0.5.1" supports-color "4.4.0" +modify-babel-preset@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/modify-babel-preset/-/modify-babel-preset-1.2.0.tgz#d1b7c8c24896e19dbc4847347213e6b7144d1bc7" + dependencies: + require-relative "^0.8.7" + modify-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2" -moment@^2.6.0: +moment@*, moment@^2.18.1, moment@^2.6.0: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.0.tgz#13f22d0c32dc5ee82a32878e3c380d875b3eab22" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e" @@ -3921,6 +5192,10 @@ nan@^2.0.5, nan@^2.0.8, nan@^2.2.1, nan@^2.3.0, nan@^2.3.3: version "2.7.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" +nan@^2.4.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + nanomatch@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.5.tgz#5c9ab02475c76676275731b0bf0a7395c624a9c4" @@ -3937,6 +5212,10 @@ nanomatch@^1.2.5: snapdragon "^0.8.1" to-regex "^3.0.1" +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + nise@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" @@ -3953,6 +5232,12 @@ node-abi@^2.1.1: dependencies: semver "^5.4.1" +node-dir@^0.1.10: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + dependencies: + minimatch "^3.0.2" + node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -3960,6 +5245,17 @@ node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@~1.7.1: encoding "^0.1.11" is-stream "^1.0.1" +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" + +node-hid@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.5.4.tgz#a7246dfc08d52774147fa264354d5da6eab40253" + dependencies: + nan "^2.4.0" + node-pre-gyp "0.6.31" + node-libs-browser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" @@ -3988,6 +5284,20 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-pre-gyp@0.6.31: + version "0.6.31" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.31.tgz#d8a00ddaa301a940615dbcc8caad4024d58f6017" + dependencies: + mkdirp "~0.5.1" + nopt "~3.0.6" + npmlog "^4.0.0" + rc "~1.1.6" + request "^2.75.0" + rimraf "~2.5.4" + semver "~5.3.0" + tar "~2.2.1" + tar-pack "~3.3.0" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -4022,6 +5332,12 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -4037,6 +5353,19 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npm-run-all@^4.0.2, npm-run-all@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056" @@ -4057,7 +5386,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: +npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: @@ -4066,10 +5395,21 @@ npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + nyc@^11.0.1: version "11.3.0" resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.3.0.tgz#a42bc17b3cfa41f7b15eb602bc98b2633ddd76f0" @@ -4149,12 +5489,32 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: +obuf@^1.0.0, obuf@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" +once@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + dependencies: + wrappy "1" + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -4178,6 +5538,12 @@ opn@^4.0.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +opn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" + dependencies: + is-wsl "^1.1.0" + optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -4185,6 +5551,12 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" +original@>=0.0.5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" + dependencies: + url-parse "1.0.x" + os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" @@ -4232,6 +5604,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + package-json@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" @@ -4294,6 +5670,10 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -4306,6 +5686,10 @@ path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" +path-exists@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -4320,6 +5704,10 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -4328,6 +5716,10 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" @@ -4406,14 +5798,274 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + pkginfo@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" +portfinder@^1.0.9: + version "1.0.13" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + dependencies: + postcss "^5.0.4" + uniqid "^4.0.0" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885" + dependencies: + chalk "^2.3.0" + source-map "^0.6.1" + supports-color "^4.4.0" + prebuild-install@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.3.0.tgz#19481247df728b854ab57b187ce234211311b485" @@ -4433,7 +6085,7 @@ prebuild-install@^2.0.0: tunnel-agent "^0.6.0" xtend "4.0.1" -prepend-http@^1.0.1: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -4464,6 +6116,10 @@ process@~0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" +progress-checkmark@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/progress-checkmark/-/progress-checkmark-0.0.1.tgz#a43248dfc1de42d6f235a06a00c168cdbe99efc8" + progress-stream@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" @@ -4482,6 +6138,27 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.0.0, prop-types@^15.5.1, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +proxy-addr@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.5.2" + prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" @@ -4543,19 +6220,30 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q@^1.4.1: +q@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + +q@^1.1.2, q@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" +qs@6.5.1, qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" -query-string@^5.0.1: +query-string@^5.0.0, query-string@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88" dependencies: @@ -4571,6 +6259,14 @@ querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" +querystringify@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" + +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -4591,6 +6287,23 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +range-parser@^1.0.3, range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-loader@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" + rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: version "1.2.2" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" @@ -4600,6 +6313,222 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@~1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-addons-create-fragment@^15.4.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-15.6.2.tgz#a394de7c2c7becd6b5475ba1b97ac472ce7c74f8" + dependencies: + fbjs "^0.8.4" + loose-envify "^1.3.1" + object-assign "^4.1.0" + +react-addons-transition-group@^15.4.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-transition-group/-/react-addons-transition-group-15.6.2.tgz#8baebc2ae91ccdbf245fe29c9fd3d36f8b471923" + dependencies: + react-transition-group "^1.2.0" + +react-copy-to-clipboard@^4.2.3: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.1.tgz#aa429ce6029077c987e2bc4af7eec9a09ba5075b" + dependencies: + copy-to-clipboard "^3" + create-react-class "^15.5.2" + prop-types "^15.5.8" + +react-document-title@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/react-document-title/-/react-document-title-2.0.3.tgz#bbf922a0d71412fc948245e4283b2412df70f2b9" + dependencies: + prop-types "^15.5.6" + react-side-effect "^1.0.2" + +react-dom@15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-dom@^15.5.4: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-event-listener@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.4.5.tgz#e3e895a0970cf14ee8f890113af68197abf3d0b1" + dependencies: + babel-runtime "^6.20.0" + fbjs "^0.8.4" + prop-types "^15.5.4" + warning "^3.0.0" + +react-highlight@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-highlight/-/react-highlight-0.10.0.tgz#d386f9dceab867dc0dcc2364153fb1cc7645d046" + dependencies: + highlight.js "^9.11.0" + react "^15.5.4" + react-dom "^15.5.4" + +react-html5video@^2.1.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-html5video/-/react-html5video-2.5.1.tgz#bbc4314be1d583cb991a11ca3480e70a3c964caf" + +react-inlinesvg@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-0.5.5.tgz#811c75decd392eaef682346751418c2fd911af42" + dependencies: + fbjs "^0.8" + httpplease "^0.16" + once "^1.4" + +react-markdown@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.1.tgz#f7a6c26a3a5faf5d4c2098155d9775e826fd56ee" + dependencies: + commonmark "^0.24.0" + commonmark-react-renderer "^4.3.4" + prop-types "^15.5.1" + +react-recaptcha@^2.3.2: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7" + +react-redux@^5.0.3: + version "5.0.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" + dependencies: + hoist-non-react-statics "^2.2.1" + invariant "^2.0.0" + lodash "^4.2.0" + lodash-es "^4.2.0" + loose-envify "^1.1.0" + prop-types "^15.5.10" + +react-router-dom@^4.1.1: + version "4.2.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" + dependencies: + history "^4.7.2" + invariant "^2.2.2" + loose-envify "^1.3.1" + prop-types "^15.5.4" + react-router "^4.2.0" + warning "^3.0.0" + +react-router-hash-link@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-1.1.1.tgz#7380cc7a8809243d94385924af52dc9d2c9fbaa1" + dependencies: + prop-types "^15.6.0" + +react-router@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.3.0" + invariant "^2.2.2" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.5.4" + warning "^3.0.0" + +react-scroll@^1.5.2: + version "1.6.4" + resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.6.4.tgz#c5bca461771bce460a1bfb19550350512b9d076b" + dependencies: + object-assign "^4.1.1" + prop-types "^15.5.8" + +react-side-effect@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.3.tgz#512c25abe0dec172834c4001ec5c51e04d41bc5c" + dependencies: + exenv "^1.2.1" + shallowequal "^1.0.1" + +react-svg-loader@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-svg-loader/-/react-svg-loader-1.1.1.tgz#ac9ffa098b620f26581a03594505244346c00d76" + dependencies: + babel-core "~6.13.2" + babel-plugin-syntax-jsx "~6.13.0" + babel-preset-es2015 "~6.13.2" + babel-preset-es2015-loose "~7.0.0" + babel-preset-react "~6.11.0" + js-yaml "~3.6.0" + loader-utils "~0.2.13" + lodash.isplainobject "~4.0.4" + svgo "~0.6.3" + yargs "~4.8.0" + +react-tap-event-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/react-tap-event-plugin/-/react-tap-event-plugin-2.0.1.tgz#316beb3bc6556e29ec869a7293e89c826a9074d2" + dependencies: + fbjs "^0.8.6" + +react-tooltip@^3.2.7: + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.4.0.tgz#037f38f797c3e6b1b58d2534ccc8c2c76af4f52d" + dependencies: + classnames "^2.2.5" + prop-types "^15.6.0" + +react-transition-group@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6" + dependencies: + chain-function "^1.0.0" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.6" + warning "^3.0.0" + +react-waypoint@^7.0.4: + version "7.3.1" + resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-7.3.1.tgz#abb165d9b6c9590f8d82ceafbe61c2c887262a37" + dependencies: + consolidated-events "^1.1.0" + prop-types "^15.0.0" + +react@15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react@^15.5.4: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + read-cmd-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" @@ -4680,6 +6609,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@~2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + dependencies: + buffer-shims "^1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -4702,6 +6643,15 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recompose@^0.23.0: + version "0.23.5" + resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.23.5.tgz#72ac8261246bec378235d187467d02a721e8b1de" + dependencies: + change-emitter "^0.1.2" + fbjs "^0.8.1" + hoist-non-react-statics "^1.0.0" + symbol-observable "^1.0.4" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -4709,6 +6659,29 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + +redux@*, redux@^3.6.0: + version "3.7.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + dependencies: + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" + regenerate@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" @@ -4737,6 +6710,14 @@ regex-not@^1.0.0: dependencies: extend-shallow "^2.0.1" +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -4827,7 +6808,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.54.0, request@^2.67.0, request@^2.79.0, request@^2.81.0: +request@^2.54.0, request@^2.67.0, request@^2.75.0, request@^2.79.0, request@^2.81.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -4866,10 +6847,32 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" +require-relative@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + +requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -4905,6 +6908,12 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2. dependencies: glob "^7.0.5" +rimraf@~2.5.1, rimraf@~2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" + dependencies: + glob "^7.0.5" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" @@ -4940,7 +6949,7 @@ rx@^2.4.3: version "2.5.3" resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4948,6 +6957,23 @@ samsam@1.x: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" +sax@^1.2.4, sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +scroll-to-element@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/scroll-to-element/-/scroll-to-element-2.0.0.tgz#3467330e3384743b7295ac64b30279990c5ac164" + dependencies: + scroll-to "0.0.2" + +scroll-to@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/scroll-to/-/scroll-to-0.0.2.tgz#936d398a9133660a2492145c2c0081dfcb0728f3" + dependencies: + component-raf "1.2.0" + component-tween "1.2.0" + scrypt.js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" @@ -4981,6 +7007,16 @@ secp256k1@^3.0.1: prebuild-install "^2.0.0" safe-buffer "^5.1.0" +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + +selfsigned@^1.9.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" + dependencies: + node-forge "0.6.33" + semaphore@>=1.0.1, semaphore@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" @@ -4989,7 +7025,7 @@ semver-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" -semver-sort@^0.0.4: +semver-sort@0.0.4, semver-sort@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/semver-sort/-/semver-sort-0.0.4.tgz#34fdbddc6a6b2b4161398c3c4dba56243bfeaa8b" dependencies: @@ -5000,6 +7036,49 @@ semver-sort@^0.0.4: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +send@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" + dependencies: + debug "2.6.9" + depd "~1.1.1" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-index@^1.7.2: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.1" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -5032,10 +7111,18 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.9" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" @@ -5049,6 +7136,10 @@ sha3@^1.1.0: dependencies: nan "^2.0.5" +shallowequal@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -5088,6 +7179,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simple-assign@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/simple-assign/-/simple-assign-0.1.0.tgz#17fd3066a5f3d7738f50321bb0f14ca281cc4baa" + simple-get@^1.4.2: version "1.4.3" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-1.4.3.tgz#e9755eda407e96da40c5e5158c9ea37b33becbeb" @@ -5159,6 +7254,24 @@ sntp@2.x.x: dependencies: hoek "4.x.x" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sockjs@0.3.18: + version "0.3.18" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207" + dependencies: + faye-websocket "^0.10.0" + uuid "^2.0.2" + solc@^0.4.2: version "0.4.18" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.18.tgz#83ac6d871dd16a9710e67dbb76dad7f614100702" @@ -5169,16 +7282,34 @@ solc@^0.4.2: semver "^5.3.0" yargs "^4.7.1" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" dependencies: is-plain-obj "^1.0.0" +source-list-map@^0.1.4: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-map-loader@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.1.6.tgz#c09903da6d73b9e53b7ed8ee5245597051e98e91" + dependencies: + async "^0.9.0" + loader-utils "~0.2.2" + source-map "~0.1.33" + source-map-resolve@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" @@ -5211,17 +7342,23 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" +source-map@0.1.x, source-map@~0.1.33: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: +source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@^0.6.0, source-map@~0.6.1: +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -5250,6 +7387,29 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdy-transport@^2.0.18: + version "2.0.20" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" + dependencies: + debug "^2.6.8" + detect-node "^2.0.3" + hpack.js "^2.1.6" + obuf "^1.1.1" + readable-stream "^2.2.9" + safe-buffer "^5.0.1" + wbuf "^1.7.2" + +spdy@^3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" + dependencies: + debug "^2.6.8" + handle-thing "^1.2.5" + http-deceiver "^1.2.7" + safe-buffer "^5.0.1" + select-hose "^2.0.0" + spdy-transport "^2.0.18" + speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" @@ -5303,6 +7463,14 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +"statuses@>= 1.3.1 < 2": + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -5363,6 +7531,10 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" +string.prototype.repeat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + string.prototype.trim@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" @@ -5443,6 +7615,12 @@ strong-log-transformer@^1.0.6: moment "^2.6.0" through "^2.3.4" +style-loader@0.13.x: + version "0.13.2" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb" + dependencies: + loader-utils "^1.0.2" + supports-color@4.4.0, supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" @@ -5453,13 +7631,41 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.2: +supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -tapable@^0.2.5, tapable@^0.2.7: +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +svgo@~0.6.3: + version "0.6.6" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.6.6.tgz#b340889036f20f9b447543077d0f5573ed044c08" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.0.0" + js-yaml "~3.6.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +symbol-observable@^1.0.3, symbol-observable@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" + +tapable@^0.2.5, tapable@^0.2.7, tapable@~0.2.5: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" @@ -5503,6 +7709,19 @@ tar-pack@^3.4.0: tar "^2.2.1" uid-number "^0.0.6" +tar-pack@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae" + dependencies: + debug "~2.2.0" + fstream "~1.0.10" + fstream-ignore "~1.0.5" + once "~1.3.3" + readable-stream "~2.1.4" + rimraf "~2.5.1" + tar "~2.2.1" + uid-number "~0.0.6" + tar-stream@^1.1.2: version "1.5.4" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016" @@ -5512,7 +7731,7 @@ tar-stream@^1.1.2: readable-stream "^2.0.0" xtend "^4.0.0" -tar@^2.2.1: +tar@^2.2.1, tar@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -5571,6 +7790,10 @@ text-extensions@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39" +thenby@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/thenby/-/thenby-1.2.3.tgz#62465b07e3d8b9466f01026df837f738e0faaa69" + through2@^2.0.0, through2@^2.0.1, through2@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -5596,6 +7819,14 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, t version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -5641,6 +7872,10 @@ to-regex@^3.0.1: extend-shallow "^2.0.1" regex-not "^1.0.0" +toggle-selection@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + tough-cookie@>=2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" @@ -5663,6 +7898,27 @@ trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" +truffle-blockchain-utils@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/truffle-blockchain-utils/-/truffle-blockchain-utils-0.0.1.tgz#07a58e55bb0555a64208c9119c0b04ffe1464aa4" + dependencies: + web3 "^0.18.0" + +truffle-contract-schema@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/truffle-contract-schema/-/truffle-contract-schema-0.0.5.tgz#5e9d20bd0bf2a27fe94310748249d484eee49961" + dependencies: + crypto-js "^3.1.9-1" + +truffle-contract@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-2.0.1.tgz#f83e3f18d8044027f2a9ee7c33767ba10fd39dd8" + dependencies: + ethjs-abi "0.1.8" + truffle-blockchain-utils "0.0.1" + truffle-contract-schema "0.0.5" + web3 "^0.18.0" + truffle-hdwallet-provider@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider/-/truffle-hdwallet-provider-0.0.3.tgz#0e1de02104b73d3875e1cf7093305b4ea8a2d843" @@ -5676,7 +7932,13 @@ tslib@^1.7.1: version "1.8.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" -tslint-react@^3.2.0: +tslint-config-0xproject@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/tslint-config-0xproject/-/tslint-config-0xproject-0.0.2.tgz#39901e0c0b3e9388f00092a28b90c015395d5bba" + dependencies: + tslint-react "^3.0.0" + +tslint-react@^3.0.0, tslint-react@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.2.0.tgz#851fb505201c63d0343c51726e6364f7e9ad2e99" dependencies: @@ -5722,6 +7984,13 @@ type-detect@^4.0.0: version "4.0.5" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + typedarray-to-buffer@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz#1017b32d984ff556eba100f501589aba1ace2e04" @@ -5776,10 +8045,14 @@ typescript@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" -typescript@2.6.1, typescript@^2.4.2, typescript@~2.6.1: +typescript@2.6.1, typescript@^2.4.1, typescript@^2.4.2, typescript@~2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -5801,7 +8074,7 @@ uglifyjs-webpack-plugin@^0.4.6: uglify-js "^2.8.29" webpack-sources "^1.0.1" -uid-number@^0.0.6: +uid-number@^0.0.6, uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -5814,6 +8087,20 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqid@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" + dependencies: + macaddress "^0.2.8" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + universalify@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" @@ -5822,6 +8109,10 @@ unorm@^1.3.3: version "1.4.1" resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -5847,6 +8138,20 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" +url-parse@1.0.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" + dependencies: + querystringify "0.0.x" + requires-port "1.0.x" + +url-parse@^1.1.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" + dependencies: + querystringify "~1.0.0" + requires-port "~1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -5854,6 +8159,18 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +urlencode@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/urlencode/-/urlencode-1.1.0.tgz#1f2ba26f013c85f0133f7a3ad6ff2730adf7cbb7" + dependencies: + iconv-lite "~0.4.11" + +urllite@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/urllite/-/urllite-0.5.0.tgz#1b7bb9ca3fb0db9520de113466bbcf7cc341451a" + dependencies: + xtend "~4.0.0" + use@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" @@ -5876,11 +8193,15 @@ util@0.10.3, util@^0.10.3: dependencies: inherits "2.0.1" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + uuid@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" -uuid@^2.0.1: +uuid@^2.0.1, uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" @@ -5899,6 +8220,18 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -5913,7 +8246,13 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" -watchpack@^1.4.0: +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + dependencies: + loose-envify "^1.0.0" + +watchpack@^1.3.1, watchpack@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: @@ -5921,12 +8260,40 @@ watchpack@^1.4.0: chokidar "^1.7.0" graceful-fs "^4.1.2" +wbuf@^1.1.0, wbuf@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe" + dependencies: + minimalistic-assert "^1.0.0" + wcwidth@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" dependencies: defaults "^1.0.3" +web3-provider-engine@^11.0.0: + version "11.0.2" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-11.0.2.tgz#d63dae73ecb37d141b4d63431766c22ff406f32f" + dependencies: + async "^2.1.2" + clone "^2.0.0" + eth-sig-util "^1.2.1" + ethereumjs-block "^1.2.2" + ethereumjs-tx "^1.2.0" + ethereumjs-util "^5.1.1" + ethereumjs-vm "^2.0.2" + fetch-ponyfill "^4.0.0" + json-rpc-error "^2.0.0" + promise-to-callback "^1.0.0" + request "^2.67.0" + semaphore "^1.0.3" + solc "^0.4.2" + tape "^4.4.0" + web3 "^0.16.0" + xhr "^2.2.0" + xtend "^4.0.1" + web3-provider-engine@^13.0.1: version "13.3.3" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.3.3.tgz#1f6df4ec540161125f3e5f501bb0e56af83a01df" @@ -5985,7 +8352,7 @@ web3@^0.16.0: utf8 "^2.1.1" xmlhttprequest "*" -web3@^0.18.2: +web3@^0.18.0, web3@^0.18.2: version "0.18.4" resolved "https://registry.yarnpkg.com/web3/-/web3-0.18.4.tgz#81ec1784145491f2eaa8955b31c06049e07c5e7d" dependencies: @@ -6005,6 +8372,48 @@ web3@^0.20.0: xhr2 "*" xmlhttprequest "*" +webpack-dev-middleware@^1.10.0, webpack-dev-middleware@^1.11.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" + dependencies: + memory-fs "~0.4.1" + mime "^1.3.4" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-dev-server@^2.5.0: + version "2.9.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.4.tgz#7883e61759c6a4b33e9b19ec4037bd4ab61428d1" + dependencies: + ansi-html "0.0.7" + array-includes "^3.0.3" + bonjour "^3.5.0" + chokidar "^1.6.0" + compression "^1.5.2" + connect-history-api-fallback "^1.3.0" + debug "^3.1.0" + del "^3.0.0" + express "^4.13.3" + html-entities "^1.2.0" + http-proxy-middleware "~0.17.4" + import-local "^0.1.1" + internal-ip "1.2.0" + ip "^1.1.5" + killable "^1.0.0" + loglevel "^1.4.1" + opn "^5.1.0" + portfinder "^1.0.9" + selfsigned "^1.9.1" + serve-index "^1.7.2" + sockjs "0.3.18" + sockjs-client "1.1.4" + spdy "^3.4.1" + strip-ansi "^3.0.1" + supports-color "^4.2.1" + webpack-dev-middleware "^1.11.0" + yargs "^6.6.0" + webpack-sources@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.2.tgz#d0148ec083b3b5ccef1035a6b3ec16442983b27a" @@ -6012,6 +8421,33 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.6.1" +webpack@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.1.0.tgz#ac0675e500db835f9ab2369d29ba096f51ad0731" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^5.1.5" + ajv-keywords "^2.0.0" + async "^2.1.2" + enhanced-resolve "^3.3.0" + escope "^3.6.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^3.1.0" + tapable "~0.2.5" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.3.1" + webpack-sources "^1.0.1" + yargs "^6.0.0" + webpack@^3.0.0, webpack@^3.1.0: version "3.8.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83" @@ -6039,6 +8475,17 @@ webpack@^3.0.0, webpack@^3.1.0: webpack-sources "^1.0.1" yargs "^8.0.2" +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + websocket@^1.0.25: version "1.0.25" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.25.tgz#998ec790f0a3eacb8b08b50a4350026692a11958" @@ -6048,10 +8495,14 @@ websocket@^1.0.25: typedarray-to-buffer "^3.1.2" yaeti "^0.0.6" -whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -6146,10 +8597,20 @@ xhr@^2.2.0: parse-headers "^2.0.0" xtend "^4.0.0" +xml-js@^1.3.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.5.2.tgz#234780d012b49a19093dbd464228d06303fddc23" + dependencies: + sax "^1.2.4" + xmlhttprequest@*: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" +xss-filters@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a" + xtend@4.0.1, "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6160,6 +8621,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" +xtend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -6179,6 +8644,12 @@ yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" @@ -6208,7 +8679,7 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.0.0" -yargs@^4.7.1: +yargs@^4.7.1, yargs@~4.8.0: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" dependencies: @@ -6227,6 +8698,24 @@ yargs@^4.7.1: y18n "^3.2.1" yargs-parser "^2.4.1" +yargs@^6.0.0, yargs@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" -- cgit v1.2.3 From cb377f29c2dc76a089a97c752888fa5c5b4a001f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:20:24 -0600 Subject: Fix build bug --- packages/website/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 15e764c02..5b3510c26 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -3,6 +3,7 @@ "allowSyntheticDefaultImports": true, "outDir": "./transpiled/", "sourceMap": true, + "lib": [ "es2015", "dom" ], "noImplicitAny": true, "module": "commonjs", "target": "es5", -- cgit v1.2.3 From 353b6f3d6df1e13ba76f86287195f7c0ce7cecc7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:20:35 -0600 Subject: add bundles to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b461f33de..68773f3c2 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,5 @@ _bundles docs/ TODO.md + +packages/website/public/bundle* -- cgit v1.2.3 From f5e0fd8de5a3201ac17a8dde68aec3314674f1c4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:21:07 -0600 Subject: Upgrade to latest 0x.js version and refactor subscriptions to use latest interface --- packages/website/package.json | 2 +- packages/website/ts/blockchain.ts | 64 ++++++++++++++-------- .../ts/local_storage/trade_history_storage.tsx | 14 ++++- yarn.lock | 4 -- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/packages/website/package.json b/packages/website/package.json index ba71ee0eb..d6d92d0f8 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -16,6 +16,7 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { + "0x.js": "^0.26.0", "accounting": "^0.4.1", "basscss": "^8.0.3", "bignumber.js": "~4.1.0", @@ -69,7 +70,6 @@ "@types/accounting": "^0.4.1", "@types/dateformat": "^1.0.1", "@types/deep-equal": "^1.0.0", - "@types/es6-promise": "0.0.32", "@types/jsonschema": "^1.1.1", "@types/lodash": "^4.14.55", "@types/material-ui": "0.18.0", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index d891acdb6..3a1b62164 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -27,7 +27,7 @@ import contract = require('truffle-contract'); import ethUtil = require('ethereumjs-util'); import ProviderEngine = require('web3-provider-engine'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); -import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted'; +import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed'; import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider'; @@ -496,7 +496,16 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - await this.addFillEventToTradeHistoryAsync(decodedLogEvent); + if (this.doesLogEventInvolveUser(decodedLogEvent)) { + return; // We aren't interested in the fill event + } + this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLogEvent); + if (decodedLogEvent.removed) { + tradeHistoryStorage.removeFillFromUser(this.userAddress, this.networkId, fill); + } else { + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + } } }); } @@ -510,30 +519,16 @@ export class Blockchain { ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, ); for (const decodedLog of decodedLogs) { - console.log('decodedLog', decodedLog); - await this.addFillEventToTradeHistoryAsync(decodedLog); + if (this.doesLogEventInvolveUser(decodedLog)) { + continue; // We aren't interested in the fill event + } + this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLog); + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); } } - private async addFillEventToTradeHistoryAsync(decodedLog: LogWithDecodedArgs) { + private async convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs) { const args = decodedLog.args as LogFillContractEventArgs; - const isUserMakerOrTaker = args.maker === this.userAddress || - args.taker === this.userAddress; - if (!isUserMakerOrTaker) { - return; // We aren't interested in the fill event - } - const isBlockPending = _.isNull(decodedLog.blockNumber); - if (!isBlockPending) { - // Hack: I've observed the behavior where a client won't register certain fill events - // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply - // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user - // would still attempt to re-fetch events from the previous 50 blocks, but won't need to - // re-fetch all events in all blocks. - // TODO: Debug if this is a race condition, and apply a more precise fix - const blockNumberToSet = decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? - 0 : - decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK; - tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); - } const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); const fill = { filledTakerTokenAmount: args.filledTakerTokenAmount, @@ -549,7 +544,28 @@ export class Blockchain { transactionHash: decodedLog.transactionHash, blockTimestamp, }; - tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + return fill; + } + private doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs) { + const args = decodedLog.args as LogFillContractEventArgs; + const isUserMakerOrTaker = args.maker === this.userAddress || + args.taker === this.userAddress; + return isUserMakerOrTaker; + } + private updateLatestFillsBlockIfNeeded(blockNumber: number) { + const isBlockPending = _.isNull(blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? + 0 : + blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); + } } private async stopWatchingExchangeLogFillEventsAsync() { this.zeroEx.exchange.unsubscribeAll(); diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index 415b380b7..dd5872609 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -31,13 +31,25 @@ export const tradeHistoryStorage = { const fillHash = this._getFillHash(fill); const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); if (doesFillExist) { - return; + return; // noop } fillsByHash[fillHash] = fill; const userFillsJSONString = JSON.stringify(fillsByHash); const userFillsKey = this._getUserFillsKey(userAddress, networkId); localStorage.setItem(userFillsKey, userFillsJSONString); }, + removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (!doesFillExist) { + return; // noop + } + delete fillsByHash[fillHash]; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, getUserFillsByHash(userAddress: string, networkId: number): {[fillHash: string]: Fill} { const userFillsKey = this._getUserFillsKey(userAddress, networkId); const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); diff --git a/yarn.lock b/yarn.lock index 71fa75f7a..6b7bb615d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,10 +14,6 @@ version "1.0.1" resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" -"@types/es6-promise@0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@types/es6-promise/-/es6-promise-0.0.32.tgz#3bcf44fb1e429f3df76188c8c6d874463ba371fd" - "@types/fetch-mock@^5.12.1": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" -- cgit v1.2.3 From 805a055946e6b4285e7db9e30263f4f7f41dd4eb Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 16:43:17 -0600 Subject: Force the clearance of tradeHistory --- packages/website/ts/utils/configs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 49ac4b5e0..63fcd27b6 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -13,6 +13,6 @@ export const configs = { symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], // WARNING: ZRX & WETH MUST always be default trackedTokens defaultTrackedTokenSymbols: ['WETH', 'ZRX'], - lastLocalStorageFillClearanceDate: '2017-09-09', + lastLocalStorageFillClearanceDate: '2017-11-22', isMainnetEnabled: true, }; -- cgit v1.2.3 From bcc5d635169e3202a5c46cb2995ff51c8482a36c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:56:16 -0600 Subject: Fix bug --- packages/website/ts/blockchain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 3a1b62164..9be764917 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -496,7 +496,7 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - if (this.doesLogEventInvolveUser(decodedLogEvent)) { + if (!this.doesLogEventInvolveUser(decodedLogEvent)) { return; // We aren't interested in the fill event } this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); @@ -519,7 +519,7 @@ export class Blockchain { ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, ); for (const decodedLog of decodedLogs) { - if (this.doesLogEventInvolveUser(decodedLog)) { + if (!this.doesLogEventInvolveUser(decodedLog)) { continue; // We aren't interested in the fill event } this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); -- cgit v1.2.3 From a42e8ae873d075c9013ed71d261c1c4f33715a00 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:56:34 -0600 Subject: Add missing keys --- packages/website/ts/components/token_balances.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index c552d19dc..2d533a9f2 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -132,6 +132,7 @@ export class TokenBalances extends React.Component Date: Thu, 23 Nov 2017 13:56:42 -0600 Subject: Fix alignment --- packages/website/ts/redux/dispatcher.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 6badf95bd..566ab8a01 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -107,25 +107,25 @@ export class Dispatcher { public encounteredBlockchainError(err: BlockchainErrs) { this.dispatch({ data: err, - type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, + type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, }); } public updateBlockchainIsLoaded(isLoaded: boolean) { this.dispatch({ data: isLoaded, - type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, + type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, }); } public addTokenToTokenByAddress(token: Token) { this.dispatch({ data: token, - type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, + type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, }); } public removeTokenToTokenByAddress(token: Token) { this.dispatch({ data: token, - type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, + type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, }); } public clearTokenByAddress() { @@ -136,13 +136,13 @@ export class Dispatcher { public updateTokenByAddress(tokens: Token[]) { this.dispatch({ data: tokens, - type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, + type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, }); } public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { this.dispatch({ data: tokenStateByAddress, - type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, + type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, }); } public removeFromTokenStateByAddress(tokenAddress: string) { @@ -181,19 +181,19 @@ export class Dispatcher { public updateSignatureData(signatureData: SignatureData) { this.dispatch({ data: signatureData, - type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, + type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, }); } public updateUserEtherBalance(balance: BigNumber) { this.dispatch({ data: balance, - type: ActionTypes.UPDATE_USER_ETHER_BALANCE, + type: ActionTypes.UPDATE_USER_ETHER_BALANCE, }); } public updateNetworkId(networkId: number) { this.dispatch({ data: networkId, - type: ActionTypes.UPDATE_NETWORK_ID, + type: ActionTypes.UPDATE_NETWORK_ID, }); } public updateOrderFillAmount(amount: BigNumber) { -- cgit v1.2.3 From 6a9f10cd36e254bfb43f13e5728b5346823b5cfa Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:57:01 -0600 Subject: Add error when unexpected condition hit --- packages/website/ts/components/trade_history/trade_history_item.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 96b755e3c..58bdf84ae 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -122,6 +122,9 @@ export class TradeHistoryItem extends React.Component Date: Thu, 23 Nov 2017 13:58:27 -0600 Subject: update tokenByAddress after tokenStateByAddress to avoid race-condition --- packages/website/ts/components/generate_order/asset_picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 59826d06e..410b0440a 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -269,7 +269,6 @@ export class AssetPicker extends React.Component Date: Thu, 23 Nov 2017 14:01:23 -0600 Subject: Add exit code 0 because it is expected that uglifying the code throws an error --- packages/website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/package.json b/packages/website/package.json index d6d92d0f8..f91b579bd 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "description": "Website and 0x portal dapp", "scripts": { - "build": "NODE_ENV=production webpack", + "build": "NODE_ENV=production webpack; exit 0;", "clean": "shx rm -f public/bundle*", "dev": "webpack-dev-server --content-base public --https", "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", -- cgit v1.2.3 From 5068f1666a9d1dec2140f2a98e96bb880e854105 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 15:20:20 -0600 Subject: Remove `Earliest` from blockParamLiterals since specifying block 0 is trivial and this type can be reused in situations where the options are pending or latest --- packages/0x.js/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index c3aabfd86..595b90482 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -350,7 +350,6 @@ export interface IndexedFilterValues { export enum BlockParamLiteral { Latest = 'latest', - Earliest = 'earliest', Pending = 'pending', } -- cgit v1.2.3 From b7b1721145f3894ade757a2ad4c366e2b6eadbe9 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 15:21:45 -0600 Subject: Pass 'latest' to ExchangeTransferSimulator when used for validating orders, and pass 'pending' when used in the order watcher. --- .../0x.js/src/contract_wrappers/exchange_wrapper.ts | 17 +++++++++-------- packages/0x.js/src/order_watcher/order_state_watcher.ts | 4 +++- .../src/stores/balance_proxy_allowance_lazy_store.ts | 8 +++++--- packages/0x.js/src/utils/exchange_transfer_simulator.ts | 4 ++-- packages/0x.js/test/exchange_transfer_simulator_test.ts | 2 +- packages/0x.js/test/exchange_wrapper_test.ts | 2 +- packages/0x.js/test/order_validation_test.ts | 4 ++-- packages/0x.js/test/token_wrapper_test.ts | 2 +- 8 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 3e631b73e..7c33dc6ec 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -29,6 +29,7 @@ import { EventCallback, ExchangeContractEventArgs, DecodedLogArgs, + BlockParamLiteral, } from '../types'; import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; @@ -178,7 +179,7 @@ export class ExchangeWrapper extends ContractWrapper { orderTransactionOpts.shouldValidate; if (shouldValidate) { const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); } @@ -250,7 +251,7 @@ export class ExchangeWrapper extends ContractWrapper { orderTransactionOpts.shouldValidate; if (shouldValidate) { const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); for (const signedOrder of signedOrders) { await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); @@ -340,7 +341,7 @@ export class ExchangeWrapper extends ContractWrapper { orderTransactionOpts.shouldValidate; if (shouldValidate) { const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); for (const orderFillRequest of orderFillRequests) { await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, @@ -420,7 +421,7 @@ export class ExchangeWrapper extends ContractWrapper { orderTransactionOpts.shouldValidate; if (shouldValidate) { const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); } @@ -484,7 +485,7 @@ export class ExchangeWrapper extends ContractWrapper { orderTransactionOpts.shouldValidate; if (shouldValidate) { const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); for (const orderFillRequest of orderFillRequests) { await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, @@ -721,7 +722,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); const zrxTokenAddress = await this.getZRXTokenAddressAsync(); const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateOrderFillableOrThrowAsync( exchangeTradeEmulator, signedOrder, zrxTokenAddress, expectedFillTakerTokenAmount, ); @@ -741,7 +742,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); } @@ -775,7 +776,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); const zrxTokenAddress = await this.getZRXTokenAddressAsync(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); } diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 44a41669d..d02e31160 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -74,7 +74,9 @@ export class OrderStateWatcher { this._web3Wrapper = web3Wrapper; const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); - this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token); + this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( + token, BlockParamLiteral.Pending, + ); this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); this._orderStateUtils = new OrderStateUtils( this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore, diff --git a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts index c83e61606..7c94031c3 100644 --- a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts +++ b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts @@ -9,6 +9,7 @@ import {BlockParamLiteral} from '../types'; */ export class BalanceAndProxyAllowanceLazyStore { private token: TokenWrapper; + private defaultBlock: BlockParamLiteral; private balance: { [tokenAddress: string]: { [userAddress: string]: BigNumber, @@ -19,15 +20,16 @@ export class BalanceAndProxyAllowanceLazyStore { [userAddress: string]: BigNumber, }, }; - constructor(token: TokenWrapper) { + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { this.token = token; + this.defaultBlock = defaultBlock; this.balance = {}; this.proxyAllowance = {}; } public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise { if (_.isUndefined(this.balance[tokenAddress]) || _.isUndefined(this.balance[tokenAddress][userAddress])) { const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, + defaultBlock: this.defaultBlock, }; const balance = await this.token.getBalanceAsync(tokenAddress, userAddress, methodOpts); this.setBalance(tokenAddress, userAddress, balance); @@ -53,7 +55,7 @@ export class BalanceAndProxyAllowanceLazyStore { if (_.isUndefined(this.proxyAllowance[tokenAddress]) || _.isUndefined(this.proxyAllowance[tokenAddress][userAddress])) { const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, + defaultBlock: this.defaultBlock, }; const proxyAllowance = await this.token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); diff --git a/packages/0x.js/src/utils/exchange_transfer_simulator.ts b/packages/0x.js/src/utils/exchange_transfer_simulator.ts index 308ef06db..eeb6081cb 100644 --- a/packages/0x.js/src/utils/exchange_transfer_simulator.ts +++ b/packages/0x.js/src/utils/exchange_transfer_simulator.ts @@ -35,8 +35,8 @@ const ERR_MSG_MAPPING = { export class ExchangeTransferSimulator { private store: BalanceAndProxyAllowanceLazyStore; private UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber; - constructor(token: TokenWrapper) { - this.store = new BalanceAndProxyAllowanceLazyStore(token); + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { + this.store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock); this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; } /** diff --git a/packages/0x.js/test/exchange_transfer_simulator_test.ts b/packages/0x.js/test/exchange_transfer_simulator_test.ts index 99cb7fb4f..ec2c045fc 100644 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ b/packages/0x.js/test/exchange_transfer_simulator_test.ts @@ -37,7 +37,7 @@ describe('ExchangeTransferSimulator', () => { }); describe('#transferFromAsync', () => { beforeEach(() => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token); + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); }); it('throws if the user doesn\'t have enough allowance', async () => { return expect(exchangeTransferSimulator.transferFromAsync( diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index add89a3b2..b5fa47f8b 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -754,7 +754,7 @@ describe('ExchangeWrapper', () => { const fillableAmount = new BigNumber(5); const shouldThrowOnInsufficientBalanceOrAllowance = true; const subscriptionOpts: SubscriptionOpts = { - fromBlock: BlockParamLiteral.Earliest, + fromBlock: 0, toBlock: BlockParamLiteral.Latest, }; let txHash: string; diff --git a/packages/0x.js/test/order_validation_test.ts b/packages/0x.js/test/order_validation_test.ts index 4f18742d3..d689ef073 100644 --- a/packages/0x.js/test/order_validation_test.ts +++ b/packages/0x.js/test/order_validation_test.ts @@ -5,7 +5,7 @@ import * as Sinon from 'sinon'; import {chaiSetup} from './utils/chai_setup'; import {web3Factory} from './utils/web3_factory'; import {ZeroEx, SignedOrder, Token, ExchangeContractErrs, ZeroExError} from '../src'; -import {TradeSide, TransferType} from '../src/types'; +import {TradeSide, TransferType, BlockParamLiteral} from '../src/types'; import {TokenUtils} from './utils/token_utils'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; import {FillScenarios} from './utils/fill_scenarios'; @@ -215,7 +215,7 @@ describe('OrderValidation', () => { return Sinon.match((value: BigNumber) => value.eq(expected)); }; beforeEach('create exchangeTransferSimulator', async () => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token); + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); transferFromAsync = Sinon.spy(); exchangeTransferSimulator.transferFromAsync = transferFromAsync as any; }); diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts index 1a7cb9e40..27fe63590 100644 --- a/packages/0x.js/test/token_wrapper_test.ts +++ b/packages/0x.js/test/token_wrapper_test.ts @@ -426,7 +426,7 @@ describe('TokenWrapper', () => { let tokenAddress: string; let tokenTransferProxyAddress: string; const subscriptionOpts: SubscriptionOpts = { - fromBlock: BlockParamLiteral.Earliest, + fromBlock: 0, toBlock: BlockParamLiteral.Latest, }; let txHash: string; -- cgit v1.2.3 From cee0d2706f29561bfa85603721ae0c800d2a80ca Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 15:32:00 -0600 Subject: Add missing type --- packages/0x.js/test/exchange_transfer_simulator_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/0x.js/test/exchange_transfer_simulator_test.ts b/packages/0x.js/test/exchange_transfer_simulator_test.ts index ec2c045fc..43a6404a1 100644 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ b/packages/0x.js/test/exchange_transfer_simulator_test.ts @@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js'; import {chaiSetup} from './utils/chai_setup'; import {web3Factory} from './utils/web3_factory'; import {ZeroEx, ExchangeContractErrs, Token} from '../src'; -import {TradeSide, TransferType} from '../src/types'; +import {TradeSide, TransferType, BlockParamLiteral} from '../src/types'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; import {ExchangeTransferSimulator} from '../src/utils/exchange_transfer_simulator'; -- cgit v1.2.3 From ab78c54d6a6e8592c43b779b651cc7203de2e66e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 15:36:46 -0600 Subject: Add comment about BlockParamLiteral explaining the omission of Earliest --- packages/0x.js/src/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 7d85a54ca..143e2b6b2 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -351,6 +351,9 @@ export interface IndexedFilterValues { [index: string]: ContractEventArg; } +// Earliest is omitted by design. It is simply an alias for the `0` constant and +// is thus not very helpful. Moreover, this type is used in places that only accept +// `latest` or `pending`. export enum BlockParamLiteral { Latest = 'latest', Pending = 'pending', -- cgit v1.2.3 From f0b3ee84b47f1b7548ac688a4e44208be1b2eb1f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 15:56:42 -0600 Subject: Fix tests now that we no longer fire duplicate orderWatcher events --- packages/0x.js/test/order_state_watcher_test.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts index 834099ef6..e635071b8 100644 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ b/packages/0x.js/test/order_state_watcher_test.ts @@ -180,16 +180,12 @@ describe('OrderStateWatcher', () => { const orderHash = ZeroEx.getOrderHashHex(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder); - let eventCount = 0; const callback = reportCallbackErrors(done)((orderState: OrderState) => { - eventCount++; expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); - if (eventCount === 2) { - done(); - } + done(); }); zeroEx.orderStateWatcher.subscribe(callback); @@ -212,9 +208,7 @@ describe('OrderStateWatcher', () => { const orderHash = ZeroEx.getOrderHashHex(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder); - let eventCount = 0; const callback = reportCallbackErrors(done)((orderState: OrderState) => { - eventCount++; expect(orderState.isValid).to.be.true(); const validOrderState = orderState as OrderStateValid; expect(validOrderState.orderHash).to.be.equal(orderHash); @@ -226,9 +220,7 @@ describe('OrderStateWatcher', () => { expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( remainingFillable); expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); - if (eventCount === 2) { - done(); - } + done(); }); zeroEx.orderStateWatcher.subscribe(callback); const shouldThrowOnInsufficientBalanceOrAllowance = true; @@ -267,9 +259,7 @@ describe('OrderStateWatcher', () => { const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); const orderHash = ZeroEx.getOrderHashHex(signedOrder); await zeroEx.orderStateWatcher.addOrderAsync(signedOrder); - let eventCount = 0; const callback = reportCallbackErrors(done)((orderState: OrderState) => { - eventCount++; expect(orderState.isValid).to.be.true(); const validOrderState = orderState as OrderStateValid; expect(validOrderState.orderHash).to.be.equal(orderHash); @@ -278,9 +268,7 @@ describe('OrderStateWatcher', () => { ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals)); expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals)); - if (eventCount === 2) { - done(); - } + done(); }); zeroEx.orderStateWatcher.subscribe(callback); const shouldThrowOnInsufficientBalanceOrAllowance = true; -- cgit v1.2.3 From d3dcb2fd4070c71f408ce8fc0199b4d01091ab20 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 17:05:12 -0600 Subject: Add validation fix to changelog --- packages/0x.js/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index f33eab94e..e1f80b00d 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -3,6 +3,7 @@ vx.x.x ------------------------ * Make `DecodedLogEvent` contain `LogWithDecodedArgs` under log key instead of merging it in like web3 does (#234) + * Modify order validation methods to validate against the `latest` block, not against the `pending` block (#236) v0.26.0 ------------------------ -- cgit v1.2.3 From b5ce876327fe6443364837ea65cf28ec0e371949 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 17:35:53 -0600 Subject: Update CHANGELOG.md --- packages/0x.js/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index e1f80b00d..81dddea53 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -3,6 +3,7 @@ vx.x.x ------------------------ * Make `DecodedLogEvent` contain `LogWithDecodedArgs` under log key instead of merging it in like web3 does (#234) + * Rename `removed` to `isRemoved` in `DecodedLogEvent` (#234) * Modify order validation methods to validate against the `latest` block, not against the `pending` block (#236) v0.26.0 -- cgit v1.2.3 From ecfee00feca331ee1efa55165471d79774cb03d2 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 18:21:48 -0600 Subject: Fix subscriptions given latest changes to 0x.js --- packages/website/ts/blockchain.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 9be764917..b13c48a65 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -488,6 +488,7 @@ export class Blockchain { const subscriptionId = await this.zeroEx.exchange.subscribeAsync( ExchangeEvents.LogFill, indexFilterValues, async (err: Error, decodedLogEvent: DecodedLogEvent) => { + const decodedLog = decodedLogEvent.log; if (err) { // Note: it's not entirely clear from the documentation which // errors will be thrown by `watch`. For now, let's log the error @@ -496,12 +497,12 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - if (!this.doesLogEventInvolveUser(decodedLogEvent)) { + if (!this.doesLogEventInvolveUser(decodedLog)) { return; // We aren't interested in the fill event } - this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); - const fill = await this.convertDecodedLogToFillAsync(decodedLogEvent); - if (decodedLogEvent.removed) { + this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLog); + if (decodedLogEvent.isRemoved) { tradeHistoryStorage.removeFillFromUser(this.userAddress, this.networkId, fill); } else { tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); -- cgit v1.2.3 From 25ec4cf7b04bc58cf192566ea680d426dba2bff1 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 27 Nov 2017 10:25:53 -0600 Subject: Fix source links in 0x.js docs --- packages/website/ts/pages/documentation/source_link.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 2fb69e2f0..08d372743 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -8,10 +8,13 @@ interface SourceLinkProps { version: string; } +const SUB_PKG = '0x.js'; + export function SourceLink(props: SourceLinkProps) { - const source = props.source; - const githubUrl = constants.GITHUB_0X_JS_URL; - const sourceCodeUrl = `${githubUrl}/blob/v${props.version}/${source.fileName}#L${source.line}`; + const src = props.source; + const url = constants.GITHUB_0X_JS_URL; + // https://github.com/0xProject/0x.js/blob/0x.js%400.26.1/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts + const sourceCodeUrl = `${url}/blob/${SUB_PKG}%40${props.version}/packages/${SUB_PKG}/${src.fileName}#L${src.line}`; return (
Date: Mon, 27 Nov 2017 10:56:33 -0600 Subject: Fix overlapping arg names --- packages/website/ts/pages/documentation/method_block.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index 6fead2f47..e31c75ffd 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -145,8 +145,7 @@ export class MethodBlock extends React.Component -
-
+
{parameter.name}
-- cgit v1.2.3 From 3e5abc60d31097346ffc3444a709aa870840a8ae Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 27 Nov 2017 12:05:39 -0600 Subject: Add "Web3" prefix to web3 alias types and link to the correct line in the web3 typings --- packages/website/ts/pages/documentation/type.tsx | 11 ++++++++++- packages/website/ts/utils/constants.ts | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx index af18f97c2..7d02d6804 100644 --- a/packages/website/ts/pages/documentation/type.tsx +++ b/packages/website/ts/pages/documentation/type.tsx @@ -17,6 +17,14 @@ const typeToUrl: {[typeName: string]: string} = { Web3: constants.WEB3_DOCS_URL, Provider: constants.WEB3_PROVIDER_DOCS_URL, BigNumber: constants.BIGNUMBERJS_GITHUB_URL, + DecodedLogEntryEvent: constants.WEB3_DECODED_LOG_ENTRY_EVENT_URL, + LogEntryEvent: constants.WEB3_LOG_ENTRY_EVENT_URL, +}; + +const typePrefix: {[typeName: string]: string} = { + Provider: 'Web3', + DecodedLogEntryEvent: 'Web3', + LogEntryEvent: 'Web3', }; const typeToSection: {[typeName: string]: string} = { @@ -119,6 +127,7 @@ export function Type(props: TypeProps): any { }); const typeNameUrlIfExists = typeToUrl[(typeName as string)]; + const typePrefixIfExists = typePrefix[(typeName as string)]; const sectionNameIfExists = typeToSection[(typeName as string)]; if (!_.isUndefined(typeNameUrlIfExists)) { typeName = ( @@ -128,7 +137,7 @@ export function Type(props: TypeProps): any { className="text-decoration-none" style={{color: colors.lightBlueA700}} > - {typeName} + {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''}{typeName}
); } else if ((isReference || isArray) && diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 42b80795e..7fc52b035 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -78,7 +78,10 @@ export const constants = { ETH_DECIMAL_PLACES: 18, MINT_AMOUNT: new BigNumber('100000000000000000000'), WEB3_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', - WEB3_PROVIDER_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API#example-7', + WEB3_PROVIDER_DOCS_URL: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + WEB3_DECODED_LOG_ENTRY_EVENT_URL: + 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', + WEB3_LOG_ENTRY_EVENT_URL: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', ZEROEX_CHAT_URL: 'https://chat.0xproject.com', // Projects ETHFINEX_URL: 'https://www.bitfinex.com/ethfinex', -- cgit v1.2.3 From 4e031555885d708f6d8ab9f7ce608794261d48ba Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 27 Nov 2017 12:06:03 -0600 Subject: Add link to Provider explanation --- packages/0x.js/src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 143e2b6b2..4d55046b4 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -383,7 +383,8 @@ export type AsyncMethod = (...args: any[]) => Promise; /** * We re-export the `Web3.Provider` type specified in the Web3 Typescript typings * since it is the type of the `provider` argument to the `ZeroEx` constructor. - * It is however a `Web3` library type, not a native `0x.js` type. + * It is however a `Web3` library type, not a native `0x.js` type. To learn more + * about providers, visit https://0xproject.com/wiki#Web3-Provider-Explained */ export type Web3Provider = Web3.Provider; -- cgit v1.2.3 From e7b523074ab124f2d4e790d71aac9df7e1e2c4cf Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 27 Nov 2017 12:24:11 -0600 Subject: remove comment --- packages/website/ts/pages/documentation/source_link.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 08d372743..24009ce8a 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -13,7 +13,6 @@ const SUB_PKG = '0x.js'; export function SourceLink(props: SourceLinkProps) { const src = props.source; const url = constants.GITHUB_0X_JS_URL; - // https://github.com/0xProject/0x.js/blob/0x.js%400.26.1/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts const sourceCodeUrl = `${url}/blob/${SUB_PKG}%40${props.version}/packages/${SUB_PKG}/${src.fileName}#L${src.line}`; return (
-- cgit v1.2.3 From b04d07815f47018655b70b4152090b566f568af1 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 27 Nov 2017 12:10:26 -0800 Subject: Add md directory to website package and change generated docs directory --- .gitignore | 2 +- packages/0x.js/package.json | 2 +- packages/0x.js/scripts/postpublish.js | 3 ++- packages/connect/package.json | 2 +- packages/connect/scripts/postpublish.js | 3 ++- packages/website/md/docs/0xjs/async.md | 23 ++++++++++++++++ packages/website/md/docs/0xjs/errors.md | 1 + packages/website/md/docs/0xjs/installation.md | 31 ++++++++++++++++++++++ packages/website/md/docs/0xjs/introduction.md | 1 + packages/website/md/docs/0xjs/versioning.md | 1 + packages/website/md/docs/connect/installation.md | 15 +++++++++++ packages/website/md/docs/connect/introduction.md | 1 + .../md/docs/smart_contracts/introduction.md | 8 ++++++ scripts/postpublish_utils.js | 2 ++ 14 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 packages/website/md/docs/0xjs/async.md create mode 100644 packages/website/md/docs/0xjs/errors.md create mode 100644 packages/website/md/docs/0xjs/installation.md create mode 100644 packages/website/md/docs/0xjs/introduction.md create mode 100644 packages/website/md/docs/0xjs/versioning.md create mode 100644 packages/website/md/docs/connect/installation.md create mode 100644 packages/website/md/docs/connect/introduction.md create mode 100644 packages/website/md/docs/smart_contracts/introduction.md diff --git a/.gitignore b/.gitignore index 68773f3c2..11fbb8499 100644 --- a/.gitignore +++ b/.gitignore @@ -63,7 +63,7 @@ lib/ _bundles # generated documentation -docs/ +generated_docs/ TODO.md diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index afca00fe9..d60d7dabb 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -15,7 +15,7 @@ "prebuild": "npm run clean", "build": "run-p build:umd:prod build:commonjs; exit 0;", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", - "upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", "lint": "tslint src/**/*.ts test/**/*.ts", "test:circleci": "run-s test:coverage report_test_coverage && if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi", "test": "run-s clean test:commonjs", diff --git a/packages/0x.js/scripts/postpublish.js b/packages/0x.js/scripts/postpublish.js index ffc68afb4..28484a8bd 100644 --- a/packages/0x.js/scripts/postpublish.js +++ b/packages/0x.js/scripts/postpublish.js @@ -21,8 +21,9 @@ postpublish_utils.getLatestTagAndVersionAsync(subPackageName) }) .then(function(release) { console.log('POSTPUBLISH: Release successful, generating docs...'); + const jsonFilePath = __dirname + '/../' + postpublish_utils.generatedDocsDirectoryName + '/index.json'; return execAsync( - 'JSON_FILE_PATH=' + __dirname + '/../docs/index.json PROJECT_DIR=' + __dirname + '/.. yarn docs:json', + 'JSON_FILE_PATH=' + jsonFilePath + ' PROJECT_DIR=' + __dirname + '/.. yarn docs:json', { cwd, } diff --git a/packages/connect/package.json b/packages/connect/package.json index 9131eef14..00378397c 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -15,7 +15,7 @@ "build": "tsc", "clean": "shx rm -rf _bundles lib test_temp", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", - "upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", "copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures", "lint": "tslint src/**/*.ts test/**/*.ts", "run_mocha": "mocha lib/test/**/*_test.js", diff --git a/packages/connect/scripts/postpublish.js b/packages/connect/scripts/postpublish.js index ba0f8507d..e6a2cc065 100644 --- a/packages/connect/scripts/postpublish.js +++ b/packages/connect/scripts/postpublish.js @@ -17,8 +17,9 @@ postpublish_utils.getLatestTagAndVersionAsync(subPackageName) }) .then(function(release) { console.log('POSTPUBLISH: Release successful, generating docs...'); + const jsonFilePath = __dirname + '/../' + postpublish_utils.generatedDocsDirectoryName + '/index.json'; return execAsync( - 'JSON_FILE_PATH=' + __dirname + '/../docs/index.json PROJECT_DIR=' + __dirname + '/.. yarn docs:json', + 'JSON_FILE_PATH=' + jsonFilePath + ' PROJECT_DIR=' + __dirname + '/.. yarn docs:json', { cwd, } diff --git a/packages/website/md/docs/0xjs/async.md b/packages/website/md/docs/0xjs/async.md new file mode 100644 index 000000000..63924c76c --- /dev/null +++ b/packages/website/md/docs/0xjs/async.md @@ -0,0 +1,23 @@ +0x.js is a promise-based library. This means that whenever an asynchronous call is required, the library method will return a native Javascript promise. You can therefore choose between using `promise` or `async/await` syntax when calling our async methods. + +*Async/await syntax (recommended):* +```javascript +try { + var availableAddresses = await zeroEx.getAvailableAddressesAsync(); +} catch (error) { + console.log('Caught error: ', error); +} +``` + +*Promise syntax:* +```javascript +zeroEx.getAvailableAddressesAsync() + .then(function(availableAddresses) { + console.log(availableAddresses); + }) + .catch(function(error) { + console.log('Caught error: ', error); + }); +``` + +As is the convention with promise-based libraries, if an error occurs, it is thrown. It is the callers responsibility to catch thrown errors and to handle them appropriately. diff --git a/packages/website/md/docs/0xjs/errors.md b/packages/website/md/docs/0xjs/errors.md new file mode 100644 index 000000000..e97973ccf --- /dev/null +++ b/packages/website/md/docs/0xjs/errors.md @@ -0,0 +1 @@ +All error messages thrown by the 0x.js library are part of a documented string enum. Each error message is in all-caps, snake-case format. This makes the error messages easily identifiable, unique and grep-able. The error enums listing all possible errors the library could throw can be found in the `Types` section. diff --git a/packages/website/md/docs/0xjs/installation.md b/packages/website/md/docs/0xjs/installation.md new file mode 100644 index 000000000..457b27a04 --- /dev/null +++ b/packages/website/md/docs/0xjs/installation.md @@ -0,0 +1,31 @@ +0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package. + +#### CommonJS *(recommended)*: + +**Install** + +```bash +npm install 0x.js --save +``` + +**Import** + +```javascript +import {ZeroEx} from '0x.js'; +``` + +#### UMD: + +**Install** + +Download the UMD module from our [releases page](https://github.com/0xProject/0x.js/releases) and add it to your project. + +**Import** + +```html + +``` + +### Wiki + +Check out our [wiki](https://0xproject.com/wiki) for articles on how to get 0x.js setup with TestRPC, Infura and more! diff --git a/packages/website/md/docs/0xjs/introduction.md b/packages/website/md/docs/0xjs/introduction.md new file mode 100644 index 000000000..7bad3eaa9 --- /dev/null +++ b/packages/website/md/docs/0xjs/introduction.md @@ -0,0 +1 @@ +Welcome to the [0x.js](https://github.com/0xProject/0x.js) documentation! 0x.js is a Javascript library for interacting with the 0x protocol. With it, you can easily make calls to the 0x smart contracts as well as any ERC20 token. Functionality includes generating, signing, filling and cancelling orders, verifying an orders signature, setting or checking a users ERC20 token balance/allowance and much more. diff --git a/packages/website/md/docs/0xjs/versioning.md b/packages/website/md/docs/0xjs/versioning.md new file mode 100644 index 000000000..6bcaa5b4d --- /dev/null +++ b/packages/website/md/docs/0xjs/versioning.md @@ -0,0 +1 @@ +This project adheres to the [Semantic Versioning 2.0.0](http://semver.org/) specification. The library's public interface includes all the methods, properties and types included in the documentation. Since the library is still an alpha, it's public interface is not yet stable and we will introduce backward incompatible changes to the interface without incrementing the major version until the `1.0.0` release. Our convention until then will be to increment the minor version whenever we introduce backward incompatible changes to the public interface, and to increment the patch version otherwise. This way, it is safe for you to include 0x.js in your projects with the tilda (e.g `~0.22.0`) without fear that a patch update would break your app. diff --git a/packages/website/md/docs/connect/installation.md b/packages/website/md/docs/connect/installation.md new file mode 100644 index 000000000..2d3755352 --- /dev/null +++ b/packages/website/md/docs/connect/installation.md @@ -0,0 +1,15 @@ +**Install** + +```bash +npm install @0xproject/connect --save +``` + +**Import** + +```javascript +import {HttpRelayerClient} from '@0xproject/connect'; +``` + +### Wiki + +Check out our [@0xproject/connect introduction tutorial](https://0xproject.com/wiki#Intro-Tutorial:Connect) for articles on how to get 0x.js setup with TestRPC, Infura and more! diff --git a/packages/website/md/docs/connect/introduction.md b/packages/website/md/docs/connect/introduction.md new file mode 100644 index 000000000..9b49cafff --- /dev/null +++ b/packages/website/md/docs/connect/introduction.md @@ -0,0 +1 @@ +Welcome to the [@0xproject/connect](https://github.com/0xProject/0x.js/tree/development/packages/connect) documentation! @0xproject/connect is a Javascript library that makes it easy to interact with Relayers that conform to the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api). Functionality includes getting supported token pairs from a relayer, getting orders filtered by different attributes, getting individual orders specified by order hash, getting orderbooks for specific token pairs, getting fee information, and submitting orders. diff --git a/packages/website/md/docs/smart_contracts/introduction.md b/packages/website/md/docs/smart_contracts/introduction.md new file mode 100644 index 000000000..406177f84 --- /dev/null +++ b/packages/website/md/docs/smart_contracts/introduction.md @@ -0,0 +1,8 @@ +Welcome to the [0x smart contracts](https://github.com/0xProject/contracts) documentation! This documentation is intended for dApp developers who want to integrate 0x exchange functionality directly into their own smart contracts. + +### Helpful wiki articles: + +- [Overview of 0x protocol architecture](https://0xproject.com/wiki#Architecture) +- [0x smart contract interactions](https://0xproject.com/wiki#Contract-Interactions) +- [Deployed smart contract addresses](https://0xproject.com/wiki#Deployed-Addresses) +- [0x protocol message format](https://0xproject.com/wiki#Message-Format) diff --git a/scripts/postpublish_utils.js b/scripts/postpublish_utils.js index 4f9798e60..e2c23ee14 100644 --- a/scripts/postpublish_utils.js +++ b/scripts/postpublish_utils.js @@ -5,6 +5,7 @@ const publishRelease = require('publish-release'); const publishReleaseAsync = promisify(publishRelease); const githubPersonalAccessToken = process.env.GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS; +const generatedDocsDirectoryName = 'generated_docs'; module.exports = { getLatestTagAndVersionAsync: function(subPackageName) { @@ -48,4 +49,5 @@ module.exports = { const releaseName = subPackageName + ' v' + version; return releaseName; }, + generatedDocsDirectoryName, }; -- cgit v1.2.3