From 172c4c71b6aae4eb9d9ba31d18154ac8ccca5887 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Tue, 25 Jul 2017 14:42:17 -0300 Subject: [PATCH 001/100] Update to new multisig bytecode --- resources/contracts/wallet.data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/contracts/wallet.data b/resources/contracts/wallet.data index 9b7027f..3329fb8 100644 --- a/resources/contracts/wallet.data +++ b/resources/contracts/wallet.data @@ -1 +1 @@ -0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173c0ffee0505d21342cd503bc57ed33fc2cec7f22591600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773c0ffee0505d21342cd503bc57ed33fc2cec7f225600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073c0ffee0505d21342cd503bc57ed33fc2cec7f225600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073c0ffee0505d21342cd503bc57ed33fc2cec7f225600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a72305820ede6afbb5aaa841614d2130e5cb1dcb9ebcbcc310ff9a3fb9e2a9afd4667d14b00290000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 \ No newline at end of file +0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173863df6bfa4469f3ead0be8f9f2aae51c91a907b491600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a72305820c20a8475c42598c198f6629bada37e1b234da85ac2c0cbac3d96089030b1804000290000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 From 625b5727905711bb2e314d8a05cd4e9a65cc8824 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Tue, 25 Jul 2017 14:43:55 -0300 Subject: [PATCH 002/100] Update enhanced-wallet.sol --- contracts/enhanced-wallet.sol | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/enhanced-wallet.sol b/contracts/enhanced-wallet.sol index ed87d11..12e8373 100644 --- a/contracts/enhanced-wallet.sol +++ b/contracts/enhanced-wallet.sol @@ -104,7 +104,7 @@ contract WalletLibrary is WalletEvents { // constructor is given number of sigs required to do protected "onlymanyowners" transactions // as well as the selection of addresses capable of confirming them. - function initMultiowned(address[] _owners, uint _required) { + function initMultiowned(address[] _owners, uint _required) only_uninitialized { m_numOwners = _owners.length + 1; m_owners[1] = uint(msg.sender); m_ownerIndex[uint(msg.sender)] = 1; @@ -198,7 +198,7 @@ contract WalletLibrary is WalletEvents { } // constructor - stores initial daily limit and records the present day's index. - function initDaylimit(uint _limit) { + function initDaylimit(uint _limit) only_uninitialized { m_dailyLimit = _limit; m_lastDay = today(); } @@ -211,9 +211,12 @@ contract WalletLibrary is WalletEvents { m_spentToday = 0; } + // throw unless the contract is not yet initialized. + modifier only_uninitialized { if (m_numOwners > 0) throw; _; } + // constructor - just pass on the owner array to the multiowned and // the limit to daylimit - function initWallet(address[] _owners, uint _required, uint _daylimit) { + function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized { initDaylimit(_daylimit); initMultiowned(_owners, _required); } @@ -367,7 +370,7 @@ contract WalletLibrary is WalletEvents { } // FIELDS - address constant _walletLibrary = 0xC0FfEE0505d21342Cd503BC57ed33fC2CeC7f225; + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; // the number of owners that must confirm the same operation before it is run. uint public m_required; @@ -445,7 +448,7 @@ contract Wallet is WalletEvents { } // FIELDS - address constant _walletLibrary = 0xC0FfEE0505d21342Cd503BC57ed33fC2CeC7f225; + address constant _walletLibrary = 0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4; // the number of owners that must confirm the same operation before it is run. uint public m_required; @@ -458,4 +461,4 @@ contract Wallet is WalletEvents { // list of owners uint[256] m_owners; -} \ No newline at end of file +} From cf233c9d4000a13a39547a69d70ebed3548aaa4c Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Wed, 26 Jul 2017 23:50:08 +0200 Subject: [PATCH 003/100] Added more details about setup --- .gitignore | 3 +++ README.md | 35 ++++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 4ec2ff4..42cf30b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ /lib /classes /checkouts +/out +/resources/public/js/compiled +/resources/public/css/style.css pom.xml *.jar *.class diff --git a/README.md b/README.md index 6656e04..8d67b9f 100644 --- a/README.md +++ b/README.md @@ -11,30 +11,47 @@ https://commiteth.com ## Prerequisites -* You will need [Leiningen][1] 2.0 or above installed. +You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. -[1]: https://github.com/technomancy/leiningen +### PostgreSQL + +Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up: + +``` +sudo -u postgres psql -c "CREATE USER commiteth WITH PASSWORD 'commiteth';" +sudo -u postgres createdb commiteth +``` ## Running - lein run - lein figwheel - lein less auto +Launch following commands each in its own shell: + +``` +lein run +lein figwheel +lein less auto + +``` ## Testing ### Clojure tests - lein test +``` +lein test +``` ### ClojureScript tests - - lein with-profile test phantom test +``` +lein with-profile test doo phantom test +``` ### Reagent component devcards - lein with-profile test figwheel devcards +``` +lein with-profile test figwheel devcards +``` Open http://localhost:3449/cards.html From 52cbc7f0aa35e7a064d1ecf488f00539250b1d03 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Sat, 19 Aug 2017 12:11:53 +0300 Subject: [PATCH 004/100] Update build instructions in README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 8d67b9f..a09a940 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ http://wiki.status.im/proposals/commiteth/ Live alpha version: https://commiteth.com +The `master` branch is automatically deployed here. + + +Live testnet (Rinkeby) version: +https://commiteth.com:444 +The `develop` branch is automatically deployed here. ## Prerequisites @@ -30,8 +36,19 @@ Launch following commands each in its own shell: lein run lein figwheel lein less auto +``` + +## Uberjar build + +To create a standalone uberjar: ``` +lein with-profile uberjar cljsbuild once min +lein uberjar +``` + +This creates `target/uberjar/commiteth-.jar` + ## Testing From d295b776a26c66adc049364ee2c9a98187427fdd Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Mon, 11 Sep 2017 09:01:34 +0300 Subject: [PATCH 005/100] Usage metrics view access control fix Allow all members of status-im organization to see Usage metrics view, instead of just Status team members. --- src/clj/commiteth/github/core.clj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/clj/commiteth/github/core.clj b/src/clj/commiteth/github/core.clj index a584589..23f27fc 100644 --- a/src/clj/commiteth/github/core.clj +++ b/src/clj/commiteth/github/core.clj @@ -5,7 +5,8 @@ [oauth :as oauth] [users :as users] [repos :as repos] - [issues :as issues]] + [issues :as issues] + [orgs :as orgs]] [ring.util.codec :as codec] [clj-http.client :as http] [commiteth.config :refer [env]] @@ -106,8 +107,12 @@ (defn status-team-member? [token] - (let [user-teams (map :name (users/my-teams (auth-params token)))] - (true? (some #(= "Status" %) user-teams)))) + (let [user-login (:login (users/me (auth-params token))) + user-teams (map :name (users/my-teams (auth-params token))) + status-org-members (map :login (orgs/members "status-im" (self-auth-params)))] + (or + (true? (some #(= "Status" %) user-teams)) + (true? (some #(= user-login %) status-org-members))))) (defn our-webhooks [owner repo token] From c10221501a954a744be664f5799787be26825773 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Mon, 9 Oct 2017 09:31:15 +0300 Subject: [PATCH 006/100] Remove extra new-line in multisig bytecode file --- resources/contracts/wallet.data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/contracts/wallet.data b/resources/contracts/wallet.data index 3329fb8..ba8ce01 100644 --- a/resources/contracts/wallet.data +++ b/resources/contracts/wallet.data @@ -1 +1 @@ -0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173863df6bfa4469f3ead0be8f9f2aae51c91a907b491600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a72305820c20a8475c42598c198f6629bada37e1b234da85ac2c0cbac3d96089030b1804000290000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173863df6bfa4469f3ead0be8f9f2aae51c91a907b491600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073863df6bfa4469f3ead0be8f9f2aae51c91a907b4600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a72305820c20a8475c42598c198f6629bada37e1b234da85ac2c0cbac3d96089030b1804000290000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 \ No newline at end of file From 67754abaf18d637ce117c412753ba78e4b74c1e1 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Mon, 9 Oct 2017 17:09:26 +0300 Subject: [PATCH 007/100] Include link to etherscan.io for deployment transaction * include a link to etherscan.io for a bounty contract's "deploying" comment Fixes: #99 --- src/clj/commiteth/bounties.clj | 18 ++++++++++-------- src/clj/commiteth/github/core.clj | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/clj/commiteth/bounties.clj b/src/clj/commiteth/bounties.clj index 1057d15..8ed0795 100644 --- a/src/clj/commiteth/bounties.clj +++ b/src/clj/commiteth/bounties.clj @@ -31,18 +31,20 @@ (log/error "Unable to deploy bounty contract because" "repo owner has no Ethereum addres") (do - (->> (github/post-deploying-comment owner - repo - issue-number) - :id - (issues/update-comment-id issue-id)) - (log/debug "Posting dep") (log/debug "deploying contract to " owner-address) (let [transaction-hash (eth/deploy-contract owner-address)] (if (nil? transaction-hash) (log/error "Failed to deploy contract to" owner-address) - (log/info "Contract deployed, transaction-hash:" - transaction-hash )) + (do + (log/info "Contract deployed, transaction-hash:" + transaction-hash) + (->> (github/post-deploying-comment owner + repo + issue-number + transaction-hash) + :id + (issues/update-comment-id issue-id))) +) (issues/update-transaction-hash issue-id transaction-hash)))) (log/debug "Issue already exists in DB, ignoring")))) diff --git a/src/clj/commiteth/github/core.clj b/src/clj/commiteth/github/core.clj index 23f27fc..8859eeb 100644 --- a/src/clj/commiteth/github/core.clj +++ b/src/clj/commiteth/github/core.clj @@ -182,10 +182,21 @@ [alt src] (str "!" (md-url alt src))) +(defn etherscan-tx-url [tx-id] + (str "https://" + (when (on-testnet?) "ropsten.") + "etherscan.io/tx/" tx-id)) (defn generate-deploying-comment - [owner repo issue-number] - (md-image "Contract deploying" (str (server-address) "/img/deploying_contract.png"))) + [owner repo issue-number tx-id] + (let [deploying-image (md-image + "Contract deploying" + (str (server-address) "/img/deploying_contract.png")) + tx-link (md-url tx-id (etherscan-tx-url tx-id))] + (format (str "%s\n" + "Transaction: %s\n") + deploying-image + tx-link))) (defn network-text [] (str "Network: " (if (on-testnet?) @@ -225,8 +236,8 @@ (defn post-deploying-comment - [owner repo issue-number] - (let [comment (generate-deploying-comment owner repo issue-number)] + [owner repo issue-number tx-id] + (let [comment (generate-deploying-comment owner repo issue-number tx-id)] (log/debug "Posting comment to" (str owner "/" repo "/" issue-number) ":" comment) (issues/create-comment owner repo issue-number comment (self-auth-params)))) From d9edcdbd6700054025816bf3916ebd35998b56e3 Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Sat, 28 Oct 2017 21:33:54 +0200 Subject: [PATCH 008/100] Shared static landing page --- static_langing_page/README.md | 15 + static_langing_page/assets/developers.svg | 40 + static_langing_page/assets/organisations.svg | 96 + static_langing_page/dest/.DS_Store | Bin 0 -> 6148 bytes static_langing_page/dest/css/main.css | 1856 +++ .../img/new-site/apple-touch-icon-1024.png | Bin 0 -> 8710 bytes .../img/new-site/apple-touch-icon-120.png | Bin 0 -> 1129 bytes .../img/new-site/apple-touch-icon-152.png | Bin 0 -> 1389 bytes .../img/new-site/apple-touch-icon-180.png | Bin 0 -> 1453 bytes .../dest/img/new-site/apple-touch-icon-76.png | Bin 0 -> 859 bytes .../dest/img/new-site/cardlogo-inversed.svg | 1 + .../dest/img/new-site/cardlogo.svg | 1 + .../dest/img/new-site/cardlogo@2x.png | Bin 0 -> 1320 bytes .../img/new-site/cardwordmark-inversed.svg | 1 + .../dest/img/new-site/cardwordmark.svg | 1 + .../dest/img/new-site/cardwordmark@2x.png | Bin 0 -> 5627 bytes .../dest/img/new-site/chip@2x.png | Bin 0 -> 481 bytes .../dest/img/new-site/footer-logo@2x.png | Bin 0 -> 1520 bytes .../dest/img/new-site/icon_dropdown-white.svg | 1 + .../dest/img/new-site/icon_external.png | Bin 0 -> 442 bytes .../dest/img/new-site/icon_fb.svg | 1 + .../dest/img/new-site/icon_gh2.svg | 1 + .../dest/img/new-site/icon_rd2.svg | 1 + .../dest/img/new-site/icon_tw2.svg | 1 + .../dest/img/new-site/icon_yt.svg | 1 + .../dest/img/new-site/image-arrow@2x.png | Bin 0 -> 3684 bytes .../dest/img/new-site/image-card-lock2@2x.png | Bin 0 -> 3179 bytes .../dest/img/new-site/image-card-lock@2x.png | Bin 0 -> 6580 bytes .../dest/img/new-site/image-card@2x.png | Bin 0 -> 2646 bytes .../dest/img/new-site/image-phone@2x.png | Bin 0 -> 3954 bytes .../dest/img/new-site/image-waves@2x.png | Bin 0 -> 2171 bytes .../dest/img/new-site/lines@2x.png | Bin 0 -> 1910 bytes .../dest/img/new-site/logo-16.png | Bin 0 -> 259 bytes .../dest/img/new-site/logo-32.png | Bin 0 -> 485 bytes .../dest/img/new-site/logo-eth@2x.png | Bin 0 -> 1673 bytes .../dest/img/new-site/logo-gnosis@2x.png | Bin 0 -> 1972 bytes .../dest/img/new-site/logo-omise@2x.png | Bin 0 -> 1914 bytes .../dest/img/new-site/logo-plus@2x.png | Bin 0 -> 224 bytes .../dest/img/new-site/logo-status@2x.png | Bin 0 -> 1258 bytes .../dest/img/new-site/logo-tenx@2x.png | Bin 0 -> 2200 bytes .../dest/img/new-site/status-symbol.svg | 1 + .../dest/img/new-site/verified.png | Bin 0 -> 587 bytes .../dest/img/new-site/waves-inversed.svg | 1 + .../dest/img/new-site/waves.svg | 1 + .../dest/img/new-site/waves@2x.png | Bin 0 -> 1724 bytes static_langing_page/dest/img/pattern@2x.png | Bin 0 -> 280 bytes static_langing_page/dest/js/app.js | 10298 ++++++++++++++++ static_langing_page/dest/js/mc-validate.js | 5 + .../fonts/PostGrotesk-Book.eot | Bin 0 -> 174344 bytes .../fonts/PostGrotesk-Book.svg | 3 + .../fonts/PostGrotesk-Book.woff | Bin 0 -> 64051 bytes .../fonts/PostGrotesk-Medium.eot | Bin 0 -> 178630 bytes .../fonts/PostGrotesk-Medium.svg | 3 + .../fonts/PostGrotesk-Medium.woff | Bin 0 -> 66471 bytes .../fonts/RobotoRegular/RobotoRegular.eot | Bin 0 -> 50854 bytes .../fonts/RobotoRegular/RobotoRegular.ttf | Bin 0 -> 50656 bytes .../fonts/RobotoRegular/RobotoRegular.woff | Bin 0 -> 26104 bytes static_langing_page/gulpfile.js | 64 + static_langing_page/index.html | 263 + static_langing_page/package.json | 41 + static_langing_page/src/.DS_Store | Bin 0 -> 6148 bytes static_langing_page/src/img/.DS_Store | Bin 0 -> 10244 bytes .../src/img/new-site/.DS_Store | Bin 0 -> 8196 bytes .../img/new-site/apple-touch-icon-1024.png | Bin 0 -> 39902 bytes .../src/img/new-site/apple-touch-icon-120.png | Bin 0 -> 2680 bytes .../src/img/new-site/apple-touch-icon-152.png | Bin 0 -> 3434 bytes .../src/img/new-site/apple-touch-icon-180.png | Bin 0 -> 3771 bytes .../src/img/new-site/apple-touch-icon-76.png | Bin 0 -> 1754 bytes .../src/img/new-site/cardlogo-inversed.svg | 16 + .../src/img/new-site/cardlogo.svg | 16 + .../src/img/new-site/cardlogo@2x.png | Bin 0 -> 2968 bytes .../img/new-site/cardwordmark-inversed.svg | 17 + .../src/img/new-site/cardwordmark.svg | 17 + .../src/img/new-site/cardwordmark@2x.png | Bin 0 -> 10020 bytes .../src/img/new-site/chip@2x.png | Bin 0 -> 1068 bytes .../src/img/new-site/footer-logo@2x.png | Bin 0 -> 1520 bytes .../src/img/new-site/icon_dropdown-white.svg | 1 + .../src/img/new-site/icon_external.png | Bin 0 -> 442 bytes .../src/img/new-site/icon_fb.svg | 1 + .../src/img/new-site/icon_gh2.svg | 1 + .../src/img/new-site/icon_rd2.svg | 1 + .../src/img/new-site/icon_tw2.svg | 1 + .../src/img/new-site/icon_yt.svg | 1 + .../src/img/new-site/image-arrow@2x.png | Bin 0 -> 4982 bytes .../src/img/new-site/image-card-lock2@2x.png | Bin 0 -> 7918 bytes .../src/img/new-site/image-card-lock@2x.png | Bin 0 -> 11885 bytes .../src/img/new-site/image-card@2x.png | Bin 0 -> 5555 bytes .../src/img/new-site/image-phone@2x.png | Bin 0 -> 7105 bytes .../src/img/new-site/image-waves@2x.png | Bin 0 -> 3788 bytes .../src/img/new-site/lines@2x.png | Bin 0 -> 7464 bytes .../src/img/new-site/logo-16.png | Bin 0 -> 371 bytes .../src/img/new-site/logo-32.png | Bin 0 -> 764 bytes .../src/img/new-site/logo-eth@2x.png | Bin 0 -> 2078 bytes .../src/img/new-site/logo-gnosis@2x.png | Bin 0 -> 1978 bytes .../src/img/new-site/logo-omise@2x.png | Bin 0 -> 1981 bytes .../src/img/new-site/logo-plus@2x.png | Bin 0 -> 518 bytes .../src/img/new-site/logo-status@2x.png | Bin 0 -> 1374 bytes .../src/img/new-site/logo-tenx@2x.png | Bin 0 -> 2320 bytes .../src/img/new-site/status-symbol.svg | 18 + .../src/img/new-site/verified.png | Bin 0 -> 1307 bytes .../src/img/new-site/waves-inversed.svg | 16 + .../src/img/new-site/waves.svg | 16 + .../src/img/new-site/waves@2x.png | Bin 0 -> 2974 bytes static_langing_page/src/img/pattern@2x.png | Bin 0 -> 587 bytes static_langing_page/src/js/.DS_Store | Bin 0 -> 6148 bytes static_langing_page/src/js/lib/Debouncer.js | 43 + static_langing_page/src/js/lib/Scrllr.js | 66 + static_langing_page/src/js/lib/ScrollOver.js | 171 + .../src/js/lib/animatescroll.js | 86 + static_langing_page/src/js/main.js | 105 + static_langing_page/src/js/mc-validate.js | 444 + static_langing_page/src/scss/actions.scss | 100 + static_langing_page/src/scss/cards.scss | 117 + static_langing_page/src/scss/colors.scss | 4 + static_langing_page/src/scss/common.scss | 69 + static_langing_page/src/scss/cookies.scss | 56 + static_langing_page/src/scss/dots.scss | 0 static_langing_page/src/scss/email-form.scss | 214 + static_langing_page/src/scss/fonts.scss | 19 + static_langing_page/src/scss/footer.scss | 222 + static_langing_page/src/scss/formReset.scss | 169 + static_langing_page/src/scss/header.scss | 251 + static_langing_page/src/scss/logos.scss | 140 + static_langing_page/src/scss/main.scss | 15 + static_langing_page/src/scss/news.scss | 0 static_langing_page/src/scss/page.scss | 299 + static_langing_page/src/scss/pages.scss | 263 + static_langing_page/src/scss/phones.scss | 0 static_langing_page/src/scss/popup.scss | 0 static_langing_page/src/scss/reset.scss | 51 + static_langing_page/src/scss/sections.scss | 212 + static_langing_page/src/scss/slide--five.scss | 86 + static_langing_page/src/scss/slide--four.scss | 109 + static_langing_page/src/scss/slide--one.scss | 123 + .../src/scss/slide--three.scss | 66 + static_langing_page/src/scss/slide--two.scss | 385 + static_langing_page/src/scss/slides.scss | 42 + static_langing_page/src/scss/tokens.scss | 378 + 138 files changed, 17124 insertions(+) create mode 100644 static_langing_page/README.md create mode 100644 static_langing_page/assets/developers.svg create mode 100644 static_langing_page/assets/organisations.svg create mode 100644 static_langing_page/dest/.DS_Store create mode 100644 static_langing_page/dest/css/main.css create mode 100644 static_langing_page/dest/img/new-site/apple-touch-icon-1024.png create mode 100644 static_langing_page/dest/img/new-site/apple-touch-icon-120.png create mode 100644 static_langing_page/dest/img/new-site/apple-touch-icon-152.png create mode 100644 static_langing_page/dest/img/new-site/apple-touch-icon-180.png create mode 100644 static_langing_page/dest/img/new-site/apple-touch-icon-76.png create mode 100644 static_langing_page/dest/img/new-site/cardlogo-inversed.svg create mode 100644 static_langing_page/dest/img/new-site/cardlogo.svg create mode 100644 static_langing_page/dest/img/new-site/cardlogo@2x.png create mode 100644 static_langing_page/dest/img/new-site/cardwordmark-inversed.svg create mode 100644 static_langing_page/dest/img/new-site/cardwordmark.svg create mode 100644 static_langing_page/dest/img/new-site/cardwordmark@2x.png create mode 100644 static_langing_page/dest/img/new-site/chip@2x.png create mode 100644 static_langing_page/dest/img/new-site/footer-logo@2x.png create mode 100644 static_langing_page/dest/img/new-site/icon_dropdown-white.svg create mode 100644 static_langing_page/dest/img/new-site/icon_external.png create mode 100644 static_langing_page/dest/img/new-site/icon_fb.svg create mode 100644 static_langing_page/dest/img/new-site/icon_gh2.svg create mode 100644 static_langing_page/dest/img/new-site/icon_rd2.svg create mode 100644 static_langing_page/dest/img/new-site/icon_tw2.svg create mode 100644 static_langing_page/dest/img/new-site/icon_yt.svg create mode 100644 static_langing_page/dest/img/new-site/image-arrow@2x.png create mode 100644 static_langing_page/dest/img/new-site/image-card-lock2@2x.png create mode 100644 static_langing_page/dest/img/new-site/image-card-lock@2x.png create mode 100644 static_langing_page/dest/img/new-site/image-card@2x.png create mode 100644 static_langing_page/dest/img/new-site/image-phone@2x.png create mode 100644 static_langing_page/dest/img/new-site/image-waves@2x.png create mode 100644 static_langing_page/dest/img/new-site/lines@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-16.png create mode 100644 static_langing_page/dest/img/new-site/logo-32.png create mode 100644 static_langing_page/dest/img/new-site/logo-eth@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-gnosis@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-omise@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-plus@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-status@2x.png create mode 100644 static_langing_page/dest/img/new-site/logo-tenx@2x.png create mode 100644 static_langing_page/dest/img/new-site/status-symbol.svg create mode 100644 static_langing_page/dest/img/new-site/verified.png create mode 100644 static_langing_page/dest/img/new-site/waves-inversed.svg create mode 100644 static_langing_page/dest/img/new-site/waves.svg create mode 100644 static_langing_page/dest/img/new-site/waves@2x.png create mode 100644 static_langing_page/dest/img/pattern@2x.png create mode 100644 static_langing_page/dest/js/app.js create mode 100644 static_langing_page/dest/js/mc-validate.js create mode 100644 static_langing_page/fonts/PostGrotesk-Book.eot create mode 100644 static_langing_page/fonts/PostGrotesk-Book.svg create mode 100644 static_langing_page/fonts/PostGrotesk-Book.woff create mode 100644 static_langing_page/fonts/PostGrotesk-Medium.eot create mode 100644 static_langing_page/fonts/PostGrotesk-Medium.svg create mode 100644 static_langing_page/fonts/PostGrotesk-Medium.woff create mode 100755 static_langing_page/fonts/RobotoRegular/RobotoRegular.eot create mode 100755 static_langing_page/fonts/RobotoRegular/RobotoRegular.ttf create mode 100755 static_langing_page/fonts/RobotoRegular/RobotoRegular.woff create mode 100755 static_langing_page/gulpfile.js create mode 100644 static_langing_page/index.html create mode 100644 static_langing_page/package.json create mode 100644 static_langing_page/src/.DS_Store create mode 100644 static_langing_page/src/img/.DS_Store create mode 100644 static_langing_page/src/img/new-site/.DS_Store create mode 100644 static_langing_page/src/img/new-site/apple-touch-icon-1024.png create mode 100644 static_langing_page/src/img/new-site/apple-touch-icon-120.png create mode 100644 static_langing_page/src/img/new-site/apple-touch-icon-152.png create mode 100644 static_langing_page/src/img/new-site/apple-touch-icon-180.png create mode 100644 static_langing_page/src/img/new-site/apple-touch-icon-76.png create mode 100644 static_langing_page/src/img/new-site/cardlogo-inversed.svg create mode 100644 static_langing_page/src/img/new-site/cardlogo.svg create mode 100644 static_langing_page/src/img/new-site/cardlogo@2x.png create mode 100644 static_langing_page/src/img/new-site/cardwordmark-inversed.svg create mode 100644 static_langing_page/src/img/new-site/cardwordmark.svg create mode 100644 static_langing_page/src/img/new-site/cardwordmark@2x.png create mode 100644 static_langing_page/src/img/new-site/chip@2x.png create mode 100644 static_langing_page/src/img/new-site/footer-logo@2x.png create mode 100644 static_langing_page/src/img/new-site/icon_dropdown-white.svg create mode 100644 static_langing_page/src/img/new-site/icon_external.png create mode 100644 static_langing_page/src/img/new-site/icon_fb.svg create mode 100644 static_langing_page/src/img/new-site/icon_gh2.svg create mode 100644 static_langing_page/src/img/new-site/icon_rd2.svg create mode 100644 static_langing_page/src/img/new-site/icon_tw2.svg create mode 100644 static_langing_page/src/img/new-site/icon_yt.svg create mode 100644 static_langing_page/src/img/new-site/image-arrow@2x.png create mode 100644 static_langing_page/src/img/new-site/image-card-lock2@2x.png create mode 100644 static_langing_page/src/img/new-site/image-card-lock@2x.png create mode 100644 static_langing_page/src/img/new-site/image-card@2x.png create mode 100644 static_langing_page/src/img/new-site/image-phone@2x.png create mode 100644 static_langing_page/src/img/new-site/image-waves@2x.png create mode 100644 static_langing_page/src/img/new-site/lines@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-16.png create mode 100644 static_langing_page/src/img/new-site/logo-32.png create mode 100644 static_langing_page/src/img/new-site/logo-eth@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-gnosis@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-omise@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-plus@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-status@2x.png create mode 100644 static_langing_page/src/img/new-site/logo-tenx@2x.png create mode 100644 static_langing_page/src/img/new-site/status-symbol.svg create mode 100644 static_langing_page/src/img/new-site/verified.png create mode 100644 static_langing_page/src/img/new-site/waves-inversed.svg create mode 100644 static_langing_page/src/img/new-site/waves.svg create mode 100644 static_langing_page/src/img/new-site/waves@2x.png create mode 100644 static_langing_page/src/img/pattern@2x.png create mode 100644 static_langing_page/src/js/.DS_Store create mode 100644 static_langing_page/src/js/lib/Debouncer.js create mode 100644 static_langing_page/src/js/lib/Scrllr.js create mode 100644 static_langing_page/src/js/lib/ScrollOver.js create mode 100644 static_langing_page/src/js/lib/animatescroll.js create mode 100644 static_langing_page/src/js/main.js create mode 100644 static_langing_page/src/js/mc-validate.js create mode 100644 static_langing_page/src/scss/actions.scss create mode 100644 static_langing_page/src/scss/cards.scss create mode 100644 static_langing_page/src/scss/colors.scss create mode 100644 static_langing_page/src/scss/common.scss create mode 100644 static_langing_page/src/scss/cookies.scss create mode 100644 static_langing_page/src/scss/dots.scss create mode 100644 static_langing_page/src/scss/email-form.scss create mode 100644 static_langing_page/src/scss/fonts.scss create mode 100644 static_langing_page/src/scss/footer.scss create mode 100644 static_langing_page/src/scss/formReset.scss create mode 100644 static_langing_page/src/scss/header.scss create mode 100644 static_langing_page/src/scss/logos.scss create mode 100644 static_langing_page/src/scss/main.scss create mode 100644 static_langing_page/src/scss/news.scss create mode 100644 static_langing_page/src/scss/page.scss create mode 100644 static_langing_page/src/scss/pages.scss create mode 100644 static_langing_page/src/scss/phones.scss create mode 100644 static_langing_page/src/scss/popup.scss create mode 100644 static_langing_page/src/scss/reset.scss create mode 100644 static_langing_page/src/scss/sections.scss create mode 100644 static_langing_page/src/scss/slide--five.scss create mode 100644 static_langing_page/src/scss/slide--four.scss create mode 100644 static_langing_page/src/scss/slide--one.scss create mode 100644 static_langing_page/src/scss/slide--three.scss create mode 100644 static_langing_page/src/scss/slide--two.scss create mode 100644 static_langing_page/src/scss/slides.scss create mode 100644 static_langing_page/src/scss/tokens.scss diff --git a/static_langing_page/README.md b/static_langing_page/README.md new file mode 100644 index 0000000..2eb7046 --- /dev/null +++ b/static_langing_page/README.md @@ -0,0 +1,15 @@ +## How to run locally + +### Install gulp + +``` +npm install --global gulp-cli +npm install gulp +``` + +### Run gulp + +``` +npm install # first time only +gulp +``` diff --git a/static_langing_page/assets/developers.svg b/static_langing_page/assets/developers.svg new file mode 100644 index 0000000..b35dd8a --- /dev/null +++ b/static_langing_page/assets/developers.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + js + + + + css + + + + + + + + ETH + + + diff --git a/static_langing_page/assets/organisations.svg b/static_langing_page/assets/organisations.svg new file mode 100644 index 0000000..47d2984 --- /dev/null +++ b/static_langing_page/assets/organisations.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3 contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3 contributors + + + + + + diff --git a/static_langing_page/dest/.DS_Store b/static_langing_page/dest/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..df75fd2b64c3d6cc713905ba23a8799c09c2bd1f GIT binary patch literal 6148 zcmeHKF>XRJ47CdbQWr+XT!9-zh@OB8pn_Dj0x_WOf9kn5NQ%CYr7NRr9?(lv(9caC7mB)Rcx1$sjTjIE&%*%UhXy6~!XYu94h-?n2ToxQ!902i zV50%p3x`BlAYLtkSEyHu;TewoR(`#3NX&5aI2n2BWUmgz<8;_txSRJxjbcCy+%ho9 z<(&2Z4SJ{lzYWq#42XfJV!)^C&3c6^Rc#$zj2!<%fz$nL9upBSK dDg7Gf*zbiyVuZsU;lQls`V5dAi5U1l1}+m%7SR9z literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/css/main.css b/static_langing_page/dest/css/main.css new file mode 100644 index 0000000..7ed6ffd --- /dev/null +++ b/static_langing_page/dest/css/main.css @@ -0,0 +1,1856 @@ +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } + +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { + display: block; } + +body { + line-height: 1; } + +ol, ul { + list-style: none; } + +blockquote, q { + quotes: none; } + +blockquote:before, blockquote:after { + content: ""; + content: none; } + +q:before, q:after { + content: ""; + content: none; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +div { + box-sizing: border-box; } + +/* ---------------------------------------------------------------------------------------------------- + +Super Form Reset + +A couple of things to watch out for: + +- IE8: If a text input doesn't have padding on all sides or none the text won't be centered. +- The default border sizes on text inputs in all UAs seem to be slightly different. You're better off using custom borders. +- You NEED to set the font-size and family on all form elements +- Search inputs need to have their appearance reset and the box-sizing set to content-box to match other UAs +- You can style the upload button in webkit using ::-webkit-file-upload-button +- ::-webkit-file-upload-button selectors can't be used in the same selector as normal ones. FF and IE freak out. +- IE: You don't need to fake inline-block with labels and form controls in IE. They function as inline-block. +- By turning off ::-webkit-search-decoration, it removes the extra whitespace on the left on search inputs + +----------------------------------------------------------------------------------------------------*/ +input, +label, +select, +button, +textarea { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + line-height: 1; + /* Browsers have different default form fonts */ + font-size: 13px; + font-family: Arial; } + +/* Remove the stupid outer glow in Webkit */ +input:focus { + outline: 0; } + +/* Box Sizing Reset +-----------------------------------------------*/ +/* All of our custom controls should be what we expect them to be */ +input, +textarea { + box-sizing: content-box; } + +/* These elements are usually rendered a certain way by the browser */ +button, +input[type=reset], +input[type=button], +input[type=submit], +input[type=checkbox], +input[type=radio], +select { + box-sizing: border-box; } + +/* Text Inputs +-----------------------------------------------*/ +/* Button Controls +-----------------------------------------------*/ +input[type=checkbox], +input[type=radio] { + width: 13px; + height: 13px; } + +/* File Uploads +-----------------------------------------------*/ +/* Search Input +-----------------------------------------------*/ +/* Make webkit render the search input like a normal text field */ +input[type=search] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; } + +/* Turn off the recent search for webkit. It adds about 15px padding on the left */ +::-webkit-search-decoration { + display: none; } + +/* Buttons +-----------------------------------------------*/ +button, +input[type="reset"], +input[type="button"], +input[type="submit"] { + /* Fix IE7 display bug */ + overflow: visible; + width: auto; } + +/* IE8 and FF freak out if this rule is within another selector */ +::-webkit-file-upload-button { + padding: 0; + border: 0; + background: none; } + +/* Textarea +-----------------------------------------------*/ +textarea { + /* Move the label to the top */ + vertical-align: top; + /* Turn off scroll bars in IE unless needed */ + overflow: auto; } + +/* Selects +-----------------------------------------------*/ +select[multiple] { + /* Move the label to the top */ + vertical-align: top; } + +@font-face { + font-family: PostGrotesk-Medium; + src: url(../../fonts/PostGrotesk-Medium.eot); + src: url(../../fonts/PostGrotesk-Medium.eot?#iefix) format("embedded-opentype"), url(../../fonts/PostGrotesk-Medium.woff) format("woff"), url(../../fonts/PostGrotesk-Medium.svg#PostGrotesk-Medium) format("svg"); + font-weight: 400; + font-style: normal; } + +@font-face { + font-family: PostGrotesk-Book; + src: url(../../fonts/PostGrotesk-Book.eot); + src: url(../../fonts/PostGrotesk-Book.eot?#iefix) format("embedded-opentype"), url(../../fonts/PostGrotesk-Book.woff) format("woff"), url(../../fonts/PostGrotesk-Book.svg#PostGrotesk-Book) format("svg"); + font-weight: 400; + font-style: normal; } + +body { + background-color: #f2f5f8; + font-family: "PostGrotesk-Book", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 15px; + line-height: 25px; + color: #49555f; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; } + +body.popup-open { + overflow: hidden; } + +a { + color: #49555f; + text-decoration: none; + transition: opacity .2s ease; } + +.button { + display: block; + height: 44px; + line-height: 43px; + padding: 0 20px; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 13px; + text-transform: uppercase; + text-align: center; + letter-spacing: 1px; + color: white; + background-color: #57a7ed; + border-radius: 8px; + transform: translate3d(0, 0, 0) scale(1); + transition: transform .1s ease, box-shadow .2s ease, background-color .2s ease; + -webkit-appearance: none; + cursor: pointer; } + +.button:active { + transform: translate3d(0, 0, 0) scale(0.99); } + +.button:disabled, +.button.button--disabled { + color: rgba(255, 255, 255, 0.4); + background-color: #57a7ed; + cursor: default; } + +.button:hover { + background-color: #57a7ed; } + +.button.button--disabled:hover, +.button:disabled:hover { + cursor: default; + color: rgba(255, 255, 255, 0.4); + background-color: #57a7ed; } + +.header { + background-color: #57a7ed; } + +@media (min-width: 1230px) { + .header { + margin: 0 auto; + margin-top: 40px; + border-radius: 10px; + box-shadow: 0 10px 18px 0 rgba(122, 135, 142, 0.33); + z-index: 900; + max-width: 1200px; + padding-bottom: 134px; } } + +.header__inner { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 16px 0 0 16px; } + +@media (min-width: 640px) { + .header__inner { + -ms-flex-direction: row; + flex-direction: row; + padding: 30px 0 0 30px; } } + +.header-section { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; } + +.header-section--center { + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; } + +@media (min-width: 640px) { + .header-section--right { + -ms-flex-pack: end; + justify-content: flex-end; } } + +.nav { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + opacity: 1; } + +.nav__item:first-child { + padding-left: 0; } + +a.logo { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + -ms-flex-align: start; + align-items: flex-start; } + +.logo__icon-wrap { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 56px; + height: 56px; + background: white; + box-shadow: 0 2px 4px 0 rgba(22, 51, 81, 0.14); + border-radius: 50%; + transition: transform .1s ease, box-shadow .2s ease, background-color .2s ease; } + +.logo__icon { + height: 24px; + width: 24px; + background-image: url(../img/new-site/status-symbol.svg); + background-size: 24px; + background-position: left; } + +.logo__text { + margin: 8px 0 0 10px; } + +.logo__title { + display: block; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 21px; + line-height: 21px; + color: white; } + +.logo__subtitle { + display: block; + font-size: 15px; + line-height: 15px; + color: #ffffff; + margin: 4px 0 0 0; + opacity: .4; } + +.nav a { + font-size: 15px; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: 15px 15px; } + +.header .nav a { + color: #ffffff; + transition: opacity .2s ease; + position: relative; } + +.header .nav a:hover { + opacity: 0.5; } + +.header #button-login { + position: absolute; + top: 16px; + right: 16px; + padding: 13px 15px; + height: inherit; + line-height: inherit; + background-color: rgba(255, 255, 255, 0.2); } + +@media (min-width: 640px) { + .header #button-login { + position: inherit; + margin-right: 30px; } } + +.nav a:hover { + opacity: .9; } + +.shares { + margin: 0 0 0 16px; } + +.social-links { + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; } + +.shares .social-links { + -ms-flex-pack: end; + justify-content: flex-end; } + +.social-links li.social-link { + margin: 0 5px; } + +.social-links li.social-link:last-child { + margin: 0 0 0 5px; } + +li.social-link a { + display: block; + width: 40px; + height: 40px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.1); + background-repeat: no-repeat; + background-size: 20px; + background-position: center; + transition: transform .2s ease; + cursor: pointer; } + +.social-link.social-link--fb a { + background-image: url(../img/new-site/icon_fb.svg); } + +.social-link.social-link--tw a { + background-image: url(../img/new-site/icon_tw2.svg); } + +.social-link.social-link--sl a { + background-image: url(../img/new-site/icon_sl2.svg); } + +.social-link.social-link--gh a { + background-image: url(../img/new-site/icon_gh2.svg); } + +.social-link.social-link--rd a { + background-image: url(../img/new-site/icon_rd2.svg); } + +.social-link:hover a { + -ms-transform: scale(1.06); + transform: scale(1.06); } + +@media (max-width: 740px) { + .logo__icon-wrap { + height: 52px; + width: 52px; } + .logo__text { + margin: 7px 0 0 10px; } + .logo__title { + font-size: 19px; } } + +.page { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: center; + align-items: center; } + +.page__inner { + width: 1120px; + position: relative; } + +.page__content-wrap { + position: relative; + width: 1180px; + margin: 0 0 40px 0; + background: white; + box-shadow: 0px -2px 18px 0px rgba(72, 89, 102, 0.25); + border-radius: 10px; } + +.page__content { + max-width: 800px; + margin: 0 auto; + padding: 50px 40px 100px; + background: white; + border-radius: 8px; } + +.page__content.page__content--centered { + max-width: 100%; + padding: 50px 160px; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-direction: column; + flex-direction: column; + text-align: center; } + +.page__title { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 24px; + line-height: 30px; + margin: 0 0 20px 0; } + +.page__subheader { + font-size: 18px; + line-height: 26px; + margin: 0 0 25px 0; + color: #92999f; } + +.page__content p { + margin: 0 0 10px 0; } + +.page__content p a { + color: #4957b8; } + +.page__content p strong { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; } + +.page__content.page__content--centered .page__title { + max-width: 500px; } + +.page__content.page__content--centered .page__text { + max-width: 500px; } + +.page h3 { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 22px; + margin: 32px 0 8px 0; } + +.page h5 { + font-size: 15px; + opacity: 1; + padding: 16px; } + +.page__social { + width: 100%; + padding: 48px 0; } + +.page__social-buttons { + width: 100%; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; } + +.page__social-button-wrap { + -ms-flex-preferred-size: 33.3%; + flex-basis: 33.3%; + -ms-flex-negative: 1; + flex-shrink: 1; + -ms-flex-positive: 1; + flex-grow: 1; + margin: 12px; + position: relative; + border-radius: 8px; + border: 1px solid #e9eaf4; + text-align: center; + box-shadow: 0 2px 5px 0 rgba(71, 91, 106, 0.12); + transition: box-shadow .2s ease; } + +.page__social-button-wrap:first-child { + margin-left: 0; } + +.page__social-button-wrap:last-child { + margin-right: 0; } + +.page__social-button-wrap:hover { + box-shadow: 0 4px 10px 0 rgba(71, 91, 106, 0.16); } + +.page__social-button { + padding: 24px; + height: 92px; + overflow: hidden; } + +.big-button-wrap { + margin: 48px 0 0 0; } + +.button.button--big { + height: 56px; + line-height: 56px; + font-size: 15px; + letter-spacing: 1.2px; } + +.button.button--secondary.button--page { + display: inline-block; + padding: 0 40px; + background-color: #4A5C69; + color: white; } + +.button.button--secondary.button--page.button--page--slack { + background-color: #E3306D; } + +.button.button--secondary.button--page.button--page--wiki { + background-color: #4A55BF; } + +.button.button--secondary.button--page:hover { + opacity: .8; + background-color: #4A5C69; } + +.button.button--secondary.button--page.button--page--slack:hover { + opacity: .8; + background-color: #E3306D; } + +.button.button--secondary.button--page.button--page--wiki:hover { + opacity: .8; + background-color: #4A55BF; } + +.page__social-button-label { + display: block; + text-align: center; + opacity: .6; + padding: 16px; } + +.page__footer { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + display: -ms-flexbox; + display: flex; + text-align: center; + font-size: 13px; + line-height: 15px; + letter-spacing: 1px; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + text-transform: uppercase; + border-top: 1px solid #e9eaf4; } + +.page__footer .page__footer-link { + display: block; + text-align: center; + width: 100%; + padding: 20px 0; + transition: opacity .2s ease; } + +.page__footer .page__footer-link:hover { + opacity: .6; } + +@media (max-width: 1220px) { + .page__content-wrap { + width: 1080px; + top: -10px; + margin: 0 0 30px 0; } } + +@media (max-width: 1140px) { + .page__social { + padding: 16px 0; } + .page__social-buttons { + -ms-flex-wrap: wrap; + flex-wrap: wrap; } + .page__social-button-wrap { + width: 100%; + -ms-flex-preferred-size: 100%; + flex-basis: 100%; } + .page__social-button-wrap { + margin-left: 0; + margin-right: 0; } + .page { + margin: 20px 20px 0 20px; } + .page__content-wrap { + width: 100%; } + .page__content { + width: auto; } } + +@media (max-width: 840px) { + .page__content { + margin: 0 30px; + padding: 50px 20px 100px; } + .page__content-wrap { + top: -20px; + margin: 0 0 20px 0; } + .page__content.page__content--centered { + padding: 100px 20px; } } + +@media (max-width: 767px) { + .page { + margin: 0; } + .page__content.page__content--centered, + .page__content { + padding: 40px 0 80px 0; } + .page__content-wrap { + top: -10px; + margin: 0; } + .page__title { + font-size: 18px; + line-height: 26px; + margin: 0 0 10px 0; } + .page__subheader { + font-size: 16px; + line-height: 20px; + margin: 0 0 15px 0; } } + +.cards { + position: relative; + width: 300px; + height: 220px; } + +.card { + position: absolute; + left: 0; + top: 0; + width: 246px; + height: 156px; + border-radius: 10px; + background-color: #FFFFFF; + box-shadow: 0 5px 11px 0 rgba(31, 16, 54, 0.57); + -ms-transform: c; + transform: c; } + +.card-wrap { + transition: transform .3s ease; } + +.shown .cards:hover .card-wrap--purple { + transform: rotate(1deg) translate3d(0, 5px, 0); } + +.shown .cards:hover .card-wrap--white { + transform: rotate(-2deg) translate3d(-5px, -4px, 0); } + +.card.card--purple { + opacity: 0; + position: absolute; + left: 35px; + top: 34px; + background-color: #6E34CC; + transform: rotate(34deg) scale(0.56) translate3d(0, 10px, 0); + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; + transition-delay: .1s; } + +.card.card--white { + opacity: 0; + left: 25px; + transform: rotate(55deg) scale(0.6) translate3d(0, 80px, 0); + transition: transform 0.7s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; } + +.shown .card.card--purple { + opacity: 1; + transform: rotate(14deg) scale(0.86) translate3d(0, 0, 0); } + +.shown .card--white { + opacity: 1; + transform: rotate(5deg) scale(0.9) translate3d(0, 0, 0); } + +.card__chip { + position: absolute; + left: 36px; + top: 46px; + width: 28px; + height: 22px; + background-image: url(../img/new-site/chip@2x.png); + background-size: 28px; } + +.card__logo { + position: absolute; + right: 20px; + top: 20px; + width: 40px; + height: 40px; + background-image: url(../img/new-site/cardlogo.svg); + background-size: 40px; } + +.card--purple .card__logo { + background-image: url(../img/new-site/cardlogo-inversed.svg); } + +.card__wordmark { + position: absolute; + left: 20px; + bottom: 20px; + width: 134px; + height: 44px; + background-image: url(../img/new-site/cardwordmark.svg); + background-size: 134px; } + +.card--purple .card__wordmark { + background-image: url(../img/new-site/cardwordmark-inversed.svg); } + +.card__waves { + position: absolute; + right: 20px; + bottom: 20px; + width: 20px; + height: 35px; + background-image: url(../img/new-site/waves@2x.png); + background-size: 20px; } + +.card--purple .card__waves { + background-image: url(../img/new-site/waves-inversed.svg); } + +@media (max-width: 400px) { + .card.card--purple { + top: 46px; } + .card.card--white { + top: 16px; } } + +.actions { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + text-align: center; + -ms-flex-pack: space-evenly; + justify-content: space-evenly; } + +@media (min-width: 640px) { + .actions { + -ms-flex-direction: row; + flex-direction: row; } } + +.action { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + margin-top: 24px; + margin-bottom: 24px; } + +@media (min-width: 640px) { + .actions { + margin-top: 64px; } } + +.action h1 { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: 500; + margin-bottom: 7px; + line-height: 1.15; + letter-spacing: 1.1px; + text-align: center; + text-transform: uppercase; + color: #ffffff; } + +.action h2 { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 25px; + font-weight: 500; + margin-bottom: 6px; + line-height: 1.36; + text-align: center; + color: #ffffff; } + +.action p { + opacity: 0.6; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 18px; + line-height: 1.39; + margin: 0 auto 26px; + max-width: 400px; + text-align: center; + color: #ffffff; } + +.action button { + width: 240px; + height: 45px; + object-fit: contain; + border-radius: 8px; + background-color: #1e3751; + box-shadow: 0 2px 4px 0 rgba(22, 51, 81, 0.14); + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: 500; + line-height: 1.15; + letter-spacing: 1px; + text-align: center; + text-transform: uppercase; + color: #ffffff; + transition: opacity .2s ease; } + +.action button:hover { + opacity: 0.5; + cursor: pointer; } + +.logo-developers { + width: 148px; + height: 159px; + object-fit: contain; } + +.logo-organisations { + width: 168px; + height: 124px; + object-fit: contain; + padding-top: 35px; } + +@media (min-width: 640px) { + .logo-organisations { + padding-top: 35px; } } + +@media (min-width: 640px) { + .container-wrap { + z-index: 2; + margin-bottom: 420px; } } + +.container-wrap.container-wrap--not-fancy { + z-index: 2; + margin-bottom: 0px; } + +.container { + margin: 0 auto; + overflow: hidden; + position: relative; + background-color: #f2f5f8; } + +.container.container--page { + min-height: 500px; } + +.slides { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + background: #f2f5f8; } + +.slide { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: center; + align-items: center; } + +.slide--hidden { + display: none; } + +.slide__inner { + position: relative; } + +.slide.slide--one .slide__inner { + width: 1200px; + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-direction: column; + flex-direction: column; } + +.hero-image-wrap { + width: 300px; + height: 240px; } + +.tagline { + text-align: center; } + +.tagline .tagline__title { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + color: #ffffff; + font-size: 37px; + line-height: 31px; + margin: 0 16px 10px 16px; + padding: 16px 0; + border-bottom: 1px rgba(255, 255, 255, 0.3) solid; } + +@media (min-width: 640px) { + .tagline { + padding: 0 16px; } + .tagline .tagline__title { + border: none; + padding: 0 24px; + margin: 0; } } + +.shown .tagline__title { + opacity: 1; + transform: translate3d(0, 0, 0); } + +.tagline .tagline__body { + color: #ffffff; + font-size: 17px; + line-height: 24px; + margin: 0 0 20px 0; + opacity: 0.5; } + +.shown .tagline__body { + opacity: .5; + transform: translate3d(0, 0, 0); } + +@media (max-width: 1200px) { + .slide.slide--one .slide__inner { + width: 100%; } } + +@media (max-width: 740px) { + .tagline .tagline__title { + font-size: 22px; + line-height: 26px; } + .tagline .tagline__body { + padding: 0 24px; } + .hero-image-wrap { + -ms-transform: scale(0.9); + transform: scale(0.9); + height: 210px; } } + +@media (max-width: 400px) { + .hero-image-wrap { + -ms-transform: scale(0.8); + transform: scale(0.8); } } + +.slide.slide--two .slide__inner { + background: #FFFFFF; + margin: 0 0 0 0; } + +@media (min-width: 1200px) { + .slide.slide--two .slide__inner { + top: -72px; + width: 1150px; + background: #FFFFFF; + box-shadow: 0 5px 16px 0 rgba(230, 235, 238, 0.68); + border-radius: 8px; } } + +/*** IMAGE ONE ***/ +.section__image-wrap--one { + width: 300px; + height: 300px; + position: relative; } + +.section__image-wrap--one .image-part { + position: absolute; + opacity: 0; + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; } + +.section__image-wrap--one .image-phone { + width: 103px; + height: 180px; + background-image: url(../img/new-site/image-phone@2x.png); + background-size: 103px; + left: 90px; + top: 60px; + -ms-transform: rotate(-49deg) scale(0.5); + transform: rotate(-49deg) scale(0.5); } + +.section__image-wrap--one .image-arrow { + width: 55px; + height: 55px; + background-image: url(../img/new-site/image-arrow@2x.png); + background-size: 55px; + left: 112px; + top: 120px; + transform: translate3d(0, 0, 0) scale(0.2) rotate(-29deg); + transition-delay: .4s; } + +.section__image-wrap--one .image-card { + width: 131px; + height: 97px; + background-image: url(../img/new-site/image-card@2x.png); + background-size: 131px; + left: 118px; + top: 30px; + transform: translate3d(20px, 0, 0) scale(0.6) rotate(44deg); } + +.section__image-wrap--one .image-waves { + width: 34px; + height: 42px; + background-image: url(../img/new-site/image-waves@2x.png); + background-size: 34px; + left: 30px; + top: 62px; + transform: translate3d(-20px, -20px, 0) scale(0.6) rotate(0); + transition-delay: .7s; } + +.section--shown .section__image-wrap--one .image-phone { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(-29deg); } + +.section--shown .section__image-wrap--one .image-arrow { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(-29deg); } + +.section--shown .section__image-wrap--one .image-card { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(24deg); } + +.section--shown .section__image-wrap--one .image-waves { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(0); } + +/*** IMAGE TWO ***/ +.section__image-wrap--two { + width: 300px; + height: 300px; + position: relative; } + +.section__image-wrap--two .image-part { + position: absolute; + opacity: 0; + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; } + +.section__image-wrap--two .image-card-lock--one { + width: 176px; + height: 184px; + background-image: url(../img/new-site/image-card-lock@2x.png); + background-size: 176px; + left: 60px; + top: 40px; + -ms-transform: rotate(0deg) scale(0.5); + transform: rotate(0deg) scale(0.5); } + +.section__image-wrap--two .image-card-lock--two { + width: 176px; + height: 130px; + background-image: url(../img/new-site/image-card-lock2@2x.png); + background-size: 176px; + left: 50px; + top: 128px; + -ms-transform: rotate(0deg) scale(0.5); + transform: rotate(0deg) scale(0.5); + transition-delay: .2s; } + +.section--shown .section__image-wrap--two .image-card-lock--one { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(-15deg); } + +.section--shown .section__image-wrap--two .image-card-lock--two { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(-15deg); } + +/*** IMAGE THREE ***/ +.section--three .section__image { + width: 270px; + height: 160px; } + +.section__image-wrap--three { + width: 270px; + height: 160px; + background-color: black; } + +.dapp-logos { + width: 270px; + height: 160px; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-pack: start; + justify-content: flex-start; + -ms-flex-align: center; + align-items: center; } + +.dapp-logo { + border-radius: 50%; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 70px; + height: 70px; + margin: 10px; + background: #7F44DF; + box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.15); + transition: transform .8s ease, opacity .4s ease; + transform: translate3d(0, 25px, 0) scale(0.9) rotate(0); + opacity: 0; } + +.dapp-logo:nth-child(1) { + transition-delay: .1s; } + +.dapp-logo:nth-child(2) { + transition-delay: .2s; } + +.dapp-logo:nth-child(3) { + transition-delay: .3s; } + +.dapp-logo:nth-child(4) { + transition-delay: .4s; } + +.dapp-logo:nth-child(5) { + transition-delay: .5s; } + +.dapp-logo:nth-child(6) { + transition-delay: .8s; } + +.dapp-logo--plus { + background-color: #EEE8F7; + box-shadow: none; + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; + transform: translate3d(0, 0, 0) scale(0.4) rotate(0); } + +.dapp-logo__icon { + display: block; + background-repeat: no-repeat; } + +.dapp-logo__icon--eth { + width: 23px; + height: 40px; + background-size: 23px; + background-image: url(../img/new-site/logo-eth@2x.png); } + +.dapp-logo__icon--tenx { + width: 40px; + height: 26px; + background-size: 40px; + background-image: url(../img/new-site/logo-tenx@2x.png); } + +.dapp-logo__icon--omise { + width: 30px; + height: 29px; + background-size: 30px; + background-image: url(../img/new-site/logo-omise@2x.png); } + +.dapp-logo__icon--status { + width: 32px; + height: 34px; + background-size: 33px; + background-image: url(../img/new-site/logo-status@2x.png); } + +.dapp-logo__icon--gnosis { + width: 44px; + height: 18px; + background-size: 45px; + background-image: url(../img/new-site/logo-gnosis@2x.png); } + +.dapp-logo__icon--plus { + width: 29px; + height: 29px; + background-size: 29px; + background-image: url(../img/new-site/logo-plus@2x.png); } + +.section--shown .dapp-logo { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1) rotate(0); } + +/*** IMAGE FOUR ***/ +.section--four .section__image { + width: 320px; + height: 120px; } + +.section__image-wrap--four { + width: 320px; + height: 120px; + position: relative; } + +.section__image-wrap--four .image-part, +.section__image-wrap--four .image-circle { + position: absolute; + opacity: 0; + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; } + +.section__image-wrap--four .image-lines { + width: 296px; + height: 112px; + background-size: 296px; + background-image: url(../img/new-site/lines@2x.png); + opacity: 1; } + +.image-cover { + position: absolute; + height: 112px; + width: 160px; + right: 0; + top: 0; + background-color: #ffffff; + transition: width 1.8s ease; } + +.section__image-wrap--four .image-logo { + left: 80px; + top: 13px; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 84px; + height: 84px; + background: #7F44DF; + border-radius: 50%; + box-shadow: 0 6px 9px 0 rgba(0, 0, 0, 0.15); + opacity: 1; } + +.image-logo .image-logo__symbol { + height: 38px; + width: 38px; + background-image: url(../img/new-site/status-symbol.svg); + background-size: 38px; + background-position: left; } + +.section__image-wrap--four .image-circle { + height: 22px; + width: 22px; + border-radius: 50%; + background: #FFFFFF; + border: 6px solid #42325A; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.4); + transition: transform 0.86s cubic-bezier(0.25, 0.46, 0, 1.105), opacity 0.2s ease; } + +.section__image-wrap--four .image-circle.image-circle--one { + left: 0px; + top: 45px; + opacity: 1; } + +.section__image-wrap--four .image-circle.image-circle--two { + left: 237px; + top: -6px; + opacity: 0; + -ms-transform: scale(0.1); + transform: scale(0.1); } + +.section__image-wrap--four .image-circle.image-circle--three { + left: 278px; + top: 94px; + -ms-transform: scale(0.1); + transform: scale(0.1); } + +.section__image-wrap--four .image-circle.image-circle--four { + left: 283px; + top: 45px; + -ms-transform: scale(0.1); + transform: scale(0.1); } + +.section--shown .image-cover { + width: 10px; } + +.section--shown .section__image-wrap--four .image-circle.image-circle--two { + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; + transition-delay: .8s; } + +.section--shown .section__image-wrap--four .image-circle.image-circle--three { + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; + transition-delay: 1.2s; } + +.section--shown .section__image-wrap--four .image-circle.image-circle--four { + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; + transition-delay: 1.6s; } + +@media (max-width: 1200px) { + .slide.slide--two .slide__inner { + width: 100%; + border-radius: 0; } } + +@media (max-width: 640px) { + .section--two .section__image { + height: 220px; } + .section__image-wrap--two .image-card-lock--one { + top: -10px; } + .section__image-wrap--two .image-card-lock--two { + top: 78px; } + .section--three .section__image { + height: 210px; } + .section--four .section__image, + .section__image-wrap--four { + height: 180px; } + .section__image-wrap--four { + margin: 16px 0 0 0; } } + +.slide.slide--three .slide__inner { + text-align: center; + margin: 60px 0 60px 0; } + +.slide__text { + width: 520px; } + +.slide__text h2 { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 24px; + font-weight: 500; + line-height: 1.42; + text-align: center; + color: #42505c; } + +.slide__text .text { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 18px; + margin-top: 12px; + line-height: 1.44; + text-align: center; + color: #8d99a4; } + +.slide__text .button { + display: inline-block; + width: 234px; + height: 45px; + margin-top: 16px; + border-radius: 8px; + background-color: #57a7ed; + box-shadow: 0 5px 8px 0 rgba(22, 51, 81, 0.06); + transition: opacity .2s ease; } + +.slide__text .button:hover { + opacity: 0.5; } + +@media (max-width: 740px) { + .slide__text { + width: auto; + padding: 0 24px; } + .slide__text h2 { + font-size: 22px; + line-height: 26px; } } + +@media (max-width: 640px) { + .slide__text .button { + width: 200px; } + .slide.slide--three .slide__inner { + margin: 24px 0 24px 0; } } + +.section { + display: -ms-flexbox; + display: flex; } + +.section:last-child { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: center; + align-items: center; + background-color: #f7f9fa; + padding-top: 42px; + padding-bottom: 40px; + border-top: 1px solid #ecf0f3; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; } + +.section:last-child h2 { + text-align: center; + margin-top: 20px; + font-size: 19px; + font-weight: 500; + line-height: 1.26; } + +.section:last-child .section__text { + text-align: center; + border: none; } + +.section:last-child .text { + max-width: 420px; + text-align: center; } + +.section:first-child { + margin-bottom: 50px; } + +.section:nth-of-type(2) .section__text { + padding-bottom: 50px; } + +.section:nth-of-type(1) { + border-bottom: solid #ecf0f3 1px; } + +@media (min-width: 640px) { + .section:nth-of-type(1) { + border-bottom: none; } } + +.section--title { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 24px; + font-weight: 500; + line-height: 1.42; + text-align: center; + margin-top: 20px; + margin-left: 40px; + margin-right: 40px; + color: #42505c; } + +@media (min-width: 640px) { + .section--title { + margin-top: 50px; + margin-bottom: 20px; } } + +.section__text { + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-preferred-size: 0; + flex-basis: 0; } + +@media (min-width: 640px) { + .section__text { + border-top: 1px solid #ecf0f3; } } + +@media (min-width: 640px) { + .section__text:not(:last-child) { + margin-left: 60px; + padding-right: 60px; + border-right: 1px solid #ecf0f3; } } + +@media (min-width: 640px) { + .section__text:last-child { + margin-right: 60px; + padding-left: 60px; } } + +.section__text h1 { + padding: 8px 16px; + margin: 16px auto; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + border-radius: 100px; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: 500; + line-height: 1.15; + letter-spacing: 1.1px; + text-align: center; + text-transform: uppercase; } + +@media (min-width: 640px) { + .section__text h1 { + margin: 30px 0; } } + +.section--dev h1 { + color: #4a98dc; + background-color: rgba(91, 171, 237, 0.2); } + +.section--org h1 { + color: white; + background-color: #5babed; } + +.section__text h2 { + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 19px; + font-weight: 500; + line-height: 1.26; + text-align: left; + color: #42505c; + margin-bottom: 14px; } + +.section__text .text + h2 { + margin-top: 30px; } + +.section__text .text { + font-family: "PostGrotesk-Book", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 18px; + color: #8D99A4; + line-height: 1.44; } + +.section__text .text strong { + font-weight: 500; + color: #42505c; } + +.section__image { + width: 300px; + height: 300px; } + +.section:nth-child(even) .section__text { + -ms-flex-order: 1; + order: 1; } + +.section:nth-child(even) .section__image { + -ms-flex-order: 0; + order: 0; } + +@media (max-width: 1024px) { + .section__text { + width: 300px; } } + +@media (max-width: 740px) { + .section__text { + padding: 0 16px; } + .section__text h2 { + font-size: 20px; + line-height: 24px; } + .section__image { + -ms-transform: scale(0.9); + transform: scale(0.9); } } + +@media (max-width: 640px) { + .section { + height: auto; + -ms-flex-direction: column; + flex-direction: column; } + .section__image { + height: 260px; } + .section__text { + width: 100%; + padding: 0 24px 24px 24px; } + .section .section__text { + -ms-flex-order: 1; + order: 1; } + .section .section__image { + -ms-flex-order: 0; + order: 0; } } + +.email-form { + display: -ms-flexbox; + display: flex; + width: 460px; + position: relative; } + +.email-form.email-form--error .email-form__inner { + animation-duration: .6s; + animation-fill-mode: both; + animation-name: shakeIt; + animation-timing-function: ease; } + +.email-form a { + color: #3685C9; + font-family: "PostGrotesk-Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; } + +.email-form__responces { + position: relative; + color: rgba(255, 255, 255, 0.6); + width: 100%; + text-align: left; + margin: 10px 0 0 0; + font-size: 14px; + line-height: 16px; + height: 50px; } + +.email-form__error-message { + opacity: 0; + transform: translate3d(0, 10px, 0); + transition: transform .2s ease, opacity .2s ease; } + +.email-form__error-message.email-form__error-message--shown { + opacity: 1; + transform: translate3d(0, 0, 0); } + +.email-form__success-message { + position: absolute; + top: 0px; + left: 0px; + width: 350px; + z-index: 900; + padding: 15px; + font-size: 15px; + border-radius: 8px; + text-align: left; + background-color: #3E4C59; + color: white; + line-height: 22px; + opacity: 0; + transform: translate3d(0, 10px, 0) scaleX(0.94) scaleY(0.94); + transition: transform 0.4s cubic-bezier(0.32, 0.092, 0, 1.3), opacity 0.2s ease; } + +.email-form__success-message::after { + content: ""; + position: absolute; + top: -5px; + left: 10px; + width: 0px; + height: 0px; + margin: 0 0 0 -7px; + border-left: 14px solid transparent; + border-right: 14px solid transparent; + border-bottom: 14px solid #3E4C59; } + +.email-form__success-message.email-form__success-message--shown { + opacity: 1; + transform: translate3d(0, 0, 0) scaleX(1) scaleY(1); } + +.email-form__inner { + display: -ms-flexbox; + display: flex; + width: 460px; + position: relative; } + +.email-form__input { + height: 45px; + line-height: 45px; } + +.email-form__input--email { + text-align: left; + background-color: #F2F6F8; + color: #49555F; + width: 240px; + padding: 0 15px; + margin: 0 0 0 0; + font-family: 'PostGrotesk-Book', sans-serif; + box-sizing: border-box; + border-radius: 8px 0 0 8px; + transition: background-color .2s ease; + font-weight: 400; + font-size: 16px; } + +.email-form--valid .email-form__input--email { + color: #92999F; + background-image: url(../img/new-site/verified.png); + background-size: 24px; + background-repeat: no-repeat; + background-position: right 10px center; } + +.email-form__input--email::-webkit-input-placeholder { + color: #AEB5BA; } + +.email-form__input--email::-moz-placeholder { + color: #AEB5BA; } + +.email-form__input--email:-ms-input-placeholder { + color: #AEB5BA; } + +.email-form__input--email:-moz-placeholder { + color: rgba(255, 255, 255, 0.6); } + +.email-form input[type="submit"] { + width: 220px; + -ms-flex-negative: 0; + flex-shrink: 0; + -ms-flex-positive: 0; + flex-grow: 0; + border-radius: 0 8px 8px 0; } + +@media (max-width: 640px) { + .email-form, .email-form__inner { + display: -ms-flexbox; + display: flex; + width: auto; } + .email-form__inner { + -ms-flex-direction: column; + flex-direction: column; } + .email-form__input--email { + width: 240px; + border-radius: 8px; + margin: 0 0 16px 0; } + .email-form input[type="submit"] { + width: 240px; + border-radius: 8px; } + .email-form__responces { + width: 240px; + text-align: center; + margin-bottom: 24px; + height: auto; } + .email-form__success-message { + width: 240px; } + .email-form__success-message::after { + left: 50%; + margin: 0 0 0 -14px; } } + +@keyframes shakeIt { + 0%, 100% { + transform: translate3d(0, 0, 0); } + 20%, 60% { + transform: translate3d(-4px, 0, 0); } + 40%, 80% { + transform: translate3d(4px, 0, 0); } } + +.footer { + position: fixed; + width: 100%; + bottom: 0; + left: 0; + z-index: -1; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + background-color: #1e3751; + border-top: 100px solid #1e3751; } + +.footer.footer--not-fancy { + position: static; + border-top: 0px; } + +.footer.footer--page { + position: static; + margin: -96px 0 0 0; + border-top: 56px solid #4A5C69; } + +.footer-inner { + display: -ms-flexbox; + display: flex; + -ms-flex-pack: justify; + justify-content: space-between; + width: 1080px; } + +.footer-logo-wrap { + width: 280px; + display: -ms-flexbox; + display: flex; } + +.footer-logo-wrap__inner { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 65px 0 75px 0; } + +.footer-logo { + width: 52px; + height: 52px; + background-size: 52px; + background-image: url(../img/new-site/footer-logo@2x.png); } + +.footer-address { + color: white; + padding: 20px 0 0 0; + opacity: .5; } + +.footer-table { + display: -ms-flexbox; + display: flex; } + +.footer-table__column { + box-sizing: border-box; + padding: 60px 40px; } + +.footer-header { + color: white; + opacity: .5; + font-size: 17px; + margin: 0 0 40px 0; } + +.footer-link { + height: 32px; + line-height: 32px; + font-size: 16px; + margin: 0 0 15px 0; } + +.footer-link a { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; } + +.footer-icon { + display: inline-block; + width: 32px; + height: 32px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 50%; + margin: 0 15px 0 0; + background-repeat: no-repeat; + background-size: 24px; + background-position: center; } + +.footer-link--fb .footer-icon { + background-image: url(../img/new-site/icon_fb.svg); } + +.footer-link--tw .footer-icon { + background-image: url(../img/new-site/icon_tw2.svg); } + +.footer-link--sl .footer-icon { + background-image: url(../img/new-site/icon_sl2.svg); } + +.footer-link--gh .footer-icon { + background-image: url(../img/new-site/icon_gh2.svg); } + +.footer-link--rd .footer-icon { + background-image: url(../img/new-site/icon_rd2.svg); } + +.footer-link--yt .footer-icon { + background-image: url(../img/new-site/icon_yt.svg); } + +.footer-link a { + color: white; } + +.footer-link a:hover { + opacity: .8; } + +.language-switcher { + font-family: "PostGrotesk-Book", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + color: white; + -webkit-appearance: none; + font-size: 16px; + line-height: 32px; + padding: 0 24px 0 0; + background-image: url(../img/new-site/icon_dropdown-white.svg); + background-size: 24px; + background-repeat: no-repeat; + background-position: right center; } + +.language-switcher:focus { + outline: none; } + +@media (max-width: 1140px) { + .footer-inner { + width: 820px; + -ms-flex-pack: distribute; + justify-content: space-around; } + .footer-logo-wrap { + width: 200px; } } + +@media (max-width: 767px) { + .footer { + position: static; + border-top: 0px; } + .footer.footer--page { + border-top: 100px solid #4A5C69; } + .footer-inner { + padding: 30px 0 0 0; + -ms-flex-direction: column; + flex-direction: column; } + .footer-table { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; } + .footer-table__column { + text-align: center; + padding: 10px 16px; } + .footer-logo-wrap { + width: auto; + -ms-flex-order: 2; + order: 2; } + .footer-header { + margin: 0 0 10px 0; } + .footer-logo-wrap__inner { + width: 100%; + padding: 0 0 40px 0; + -ms-flex-align: center; + align-items: center; + text-align: center; } + .footer-link { + text-align: center; + height: 24px; + line-height: 24px; + margin: 0 0 10px 0; } + .footer-link a { + -ms-flex-pack: center; + justify-content: center; + text-align: center; } + .footer-icon { + display: none; } + .footer-logo { + display: none; } } diff --git a/static_langing_page/dest/img/new-site/apple-touch-icon-1024.png b/static_langing_page/dest/img/new-site/apple-touch-icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..7d402d8700915f85e73b0ffbcc7a3df9a781aaac GIT binary patch literal 8710 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 Hu0mjfV9FJW literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/apple-touch-icon-76.png b/static_langing_page/dest/img/new-site/apple-touch-icon-76.png new file mode 100644 index 0000000000000000000000000000000000000000..b9490118889404f4a95daaa9084ea25fc473a758 GIT binary patch literal 859 zcmV-h1El;Dbuwg-qavPvD17 z;fGM+h*9B*QsIkL;*D71jalN3TH=peh?Y*Avy`S#C zqVK_^@589@#HsMbtMJOQ@yxaI&A0N-xAM-o^3S>R(7W@~zx33=^w-7p*v9qP$MxCB z_21C<-_iHt)%WAp_~Y03*M!c{Pghr z_VWDp^ZfVp{rL6$`S$(#`ThI){`~v?{QUm?{Qmv@{{H^{|NsAwKTHn*000VfQchC< z1uKt}ywuh5{2fKy*#H0m(Md!>R9M69*nd~SP#6dB`56;ZiYSSsBqb3_NgA^B!$Ko! zyYK%sgwE*{yZ4-DyMH}JE%vanQQ znP_H~Qq1G?veUxsM#jWai-}pHhglgLJZ0Gs zTtEXBEKBHL`U%z8*(aLKAgT^qyGIJ)RbjqYq?n`%%=@Hlp&}ex;V~9!!Ldu6g*ssD zK*nN2DCT;>T1bFmr+5oBp;(K6Wr5f}!IHLsSc!lYpc6ni{+b(zdGPn5?1Z+);rL|4 z-awj_++D z#8Pl9UZ1-b@}MVa2Sc*4VVc4A&M%0)Ll_IrM)g9a~_G>SD=CbLB;K8Ao+Z lNOgfpceP4&=}U1P{SAnQ6Qftygu4I$002ovPDHLkV1n1b<#qr7 literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/cardlogo-inversed.svg b/static_langing_page/dest/img/new-site/cardlogo-inversed.svg new file mode 100644 index 0000000..b8535fe --- /dev/null +++ b/static_langing_page/dest/img/new-site/cardlogo-inversed.svg @@ -0,0 +1 @@ +Combined Shape \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/cardlogo.svg b/static_langing_page/dest/img/new-site/cardlogo.svg new file mode 100644 index 0000000..d838490 --- /dev/null +++ b/static_langing_page/dest/img/new-site/cardlogo.svg @@ -0,0 +1 @@ +Combined Shape \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/cardlogo@2x.png b/static_langing_page/dest/img/new-site/cardlogo@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4bc9c8d5b5e1c4d6f124b1feed65dd924568b7 GIT binary patch literal 1320 zcmV+@1=sqCP)o2dPdED zOwN8y&xBRcg;vprSki}C(ui8pj9}D_VbqUg)sbe^lWNzLYuA-**OzbDm~hybaoCx0 z*_m?Lop;)uc-p6i-l&J(s*2yMi{G!1;jxqAv6SMpnB%sZay`$#7rRK=9>dLk2 z&AIH&y6n%r?a{#Q(!uW3#P8S0@Yu=l+RX9W&GFvS^54?)-_!Hp)br)r_2%65>*V+B z<@oL9`0wcX@9FvR?D_NV`t$Gl^zi%j@%#7m{P*?z`1btz`2G6%{rvm>{r&#_{{H{} z|3nH9vj6}9D0EUzQvd=31qL7?BP%Q~Fi}}tUS@cFfP{&Yl%=V8QRDM5N?#fBxZC1Oj|L`AU?YcvsijYhe@ zJ|8roa%X3OyYD9sXXiQh?Cj3W&H#|o8H_fk#~+HuqAt_jScd(dyG=sa&diXE(B2`$t##0`&f8W|(Uhf+ztV=qmp1=pP)LoSB{&>1(H49)&oQ zqPRRTwH2(rZ)9S6W^!z>zoYnP*8sT{eCG{k_U{WG%U>PsZ)G>K+XtulH3A&s9=EAO z^sId&z<$mJ>DlDmjZp&Z!LM}kh{L@}5MU=Bgm!qrXPt7n4Go*)+-NyQfsJUO&+@oA z@*Eq`)O5xJf1$V$9hyuZINUw*gI##~|ATYyK9FDwUa?3l~=~js#iV>B0;mBTX@eumU53jiEzEdQz3Qn_BdoZg`&KPdfarUFJE9! zmU_jqN8xe9d5$GgtGnm7f3I^t7Ken>l3D%(QreSO-XmRJTR~bQL|*GgTC+)BdrV$K z58%C|7NoqUskAn)yvDS=R=ULIe}&@;z0jCR(tt(YNJ!!kOXAo~-k?z8X!A);*f6%K e`ay4HGroup \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/cardwordmark.svg b/static_langing_page/dest/img/new-site/cardwordmark.svg new file mode 100644 index 0000000..9f8d33e --- /dev/null +++ b/static_langing_page/dest/img/new-site/cardwordmark.svg @@ -0,0 +1 @@ +Group \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/cardwordmark@2x.png b/static_langing_page/dest/img/new-site/cardwordmark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7b13fd0d0dffbed0b5cdcc3a197f7fc8659e81d7 GIT binary patch literal 5627 zcmbVQRZtWTw_m!Ju4P5K%cYc%l#T`IW>Gp6q``#+sYL_L$ue2}(_H)&3?E;4w}pG*(v>mB; zY+as_YnMm)>zIqwD_qufEm$CuZ(Y`3Z_NGZ*w@nof`Fj^8{ereHNV8gR-Yd%n>=pM zB)q9J5%Kj9dZC~^-&vm7NRFGkKO&~zNO{=pZFV$P=(2v?Df#MH%BGBd#6hlm7CyN<}`n7U5)ta*sCY`kS!si+A?*ui8JXt}W$Y z2dBCN2PFpMx1XHZ?qO|0#-seLf>!RSZ_0RLd{+x30zb9Jx%Ld=HVU*5B8*z zJlyP$@}+I`=WWY_wl%JgiTc?G1DjnG2k52?3D=2fCIoTvwM>w8LU=miX)i(P54tX} zy2y0tjYz>iPD)(&Lm$`24V;n;*EiADuRRW5tXnyer;Dd1Thrx`RVmW4pb!#gC*6$l zcW2M#W*K~5ce}6es3bDEdX%nMBsvd0Zi_k!w|hgv_H{9DqIE-GM7`>n(0k=)n|&01 z+h0v`{(LS607;cPT|TXzRWiGFWPUKad^3~!~HQtM6lBo;{yLi(JkG`xnsGzQKYDTPDcrssv%A9 zYJf+Q6<{E8#rL?V+bi+%V62J?)HxlasIxdH%(xk0!AGqG(lwXN=JMGmMgHtULf7>I zMX-VwQi;~Wj-%=r8#@X#Lb54RZ?z7gg6Xn4JtBcsqoZ_rD>uLJP5J9OUO4T!>#V6e ze?~tz$<7POavx#ka{e;<5=9A8s4aLSop;g%q7=`^tG{n$T*W`_9b z%gy#zmE!{+$?@5}QJPX{zv?t-z#a6tTBY{4>C%b8DUnu&1^#s5k$Bu8C8aqCUV1-? z97oKRAJ@f0%W>_03%=i4;-sLD4bU(c&92H;9-OHJ6O)ytvjeDVTBL!*)P0ZB06<4? z9oEK**-u>yo7OB4^6eG5cLLv;LdN|qny*gQFX{;!LBUz@tc)iyhRrnG_Q~hc* zSIKTSCp4g-g_6j5vTto}(-YMYA56!-nv=Qxkik`bLdxz4YT*olsU@WhNddQ9ckyEW zlsW@|!TnEcbT`Zdi*nTf`SB!!Pi*4mRHeG%Eo1c_>&xU!e0aV%uF9AQ=n^?%JuWY~ z)?}v*M-p>ao~;PH#-=giCnyay_OHVW%_R_fM5ME`3 z5LY?-m>lY1urv`yT9rZ^qEzTaUNUSeqM&D2Yt<`9l}|H}G{K0;6**~J;|rYn)_J`f z#rtWba#^hZ`#o+v4X)526XV|dy6ugzeo(Ku*JQ9|Yf5xxaoVH4?Um5N*Gz(`*_9AFb}u})??NcWQG!u|40Fszz|bNSF{F6k!i>k9xTS_%B5J-i zO8P#`@gl9Uem~LDUbB0ctknH+PKr$L!wg)BR^2mxJc3nOpMxiwZg+Rm%?{9h_t{sG z_M9`mWDIcS%j=)%`&d_yk4NihgTMpRz#>8MeNQrr8W@>}YAm1EQ4M3}y_JNa71(@Yei4-2*7WaN(%;VA^5DN&1QrUx-85L7&76dznvuHs!grB>6fD_Y;t|x zWZfy^ps)2J&^yANh3n(Fh@1lwPII00`3GV>5#A;(X+*7a+-jgs1Z0%dC4wv`=oVYW z+GF{jJe;_?ASaj>6|vOUTd6%@7RCcZj7?HX46PEka?23w8q9QAV%JN)oP#$VsusWP z(xgp%^1lK4eATS$`wpU)qGZz*B!DnVP_yYf;rv z{)nGg^2399O(G!kFS>N?iyN~uhSzP)UYGQ3wta6%%V*=BsSzdkOsn-hz!#;!GV@Ba zZVM{xNHy;n2C-f8g86Et8c8`-VnNPi;&Xj+LU5s6g)>A}R^VP2b#B1L8!d8WUVI=; zh&4x_F)BTkqj%6qBn8C)OUPl^g_nN`<3bCZh%R&a>TKq2JTenHwM}7}10Hj$gKf7q zvz|_gDPiblzB%i>Ua-3yCoON?;Z|QBYmc+OXhj7sx3O|sZZQ-7AaZ@fNSCo#7n{=b zi*d`Hqf!Up4FqF0_N@SfX*(Qpn=zs%DkVYNTxE+|7(TQtiIDs$iF;iba^0;g+kc+s z4C#^-YgkZAEyZPu`0Cq6zfi;V_@Fv;xebi}ZmZFqDY(b;ccH_=*9Kfhh=ZR=!xUrl z3-n;cvI3W)gPX&%|2G*GH#mf&FkcKv+nB~0Ti;AE7={lHm8xS?_E=bybdBX#KmUEKeGdsr6Y58ZL#3*#BA!1ytr3SoV37a7zsi3Z|Lgl# zG)Tdg3+lb!_#U3$75hHxcC(+!8(o{qXb6YSfAgEVg66HWLc^18{A)jBE8+Itd#t)S zXSU$Aci)%8xWk|1Ipn?lu-Eg3JzRJ1wspL=S@a>>>%fV${goqZbd$~TKy!SU`5oKq zH&QEEc}p&G_C;6L4O9fEa^d zvY|$Qf(7mg*b;<{3rB3~FV7>7-GiVyP?B>rwTN*e7JSj;VMuNWTE`lWm#-z~hK zzVN1^FmR%gg~G;B`FbqrYmUsRw*8j$VL1Jr*q#WB%ZK7KOYr>Wj|&FxSq6yCXe$9p!{-OMHp1o$m_LwmYLwr%Q??(IGr{J~TmULW+}z-1S?_(>rE=Uk4r5xpcb zV$n?nM4#Nh5$GY}HF6ZEf_mvVyaXz?B4ucU9X&y05K3tCeAK1d3&tZ<_OyRWQW!S z1`o@ovYoZc#G?p-Rt+5o3qKGMsU3@FCqzIQQ=)BZ@oVESD&c}FTEQ>2u=J={VC1?tnQ4UqH3$f%uV+v6J z&{XljJg4!1YR_Tz?(TG$y~;Ogoq+T}~hDx69TwZ1UdPz96qq{X(&HJ~3heR?;<~APpnsq|;Kix9rdvM&W z7DC1OB6)-KL&vk3P0Gojv(C?TM5V4Lbi>HO5&0ugyFJDTm(sM-^Y5!FdVO~ z^%t~fM%oU)9%)~Ij9}aGl@)+k6-6_o{^|MjTU7#LvDw_+;|r~xX-$PZ0W>$Ruu%O9 zkGRk%E-nr|Ke)EBi}(S1<(HV=H{(YPr$e%(vrF7jb>6?({8)xx?T-hFxs}9A!sQ7p zKc*V(52S;1ThR1`v^rAiKb@WtHSd+#0%l zax7Su0|0-$6YvodSZ4jwd{2$UsuM7fMNZ$UdmvJ8FzuY3vc+m?!IW<%jfNcj5z=-w zB>S=Wbx7nhVN>h5yj!Y0DG?~fx5ZJB|G+TqQ;~Wv8z7{DKPrg`%m$qsmSsPumB?eR zLz>Q!U2#izd49utqo>az!`~zDrT;Yj{8>8F4mNXY8g#Hr>H9 z;w)Uhl3`8(QM;m$vkT8Yd!CJbWeh?Kkk$?rw4nM~TSb&7a7J2V=T2X`s{M}zNih-6 z3VahMDT}y8ttz0sLrR7i2U9scCHuFohOOr^{s@)cDvmEZu6V?vsL{!Ca~tl5x?F&P z^n5u6b~GqHyT9aldR#n44JC&Lg)zH2t2lG6_%i;qj?5ahv>+|beIO%i|EogteWd(- zMXP-taorLywMEs#_=YpzICVe+Ek zg+^v^Wj#Kxsm<(uLQypc-Vu<7@wIcLPXIfo;M{i;{xkkl1|^AMoC_20}A zKfr8|VsMOuQn6(0-*NR>Co6X(ZIA=ga8q7_f;)dW4WRqJMuzyM8sYY(5!6d1VY$1O z7p0^ju}0)Ds+p-8vd;jMYs`;{Fu|4(0t?!#1=6R=13t^%xR~nEy-WO7#~PRadlwdg zsfou26-LU=z)4ln6*{c1^Jo!!riGPL`9s+a3~2tBPwFlz?k&gG5|jBbwumwRnlekS zZ#cpu7-tEljE6^kyQDBJxK}{{x=NjGWaVKPekH2`Omiv7!yDVHc?uvvK5~Exx_WG4 zrT(r)WPu&f_%X9*o3_!;Uca^HmWzN!>Y`J@K{+wd@&jS8p1f%mPpfQ1w)P4@LnUol zU{YeP+?7R0nk;QMmXn(_1G0KlzSLaq^#yMB{c(bTwxx_0@tAb`-j@GmBy`6@b0YJ7 zM#1~1A)mR9?QHfR18vdDWyUG7;%0C(&6}`bQ5{?S7?IgVG^B=1j>K`C#X6fe)x^^{b;*S3?VJd)301_=F7!LmR5wei*GYKB(kMw zf4}85RO{JBWGMkBB`kfpT#MhHv#l8CU)eELOGg`+*9Z~;A8i`jTl{2%n5cHl{*2`W zVV=g9^e@8b@uTf@5MkXKpz9AB2eGtG5me9v_mj5Da}5*x2}8tF@335Ecn?psr!-+g z@5dL3GI{e(0i{B)pmB@ncsmt!2|2HF*8l*MlNWGObSz9jvGi29Zd=KJ*5~bLj1XX; zPvG^36NH`ym>ANLXmHL*D;HvG`?h-Bsw-jjPka83x+`D4kLhVUPu-uVWLJd)PlP(6 z!SsKI==A72wV=Ugg&O;Ng5Q$Jdaxc(hst;D<=X@S1Y2ewv&R?O?#R@v3_VR>BA)t9 z>=AIv=?2gCr2Rb+Wb3;7b-SM8`K6l``gMQwmitD2c;gw6P?|p`Z8PtUc|_ag(IOu{ z-6imAiBnb5a76@VjRu7$&TpPMyV)Zh!))Cc%U{!bP=n|_&HK8n*V}n6iZ~62yG($P zj;*GfRD86Ifn3r;k)&|4D?bBQG#lzgtjtC#-B}H1x9&!hpwt=v+Yp<}rn2ITiYDz} z3Pa~?(wxuh9IKVlWtyt5s6YB^!6KQ~;aA_fG7bOiXa0gxYVBr5S+veC&vwzt^1aa4 zoMIvMjGc>=`YzifpK{V+Aj)yOY=`8NjAJ8I_W^$*bv#$iO9_(FK9=uI0~Usn4yz(p>*Ob+y%@ KYSpT+$o~OG&!klV literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/chip@2x.png b/static_langing_page/dest/img/new-site/chip@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..698a8928f055c1fbd6f5324deec22504fc9be9d4 GIT binary patch literal 481 zcmV<70UrK|P)!fIgmuL8pyHsE$Rilt{Ii zO1+>?z@tyar%}?fR@Svx*tc2TyIbMEUFpSO>c(K}$6@cvV(`mj&aaGS00009bW%=J z00t|Ml)csS{VNuQ-T(jrCrLy>R7l6Y*4vK4AP@%Nfl^?^Lp`*vR(Ib2E%!njK*}+3 z{~I%HzL>Va3;+P|PAbX=k%*M+IK0AyrlKIeezjl#0IVmfgG4<8073PT1OV49P;=<0 z1yTpH4N6dLP(isPgHK!3z&+mD;0Z5jI1L8hF~el9Uvc+H!83X}+dBt5{>I^in{i%> zdEB7=&-)gSk0=~)bjuM3Tl{Qscey>+p~2ZLXEaWAKP*oG$+{H!w6cGq6(+1FyluzM-Fx@#KXCBS;Ugzbo;q{x;-$;iZrr+a@BV{_j~+jH`t13O zm#<#GefR#u$4{TXe*6C8=da&?|5%>~qkjCGDZ!e7M&7L)k&TkUGC0o&sQouCNaD6X5kG*&Rd1Q zWxc(BY?}OeYLbF~{v_Q~7xXeRo*Pdy4VH^|;nef=M1OC?gjtot7-YlYObeK(C3*KX-}{X9?pFYBV^=O42qMEV1>HG`+C KpUXO@geCwjRKw^1 literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/icon_dropdown-white.svg b/static_langing_page/dest/img/new-site/icon_dropdown-white.svg new file mode 100644 index 0000000..9a93271 --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_dropdown-white.svg @@ -0,0 +1 @@ +icon_dropdown-dark \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/icon_external.png b/static_langing_page/dest/img/new-site/icon_external.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e97710f90058bc37e68c69f3851e355a09187c GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dynE^f_u0Z-fUBDmdwPirdluCm9 zf*F`tMWnPWJUqR;{rtltqN4jIPTIBm@|9b+A3S>ee&(p;*#NzbY z$+yLt6a-w=rx@+-oHosC(f|Jwc^~w?4u0Oa_1NC)3oIBCG_sF#UY_fq`g*yg&GAW7 zl2%shu8FZ#)L*=%g>%8JF1^c6=gOI;{@w4&7547)z5^=$3P;p43Mzl>EJ`?V^nU!E zDGTmjIjyLoW23SrW*&QaYwzQU-*+m%*>UEe*gCcH*8Y>R3(a{{x$1X)wNV~=D+&wmOi75dwf0w< rJ<;pOMvaqubP0l+XkK5@Y#p literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/icon_fb.svg b/static_langing_page/dest/img/new-site/icon_fb.svg new file mode 100644 index 0000000..2dce205 --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_fb.svg @@ -0,0 +1 @@ +icon_fb3 \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/icon_gh2.svg b/static_langing_page/dest/img/new-site/icon_gh2.svg new file mode 100644 index 0000000..d83b4b7 --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_gh2.svg @@ -0,0 +1 @@ +icon_gh \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/icon_rd2.svg b/static_langing_page/dest/img/new-site/icon_rd2.svg new file mode 100644 index 0000000..d179ce6 --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_rd2.svg @@ -0,0 +1 @@ +icon_rd \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/icon_tw2.svg b/static_langing_page/dest/img/new-site/icon_tw2.svg new file mode 100644 index 0000000..49b78fa --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_tw2.svg @@ -0,0 +1 @@ +icon_tw \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/icon_yt.svg b/static_langing_page/dest/img/new-site/icon_yt.svg new file mode 100644 index 0000000..b636bb7 --- /dev/null +++ b/static_langing_page/dest/img/new-site/icon_yt.svg @@ -0,0 +1 @@ +icon_yt \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/image-arrow@2x.png b/static_langing_page/dest/img/new-site/image-arrow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a68564845e1f5d63fdc363811827feaf6752cfb3 GIT binary patch literal 3684 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 GP5l=~Kn|4v literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/image-card-lock@2x.png b/static_langing_page/dest/img/new-site/image-card-lock@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0417dd9b0c33bce86e5f19e3d6fdc3ddc37d457c GIT binary patch literal 6580 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 Tm3x%-*MD}?2#tD<#K!#-vwn^| literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/image-phone@2x.png b/static_langing_page/dest/img/new-site/image-phone@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d28cdc88b1ba1d413f4cd44f4ae27132edc4816 GIT binary patch literal 3954 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 literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/lines@2x.png b/static_langing_page/dest/img/new-site/lines@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..330ba33dc68fa16752520027f7176d01eeb6ced4 GIT binary patch literal 1910 zcma)7Yfuws6wNLhNu-!Sq##OM#2PzF070w>WqBx%XiP;wK+vWXp{TqOh`@?au*%Ci zf(kK6@ma%Q5g`;%8*8;vAc9mtq7fwtBmo5yLX~a;KicWE)15Qle_o1Nm1_3Qo7pLa4oW4q zRgcLu<8qBsHm#Ct$40bD`OKJ9Z`9i{*O+`7gU&0{=%L#xrPC@5Sd<=0sah%1s4&Pi zI=jxLo`X`R#=JTg1}XL+?4(+SS@g#FsDJk=rMf5`=FaFx?1u4AbKYV}`X}#T2H*e? zzrc{Nh)h{#MTJ*%TK_&%Tt@L!85zS=spoMa7les_O49Ub^;u+fTv= z58Fk3{R85cuSXTCw`$F_cIMstf#As|91gGb^Y++ju|#oaB#0uj%< zb5=Lr+u^;G$bx)XPu}h6yy&=2(CV30pU$u<-^cuXe`NY+Lub&?$Z)Yd3Y3>{U~^Xf zU4uN-CCwMHa$WiSQ^(@ohQUm2V#c7FWD?)oEuXs2^|9fx-#qAN6f+S?*N9_2L33Yp zF4mA}5qfyG{c~`EWq&aL2-@c6mTC+625|VtaO-Ci!y(t^_yf6hz3=5(!pnm%e^$#! z(;`-}RGjj=h>%Dcy88)d zs^?I0sHNYN$?FGKov3@AcTKy=xN!Ba$5(XZIyg^04Wz9Bw=@i2JsJYoaO78?5doXn zWzFmWN%DIIM{{D+ld9!QjsCx;OJJ?2ePS~>R>pky`~7| z9VnZ^LoG?NJe0Sc?q*J&xL5Nf;Bkq*9uHMEJK4dPRib(nra|5tt>K46;4>znAkh;! zMFB5Vkjbxd(bfOcML3&!#SV@htVseqwv{pQkZtqCW~B2h<2(c%XqsRmovn;8Fvo>{ zomdbOFR4Sz8yGE100%>Q=3=sAZ&FMc@<>3r2C|EG3o1;>qD0AcwA_ovB@kjZCOCf# zU+7AZ+(b*PX#C~Gf?cvr<698n=rM};Gz4v3_Q_-fBIE}I9|u9_@H$&~?_;z3#(>Sa z-&1-5u*ogHhl`e8uV|FNJw?5|*6tfuN>=sHOmU$3HaGi_h{frvzSQyUZro$Jrl_`K z;>Q&05}3v#XXQ6d+GNLhk(d}0sHRy>c~kjZ)wn;YIDg3c zMcEl{9Nj&;PCVXn(0Kxv?z`Ztl?Lli9y-Q#N0iZ?tfbvMcRFn2JdC}25!fD z?-xiKtzq_KvwqMswNenenCy`7yqg19|3G6N4l^a+5FL^fqUHK8Pl!pbNVA16z7jtD zZmv89F&oE&kj_#@Spdj>Qn|})zL=dy7|Zd{O^lj^xq9=87_nf^RCu2$x&D=C6W|d` z&%~CaBJ+8|v^OWy;po1a*8k19|6;1V*~u1`4%DR2lRYybCIsoEm2r&-8N(tYz@wk8 zFd~E|U9o|sbrd6z9hE`9iH9QlaU0ngDQ8KlO0oJqEeKnYdWE8f`o-U8ahL< z1srNCrA|<;t00x_gFISF)2;zMPq%JNShofqY6&o;fGa+%t>`l&-%xzB4KOME0&Dhy zG=2XA9KK7rnZ4jj2j3;c#9r_>$*7wey{3}ZJDvP~B<$JNIjPYsFNDMBw1L7puIs9Ro>`zT?0rVOm(d9JZ^8L(W_t5I H_vZWw4Q#q3 literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/logo-16.png b/static_langing_page/dest/img/new-site/logo-16.png new file mode 100644 index 0000000000000000000000000000000000000000..3038cc47302b430f09d3b23f24524f66d88a96fc GIT binary patch literal 259 zcmV+e0sQ`nP)^k@c<85p>8yt8u8Hcik?gsd z?Yy1ty`S#CqVLA6@XE6B%(U{CKdhO-AD)l5WFs2!h)>c2 z7z=4ifQhc3<{<);Wm^}y1haS2lmiyht%6~#L`Q=ck)&QsIkL;*3|~j#}c6TjP&h zaU9H zvX1MulkB*d?Yf%ny`JvFsPM$8@Wra|$glCxy7Sb)^w`Gr+RF9c(D&li_v6?2<=Ob= z+xY3<`Rd^L@$38a@cj4m{rL9%`}+R;`~Lm>{{R2~FBA@p00006bW%=J034>}=Iz=5 z<1YXJ0M1E7K~y-)y_44xf-n$8H$f2v8;S+Hg1urvK;8fUD<2qyaW?Rz_w}4fa(A)- zj7zd05@!G`FQ+8WfXk`jkV_XNkTdcNp=eIVhFGX<14P7YQy}y`4C9taq4&TjElMbR zAC6N>*v6bZLG@$q773vp^NUWks2Gi=IazL2c88vWQmbnzlbl^&h#1^XN5`gwb)vz5 z5cCAUD}*pg2p08*VayNYBdgDdA62@=JjDK@AgbBjIp@8l{o_x53z>_Pxq6w)3yceI b$#Dj6f%rP>OEjJb00000NkvXXu0mjfZnOV# literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/logo-eth@2x.png b/static_langing_page/dest/img/new-site/logo-eth@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..578982c55fceba8c7dec3dcccca30f8b1158f2ab GIT binary patch literal 1673 zcmeAS@N?(olHy`uVBq!ia0vp^dO#e&!3-n={kLynU|`Y+@Ck7Ra{rHl(GVaM0@bQF z9stwIj*=k1U}`JIdkXDU$AiTlBLU*uUNTi^_sQoH*DOrdF!_AyLRu{yYJwk zBgamhJbmWug^QQ2T)lSv#?4!|@7}xr;NhbuPoF)1@$&VXx9>iD{Pg+D*KgmyDKlv^ zFfd1Wx;Tb-9G`mqd-P=o3HA?SERBmeEF`q}Tv%P(G&(vStuk_v(a@XI!4Yyus9R&X zQr?9wjtH~qI|90Xe)pN>UF7+G(wDyr{U_JR&1$cCzt`MMdZo05q zIjHZr%>3vkzj;kOD>j~a@6gz6(p~j=&iwf8C!!b3Z}#!FVpfWiPip;sLgq>2h01`3 z>kNW(--laTB%JbAICgunPst7TIn0}v%x#c**JpSo&^e0Fi?>qs%Ex)8Y*z1P&ui9V zj$>x7cyG1JZx+v!2TD`#x3TbT3;41uQfa^2!?P1k9Q0e{*Dg{wUE;}c6TTu&EAgPp z;DiZpYfJdPv42R)IB;9(>%rRmBL@Rz%blG6c}PFe=r?3nH~k}G^3Zm*p{(_?qxb(; z*zddW%d02;!h`wTHui@P>i@Mk-ZWv;+1hvoGR}p?u&Y8er5NJ=Sx2o_sy29 zS@$DkiTSC6e>WcZ9Cfa+;Dzd=aKZdT@14%KN>ywBbCuDG&e<;butE-jxsFC~3qHlIDEaO>2alX7>*WHBrEO{wUXUw`mc zY`RS0@;3`i-spTXn`7YgBt6skprcUwqLs!j%#F)0Kc5-l&!BHU-_meh%bb|4QR~{~ z#PGe9d1*2`Xz2^(xO6*~@YONWMMa($(>WB?r`!MH(R}h`)r1|lAI+X7QQIa_RK$6a v|Jh#84m)Q~%biv;)+F515I&_L{*Sr0{_Dq?O&-^Qz@N38$@`cin532r02$ zMV}pI0e~pik?4xsjXS^byMg~}17orJg58adg*n^103xDd;u3o#rS^iPWx%o!IeF+l z*!}|whZL2R;VP=?8VIDO7E1fbQ61f5ddCfoj7?6Oo-#8>|B1m`SmLblrwKN;L_2#2 zCubK|H+PRSo?hNQq_bq-^A!Jpzzfu%iguXVzBvCp z^9milWYz^QUqqI*r%2kPm{~@pD!kyEROlYGiOd!sLn-Rw4l{-WLlTiKTHfp3?mJZ7adMU4iVCfz8lO=UmhQ!bEA8Z5 z(~@5Jo}2##$0R0DYQnt1@u{$O(s6WWb4BFjM+Vf-^f3*gGnBL>O}EA(8@|>Ze4-b= zVJ%t?f^s;haWa-g_k}nKL+e+znvDj|-9d!Ux%uVR+Eo)uK0hray4{LW60PtVq}z*! zZUj8%a*7ih)XWerN;){7va1+ETjn04HTL5ET>$!PgqFD;tYx z3OOeq5=t>cAIeM4!LhbUlqi+urx*FEpfXP^Ah(!Wbcf@PxewN&4|TZ&FxTjV#wCQo zkx?FwI8%sTg0)3cEp?`#5eyi)8`f5mIXG zA-`^WVzu}VpPZZh2rqJ?mCKJ$w9_^uf~5A(8z18MUd`;FQG}&IO7_D?on-ID6wnZF zP%7BV!*1>G4A>NtjGRp8@61n{rVc#|eG_C$Qi|Avc?w6_`tq|dgAt90?&Pz?f;qA9 zP{qYib-zy$phk$$B-o0;3&QHPy{#7lYejeQkVu0Z^vwmY0spm?+0yeyOR37{qY?Qc z;d7Ptt3s+HrCxfJnn0SZvWE9hTR?L9lgU-H8SdGjgrlu7uO7qB>^0Qe&9 z0DUfk?GY@)?7zJZX7}zz`eNVkM<#h|CNvvcnIDb&XAJJP1t^B{86KmO-mQc)GMax- ze+6p=7npXJboV-NRk!W(D#|LGrZaNtI8s^@J4F<6t1SUWjgtWABAd06RW$ojiY;WL z<(8LF5&BtrVm#G)f%5Dtrvl;)p7_*&?2<|GIy+zDI#io}MBr6LGAp_bYf;~l#7^%_ U+T?Y*?_L`~z}Z_>S&(D@162b1NB{r; literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/new-site/logo-omise@2x.png b/static_langing_page/dest/img/new-site/logo-omise@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..25a9f8aa237bc53dbe912435e47423dff7358563 GIT binary patch literal 1914 zcmeHH`#aMM9R6-|m*tj_%b|nTR;(n1T(*TV%(XIR-<%{#qK0Ebb`7o$wYB-dPX{1@k!-sgEg@AE#-`_ub;o;TH%a7s=_T?POEIR|?TaYrq7 zXf6fYxo=~*BmjV>xH@@YckK30{A}R=ZUC1Nr?&HpBN0S`I{<;g#Ka{erKDwKcgewb z%PS}yM0-g)}?R?OM;EN%bLNAA136Ho&jf#%>J(fm~yOEHblA3li zJ>%BxJDFJw=H2YvdwKT@Sj8o!W#xZ8s`$IIs=BtWzTrvJ(`U`kU$nNhzwGGh=JxjW z^Ii`O4h{c1!XJI}_TBq2!T7|7N#WF|&tIlzM6+M#zRfQzE-kNoUtL?@*xdRttM`Bh z05Glt#>PWct)Xqh-#rrOn&5S|+M;YQ^|C4Bmi=``L(R;<(@{OdL2klP5 z35CNs(u;=3L$PPF6K8#(%QHFEl`RCmI#zBTG&byKa!yo1dL^(z1~az^g7;y~>WDeI+%-^ry-kRe{WXm%FtJUv_%qwnLe_gPUBgk+Uo%q6QMQ!(UMu%fI)urcwzeHwQ-QsD+_pLm4eL=sSKkrW}rbQ zisN_!nt-!k(1JigWn`IjV{{ox{0aRmdl*rn05VYSMpY`02FC|g@yhcm+0HQKiF;Sz zA45vpt=fV}|KJsRs!c%AGNRo*Q8;y;x2wNTZDYj^bvy~7TLQrkCL!Vn7Sp`8l7tc3 zSiKSQbs+>>GS~Js=oW=pukfkWI&z`%lEJGLV@{9Y4Ivr7*phZtpk=O^cOZGQM6iK= z6;d$8c-eg&E>3^U*t0ukNRLMnZ)#co^T-0*VqKf6jd~lq`KD&7ya z9n#$WK-cSAyt1As(JMrq%`D_3Y!T7dr_(=FLw#rjZ(Dr{G~C%0gNp*dCoaHV9IK1!&BB nrbpxY45NQ9ZS?~uL~lx)DSo&}!F|g(0xDU&pN5zgMl|4b-VXJIUb+A;JO8aT#1Z<5Wi|0(0m3@S3j3^ HP6Cq zXID3O4^J;2|A4^I@c4xEjLhtu+`Rn4;_`~hs_NRhhUS*m_Kwc3p56%)CrzF@ZN{wG zbLY)puyFB`Wy@EtS-WA=_8q(T9yoaD@R6g(PMkb-?&6he*Kgdsb^FfUd-oqadh+ts z>o@N|eEjtJ%hzw;fBgLQ`_JEhf7%_B85o$vJY5_^BqZ-1d>P)8D8u%kdRE}96Dg;B zTFiu;mQ2``@5$}D@KVznWs|8(eLc93`ph&b{lD^Dp@sXr=e76me_qPJj(c@_Y}vtm z&aJoq>dm+vP-Sa#_Wr(aSKc>vUU_U=_%Hd%9C4?8zc@;dbt^pFEwylJcEOW%eV5#H zWq!n8h-1wW^}l?uhyR+;tLZOan623wr0}eDDqGi-Lx)^XZE;@T@-z$afFDJzNR_XRY$28jjt?uL!2kN3o~lW%3Q$DbzbD@v@++7)7hjJ ztXde8uN0ElW$V*)N_O!Dr?uQtGp9tpyT`J2(v1_BtJj)K&5qtRujTl@;P`psH<*oK4DtN|NFQ0B#XbwE4%kTD|=Vbvlr`1 z%y}xlZSU19T>8Uh$L@zu9?qRzds0rN;_{|0?Q)0n^{=_#>VBRs^kT`CGl3c$y=M%) zT<->m)+~$QSmn@C^n9TQ$7KgSk@U<2j#b}H0$5!cd3Am)%vjWD9nh*FexK{A!c(b* zhRz%LPhOa@JVLPKnC4Z!wc#Saw#Y^GYZz~3t(qz_^K5HtiPss?Lzl9xX6v_YRkD&g z)i(Ed$%Ut_y8qp8&D3t6d_YC_sC9FJ*o&q2lH3K9)&*}4smog1dI?LUtIYH<4vW*)$D-lITUoz_k0k zB*-tAfsu)sg_Vt+or9B$i<^g+k6%DgNLWNvOk7+-Qc_AdRhmRaRe&Xb*(`U|}JAdKg`*cke%Z{Pg+D*Kgl{{QUL%&)a)*QmmS1aK7MKV#Ix~esSWnxyA2y7vJA={qM`Y=J)56-?zN~^ISRS#I-#V zi#F#duQ~EVYelWHot08rZ*~#?;g4(~zipRp3{>>K^X1VDiRO<%{1uv|eZ6e;OM8Q= zvKE{S*t6q(9{a^DYT?pxm+tf~_-)jC#CZJ+j)gDRndo-x{g@}Q>17==qxkNXo5Jp^ zyFa6u8C*jx5@m9|ICF;9@*Y=xtUe(74+)J+j0w!TaOE+zw=L^Abso&RyiHmze91XCCb?6O{#ovwTBLup>=)PmXTeizQvPwJ zTJ-Q1rFBG}>*d<^@w>p@z=SIk?zhG*na-kWzW&g?IJVi9lA_u1@f&~WynT4}Q;WqL zp)!ZKKgEZhXcjT>DbINAsoykc3xJ&HhmoFPv z&*?=6>CO40Ljs+?q^p#0#g`;tr3gW6a9`KjRa>p|{k zm2Q@Q*L=U7Om~~1+Ocv4li%;XtMt}Qe-NMXu)D}9Xxka#3#s#hRy)jA{_9dR(c5>n zGP8@z9kUCr_uu^BNXmb^)U7}7g;2*{dvA^op+mfx>%Q&_;Qjtj{jZ?%k|5n5pAC-Q zFFJf^8~2U6a!;l3ee#ZznZ4_sEAJhuKWcn^=`6=hs}i*58doQkX%%GXZhv}3;pVl? zZTclnU9a0#a?ff%S=9Wa$T>T>>lE`EPqyS=>^8-=T0!e1tanug@gFpO%g_7#}iuvHNRx-oiMk)>mu(=yJOql)#z3lPkZw^WoK!}{jRu#eCCk7 zk1PEc*S?;x;hh%Cd!Oq@-h26fx=3;NU26MuCMShape \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/verified.png b/static_langing_page/dest/img/new-site/verified.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e4e0747e9eb058988b70daec3ea7b107c64f64 GIT binary patch literal 587 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sa{_!qT!D1x|5cWc3w@uZ**uK0 zycuV4HNxa-gz42tQxF+pdIgBWO)iIn7?&eVfNYQmLJ%Z!Io$DTlJm8cpqmAWcWRRF z)}`NT%)j4J^q{-z>Ac=&3woa|?0dFo;`1ewpD&yFV)fjY>z2RSw(`x6b#M3F`u_0I zkLO?ieE;_M`}e;;e*gRP_ut=t|Ni~||3BcnB*=Z@B|(0{49uKdg3{)GF$rlk{p+`% zzWwaYr?20CPWr7E%fP_s?&;zf;&FWLw5w%@4R{=;np`N5xwN9WOtO5*|Nq-g^yDFq?1uuRKdzrkWrUuB`ekuRX)z9xT9bP4?c`k^1oUGz~QCz9& z(vlauIBfRWz35`te`W)>Combined Shape \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/waves.svg b/static_langing_page/dest/img/new-site/waves.svg new file mode 100644 index 0000000..82e7f03 --- /dev/null +++ b/static_langing_page/dest/img/new-site/waves.svg @@ -0,0 +1 @@ +Combined Shape \ No newline at end of file diff --git a/static_langing_page/dest/img/new-site/waves@2x.png b/static_langing_page/dest/img/new-site/waves@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c5bb26bbc01bd6f698cbac5ca45cdbd5ff37963e GIT binary patch literal 1724 zcmZvc2~ZPP7{}9~KmtJ~@c^U+1(8^-fSdtQNVsB*5Kz3T#u8Mj6bl~cco0EUY-vl7 zpx^}&1+^6Lz%$5LG2oGG0@-Xrf+U-SLlL}asq}5YPN&nIZ+G8*@BM!7zcV{~G@czp zBDxT9I2`Bt$gEcMXxe);9eNdnA_}9r-2}_ch{AwKQMC-!KqxFX!V05GVN%KsumV-dP()@# zAfsAtR4RbPpn?ntget)lWK=6x z4bv$puGRzjIj(+$fflhXzl~Cx*eqvWd@c$7l6j?t?dca4zaW6tC$2r@eGFev<9^<$ zwtpB>4ei$sTb(-o%qFPb+HS}C%30IxIJ?XHRv*s0DHh%%R$f2zRF_ss{%|7I^i?H! zYlX9SBy&DA;>ch=la(!_Gv~|vbzUb>kC#(u3wnP_($$|1b?k_&noW7^>XBcqLYYzy;LD9Yzq!UZCi z&0$C4uVksXgQFsOb6@2Mes)a(`O?AcGct!nN6}<4by5Ob9L8{NJve-{ZXz0NcxE3A zk0w-aSWV66rk7s`!$A=txFNEL(8-hd3X`c%dx9SaGrhVcFAp@U5Wb-Q!DJne-_tGOAR$F5vG9!F3bie)YdS$jUp(>z-#00 z@pt_=h;K`ftjTbIvWZ?kk&SNXT%KDcjbUCtq5h~JCD&Y~E)c%0$U2?#o@THU77Jn> z#qS8TWZ8x~Ybwd-tsotD&A*#Buv1FS*-~+b=z;^a@^0La1E(d7J``t-?m{~JHQ7RKGu?k-tIz$f-|Si8o;R|{lr_~| w6yv=qvleH3oMAOJ<>5-L*WCCWj)adk)6R71>(5pdg8v6DE{e^#8o7c0H?$J=7ytkO literal 0 HcmV?d00001 diff --git a/static_langing_page/dest/img/pattern@2x.png b/static_langing_page/dest/img/pattern@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e44f757b1b51313f3578651a84ec49fb38019fb6 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$FDgizru0Wa~SQf$L4phWb666=m zz{teR%ErOX%P%1Gd1gFF|8!3m#}JM4$q5os4VuYc*O{@JY4WVq-1X>^JR b ? 1 : a >= b ? 0 : NaN; + }; + + var bisector = function bisector(compare) { + if (compare.length === 1) compare = ascendingComparator(compare); + return { + left: function left(a, x, lo, hi) { + if (lo == null) lo = 0; + if (hi == null) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid; + } + return lo; + }, + right: function right(a, x, lo, hi) { + if (lo == null) lo = 0; + if (hi == null) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1; + } + return lo; + } + }; + }; + + function ascendingComparator(f) { + return function (d, x) { + return ascending(f(d), x); + }; + } + + var ascendingBisect = bisector(ascending); + var bisectRight = ascendingBisect.right; + var bisectLeft = ascendingBisect.left; + + var pairs = function pairs(array, f) { + if (f == null) f = pair; + var i = 0, + n = array.length - 1, + p = array[0], + pairs = new Array(n < 0 ? 0 : n); + while (i < n) { + pairs[i] = f(p, p = array[++i]); + }return pairs; + }; + + function pair(a, b) { + return [a, b]; + } + + var cross = function cross(values0, values1, reduce) { + var n0 = values0.length, + n1 = values1.length, + values = new Array(n0 * n1), + i0, + i1, + i, + value0; + + if (reduce == null) reduce = pair; + + for (i0 = i = 0; i0 < n0; ++i0) { + for (value0 = values0[i0], i1 = 0; i1 < n1; ++i1, ++i) { + values[i] = reduce(value0, values1[i1]); + } + } + + return values; + }; + + var descending = function descending(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + + var number = function number(x) { + return x === null ? NaN : +x; + }; + + var variance = function variance(values, valueof) { + var n = values.length, + m = 0, + i = -1, + mean = 0, + value, + delta, + sum = 0; + + if (valueof == null) { + while (++i < n) { + if (!isNaN(value = number(values[i]))) { + delta = value - mean; + mean += delta / ++m; + sum += delta * (value - mean); + } + } + } else { + while (++i < n) { + if (!isNaN(value = number(valueof(values[i], i, values)))) { + delta = value - mean; + mean += delta / ++m; + sum += delta * (value - mean); + } + } + } + + if (m > 1) return sum / (m - 1); + }; + + var deviation = function deviation(array, f) { + var v = variance(array, f); + return v ? Math.sqrt(v) : v; + }; + + var extent = function extent(values, valueof) { + var n = values.length, + i = -1, + value, + min, + max; + + if (valueof == null) { + while (++i < n) { + // Find the first comparable value. + if ((value = values[i]) != null && value >= value) { + min = max = value; + while (++i < n) { + // Compare the remaining values. + if ((value = values[i]) != null) { + if (min > value) min = value; + if (max < value) max = value; + } + } + } + } + } else { + while (++i < n) { + // Find the first comparable value. + if ((value = valueof(values[i], i, values)) != null && value >= value) { + min = max = value; + while (++i < n) { + // Compare the remaining values. + if ((value = valueof(values[i], i, values)) != null) { + if (min > value) min = value; + if (max < value) max = value; + } + } + } + } + } + + return [min, max]; + }; + + var array = Array.prototype; + + var slice = array.slice; + var map = array.map; + + var constant = function constant(x) { + return function () { + return x; + }; + }; + + var identity = function identity(x) { + return x; + }; + + var range = function range(start, stop, step) { + start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step; + + var i = -1, + n = Math.max(0, Math.ceil((stop - start) / step)) | 0, + range = new Array(n); + + while (++i < n) { + range[i] = start + i * step; + } + + return range; + }; + + var e10 = Math.sqrt(50); + var e5 = Math.sqrt(10); + var e2 = Math.sqrt(2); + + var ticks = function ticks(start, stop, count) { + var reverse, + i = -1, + n, + ticks, + step; + + stop = +stop, start = +start, count = +count; + if (start === stop && count > 0) return [start]; + if (reverse = stop < start) n = start, start = stop, stop = n; + if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) return []; + + if (step > 0) { + start = Math.ceil(start / step); + stop = Math.floor(stop / step); + ticks = new Array(n = Math.ceil(stop - start + 1)); + while (++i < n) { + ticks[i] = (start + i) * step; + } + } else { + start = Math.floor(start * step); + stop = Math.ceil(stop * step); + ticks = new Array(n = Math.ceil(start - stop + 1)); + while (++i < n) { + ticks[i] = (start - i) / step; + } + } + + if (reverse) ticks.reverse(); + + return ticks; + }; + + function tickIncrement(start, stop, count) { + var step = (stop - start) / Math.max(0, count), + power = Math.floor(Math.log(step) / Math.LN10), + error = step / Math.pow(10, power); + return power >= 0 ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(10, power) : -Math.pow(10, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1); + } + + function tickStep(start, stop, count) { + var step0 = Math.abs(stop - start) / Math.max(0, count), + step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), + error = step0 / step1; + if (error >= e10) step1 *= 10;else if (error >= e5) step1 *= 5;else if (error >= e2) step1 *= 2; + return stop < start ? -step1 : step1; + } + + var sturges = function sturges(values) { + return Math.ceil(Math.log(values.length) / Math.LN2) + 1; + }; + + var histogram = function histogram() { + var value = identity, + domain = extent, + threshold = sturges; + + function histogram(data) { + var i, + n = data.length, + x, + values = new Array(n); + + for (i = 0; i < n; ++i) { + values[i] = value(data[i], i, data); + } + + var xz = domain(values), + x0 = xz[0], + x1 = xz[1], + tz = threshold(values, x0, x1); + + // Convert number of thresholds into uniform thresholds. + if (!Array.isArray(tz)) { + tz = tickStep(x0, x1, tz); + tz = range(Math.ceil(x0 / tz) * tz, Math.floor(x1 / tz) * tz, tz); // exclusive + } + + // Remove any thresholds outside the domain. + var m = tz.length; + while (tz[0] <= x0) { + tz.shift(), --m; + }while (tz[m - 1] > x1) { + tz.pop(), --m; + }var bins = new Array(m + 1), + bin; + + // Initialize bins. + for (i = 0; i <= m; ++i) { + bin = bins[i] = []; + bin.x0 = i > 0 ? tz[i - 1] : x0; + bin.x1 = i < m ? tz[i] : x1; + } + + // Assign data to bins by value, ignoring any outside the domain. + for (i = 0; i < n; ++i) { + x = values[i]; + if (x0 <= x && x <= x1) { + bins[bisectRight(tz, x, 0, m)].push(data[i]); + } + } + + return bins; + } + + histogram.value = function (_) { + return arguments.length ? (value = typeof _ === "function" ? _ : constant(_), histogram) : value; + }; + + histogram.domain = function (_) { + return arguments.length ? (domain = typeof _ === "function" ? _ : constant([_[0], _[1]]), histogram) : domain; + }; + + histogram.thresholds = function (_) { + return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), histogram) : threshold; + }; + + return histogram; + }; + + var quantile = function quantile(values, p, valueof) { + if (valueof == null) valueof = number; + if (!(n = values.length)) return; + if ((p = +p) <= 0 || n < 2) return +valueof(values[0], 0, values); + if (p >= 1) return +valueof(values[n - 1], n - 1, values); + var n, + i = (n - 1) * p, + i0 = Math.floor(i), + value0 = +valueof(values[i0], i0, values), + value1 = +valueof(values[i0 + 1], i0 + 1, values); + return value0 + (value1 - value0) * (i - i0); + }; + + var freedmanDiaconis = function freedmanDiaconis(values, min, max) { + values = map.call(values, number).sort(ascending); + return Math.ceil((max - min) / (2 * (quantile(values, 0.75) - quantile(values, 0.25)) * Math.pow(values.length, -1 / 3))); + }; + + var scott = function scott(values, min, max) { + return Math.ceil((max - min) / (3.5 * deviation(values) * Math.pow(values.length, -1 / 3))); + }; + + var max = function max(values, valueof) { + var n = values.length, + i = -1, + value, + max; + + if (valueof == null) { + while (++i < n) { + // Find the first comparable value. + if ((value = values[i]) != null && value >= value) { + max = value; + while (++i < n) { + // Compare the remaining values. + if ((value = values[i]) != null && value > max) { + max = value; + } + } + } + } + } else { + while (++i < n) { + // Find the first comparable value. + if ((value = valueof(values[i], i, values)) != null && value >= value) { + max = value; + while (++i < n) { + // Compare the remaining values. + if ((value = valueof(values[i], i, values)) != null && value > max) { + max = value; + } + } + } + } + } + + return max; + }; + + var mean = function mean(values, valueof) { + var n = values.length, + m = n, + i = -1, + value, + sum = 0; + + if (valueof == null) { + while (++i < n) { + if (!isNaN(value = number(values[i]))) sum += value;else --m; + } + } else { + while (++i < n) { + if (!isNaN(value = number(valueof(values[i], i, values)))) sum += value;else --m; + } + } + + if (m) return sum / m; + }; + + var median = function median(values, valueof) { + var n = values.length, + i = -1, + value, + numbers = []; + + if (valueof == null) { + while (++i < n) { + if (!isNaN(value = number(values[i]))) { + numbers.push(value); + } + } + } else { + while (++i < n) { + if (!isNaN(value = number(valueof(values[i], i, values)))) { + numbers.push(value); + } + } + } + + return quantile(numbers.sort(ascending), 0.5); + }; + + var merge = function merge(arrays) { + var n = arrays.length, + m, + i = -1, + j = 0, + merged, + array; + + while (++i < n) { + j += arrays[i].length; + }merged = new Array(j); + + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + + return merged; + }; + + var min = function min(values, valueof) { + var n = values.length, + i = -1, + value, + min; + + if (valueof == null) { + while (++i < n) { + // Find the first comparable value. + if ((value = values[i]) != null && value >= value) { + min = value; + while (++i < n) { + // Compare the remaining values. + if ((value = values[i]) != null && min > value) { + min = value; + } + } + } + } + } else { + while (++i < n) { + // Find the first comparable value. + if ((value = valueof(values[i], i, values)) != null && value >= value) { + min = value; + while (++i < n) { + // Compare the remaining values. + if ((value = valueof(values[i], i, values)) != null && min > value) { + min = value; + } + } + } + } + } + + return min; + }; + + var permute = function permute(array, indexes) { + var i = indexes.length, + permutes = new Array(i); + while (i--) { + permutes[i] = array[indexes[i]]; + }return permutes; + }; + + var scan = function scan(values, compare) { + if (!(n = values.length)) return; + var n, + i = 0, + j = 0, + xi, + xj = values[j]; + + if (compare == null) compare = ascending; + + while (++i < n) { + if (compare(xi = values[i], xj) < 0 || compare(xj, xj) !== 0) { + xj = xi, j = i; + } + } + + if (compare(xj, xj) === 0) return j; + }; + + var shuffle = function shuffle(array, i0, i1) { + var m = (i1 == null ? array.length : i1) - (i0 = i0 == null ? 0 : +i0), + t, + i; + + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0]; + array[m + i0] = array[i + i0]; + array[i + i0] = t; + } + + return array; + }; + + var sum = function sum(values, valueof) { + var n = values.length, + i = -1, + value, + sum = 0; + + if (valueof == null) { + while (++i < n) { + if (value = +values[i]) sum += value; // Note: zero and null are equivalent. + } + } else { + while (++i < n) { + if (value = +valueof(values[i], i, values)) sum += value; + } + } + + return sum; + }; + + var transpose = function transpose(matrix) { + if (!(n = matrix.length)) return []; + for (var i = -1, m = min(matrix, length), transpose = new Array(m); ++i < m;) { + for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) { + row[j] = matrix[j][i]; + } + } + return transpose; + }; + + function length(d) { + return d.length; + } + + var zip = function zip() { + return transpose(arguments); + }; + + exports.bisect = bisectRight; + exports.bisectRight = bisectRight; + exports.bisectLeft = bisectLeft; + exports.ascending = ascending; + exports.bisector = bisector; + exports.cross = cross; + exports.descending = descending; + exports.deviation = deviation; + exports.extent = extent; + exports.histogram = histogram; + exports.thresholdFreedmanDiaconis = freedmanDiaconis; + exports.thresholdScott = scott; + exports.thresholdSturges = sturges; + exports.max = max; + exports.mean = mean; + exports.median = median; + exports.merge = merge; + exports.min = min; + exports.pairs = pairs; + exports.permute = permute; + exports.quantile = quantile; + exports.range = range; + exports.scan = scan; + exports.shuffle = shuffle; + exports.sum = sum; + exports.ticks = ticks; + exports.tickIncrement = tickIncrement; + exports.tickStep = tickStep; + exports.transpose = transpose; + exports.variance = variance; + exports.zip = zip; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 2: [function (require, module, exports) { + // https://d3js.org/d3-collection/ Version 1.0.4. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.d3 = global.d3 || {}); + })(this, function (exports) { + 'use strict'; + + var prefix = "$"; + + function Map() {} + + Map.prototype = map.prototype = { + constructor: Map, + has: function has(key) { + return prefix + key in this; + }, + get: function get(key) { + return this[prefix + key]; + }, + set: function set(key, value) { + this[prefix + key] = value; + return this; + }, + remove: function remove(key) { + var property = prefix + key; + return property in this && delete this[property]; + }, + clear: function clear() { + for (var property in this) { + if (property[0] === prefix) delete this[property]; + } + }, + keys: function keys() { + var keys = []; + for (var property in this) { + if (property[0] === prefix) keys.push(property.slice(1)); + }return keys; + }, + values: function values() { + var values = []; + for (var property in this) { + if (property[0] === prefix) values.push(this[property]); + }return values; + }, + entries: function entries() { + var entries = []; + for (var property in this) { + if (property[0] === prefix) entries.push({ key: property.slice(1), value: this[property] }); + }return entries; + }, + size: function size() { + var size = 0; + for (var property in this) { + if (property[0] === prefix) ++size; + }return size; + }, + empty: function empty() { + for (var property in this) { + if (property[0] === prefix) return false; + }return true; + }, + each: function each(f) { + for (var property in this) { + if (property[0] === prefix) f(this[property], property.slice(1), this); + } + } + }; + + function map(object, f) { + var map = new Map(); + + // Copy constructor. + if (object instanceof Map) object.each(function (value, key) { + map.set(key, value); + }); + + // Index array by numeric index or specified key function. + else if (Array.isArray(object)) { + var i = -1, + n = object.length, + o; + + if (f == null) while (++i < n) { + map.set(i, object[i]); + } else while (++i < n) { + map.set(f(o = object[i], i, object), o); + } + } + + // Convert object to map. + else if (object) for (var key in object) { + map.set(key, object[key]); + }return map; + } + + var nest = function nest() { + var keys = [], + _sortKeys = [], + _sortValues, + _rollup, + nest; + + function apply(array, depth, createResult, setResult) { + if (depth >= keys.length) { + if (_sortValues != null) array.sort(_sortValues); + return _rollup != null ? _rollup(array) : array; + } + + var i = -1, + n = array.length, + key = keys[depth++], + keyValue, + value, + valuesByKey = map(), + values, + result = createResult(); + + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(value = array[i]) + "")) { + values.push(value); + } else { + valuesByKey.set(keyValue, [value]); + } + } + + valuesByKey.each(function (values, key) { + setResult(result, key, apply(values, depth, createResult, setResult)); + }); + + return result; + } + + function _entries(map$$1, depth) { + if (++depth > keys.length) return map$$1; + var array, + sortKey = _sortKeys[depth - 1]; + if (_rollup != null && depth >= keys.length) array = map$$1.entries();else array = [], map$$1.each(function (v, k) { + array.push({ key: k, values: _entries(v, depth) }); + }); + return sortKey != null ? array.sort(function (a, b) { + return sortKey(a.key, b.key); + }) : array; + } + + return nest = { + object: function object(array) { + return apply(array, 0, createObject, setObject); + }, + map: function map(array) { + return apply(array, 0, createMap, setMap); + }, + entries: function entries(array) { + return _entries(apply(array, 0, createMap, setMap), 0); + }, + key: function key(d) { + keys.push(d);return nest; + }, + sortKeys: function sortKeys(order) { + _sortKeys[keys.length - 1] = order;return nest; + }, + sortValues: function sortValues(order) { + _sortValues = order;return nest; + }, + rollup: function rollup(f) { + _rollup = f;return nest; + } + }; + }; + + function createObject() { + return {}; + } + + function setObject(object, key, value) { + object[key] = value; + } + + function createMap() { + return map(); + } + + function setMap(map$$1, key, value) { + map$$1.set(key, value); + } + + function Set() {} + + var proto = map.prototype; + + Set.prototype = set.prototype = { + constructor: Set, + has: proto.has, + add: function add(value) { + value += ""; + this[prefix + value] = value; + return this; + }, + remove: proto.remove, + clear: proto.clear, + values: proto.keys, + size: proto.size, + empty: proto.empty, + each: proto.each + }; + + function set(object, f) { + var set = new Set(); + + // Copy constructor. + if (object instanceof Set) object.each(function (value) { + set.add(value); + }); + + // Otherwise, assume it’s an array. + else if (object) { + var i = -1, + n = object.length; + if (f == null) while (++i < n) { + set.add(object[i]); + } else while (++i < n) { + set.add(f(object[i], i, object)); + } + } + + return set; + } + + var keys = function keys(map) { + var keys = []; + for (var key in map) { + keys.push(key); + }return keys; + }; + + var values = function values(map) { + var values = []; + for (var key in map) { + values.push(map[key]); + }return values; + }; + + var entries = function entries(map) { + var entries = []; + for (var key in map) { + entries.push({ key: key, value: map[key] }); + }return entries; + }; + + exports.nest = nest; + exports.set = set; + exports.map = map; + exports.keys = keys; + exports.values = values; + exports.entries = entries; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 3: [function (require, module, exports) { + // https://d3js.org/d3-color/ Version 1.0.3. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.d3 = global.d3 || {}); + })(this, function (exports) { + 'use strict'; + + var define = function define(constructor, factory, prototype) { + constructor.prototype = factory.prototype = prototype; + prototype.constructor = constructor; + }; + + function extend(parent, definition) { + var prototype = Object.create(parent.prototype); + for (var key in definition) { + prototype[key] = definition[key]; + }return prototype; + } + + function Color() {} + + var _darker = 0.7; + var _brighter = 1 / _darker; + + var reI = "\\s*([+-]?\\d+)\\s*"; + var reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*"; + var reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*"; + var reHex3 = /^#([0-9a-f]{3})$/; + var reHex6 = /^#([0-9a-f]{6})$/; + var reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"); + var reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"); + var reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"); + var reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"); + var reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"); + var reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$"); + + var named = { + aliceblue: 0xf0f8ff, + antiquewhite: 0xfaebd7, + aqua: 0x00ffff, + aquamarine: 0x7fffd4, + azure: 0xf0ffff, + beige: 0xf5f5dc, + bisque: 0xffe4c4, + black: 0x000000, + blanchedalmond: 0xffebcd, + blue: 0x0000ff, + blueviolet: 0x8a2be2, + brown: 0xa52a2a, + burlywood: 0xdeb887, + cadetblue: 0x5f9ea0, + chartreuse: 0x7fff00, + chocolate: 0xd2691e, + coral: 0xff7f50, + cornflowerblue: 0x6495ed, + cornsilk: 0xfff8dc, + crimson: 0xdc143c, + cyan: 0x00ffff, + darkblue: 0x00008b, + darkcyan: 0x008b8b, + darkgoldenrod: 0xb8860b, + darkgray: 0xa9a9a9, + darkgreen: 0x006400, + darkgrey: 0xa9a9a9, + darkkhaki: 0xbdb76b, + darkmagenta: 0x8b008b, + darkolivegreen: 0x556b2f, + darkorange: 0xff8c00, + darkorchid: 0x9932cc, + darkred: 0x8b0000, + darksalmon: 0xe9967a, + darkseagreen: 0x8fbc8f, + darkslateblue: 0x483d8b, + darkslategray: 0x2f4f4f, + darkslategrey: 0x2f4f4f, + darkturquoise: 0x00ced1, + darkviolet: 0x9400d3, + deeppink: 0xff1493, + deepskyblue: 0x00bfff, + dimgray: 0x696969, + dimgrey: 0x696969, + dodgerblue: 0x1e90ff, + firebrick: 0xb22222, + floralwhite: 0xfffaf0, + forestgreen: 0x228b22, + fuchsia: 0xff00ff, + gainsboro: 0xdcdcdc, + ghostwhite: 0xf8f8ff, + gold: 0xffd700, + goldenrod: 0xdaa520, + gray: 0x808080, + green: 0x008000, + greenyellow: 0xadff2f, + grey: 0x808080, + honeydew: 0xf0fff0, + hotpink: 0xff69b4, + indianred: 0xcd5c5c, + indigo: 0x4b0082, + ivory: 0xfffff0, + khaki: 0xf0e68c, + lavender: 0xe6e6fa, + lavenderblush: 0xfff0f5, + lawngreen: 0x7cfc00, + lemonchiffon: 0xfffacd, + lightblue: 0xadd8e6, + lightcoral: 0xf08080, + lightcyan: 0xe0ffff, + lightgoldenrodyellow: 0xfafad2, + lightgray: 0xd3d3d3, + lightgreen: 0x90ee90, + lightgrey: 0xd3d3d3, + lightpink: 0xffb6c1, + lightsalmon: 0xffa07a, + lightseagreen: 0x20b2aa, + lightskyblue: 0x87cefa, + lightslategray: 0x778899, + lightslategrey: 0x778899, + lightsteelblue: 0xb0c4de, + lightyellow: 0xffffe0, + lime: 0x00ff00, + limegreen: 0x32cd32, + linen: 0xfaf0e6, + magenta: 0xff00ff, + maroon: 0x800000, + mediumaquamarine: 0x66cdaa, + mediumblue: 0x0000cd, + mediumorchid: 0xba55d3, + mediumpurple: 0x9370db, + mediumseagreen: 0x3cb371, + mediumslateblue: 0x7b68ee, + mediumspringgreen: 0x00fa9a, + mediumturquoise: 0x48d1cc, + mediumvioletred: 0xc71585, + midnightblue: 0x191970, + mintcream: 0xf5fffa, + mistyrose: 0xffe4e1, + moccasin: 0xffe4b5, + navajowhite: 0xffdead, + navy: 0x000080, + oldlace: 0xfdf5e6, + olive: 0x808000, + olivedrab: 0x6b8e23, + orange: 0xffa500, + orangered: 0xff4500, + orchid: 0xda70d6, + palegoldenrod: 0xeee8aa, + palegreen: 0x98fb98, + paleturquoise: 0xafeeee, + palevioletred: 0xdb7093, + papayawhip: 0xffefd5, + peachpuff: 0xffdab9, + peru: 0xcd853f, + pink: 0xffc0cb, + plum: 0xdda0dd, + powderblue: 0xb0e0e6, + purple: 0x800080, + rebeccapurple: 0x663399, + red: 0xff0000, + rosybrown: 0xbc8f8f, + royalblue: 0x4169e1, + saddlebrown: 0x8b4513, + salmon: 0xfa8072, + sandybrown: 0xf4a460, + seagreen: 0x2e8b57, + seashell: 0xfff5ee, + sienna: 0xa0522d, + silver: 0xc0c0c0, + skyblue: 0x87ceeb, + slateblue: 0x6a5acd, + slategray: 0x708090, + slategrey: 0x708090, + snow: 0xfffafa, + springgreen: 0x00ff7f, + steelblue: 0x4682b4, + tan: 0xd2b48c, + teal: 0x008080, + thistle: 0xd8bfd8, + tomato: 0xff6347, + turquoise: 0x40e0d0, + violet: 0xee82ee, + wheat: 0xf5deb3, + white: 0xffffff, + whitesmoke: 0xf5f5f5, + yellow: 0xffff00, + yellowgreen: 0x9acd32 + }; + + define(Color, color, { + displayable: function displayable() { + return this.rgb().displayable(); + }, + toString: function toString() { + return this.rgb() + ""; + } + }); + + function color(format) { + var m; + format = (format + "").trim().toLowerCase(); + return (m = reHex3.exec(format)) ? (m = parseInt(m[1], 16), new Rgb(m >> 8 & 0xf | m >> 4 & 0x0f0, m >> 4 & 0xf | m & 0xf0, (m & 0xf) << 4 | m & 0xf, 1) // #f00 + ) : (m = reHex6.exec(format)) ? rgbn(parseInt(m[1], 16)) // #ff0000 + : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) + : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) + : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) + : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) + : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) + : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) + : named.hasOwnProperty(format) ? rgbn(named[format]) : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) : null; + } + + function rgbn(n) { + return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); + } + + function rgba(r, g, b, a) { + if (a <= 0) r = g = b = NaN; + return new Rgb(r, g, b, a); + } + + function rgbConvert(o) { + if (!(o instanceof Color)) o = color(o); + if (!o) return new Rgb(); + o = o.rgb(); + return new Rgb(o.r, o.g, o.b, o.opacity); + } + + function rgb(r, g, b, opacity) { + return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); + } + + function Rgb(r, g, b, opacity) { + this.r = +r; + this.g = +g; + this.b = +b; + this.opacity = +opacity; + } + + define(Rgb, rgb, extend(Color, { + brighter: function brighter(k) { + k = k == null ? _brighter : Math.pow(_brighter, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + darker: function darker(k) { + k = k == null ? _darker : Math.pow(_darker, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + rgb: function rgb() { + return this; + }, + displayable: function displayable() { + return 0 <= this.r && this.r <= 255 && 0 <= this.g && this.g <= 255 && 0 <= this.b && this.b <= 255 && 0 <= this.opacity && this.opacity <= 1; + }, + toString: function toString() { + var a = this.opacity;a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); + return (a === 1 ? "rgb(" : "rgba(") + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", " + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", " + Math.max(0, Math.min(255, Math.round(this.b) || 0)) + (a === 1 ? ")" : ", " + a + ")"); + } + })); + + function hsla(h, s, l, a) { + if (a <= 0) h = s = l = NaN;else if (l <= 0 || l >= 1) h = s = NaN;else if (s <= 0) h = NaN; + return new Hsl(h, s, l, a); + } + + function hslConvert(o) { + if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Color)) o = color(o); + if (!o) return new Hsl(); + if (o instanceof Hsl) return o; + o = o.rgb(); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + h = NaN, + s = max - min, + l = (max + min) / 2; + if (s) { + if (r === max) h = (g - b) / s + (g < b) * 6;else if (g === max) h = (b - r) / s + 2;else h = (r - g) / s + 4; + s /= l < 0.5 ? max + min : 2 - max - min; + h *= 60; + } else { + s = l > 0 && l < 1 ? 0 : h; + } + return new Hsl(h, s, l, o.opacity); + } + + function hsl(h, s, l, opacity) { + return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); + } + + function Hsl(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; + } + + define(Hsl, hsl, extend(Color, { + brighter: function brighter(k) { + k = k == null ? _brighter : Math.pow(_brighter, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + darker: function darker(k) { + k = k == null ? _darker : Math.pow(_darker, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + rgb: function rgb() { + var h = this.h % 360 + (this.h < 0) * 360, + s = isNaN(h) || isNaN(this.s) ? 0 : this.s, + l = this.l, + m2 = l + (l < 0.5 ? l : 1 - l) * s, + m1 = 2 * l - m2; + return new Rgb(hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), hsl2rgb(h, m1, m2), hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), this.opacity); + }, + displayable: function displayable() { + return (0 <= this.s && this.s <= 1 || isNaN(this.s)) && 0 <= this.l && this.l <= 1 && 0 <= this.opacity && this.opacity <= 1; + } + })); + + /* From FvD 13.37, CSS Color Module Level 3 */ + function hsl2rgb(h, m1, m2) { + return (h < 60 ? m1 + (m2 - m1) * h / 60 : h < 180 ? m2 : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 : m1) * 255; + } + + var deg2rad = Math.PI / 180; + var rad2deg = 180 / Math.PI; + + var Kn = 18; + var Xn = 0.950470; + var Yn = 1; + var Zn = 1.088830; + var t0 = 4 / 29; + var t1 = 6 / 29; + var t2 = 3 * t1 * t1; + var t3 = t1 * t1 * t1; + + function labConvert(o) { + if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity); + if (o instanceof Hcl) { + var h = o.h * deg2rad; + return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); + } + if (!(o instanceof Rgb)) o = rgbConvert(o); + var b = rgb2xyz(o.r), + a = rgb2xyz(o.g), + l = rgb2xyz(o.b), + x = xyz2lab((0.4124564 * b + 0.3575761 * a + 0.1804375 * l) / Xn), + y = xyz2lab((0.2126729 * b + 0.7151522 * a + 0.0721750 * l) / Yn), + z = xyz2lab((0.0193339 * b + 0.1191920 * a + 0.9503041 * l) / Zn); + return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity); + } + + function lab(l, a, b, opacity) { + return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity); + } + + function Lab(l, a, b, opacity) { + this.l = +l; + this.a = +a; + this.b = +b; + this.opacity = +opacity; + } + + define(Lab, lab, extend(Color, { + brighter: function brighter(k) { + return new Lab(this.l + Kn * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + darker: function darker(k) { + return new Lab(this.l - Kn * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + rgb: function rgb() { + var y = (this.l + 16) / 116, + x = isNaN(this.a) ? y : y + this.a / 500, + z = isNaN(this.b) ? y : y - this.b / 200; + y = Yn * lab2xyz(y); + x = Xn * lab2xyz(x); + z = Zn * lab2xyz(z); + return new Rgb(xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z), // D65 -> sRGB + xyz2rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z), xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z), this.opacity); + } + })); + + function xyz2lab(t) { + return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0; + } + + function lab2xyz(t) { + return t > t1 ? t * t * t : t2 * (t - t0); + } + + function xyz2rgb(x) { + return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); + } + + function rgb2xyz(x) { + return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); + } + + function hclConvert(o) { + if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity); + if (!(o instanceof Lab)) o = labConvert(o); + var h = Math.atan2(o.b, o.a) * rad2deg; + return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity); + } + + function hcl(h, c, l, opacity) { + return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity); + } + + function Hcl(h, c, l, opacity) { + this.h = +h; + this.c = +c; + this.l = +l; + this.opacity = +opacity; + } + + define(Hcl, hcl, extend(Color, { + brighter: function brighter(k) { + return new Hcl(this.h, this.c, this.l + Kn * (k == null ? 1 : k), this.opacity); + }, + darker: function darker(k) { + return new Hcl(this.h, this.c, this.l - Kn * (k == null ? 1 : k), this.opacity); + }, + rgb: function rgb() { + return labConvert(this).rgb(); + } + })); + + var A = -0.14861; + var B = +1.78277; + var C = -0.29227; + var D = -0.90649; + var E = +1.97294; + var ED = E * D; + var EB = E * B; + var BC_DA = B * C - D * A; + + function cubehelixConvert(o) { + if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Rgb)) o = rgbConvert(o); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), + bl = b - l, + k = (E * (g - l) - C * bl) / D, + s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), + // NaN if l=0 or l=1 + h = s ? Math.atan2(k, bl) * rad2deg - 120 : NaN; + return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); + } + + function cubehelix(h, s, l, opacity) { + return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); + } + + function Cubehelix(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; + } + + define(Cubehelix, cubehelix, extend(Color, { + brighter: function brighter(k) { + k = k == null ? _brighter : Math.pow(_brighter, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + darker: function darker(k) { + k = k == null ? _darker : Math.pow(_darker, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + rgb: function rgb() { + var h = isNaN(this.h) ? 0 : (this.h + 120) * deg2rad, + l = +this.l, + a = isNaN(this.s) ? 0 : this.s * l * (1 - l), + cosh = Math.cos(h), + sinh = Math.sin(h); + return new Rgb(255 * (l + a * (A * cosh + B * sinh)), 255 * (l + a * (C * cosh + D * sinh)), 255 * (l + a * (E * cosh)), this.opacity); + } + })); + + exports.color = color; + exports.rgb = rgb; + exports.hsl = hsl; + exports.lab = lab; + exports.hcl = hcl; + exports.cubehelix = cubehelix; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 4: [function (require, module, exports) { + // https://d3js.org/d3-ease/ Version 1.0.3. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.d3 = global.d3 || {}); + })(this, function (exports) { + 'use strict'; + + function linear(t) { + return +t; + } + + function quadIn(t) { + return t * t; + } + + function quadOut(t) { + return t * (2 - t); + } + + function quadInOut(t) { + return ((t *= 2) <= 1 ? t * t : --t * (2 - t) + 1) / 2; + } + + function cubicIn(t) { + return t * t * t; + } + + function cubicOut(t) { + return --t * t * t + 1; + } + + function cubicInOut(t) { + return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; + } + + var exponent = 3; + + var polyIn = function custom(e) { + e = +e; + + function polyIn(t) { + return Math.pow(t, e); + } + + polyIn.exponent = custom; + + return polyIn; + }(exponent); + + var polyOut = function custom(e) { + e = +e; + + function polyOut(t) { + return 1 - Math.pow(1 - t, e); + } + + polyOut.exponent = custom; + + return polyOut; + }(exponent); + + var polyInOut = function custom(e) { + e = +e; + + function polyInOut(t) { + return ((t *= 2) <= 1 ? Math.pow(t, e) : 2 - Math.pow(2 - t, e)) / 2; + } + + polyInOut.exponent = custom; + + return polyInOut; + }(exponent); + + var pi = Math.PI; + var halfPi = pi / 2; + + function sinIn(t) { + return 1 - Math.cos(t * halfPi); + } + + function sinOut(t) { + return Math.sin(t * halfPi); + } + + function sinInOut(t) { + return (1 - Math.cos(pi * t)) / 2; + } + + function expIn(t) { + return Math.pow(2, 10 * t - 10); + } + + function expOut(t) { + return 1 - Math.pow(2, -10 * t); + } + + function expInOut(t) { + return ((t *= 2) <= 1 ? Math.pow(2, 10 * t - 10) : 2 - Math.pow(2, 10 - 10 * t)) / 2; + } + + function circleIn(t) { + return 1 - Math.sqrt(1 - t * t); + } + + function circleOut(t) { + return Math.sqrt(1 - --t * t); + } + + function circleInOut(t) { + return ((t *= 2) <= 1 ? 1 - Math.sqrt(1 - t * t) : Math.sqrt(1 - (t -= 2) * t) + 1) / 2; + } + + var b1 = 4 / 11; + var b2 = 6 / 11; + var b3 = 8 / 11; + var b4 = 3 / 4; + var b5 = 9 / 11; + var b6 = 10 / 11; + var b7 = 15 / 16; + var b8 = 21 / 22; + var b9 = 63 / 64; + var b0 = 1 / b1 / b1; + + function bounceIn(t) { + return 1 - bounceOut(1 - t); + } + + function bounceOut(t) { + return (t = +t) < b1 ? b0 * t * t : t < b3 ? b0 * (t -= b2) * t + b4 : t < b6 ? b0 * (t -= b5) * t + b7 : b0 * (t -= b8) * t + b9; + } + + function bounceInOut(t) { + return ((t *= 2) <= 1 ? 1 - bounceOut(1 - t) : bounceOut(t - 1) + 1) / 2; + } + + var overshoot = 1.70158; + + var backIn = function custom(s) { + s = +s; + + function backIn(t) { + return t * t * ((s + 1) * t - s); + } + + backIn.overshoot = custom; + + return backIn; + }(overshoot); + + var backOut = function custom(s) { + s = +s; + + function backOut(t) { + return --t * t * ((s + 1) * t + s) + 1; + } + + backOut.overshoot = custom; + + return backOut; + }(overshoot); + + var backInOut = function custom(s) { + s = +s; + + function backInOut(t) { + return ((t *= 2) < 1 ? t * t * ((s + 1) * t - s) : (t -= 2) * t * ((s + 1) * t + s) + 2) / 2; + } + + backInOut.overshoot = custom; + + return backInOut; + }(overshoot); + + var tau = 2 * Math.PI; + var amplitude = 1; + var period = 0.3; + + var elasticIn = function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau); + + function elasticIn(t) { + return a * Math.pow(2, 10 * --t) * Math.sin((s - t) / p); + } + + elasticIn.amplitude = function (a) { + return custom(a, p * tau); + }; + elasticIn.period = function (p) { + return custom(a, p); + }; + + return elasticIn; + }(amplitude, period); + + var elasticOut = function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau); + + function elasticOut(t) { + return 1 - a * Math.pow(2, -10 * (t = +t)) * Math.sin((t + s) / p); + } + + elasticOut.amplitude = function (a) { + return custom(a, p * tau); + }; + elasticOut.period = function (p) { + return custom(a, p); + }; + + return elasticOut; + }(amplitude, period); + + var elasticInOut = function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau); + + function elasticInOut(t) { + return ((t = t * 2 - 1) < 0 ? a * Math.pow(2, 10 * t) * Math.sin((s - t) / p) : 2 - a * Math.pow(2, -10 * t) * Math.sin((s + t) / p)) / 2; + } + + elasticInOut.amplitude = function (a) { + return custom(a, p * tau); + }; + elasticInOut.period = function (p) { + return custom(a, p); + }; + + return elasticInOut; + }(amplitude, period); + + exports.easeLinear = linear; + exports.easeQuad = quadInOut; + exports.easeQuadIn = quadIn; + exports.easeQuadOut = quadOut; + exports.easeQuadInOut = quadInOut; + exports.easeCubic = cubicInOut; + exports.easeCubicIn = cubicIn; + exports.easeCubicOut = cubicOut; + exports.easeCubicInOut = cubicInOut; + exports.easePoly = polyInOut; + exports.easePolyIn = polyIn; + exports.easePolyOut = polyOut; + exports.easePolyInOut = polyInOut; + exports.easeSin = sinInOut; + exports.easeSinIn = sinIn; + exports.easeSinOut = sinOut; + exports.easeSinInOut = sinInOut; + exports.easeExp = expInOut; + exports.easeExpIn = expIn; + exports.easeExpOut = expOut; + exports.easeExpInOut = expInOut; + exports.easeCircle = circleInOut; + exports.easeCircleIn = circleIn; + exports.easeCircleOut = circleOut; + exports.easeCircleInOut = circleInOut; + exports.easeBounce = bounceOut; + exports.easeBounceIn = bounceIn; + exports.easeBounceOut = bounceOut; + exports.easeBounceInOut = bounceInOut; + exports.easeBack = backInOut; + exports.easeBackIn = backIn; + exports.easeBackOut = backOut; + exports.easeBackInOut = backInOut; + exports.easeElastic = elasticOut; + exports.easeElasticIn = elasticIn; + exports.easeElasticOut = elasticOut; + exports.easeElasticInOut = elasticInOut; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 5: [function (require, module, exports) { + // https://d3js.org/d3-format/ Version 1.2.0. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.d3 = global.d3 || {}); + })(this, function (exports) { + 'use strict'; + + // Computes the decimal coefficient and exponent of the specified number x with + // significant digits p, where x is positive and p is in [1, 21] or undefined. + // For example, formatDecimal(1.23) returns ["123", 0]. + + var formatDecimal = function formatDecimal(x, p) { + if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity + var i, + coefficient = x.slice(0, i); + + // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ + // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). + return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)]; + }; + + var exponent = function exponent(x) { + return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN; + }; + + var formatGroup = function formatGroup(grouping, thousands) { + return function (value, width) { + var i = value.length, + t = [], + j = 0, + g = grouping[0], + length = 0; + + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = grouping[j = (j + 1) % grouping.length]; + } + + return t.reverse().join(thousands); + }; + }; + + var formatNumerals = function formatNumerals(numerals) { + return function (value) { + return value.replace(/[0-9]/g, function (i) { + return numerals[+i]; + }); + }; + }; + + var formatDefault = function formatDefault(x, p) { + x = x.toPrecision(p); + + out: for (var n = x.length, i = 1, i0 = -1, i1; i < n; ++i) { + switch (x[i]) { + case ".": + i0 = i1 = i;break; + case "0": + if (i0 === 0) i0 = i;i1 = i;break; + case "e": + break out; + default: + if (i0 > 0) i0 = 0;break; + } + } + + return i0 > 0 ? x.slice(0, i0) + x.slice(i1 + 1) : x; + }; + + var prefixExponent; + + var formatPrefixAuto = function formatPrefixAuto(x, p) { + var d = formatDecimal(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1], + i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, + n = coefficient.length; + return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y! + }; + + var formatRounded = function formatRounded(x, p) { + var d = formatDecimal(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1]; + return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0"); + }; + + var formatTypes = { + "": formatDefault, + "%": function _(x, p) { + return (x * 100).toFixed(p); + }, + "b": function b(x) { + return Math.round(x).toString(2); + }, + "c": function c(x) { + return x + ""; + }, + "d": function d(x) { + return Math.round(x).toString(10); + }, + "e": function e(x, p) { + return x.toExponential(p); + }, + "f": function f(x, p) { + return x.toFixed(p); + }, + "g": function g(x, p) { + return x.toPrecision(p); + }, + "o": function o(x) { + return Math.round(x).toString(8); + }, + "p": function p(x, _p) { + return formatRounded(x * 100, _p); + }, + "r": formatRounded, + "s": formatPrefixAuto, + "X": function X(x) { + return Math.round(x).toString(16).toUpperCase(); + }, + "x": function x(_x) { + return Math.round(_x).toString(16); + } + }; + + // [[fill]align][sign][symbol][0][width][,][.precision][type] + var re = /^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i; + + function formatSpecifier(specifier) { + return new FormatSpecifier(specifier); + } + + formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof + + function FormatSpecifier(specifier) { + if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); + + var match, + fill = match[1] || " ", + align = match[2] || ">", + sign = match[3] || "-", + symbol = match[4] || "", + zero = !!match[5], + width = match[6] && +match[6], + comma = !!match[7], + precision = match[8] && +match[8].slice(1), + type = match[9] || ""; + + // The "n" type is an alias for ",g". + if (type === "n") comma = true, type = "g"; + + // Map invalid types to the default format. + else if (!formatTypes[type]) type = ""; + + // If zero fill is specified, padding goes after sign and before digits. + if (zero || fill === "0" && align === "=") zero = true, fill = "0", align = "="; + + this.fill = fill; + this.align = align; + this.sign = sign; + this.symbol = symbol; + this.zero = zero; + this.width = width; + this.comma = comma; + this.precision = precision; + this.type = type; + } + + FormatSpecifier.prototype.toString = function () { + return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width == null ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision == null ? "" : "." + Math.max(0, this.precision | 0)) + this.type; + }; + + var identity = function identity(x) { + return x; + }; + + var prefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"]; + + var formatLocale = function formatLocale(locale) { + var group = locale.grouping && locale.thousands ? formatGroup(locale.grouping, locale.thousands) : identity, + currency = locale.currency, + decimal = locale.decimal, + numerals = locale.numerals ? formatNumerals(locale.numerals) : identity, + percent = locale.percent || "%"; + + function newFormat(specifier) { + specifier = formatSpecifier(specifier); + + var fill = specifier.fill, + align = specifier.align, + sign = specifier.sign, + symbol = specifier.symbol, + zero = specifier.zero, + width = specifier.width, + comma = specifier.comma, + precision = specifier.precision, + type = specifier.type; + + // Compute the prefix and suffix. + // For SI-prefix, the suffix is lazily computed. + var prefix = symbol === "$" ? currency[0] : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", + suffix = symbol === "$" ? currency[1] : /[%p]/.test(type) ? percent : ""; + + // What format function should we use? + // Is this an integer type? + // Can this type generate exponential notation? + var formatType = formatTypes[type], + maybeSuffix = !type || /[defgprs%]/.test(type); + + // Set the default precision if not specified, + // or clamp the specified precision to the supported range. + // For significant precision, it must be in [1, 21]. + // For fixed precision, it must be in [0, 20]. + precision = precision == null ? type ? 6 : 12 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision)); + + function format(value) { + var valuePrefix = prefix, + valueSuffix = suffix, + i, + n, + c; + + if (type === "c") { + valueSuffix = formatType(value) + valueSuffix; + value = ""; + } else { + value = +value; + + // Perform the initial formatting. + var valueNegative = value < 0; + value = formatType(Math.abs(value), precision); + + // If a negative value rounds to zero during formatting, treat as positive. + if (valueNegative && +value === 0) valueNegative = false; + + // Compute the prefix and suffix. + valuePrefix = (valueNegative ? sign === "(" ? sign : "-" : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; + valueSuffix = valueSuffix + (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + (valueNegative && sign === "(" ? ")" : ""); + + // Break the formatted value into the integer “value” part that can be + // grouped, and fractional or exponential “suffix” part that is not. + if (maybeSuffix) { + i = -1, n = value.length; + while (++i < n) { + if (c = value.charCodeAt(i), 48 > c || c > 57) { + valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; + value = value.slice(0, i); + break; + } + } + } + } + + // If the fill character is not "0", grouping is applied before padding. + if (comma && !zero) value = group(value, Infinity); + + // Compute the padding. + var length = valuePrefix.length + value.length + valueSuffix.length, + padding = length < width ? new Array(width - length + 1).join(fill) : ""; + + // If the fill character is "0", grouping is applied after padding. + if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; + + // Reconstruct the final output based on the desired alignment. + switch (align) { + case "<": + value = valuePrefix + value + valueSuffix + padding;break; + case "=": + value = valuePrefix + padding + value + valueSuffix;break; + case "^": + value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);break; + default: + value = padding + valuePrefix + value + valueSuffix;break; + } + + return numerals(value); + } + + format.toString = function () { + return specifier + ""; + }; + + return format; + } + + function formatPrefix(specifier, value) { + var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), + e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, + k = Math.pow(10, -e), + prefix = prefixes[8 + e / 3]; + return function (value) { + return f(k * value) + prefix; + }; + } + + return { + format: newFormat, + formatPrefix: formatPrefix + }; + }; + + var locale; + + defaultLocale({ + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["$", ""] + }); + + function defaultLocale(definition) { + locale = formatLocale(definition); + exports.format = locale.format; + exports.formatPrefix = locale.formatPrefix; + return locale; + } + + var precisionFixed = function precisionFixed(step) { + return Math.max(0, -exponent(Math.abs(step))); + }; + + var precisionPrefix = function precisionPrefix(step, value) { + return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); + }; + + var precisionRound = function precisionRound(step, max) { + step = Math.abs(step), max = Math.abs(max) - step; + return Math.max(0, exponent(max) - exponent(step)) + 1; + }; + + exports.formatDefaultLocale = defaultLocale; + exports.formatLocale = formatLocale; + exports.formatSpecifier = formatSpecifier; + exports.precisionFixed = precisionFixed; + exports.precisionPrefix = precisionPrefix; + exports.precisionRound = precisionRound; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 6: [function (require, module, exports) { + // https://d3js.org/d3-interpolate/ Version 1.1.5. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-color')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-color'], factory) : factory(global.d3 = global.d3 || {}, global.d3); + })(this, function (exports, d3Color) { + 'use strict'; + + function basis(t1, v0, v1, v2, v3) { + var t2 = t1 * t1, + t3 = t2 * t1; + return ((1 - 3 * t1 + 3 * t2 - t3) * v0 + (4 - 6 * t2 + 3 * t3) * v1 + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + t3 * v3) / 6; + } + + var basis$1 = function basis$1(values) { + var n = values.length - 1; + return function (t) { + var i = t <= 0 ? t = 0 : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n), + v1 = values[i], + v2 = values[i + 1], + v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, + v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1; + return basis((t - i / n) * n, v0, v1, v2, v3); + }; + }; + + var basisClosed = function basisClosed(values) { + var n = values.length; + return function (t) { + var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n), + v0 = values[(i + n - 1) % n], + v1 = values[i % n], + v2 = values[(i + 1) % n], + v3 = values[(i + 2) % n]; + return basis((t - i / n) * n, v0, v1, v2, v3); + }; + }; + + var constant = function constant(x) { + return function () { + return x; + }; + }; + + function linear(a, d) { + return function (t) { + return a + t * d; + }; + } + + function exponential(a, b, y) { + return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function (t) { + return Math.pow(a + t * b, y); + }; + } + + function hue(a, b) { + var d = b - a; + return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant(isNaN(a) ? b : a); + } + + function gamma(y) { + return (y = +y) === 1 ? nogamma : function (a, b) { + return b - a ? exponential(a, b, y) : constant(isNaN(a) ? b : a); + }; + } + + function nogamma(a, b) { + var d = b - a; + return d ? linear(a, d) : constant(isNaN(a) ? b : a); + } + + var rgb$1 = function rgbGamma(y) { + var color$$1 = gamma(y); + + function rgb$$1(start, end) { + var r = color$$1((start = d3Color.rgb(start)).r, (end = d3Color.rgb(end)).r), + g = color$$1(start.g, end.g), + b = color$$1(start.b, end.b), + opacity = nogamma(start.opacity, end.opacity); + return function (t) { + start.r = r(t); + start.g = g(t); + start.b = b(t); + start.opacity = opacity(t); + return start + ""; + }; + } + + rgb$$1.gamma = rgbGamma; + + return rgb$$1; + }(1); + + function rgbSpline(spline) { + return function (colors) { + var n = colors.length, + r = new Array(n), + g = new Array(n), + b = new Array(n), + i, + color$$1; + for (i = 0; i < n; ++i) { + color$$1 = d3Color.rgb(colors[i]); + r[i] = color$$1.r || 0; + g[i] = color$$1.g || 0; + b[i] = color$$1.b || 0; + } + r = spline(r); + g = spline(g); + b = spline(b); + color$$1.opacity = 1; + return function (t) { + color$$1.r = r(t); + color$$1.g = g(t); + color$$1.b = b(t); + return color$$1 + ""; + }; + }; + } + + var rgbBasis = rgbSpline(basis$1); + var rgbBasisClosed = rgbSpline(basisClosed); + + var array = function array(a, b) { + var nb = b ? b.length : 0, + na = a ? Math.min(nb, a.length) : 0, + x = new Array(nb), + c = new Array(nb), + i; + + for (i = 0; i < na; ++i) { + x[i] = value(a[i], b[i]); + }for (; i < nb; ++i) { + c[i] = b[i]; + }return function (t) { + for (i = 0; i < na; ++i) { + c[i] = x[i](t); + }return c; + }; + }; + + var date = function date(a, b) { + var d = new Date(); + return a = +a, b -= a, function (t) { + return d.setTime(a + b * t), d; + }; + }; + + var number = function number(a, b) { + return a = +a, b -= a, function (t) { + return a + b * t; + }; + }; + + var object = function object(a, b) { + var i = {}, + c = {}, + k; + + if (a === null || (typeof a === "undefined" ? "undefined" : _typeof(a)) !== "object") a = {}; + if (b === null || (typeof b === "undefined" ? "undefined" : _typeof(b)) !== "object") b = {}; + + for (k in b) { + if (k in a) { + i[k] = value(a[k], b[k]); + } else { + c[k] = b[k]; + } + } + + return function (t) { + for (k in i) { + c[k] = i[k](t); + }return c; + }; + }; + + var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + var reB = new RegExp(reA.source, "g"); + + function zero(b) { + return function () { + return b; + }; + } + + function one(b) { + return function (t) { + return b(t) + ""; + }; + } + + var string = function string(a, b) { + var bi = reA.lastIndex = reB.lastIndex = 0, + // scan index for next number in b + am, + // current match in a + bm, + // current match in b + bs, + // string preceding current number in b, if any + i = -1, + // index in s + s = [], + // string constants and placeholders + q = []; // number interpolators + + // Coerce inputs to strings. + a = a + "", b = b + ""; + + // Interpolate pairs of numbers in a & b. + while ((am = reA.exec(a)) && (bm = reB.exec(b))) { + if ((bs = bm.index) > bi) { + // a string precedes the next number in b + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + // numbers in a & b match + if (s[i]) s[i] += bm; // coalesce with previous string + else s[++i] = bm; + } else { + // interpolate non-matching numbers + s[++i] = null; + q.push({ i: i, x: number(am, bm) }); + } + bi = reB.lastIndex; + } + + // Add remains of b. + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + + // Special optimization for only a single match. + // Otherwise, interpolate each of the numbers and rejoin the string. + return s.length < 2 ? q[0] ? one(q[0].x) : zero(b) : (b = q.length, function (t) { + for (var i = 0, o; i < b; ++i) { + s[(o = q[i]).i] = o.x(t); + }return s.join(""); + }); + }; + + var value = function value(a, b) { + var t = typeof b === "undefined" ? "undefined" : _typeof(b), + c; + return b == null || t === "boolean" ? constant(b) : (t === "number" ? number : t === "string" ? (c = d3Color.color(b)) ? (b = c, rgb$1) : string : b instanceof d3Color.color ? rgb$1 : b instanceof Date ? date : Array.isArray(b) ? array : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? object : number)(a, b); + }; + + var round = function round(a, b) { + return a = +a, b -= a, function (t) { + return Math.round(a + b * t); + }; + }; + + var degrees = 180 / Math.PI; + + var identity = { + translateX: 0, + translateY: 0, + rotate: 0, + skewX: 0, + scaleX: 1, + scaleY: 1 + }; + + var decompose = function decompose(a, b, c, d, e, f) { + var scaleX, scaleY, skewX; + if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; + if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; + if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; + if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; + return { + translateX: e, + translateY: f, + rotate: Math.atan2(b, a) * degrees, + skewX: Math.atan(skewX) * degrees, + scaleX: scaleX, + scaleY: scaleY + }; + }; + + var cssNode; + var cssRoot; + var cssView; + var svgNode; + + function parseCss(value) { + if (value === "none") return identity; + if (!cssNode) cssNode = document.createElement("DIV"), cssRoot = document.documentElement, cssView = document.defaultView; + cssNode.style.transform = value; + value = cssView.getComputedStyle(cssRoot.appendChild(cssNode), null).getPropertyValue("transform"); + cssRoot.removeChild(cssNode); + value = value.slice(7, -1).split(","); + return decompose(+value[0], +value[1], +value[2], +value[3], +value[4], +value[5]); + } + + function parseSvg(value) { + if (value == null) return identity; + if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); + svgNode.setAttribute("transform", value); + if (!(value = svgNode.transform.baseVal.consolidate())) return identity; + value = value.matrix; + return decompose(value.a, value.b, value.c, value.d, value.e, value.f); + } + + function interpolateTransform(parse, pxComma, pxParen, degParen) { + + function pop(s) { + return s.length ? s.pop() + " " : ""; + } + + function translate(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push("translate(", null, pxComma, null, pxParen); + q.push({ i: i - 4, x: number(xa, xb) }, { i: i - 2, x: number(ya, yb) }); + } else if (xb || yb) { + s.push("translate(" + xb + pxComma + yb + pxParen); + } + } + + function rotate(a, b, s, q) { + if (a !== b) { + if (a - b > 180) b += 360;else if (b - a > 180) a += 360; // shortest path + q.push({ i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: number(a, b) }); + } else if (b) { + s.push(pop(s) + "rotate(" + b + degParen); + } + } + + function skewX(a, b, s, q) { + if (a !== b) { + q.push({ i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: number(a, b) }); + } else if (b) { + s.push(pop(s) + "skewX(" + b + degParen); + } + } + + function scale(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push(pop(s) + "scale(", null, ",", null, ")"); + q.push({ i: i - 4, x: number(xa, xb) }, { i: i - 2, x: number(ya, yb) }); + } else if (xb !== 1 || yb !== 1) { + s.push(pop(s) + "scale(" + xb + "," + yb + ")"); + } + } + + return function (a, b) { + var s = [], + // string constants and placeholders + q = []; // number interpolators + a = parse(a), b = parse(b); + translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); + rotate(a.rotate, b.rotate, s, q); + skewX(a.skewX, b.skewX, s, q); + scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); + a = b = null; // gc + return function (t) { + var i = -1, + n = q.length, + o; + while (++i < n) { + s[(o = q[i]).i] = o.x(t); + }return s.join(""); + }; + }; + } + + var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); + var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); + + var rho = Math.SQRT2; + var rho2 = 2; + var rho4 = 4; + var epsilon2 = 1e-12; + + function cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + + function sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + + function tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + + // p0 = [ux0, uy0, w0] + // p1 = [ux1, uy1, w1] + var zoom = function zoom(p0, p1) { + var ux0 = p0[0], + uy0 = p0[1], + w0 = p0[2], + ux1 = p1[0], + uy1 = p1[1], + w1 = p1[2], + dx = ux1 - ux0, + dy = uy1 - uy0, + d2 = dx * dx + dy * dy, + i, + S; + + // Special case for u0 ≅ u1. + if (d2 < epsilon2) { + S = Math.log(w1 / w0) / rho; + i = function i(t) { + return [ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(rho * t * S)]; + }; + } + + // General case. + else { + var d1 = Math.sqrt(d2), + b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1), + b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1), + r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), + r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / rho; + i = function i(t) { + var s = t * S, + coshr0 = cosh(r0), + u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(rho * s + r0)]; + }; + } + + i.duration = S * 1000; + + return i; + }; + + function hsl$1(hue$$1) { + return function (start, end) { + var h = hue$$1((start = d3Color.hsl(start)).h, (end = d3Color.hsl(end)).h), + s = nogamma(start.s, end.s), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function (t) { + start.h = h(t); + start.s = s(t); + start.l = l(t); + start.opacity = opacity(t); + return start + ""; + }; + }; + } + + var hsl$2 = hsl$1(hue); + var hslLong = hsl$1(nogamma); + + function lab$1(start, end) { + var l = nogamma((start = d3Color.lab(start)).l, (end = d3Color.lab(end)).l), + a = nogamma(start.a, end.a), + b = nogamma(start.b, end.b), + opacity = nogamma(start.opacity, end.opacity); + return function (t) { + start.l = l(t); + start.a = a(t); + start.b = b(t); + start.opacity = opacity(t); + return start + ""; + }; + } + + function hcl$1(hue$$1) { + return function (start, end) { + var h = hue$$1((start = d3Color.hcl(start)).h, (end = d3Color.hcl(end)).h), + c = nogamma(start.c, end.c), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function (t) { + start.h = h(t); + start.c = c(t); + start.l = l(t); + start.opacity = opacity(t); + return start + ""; + }; + }; + } + + var hcl$2 = hcl$1(hue); + var hclLong = hcl$1(nogamma); + + function cubehelix$1(hue$$1) { + return function cubehelixGamma(y) { + y = +y; + + function cubehelix$$1(start, end) { + var h = hue$$1((start = d3Color.cubehelix(start)).h, (end = d3Color.cubehelix(end)).h), + s = nogamma(start.s, end.s), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function (t) { + start.h = h(t); + start.s = s(t); + start.l = l(Math.pow(t, y)); + start.opacity = opacity(t); + return start + ""; + }; + } + + cubehelix$$1.gamma = cubehelixGamma; + + return cubehelix$$1; + }(1); + } + + var cubehelix$2 = cubehelix$1(hue); + var cubehelixLong = cubehelix$1(nogamma); + + var quantize = function quantize(interpolator, n) { + var samples = new Array(n); + for (var i = 0; i < n; ++i) { + samples[i] = interpolator(i / (n - 1)); + }return samples; + }; + + exports.interpolate = value; + exports.interpolateArray = array; + exports.interpolateBasis = basis$1; + exports.interpolateBasisClosed = basisClosed; + exports.interpolateDate = date; + exports.interpolateNumber = number; + exports.interpolateObject = object; + exports.interpolateRound = round; + exports.interpolateString = string; + exports.interpolateTransformCss = interpolateTransformCss; + exports.interpolateTransformSvg = interpolateTransformSvg; + exports.interpolateZoom = zoom; + exports.interpolateRgb = rgb$1; + exports.interpolateRgbBasis = rgbBasis; + exports.interpolateRgbBasisClosed = rgbBasisClosed; + exports.interpolateHsl = hsl$2; + exports.interpolateHslLong = hslLong; + exports.interpolateLab = lab$1; + exports.interpolateHcl = hcl$2; + exports.interpolateHclLong = hclLong; + exports.interpolateCubehelix = cubehelix$2; + exports.interpolateCubehelixLong = cubehelixLong; + exports.quantize = quantize; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, { "d3-color": 3 }], 7: [function (require, module, exports) { + // https://d3js.org/d3-scale/ Version 1.0.6. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-interpolate'), require('d3-format'), require('d3-time'), require('d3-time-format'), require('d3-color')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-interpolate', 'd3-format', 'd3-time', 'd3-time-format', 'd3-color'], factory) : factory(global.d3 = global.d3 || {}, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3); + })(this, function (exports, d3Array, d3Collection, d3Interpolate, d3Format, d3Time, d3TimeFormat, d3Color) { + 'use strict'; + + var array = Array.prototype; + + var map$1 = array.map; + var slice = array.slice; + + var implicit = { name: "implicit" }; + + function ordinal(range$$1) { + var index = d3Collection.map(), + domain = [], + unknown = implicit; + + range$$1 = range$$1 == null ? [] : slice.call(range$$1); + + function scale(d) { + var key = d + "", + i = index.get(key); + if (!i) { + if (unknown !== implicit) return unknown; + index.set(key, i = domain.push(d)); + } + return range$$1[(i - 1) % range$$1.length]; + } + + scale.domain = function (_) { + if (!arguments.length) return domain.slice(); + domain = [], index = d3Collection.map(); + var i = -1, + n = _.length, + d, + key; + while (++i < n) { + if (!index.has(key = (d = _[i]) + "")) index.set(key, domain.push(d)); + }return scale; + }; + + scale.range = function (_) { + return arguments.length ? (range$$1 = slice.call(_), scale) : range$$1.slice(); + }; + + scale.unknown = function (_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.copy = function () { + return ordinal().domain(domain).range(range$$1).unknown(unknown); + }; + + return scale; + } + + function band() { + var scale = ordinal().unknown(undefined), + domain = scale.domain, + ordinalRange = scale.range, + range$$1 = [0, 1], + step, + bandwidth, + round = false, + paddingInner = 0, + paddingOuter = 0, + align = 0.5; + + delete scale.unknown; + + function rescale() { + var n = domain().length, + reverse = range$$1[1] < range$$1[0], + start = range$$1[reverse - 0], + stop = range$$1[1 - reverse]; + step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2); + if (round) step = Math.floor(step); + start += (stop - start - step * (n - paddingInner)) * align; + bandwidth = step * (1 - paddingInner); + if (round) start = Math.round(start), bandwidth = Math.round(bandwidth); + var values = d3Array.range(n).map(function (i) { + return start + step * i; + }); + return ordinalRange(reverse ? values.reverse() : values); + } + + scale.domain = function (_) { + return arguments.length ? (domain(_), rescale()) : domain(); + }; + + scale.range = function (_) { + return arguments.length ? (range$$1 = [+_[0], +_[1]], rescale()) : range$$1.slice(); + }; + + scale.rangeRound = function (_) { + return range$$1 = [+_[0], +_[1]], round = true, rescale(); + }; + + scale.bandwidth = function () { + return bandwidth; + }; + + scale.step = function () { + return step; + }; + + scale.round = function (_) { + return arguments.length ? (round = !!_, rescale()) : round; + }; + + scale.padding = function (_) { + return arguments.length ? (paddingInner = paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingInner; + }; + + scale.paddingInner = function (_) { + return arguments.length ? (paddingInner = Math.max(0, Math.min(1, _)), rescale()) : paddingInner; + }; + + scale.paddingOuter = function (_) { + return arguments.length ? (paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingOuter; + }; + + scale.align = function (_) { + return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align; + }; + + scale.copy = function () { + return band().domain(domain()).range(range$$1).round(round).paddingInner(paddingInner).paddingOuter(paddingOuter).align(align); + }; + + return rescale(); + } + + function pointish(scale) { + var copy = scale.copy; + + scale.padding = scale.paddingOuter; + delete scale.paddingInner; + delete scale.paddingOuter; + + scale.copy = function () { + return pointish(copy()); + }; + + return scale; + } + + function point() { + return pointish(band().paddingInner(1)); + } + + var constant = function constant(x) { + return function () { + return x; + }; + }; + + var number = function number(x) { + return +x; + }; + + var unit = [0, 1]; + + function deinterpolateLinear(a, b) { + return (b -= a = +a) ? function (x) { + return (x - a) / b; + } : constant(b); + } + + function deinterpolateClamp(deinterpolate) { + return function (a, b) { + var d = deinterpolate(a = +a, b = +b); + return function (x) { + return x <= a ? 0 : x >= b ? 1 : d(x); + }; + }; + } + + function reinterpolateClamp(reinterpolate) { + return function (a, b) { + var r = reinterpolate(a = +a, b = +b); + return function (t) { + return t <= 0 ? a : t >= 1 ? b : r(t); + }; + }; + } + + function bimap(domain, range$$1, deinterpolate, reinterpolate) { + var d0 = domain[0], + d1 = domain[1], + r0 = range$$1[0], + r1 = range$$1[1]; + if (d1 < d0) d0 = deinterpolate(d1, d0), r0 = reinterpolate(r1, r0);else d0 = deinterpolate(d0, d1), r0 = reinterpolate(r0, r1); + return function (x) { + return r0(d0(x)); + }; + } + + function polymap(domain, range$$1, deinterpolate, reinterpolate) { + var j = Math.min(domain.length, range$$1.length) - 1, + d = new Array(j), + r = new Array(j), + i = -1; + + // Reverse descending domains. + if (domain[j] < domain[0]) { + domain = domain.slice().reverse(); + range$$1 = range$$1.slice().reverse(); + } + + while (++i < j) { + d[i] = deinterpolate(domain[i], domain[i + 1]); + r[i] = reinterpolate(range$$1[i], range$$1[i + 1]); + } + + return function (x) { + var i = d3Array.bisect(domain, x, 1, j) - 1; + return r[i](d[i](x)); + }; + } + + function copy(source, target) { + return target.domain(source.domain()).range(source.range()).interpolate(source.interpolate()).clamp(source.clamp()); + } + + // deinterpolate(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. + // reinterpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding domain value x in [a,b]. + function continuous(deinterpolate, reinterpolate) { + var domain = unit, + range$$1 = unit, + interpolate$$1 = d3Interpolate.interpolate, + clamp = false, + piecewise, + output, + input; + + function rescale() { + piecewise = Math.min(domain.length, range$$1.length) > 2 ? polymap : bimap; + output = input = null; + return scale; + } + + function scale(x) { + return (output || (output = piecewise(domain, range$$1, clamp ? deinterpolateClamp(deinterpolate) : deinterpolate, interpolate$$1)))(+x); + } + + scale.invert = function (y) { + return (input || (input = piecewise(range$$1, domain, deinterpolateLinear, clamp ? reinterpolateClamp(reinterpolate) : reinterpolate)))(+y); + }; + + scale.domain = function (_) { + return arguments.length ? (domain = map$1.call(_, number), rescale()) : domain.slice(); + }; + + scale.range = function (_) { + return arguments.length ? (range$$1 = slice.call(_), rescale()) : range$$1.slice(); + }; + + scale.rangeRound = function (_) { + return range$$1 = slice.call(_), interpolate$$1 = d3Interpolate.interpolateRound, rescale(); + }; + + scale.clamp = function (_) { + return arguments.length ? (clamp = !!_, rescale()) : clamp; + }; + + scale.interpolate = function (_) { + return arguments.length ? (interpolate$$1 = _, rescale()) : interpolate$$1; + }; + + return rescale(); + } + + var tickFormat = function tickFormat(domain, count, specifier) { + var start = domain[0], + stop = domain[domain.length - 1], + step = d3Array.tickStep(start, stop, count == null ? 10 : count), + precision; + specifier = d3Format.formatSpecifier(specifier == null ? ",f" : specifier); + switch (specifier.type) { + case "s": + { + var value = Math.max(Math.abs(start), Math.abs(stop)); + if (specifier.precision == null && !isNaN(precision = d3Format.precisionPrefix(step, value))) specifier.precision = precision; + return d3Format.formatPrefix(specifier, value); + } + case "": + case "e": + case "g": + case "p": + case "r": + { + if (specifier.precision == null && !isNaN(precision = d3Format.precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); + break; + } + case "f": + case "%": + { + if (specifier.precision == null && !isNaN(precision = d3Format.precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; + break; + } + } + return d3Format.format(specifier); + }; + + function linearish(scale) { + var domain = scale.domain; + + scale.ticks = function (count) { + var d = domain(); + return d3Array.ticks(d[0], d[d.length - 1], count == null ? 10 : count); + }; + + scale.tickFormat = function (count, specifier) { + return tickFormat(domain(), count, specifier); + }; + + scale.nice = function (count) { + if (count == null) count = 10; + + var d = domain(), + i0 = 0, + i1 = d.length - 1, + start = d[i0], + stop = d[i1], + step; + + if (stop < start) { + step = start, start = stop, stop = step; + step = i0, i0 = i1, i1 = step; + } + + step = d3Array.tickIncrement(start, stop, count); + + if (step > 0) { + start = Math.floor(start / step) * step; + stop = Math.ceil(stop / step) * step; + step = d3Array.tickIncrement(start, stop, count); + } else if (step < 0) { + start = Math.ceil(start * step) / step; + stop = Math.floor(stop * step) / step; + step = d3Array.tickIncrement(start, stop, count); + } + + if (step > 0) { + d[i0] = Math.floor(start / step) * step; + d[i1] = Math.ceil(stop / step) * step; + domain(d); + } else if (step < 0) { + d[i0] = Math.ceil(start * step) / step; + d[i1] = Math.floor(stop * step) / step; + domain(d); + } + + return scale; + }; + + return scale; + } + + function linear() { + var scale = continuous(deinterpolateLinear, d3Interpolate.interpolateNumber); + + scale.copy = function () { + return copy(scale, linear()); + }; + + return linearish(scale); + } + + function identity() { + var domain = [0, 1]; + + function scale(x) { + return +x; + } + + scale.invert = scale; + + scale.domain = scale.range = function (_) { + return arguments.length ? (domain = map$1.call(_, number), scale) : domain.slice(); + }; + + scale.copy = function () { + return identity().domain(domain); + }; + + return linearish(scale); + } + + var nice = function nice(domain, interval) { + domain = domain.slice(); + + var i0 = 0, + i1 = domain.length - 1, + x0 = domain[i0], + x1 = domain[i1], + t; + + if (x1 < x0) { + t = i0, i0 = i1, i1 = t; + t = x0, x0 = x1, x1 = t; + } + + domain[i0] = interval.floor(x0); + domain[i1] = interval.ceil(x1); + return domain; + }; + + function deinterpolate(a, b) { + return (b = Math.log(b / a)) ? function (x) { + return Math.log(x / a) / b; + } : constant(b); + } + + function reinterpolate(a, b) { + return a < 0 ? function (t) { + return -Math.pow(-b, t) * Math.pow(-a, 1 - t); + } : function (t) { + return Math.pow(b, t) * Math.pow(a, 1 - t); + }; + } + + function pow10(x) { + return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; + } + + function powp(base) { + return base === 10 ? pow10 : base === Math.E ? Math.exp : function (x) { + return Math.pow(base, x); + }; + } + + function logp(base) { + return base === Math.E ? Math.log : base === 10 && Math.log10 || base === 2 && Math.log2 || (base = Math.log(base), function (x) { + return Math.log(x) / base; + }); + } + + function reflect(f) { + return function (x) { + return -f(-x); + }; + } + + function log() { + var scale = continuous(deinterpolate, reinterpolate).domain([1, 10]), + domain = scale.domain, + base = 10, + logs = logp(10), + pows = powp(10); + + function rescale() { + logs = logp(base), pows = powp(base); + if (domain()[0] < 0) logs = reflect(logs), pows = reflect(pows); + return scale; + } + + scale.base = function (_) { + return arguments.length ? (base = +_, rescale()) : base; + }; + + scale.domain = function (_) { + return arguments.length ? (domain(_), rescale()) : domain(); + }; + + scale.ticks = function (count) { + var d = domain(), + u = d[0], + v = d[d.length - 1], + r; + + if (r = v < u) i = u, u = v, v = i; + + var i = logs(u), + j = logs(v), + p, + k, + t, + n = count == null ? 10 : +count, + z = []; + + if (!(base % 1) && j - i < n) { + i = Math.round(i) - 1, j = Math.round(j) + 1; + if (u > 0) for (; i < j; ++i) { + for (k = 1, p = pows(i); k < base; ++k) { + t = p * k; + if (t < u) continue; + if (t > v) break; + z.push(t); + } + } else for (; i < j; ++i) { + for (k = base - 1, p = pows(i); k >= 1; --k) { + t = p * k; + if (t < u) continue; + if (t > v) break; + z.push(t); + } + } + } else { + z = d3Array.ticks(i, j, Math.min(j - i, n)).map(pows); + } + + return r ? z.reverse() : z; + }; + + scale.tickFormat = function (count, specifier) { + if (specifier == null) specifier = base === 10 ? ".0e" : ","; + if (typeof specifier !== "function") specifier = d3Format.format(specifier); + if (count === Infinity) return specifier; + if (count == null) count = 10; + var k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? + return function (d) { + var i = d / pows(Math.round(logs(d))); + if (i * base < base - 0.5) i *= base; + return i <= k ? specifier(d) : ""; + }; + }; + + scale.nice = function () { + return domain(nice(domain(), { + floor: function floor(x) { + return pows(Math.floor(logs(x))); + }, + ceil: function ceil(x) { + return pows(Math.ceil(logs(x))); + } + })); + }; + + scale.copy = function () { + return copy(scale, log().base(base)); + }; + + return scale; + } + + function raise(x, exponent) { + return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); + } + + function pow() { + var exponent = 1, + scale = continuous(deinterpolate, reinterpolate), + domain = scale.domain; + + function deinterpolate(a, b) { + return (b = raise(b, exponent) - (a = raise(a, exponent))) ? function (x) { + return (raise(x, exponent) - a) / b; + } : constant(b); + } + + function reinterpolate(a, b) { + b = raise(b, exponent) - (a = raise(a, exponent)); + return function (t) { + return raise(a + b * t, 1 / exponent); + }; + } + + scale.exponent = function (_) { + return arguments.length ? (exponent = +_, domain(domain())) : exponent; + }; + + scale.copy = function () { + return copy(scale, pow().exponent(exponent)); + }; + + return linearish(scale); + } + + function sqrt() { + return pow().exponent(0.5); + } + + function quantile$1() { + var domain = [], + range$$1 = [], + thresholds = []; + + function rescale() { + var i = 0, + n = Math.max(1, range$$1.length); + thresholds = new Array(n - 1); + while (++i < n) { + thresholds[i - 1] = d3Array.quantile(domain, i / n); + }return scale; + } + + function scale(x) { + if (!isNaN(x = +x)) return range$$1[d3Array.bisect(thresholds, x)]; + } + + scale.invertExtent = function (y) { + var i = range$$1.indexOf(y); + return i < 0 ? [NaN, NaN] : [i > 0 ? thresholds[i - 1] : domain[0], i < thresholds.length ? thresholds[i] : domain[domain.length - 1]]; + }; + + scale.domain = function (_) { + if (!arguments.length) return domain.slice(); + domain = []; + for (var i = 0, n = _.length, d; i < n; ++i) { + if (d = _[i], d != null && !isNaN(d = +d)) domain.push(d); + }domain.sort(d3Array.ascending); + return rescale(); + }; + + scale.range = function (_) { + return arguments.length ? (range$$1 = slice.call(_), rescale()) : range$$1.slice(); + }; + + scale.quantiles = function () { + return thresholds.slice(); + }; + + scale.copy = function () { + return quantile$1().domain(domain).range(range$$1); + }; + + return scale; + } + + function quantize() { + var x0 = 0, + x1 = 1, + n = 1, + domain = [0.5], + range$$1 = [0, 1]; + + function scale(x) { + if (x <= x) return range$$1[d3Array.bisect(domain, x, 0, n)]; + } + + function rescale() { + var i = -1; + domain = new Array(n); + while (++i < n) { + domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); + }return scale; + } + + scale.domain = function (_) { + return arguments.length ? (x0 = +_[0], x1 = +_[1], rescale()) : [x0, x1]; + }; + + scale.range = function (_) { + return arguments.length ? (n = (range$$1 = slice.call(_)).length - 1, rescale()) : range$$1.slice(); + }; + + scale.invertExtent = function (y) { + var i = range$$1.indexOf(y); + return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]]; + }; + + scale.copy = function () { + return quantize().domain([x0, x1]).range(range$$1); + }; + + return linearish(scale); + } + + function threshold() { + var domain = [0.5], + range$$1 = [0, 1], + n = 1; + + function scale(x) { + if (x <= x) return range$$1[d3Array.bisect(domain, x, 0, n)]; + } + + scale.domain = function (_) { + return arguments.length ? (domain = slice.call(_), n = Math.min(domain.length, range$$1.length - 1), scale) : domain.slice(); + }; + + scale.range = function (_) { + return arguments.length ? (range$$1 = slice.call(_), n = Math.min(domain.length, range$$1.length - 1), scale) : range$$1.slice(); + }; + + scale.invertExtent = function (y) { + var i = range$$1.indexOf(y); + return [domain[i - 1], domain[i]]; + }; + + scale.copy = function () { + return threshold().domain(domain).range(range$$1); + }; + + return scale; + } + + var durationSecond = 1000; + var durationMinute = durationSecond * 60; + var durationHour = durationMinute * 60; + var durationDay = durationHour * 24; + var durationWeek = durationDay * 7; + var durationMonth = durationDay * 30; + var durationYear = durationDay * 365; + + function date(t) { + return new Date(t); + } + + function number$1(t) { + return t instanceof Date ? +t : +new Date(+t); + } + + function calendar(year, month, week, day, hour, minute, second, millisecond, format$$1) { + var scale = continuous(deinterpolateLinear, d3Interpolate.interpolateNumber), + invert = scale.invert, + domain = scale.domain; + + var formatMillisecond = format$$1(".%L"), + formatSecond = format$$1(":%S"), + formatMinute = format$$1("%I:%M"), + formatHour = format$$1("%I %p"), + formatDay = format$$1("%a %d"), + formatWeek = format$$1("%b %d"), + formatMonth = format$$1("%B"), + formatYear = format$$1("%Y"); + + var tickIntervals = [[second, 1, durationSecond], [second, 5, 5 * durationSecond], [second, 15, 15 * durationSecond], [second, 30, 30 * durationSecond], [minute, 1, durationMinute], [minute, 5, 5 * durationMinute], [minute, 15, 15 * durationMinute], [minute, 30, 30 * durationMinute], [hour, 1, durationHour], [hour, 3, 3 * durationHour], [hour, 6, 6 * durationHour], [hour, 12, 12 * durationHour], [day, 1, durationDay], [day, 2, 2 * durationDay], [week, 1, durationWeek], [month, 1, durationMonth], [month, 3, 3 * durationMonth], [year, 1, durationYear]]; + + function tickFormat(date) { + return (second(date) < date ? formatMillisecond : minute(date) < date ? formatSecond : hour(date) < date ? formatMinute : day(date) < date ? formatHour : month(date) < date ? week(date) < date ? formatDay : formatWeek : year(date) < date ? formatMonth : formatYear)(date); + } + + function tickInterval(interval, start, stop, step) { + if (interval == null) interval = 10; + + // If a desired tick count is specified, pick a reasonable tick interval + // based on the extent of the domain and a rough estimate of tick size. + // Otherwise, assume interval is already a time interval and use it. + if (typeof interval === "number") { + var target = Math.abs(stop - start) / interval, + i = d3Array.bisector(function (i) { + return i[2]; + }).right(tickIntervals, target); + if (i === tickIntervals.length) { + step = d3Array.tickStep(start / durationYear, stop / durationYear, interval); + interval = year; + } else if (i) { + i = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; + step = i[1]; + interval = i[0]; + } else { + step = d3Array.tickStep(start, stop, interval); + interval = millisecond; + } + } + + return step == null ? interval : interval.every(step); + } + + scale.invert = function (y) { + return new Date(invert(y)); + }; + + scale.domain = function (_) { + return arguments.length ? domain(map$1.call(_, number$1)) : domain().map(date); + }; + + scale.ticks = function (interval, step) { + var d = domain(), + t0 = d[0], + t1 = d[d.length - 1], + r = t1 < t0, + t; + if (r) t = t0, t0 = t1, t1 = t; + t = tickInterval(interval, t0, t1, step); + t = t ? t.range(t0, t1 + 1) : []; // inclusive stop + return r ? t.reverse() : t; + }; + + scale.tickFormat = function (count, specifier) { + return specifier == null ? tickFormat : format$$1(specifier); + }; + + scale.nice = function (interval, step) { + var d = domain(); + return (interval = tickInterval(interval, d[0], d[d.length - 1], step)) ? domain(nice(d, interval)) : scale; + }; + + scale.copy = function () { + return copy(scale, calendar(year, month, week, day, hour, minute, second, millisecond, format$$1)); + }; + + return scale; + } + + var time = function time() { + return calendar(d3Time.timeYear, d3Time.timeMonth, d3Time.timeWeek, d3Time.timeDay, d3Time.timeHour, d3Time.timeMinute, d3Time.timeSecond, d3Time.timeMillisecond, d3TimeFormat.timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]); + }; + + var utcTime = function utcTime() { + return calendar(d3Time.utcYear, d3Time.utcMonth, d3Time.utcWeek, d3Time.utcDay, d3Time.utcHour, d3Time.utcMinute, d3Time.utcSecond, d3Time.utcMillisecond, d3TimeFormat.utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]); + }; + + var colors = function colors(s) { + return s.match(/.{6}/g).map(function (x) { + return "#" + x; + }); + }; + + var category10 = colors("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"); + + var category20b = colors("393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6"); + + var category20c = colors("3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9"); + + var category20 = colors("1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"); + + var cubehelix$1 = d3Interpolate.interpolateCubehelixLong(d3Color.cubehelix(300, 0.5, 0.0), d3Color.cubehelix(-240, 0.5, 1.0)); + + var warm = d3Interpolate.interpolateCubehelixLong(d3Color.cubehelix(-100, 0.75, 0.35), d3Color.cubehelix(80, 1.50, 0.8)); + + var cool = d3Interpolate.interpolateCubehelixLong(d3Color.cubehelix(260, 0.75, 0.35), d3Color.cubehelix(80, 1.50, 0.8)); + + var rainbow = d3Color.cubehelix(); + + var rainbow$1 = function rainbow$1(t) { + if (t < 0 || t > 1) t -= Math.floor(t); + var ts = Math.abs(t - 0.5); + rainbow.h = 360 * t - 100; + rainbow.s = 1.5 - 1.5 * ts; + rainbow.l = 0.8 - 0.9 * ts; + return rainbow + ""; + }; + + function ramp(range$$1) { + var n = range$$1.length; + return function (t) { + return range$$1[Math.max(0, Math.min(n - 1, Math.floor(t * n)))]; + }; + } + + var viridis = ramp(colors("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")); + + var magma = ramp(colors("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")); + + var inferno = ramp(colors("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")); + + var plasma = ramp(colors("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")); + + function sequential(interpolator) { + var x0 = 0, + x1 = 1, + clamp = false; + + function scale(x) { + var t = (x - x0) / (x1 - x0); + return interpolator(clamp ? Math.max(0, Math.min(1, t)) : t); + } + + scale.domain = function (_) { + return arguments.length ? (x0 = +_[0], x1 = +_[1], scale) : [x0, x1]; + }; + + scale.clamp = function (_) { + return arguments.length ? (clamp = !!_, scale) : clamp; + }; + + scale.interpolator = function (_) { + return arguments.length ? (interpolator = _, scale) : interpolator; + }; + + scale.copy = function () { + return sequential(interpolator).domain([x0, x1]).clamp(clamp); + }; + + return linearish(scale); + } + + exports.scaleBand = band; + exports.scalePoint = point; + exports.scaleIdentity = identity; + exports.scaleLinear = linear; + exports.scaleLog = log; + exports.scaleOrdinal = ordinal; + exports.scaleImplicit = implicit; + exports.scalePow = pow; + exports.scaleSqrt = sqrt; + exports.scaleQuantile = quantile$1; + exports.scaleQuantize = quantize; + exports.scaleThreshold = threshold; + exports.scaleTime = time; + exports.scaleUtc = utcTime; + exports.schemeCategory10 = category10; + exports.schemeCategory20b = category20b; + exports.schemeCategory20c = category20c; + exports.schemeCategory20 = category20; + exports.interpolateCubehelixDefault = cubehelix$1; + exports.interpolateRainbow = rainbow$1; + exports.interpolateWarm = warm; + exports.interpolateCool = cool; + exports.interpolateViridis = viridis; + exports.interpolateMagma = magma; + exports.interpolateInferno = inferno; + exports.interpolatePlasma = plasma; + exports.scaleSequential = sequential; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, { "d3-array": 1, "d3-collection": 2, "d3-color": 3, "d3-format": 5, "d3-interpolate": 6, "d3-time": 9, "d3-time-format": 8 }], 8: [function (require, module, exports) { + // https://d3js.org/d3-time-format/ Version 2.1.0. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-time')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-time'], factory) : factory(global.d3 = global.d3 || {}, global.d3); + })(this, function (exports, d3Time) { + 'use strict'; + + function localDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); + date.setFullYear(d.y); + return date; + } + return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); + } + + function utcDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); + date.setUTCFullYear(d.y); + return date; + } + return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); + } + + function newYear(y) { + return { y: y, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0 }; + } + + function formatLocale(locale) { + var locale_dateTime = locale.dateTime, + locale_date = locale.date, + locale_time = locale.time, + locale_periods = locale.periods, + locale_weekdays = locale.days, + locale_shortWeekdays = locale.shortDays, + locale_months = locale.months, + locale_shortMonths = locale.shortMonths; + + var periodRe = formatRe(locale_periods), + periodLookup = formatLookup(locale_periods), + weekdayRe = formatRe(locale_weekdays), + weekdayLookup = formatLookup(locale_weekdays), + shortWeekdayRe = formatRe(locale_shortWeekdays), + shortWeekdayLookup = formatLookup(locale_shortWeekdays), + monthRe = formatRe(locale_months), + monthLookup = formatLookup(locale_months), + shortMonthRe = formatRe(locale_shortMonths), + shortMonthLookup = formatLookup(locale_shortMonths); + + var formats = { + "a": formatShortWeekday, + "A": formatWeekday, + "b": formatShortMonth, + "B": formatMonth, + "c": null, + "d": formatDayOfMonth, + "e": formatDayOfMonth, + "f": formatMicroseconds, + "H": formatHour24, + "I": formatHour12, + "j": formatDayOfYear, + "L": formatMilliseconds, + "m": formatMonthNumber, + "M": formatMinutes, + "p": formatPeriod, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatSeconds, + "u": formatWeekdayNumberMonday, + "U": formatWeekNumberSunday, + "V": formatWeekNumberISO, + "w": formatWeekdayNumberSunday, + "W": formatWeekNumberMonday, + "x": null, + "X": null, + "y": formatYear, + "Y": formatFullYear, + "Z": formatZone, + "%": formatLiteralPercent + }; + + var utcFormats = { + "a": formatUTCShortWeekday, + "A": formatUTCWeekday, + "b": formatUTCShortMonth, + "B": formatUTCMonth, + "c": null, + "d": formatUTCDayOfMonth, + "e": formatUTCDayOfMonth, + "f": formatUTCMicroseconds, + "H": formatUTCHour24, + "I": formatUTCHour12, + "j": formatUTCDayOfYear, + "L": formatUTCMilliseconds, + "m": formatUTCMonthNumber, + "M": formatUTCMinutes, + "p": formatUTCPeriod, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatUTCSeconds, + "u": formatUTCWeekdayNumberMonday, + "U": formatUTCWeekNumberSunday, + "V": formatUTCWeekNumberISO, + "w": formatUTCWeekdayNumberSunday, + "W": formatUTCWeekNumberMonday, + "x": null, + "X": null, + "y": formatUTCYear, + "Y": formatUTCFullYear, + "Z": formatUTCZone, + "%": formatLiteralPercent + }; + + var parses = { + "a": parseShortWeekday, + "A": parseWeekday, + "b": parseShortMonth, + "B": parseMonth, + "c": parseLocaleDateTime, + "d": parseDayOfMonth, + "e": parseDayOfMonth, + "f": parseMicroseconds, + "H": parseHour24, + "I": parseHour24, + "j": parseDayOfYear, + "L": parseMilliseconds, + "m": parseMonthNumber, + "M": parseMinutes, + "p": parsePeriod, + "Q": parseUnixTimestamp, + "s": parseUnixTimestampSeconds, + "S": parseSeconds, + "u": parseWeekdayNumberMonday, + "U": parseWeekNumberSunday, + "V": parseWeekNumberISO, + "w": parseWeekdayNumberSunday, + "W": parseWeekNumberMonday, + "x": parseLocaleDate, + "X": parseLocaleTime, + "y": parseYear, + "Y": parseFullYear, + "Z": parseZone, + "%": parseLiteralPercent + }; + + // These recursive directive definitions must be deferred. + formats.x = newFormat(locale_date, formats); + formats.X = newFormat(locale_time, formats); + formats.c = newFormat(locale_dateTime, formats); + utcFormats.x = newFormat(locale_date, utcFormats); + utcFormats.X = newFormat(locale_time, utcFormats); + utcFormats.c = newFormat(locale_dateTime, utcFormats); + + function newFormat(specifier, formats) { + return function (date) { + var string = [], + i = -1, + j = 0, + n = specifier.length, + c, + pad, + format; + + if (!(date instanceof Date)) date = new Date(+date); + + while (++i < n) { + if (specifier.charCodeAt(i) === 37) { + string.push(specifier.slice(j, i)); + if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);else pad = c === "e" ? " " : "0"; + if (format = formats[c]) c = format(date, pad); + string.push(c); + j = i + 1; + } + } + + string.push(specifier.slice(j, i)); + return string.join(""); + }; + } + + function newParse(specifier, newDate) { + return function (string) { + var d = newYear(1900), + i = parseSpecifier(d, specifier, string += "", 0), + week, + day; + if (i != string.length) return null; + + // If a UNIX timestamp is specified, return it. + if ("Q" in d) return new Date(d.Q); + + // The am-pm flag is 0 for AM, and 1 for PM. + if ("p" in d) d.H = d.H % 12 + d.p * 12; + + // Convert day-of-week and week-of-year to day-of-year. + if ("V" in d) { + if (d.V < 1 || d.V > 53) return null; + if (!("w" in d)) d.w = 1; + if ("Z" in d) { + week = utcDate(newYear(d.y)), day = week.getUTCDay(); + week = day > 4 || day === 0 ? d3Time.utcMonday.ceil(week) : d3Time.utcMonday(week); + week = d3Time.utcDay.offset(week, (d.V - 1) * 7); + d.y = week.getUTCFullYear(); + d.m = week.getUTCMonth(); + d.d = week.getUTCDate() + (d.w + 6) % 7; + } else { + week = newDate(newYear(d.y)), day = week.getDay(); + week = day > 4 || day === 0 ? d3Time.timeMonday.ceil(week) : d3Time.timeMonday(week); + week = d3Time.timeDay.offset(week, (d.V - 1) * 7); + d.y = week.getFullYear(); + d.m = week.getMonth(); + d.d = week.getDate() + (d.w + 6) % 7; + } + } else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; + day = "Z" in d ? utcDate(newYear(d.y)).getUTCDay() : newDate(newYear(d.y)).getDay(); + d.m = 0; + d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7; + } + + // If a time zone is specified, all fields are interpreted as UTC and then + // offset according to the specified time zone. + if ("Z" in d) { + d.H += d.Z / 100 | 0; + d.M += d.Z % 100; + return utcDate(d); + } + + // Otherwise, all fields are in local time. + return newDate(d); + }; + } + + function parseSpecifier(d, specifier, string, j) { + var i = 0, + n = specifier.length, + m = string.length, + c, + parse; + + while (i < n) { + if (j >= m) return -1; + c = specifier.charCodeAt(i++); + if (c === 37) { + c = specifier.charAt(i++); + parse = parses[c in pads ? specifier.charAt(i++) : c]; + if (!parse || (j = parse(d, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + + return j; + } + + function parsePeriod(d, string, i) { + var n = periodRe.exec(string.slice(i)); + return n ? (d.p = periodLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseShortWeekday(d, string, i) { + var n = shortWeekdayRe.exec(string.slice(i)); + return n ? (d.w = shortWeekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseWeekday(d, string, i) { + var n = weekdayRe.exec(string.slice(i)); + return n ? (d.w = weekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseShortMonth(d, string, i) { + var n = shortMonthRe.exec(string.slice(i)); + return n ? (d.m = shortMonthLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseMonth(d, string, i) { + var n = monthRe.exec(string.slice(i)); + return n ? (d.m = monthLookup[n[0].toLowerCase()], i + n[0].length) : -1; + } + + function parseLocaleDateTime(d, string, i) { + return parseSpecifier(d, locale_dateTime, string, i); + } + + function parseLocaleDate(d, string, i) { + return parseSpecifier(d, locale_date, string, i); + } + + function parseLocaleTime(d, string, i) { + return parseSpecifier(d, locale_time, string, i); + } + + function formatShortWeekday(d) { + return locale_shortWeekdays[d.getDay()]; + } + + function formatWeekday(d) { + return locale_weekdays[d.getDay()]; + } + + function formatShortMonth(d) { + return locale_shortMonths[d.getMonth()]; + } + + function formatMonth(d) { + return locale_months[d.getMonth()]; + } + + function formatPeriod(d) { + return locale_periods[+(d.getHours() >= 12)]; + } + + function formatUTCShortWeekday(d) { + return locale_shortWeekdays[d.getUTCDay()]; + } + + function formatUTCWeekday(d) { + return locale_weekdays[d.getUTCDay()]; + } + + function formatUTCShortMonth(d) { + return locale_shortMonths[d.getUTCMonth()]; + } + + function formatUTCMonth(d) { + return locale_months[d.getUTCMonth()]; + } + + function formatUTCPeriod(d) { + return locale_periods[+(d.getUTCHours() >= 12)]; + } + + return { + format: function format(specifier) { + var f = newFormat(specifier += "", formats); + f.toString = function () { + return specifier; + }; + return f; + }, + parse: function parse(specifier) { + var p = newParse(specifier += "", localDate); + p.toString = function () { + return specifier; + }; + return p; + }, + utcFormat: function utcFormat(specifier) { + var f = newFormat(specifier += "", utcFormats); + f.toString = function () { + return specifier; + }; + return f; + }, + utcParse: function utcParse(specifier) { + var p = newParse(specifier, utcDate); + p.toString = function () { + return specifier; + }; + return p; + } + }; + } + + var pads = { "-": "", "_": " ", "0": "0" }; + var numberRe = /^\s*\d+/; + var percentRe = /^%/; + var requoteRe = /[\\^$*+?|[\]().{}]/g; + + function pad(value, fill, width) { + var sign = value < 0 ? "-" : "", + string = (sign ? -value : value) + "", + length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + + function requote(s) { + return s.replace(requoteRe, "\\$&"); + } + + function formatRe(names) { + return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); + } + + function formatLookup(names) { + var map = {}, + i = -1, + n = names.length; + while (++i < n) { + map[names[i].toLowerCase()] = i; + }return map; + } + + function parseWeekdayNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.w = +n[0], i + n[0].length) : -1; + } + + function parseWeekdayNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.u = +n[0], i + n[0].length) : -1; + } + + function parseWeekNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.U = +n[0], i + n[0].length) : -1; + } + + function parseWeekNumberISO(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.V = +n[0], i + n[0].length) : -1; + } + + function parseWeekNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.W = +n[0], i + n[0].length) : -1; + } + + function parseFullYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 4)); + return n ? (d.y = +n[0], i + n[0].length) : -1; + } + + function parseYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; + } + + function parseZone(d, string, i) { + var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); + return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; + } + + function parseMonthNumber(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.m = n[0] - 1, i + n[0].length) : -1; + } + + function parseDayOfMonth(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.d = +n[0], i + n[0].length) : -1; + } + + function parseDayOfYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; + } + + function parseHour24(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.H = +n[0], i + n[0].length) : -1; + } + + function parseMinutes(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.M = +n[0], i + n[0].length) : -1; + } + + function parseSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.S = +n[0], i + n[0].length) : -1; + } + + function parseMilliseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.L = +n[0], i + n[0].length) : -1; + } + + function parseMicroseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 6)); + return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; + } + + function parseLiteralPercent(d, string, i) { + var n = percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + + function parseUnixTimestamp(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.Q = +n[0], i + n[0].length) : -1; + } + + function parseUnixTimestampSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.Q = +n[0] * 1000, i + n[0].length) : -1; + } + + function formatDayOfMonth(d, p) { + return pad(d.getDate(), p, 2); + } + + function formatHour24(d, p) { + return pad(d.getHours(), p, 2); + } + + function formatHour12(d, p) { + return pad(d.getHours() % 12 || 12, p, 2); + } + + function formatDayOfYear(d, p) { + return pad(1 + d3Time.timeDay.count(d3Time.timeYear(d), d), p, 3); + } + + function formatMilliseconds(d, p) { + return pad(d.getMilliseconds(), p, 3); + } + + function formatMicroseconds(d, p) { + return formatMilliseconds(d, p) + "000"; + } + + function formatMonthNumber(d, p) { + return pad(d.getMonth() + 1, p, 2); + } + + function formatMinutes(d, p) { + return pad(d.getMinutes(), p, 2); + } + + function formatSeconds(d, p) { + return pad(d.getSeconds(), p, 2); + } + + function formatWeekdayNumberMonday(d) { + var day = d.getDay(); + return day === 0 ? 7 : day; + } + + function formatWeekNumberSunday(d, p) { + return pad(d3Time.timeSunday.count(d3Time.timeYear(d), d), p, 2); + } + + function formatWeekNumberISO(d, p) { + var day = d.getDay(); + d = day >= 4 || day === 0 ? d3Time.timeThursday(d) : d3Time.timeThursday.ceil(d); + return pad(d3Time.timeThursday.count(d3Time.timeYear(d), d) + (d3Time.timeYear(d).getDay() === 4), p, 2); + } + + function formatWeekdayNumberSunday(d) { + return d.getDay(); + } + + function formatWeekNumberMonday(d, p) { + return pad(d3Time.timeMonday.count(d3Time.timeYear(d), d), p, 2); + } + + function formatYear(d, p) { + return pad(d.getFullYear() % 100, p, 2); + } + + function formatFullYear(d, p) { + return pad(d.getFullYear() % 10000, p, 4); + } + + function formatZone(d) { + var z = d.getTimezoneOffset(); + return (z > 0 ? "-" : (z *= -1, "+")) + pad(z / 60 | 0, "0", 2) + pad(z % 60, "0", 2); + } + + function formatUTCDayOfMonth(d, p) { + return pad(d.getUTCDate(), p, 2); + } + + function formatUTCHour24(d, p) { + return pad(d.getUTCHours(), p, 2); + } + + function formatUTCHour12(d, p) { + return pad(d.getUTCHours() % 12 || 12, p, 2); + } + + function formatUTCDayOfYear(d, p) { + return pad(1 + d3Time.utcDay.count(d3Time.utcYear(d), d), p, 3); + } + + function formatUTCMilliseconds(d, p) { + return pad(d.getUTCMilliseconds(), p, 3); + } + + function formatUTCMicroseconds(d, p) { + return formatUTCMilliseconds(d, p) + "000"; + } + + function formatUTCMonthNumber(d, p) { + return pad(d.getUTCMonth() + 1, p, 2); + } + + function formatUTCMinutes(d, p) { + return pad(d.getUTCMinutes(), p, 2); + } + + function formatUTCSeconds(d, p) { + return pad(d.getUTCSeconds(), p, 2); + } + + function formatUTCWeekdayNumberMonday(d) { + var dow = d.getUTCDay(); + return dow === 0 ? 7 : dow; + } + + function formatUTCWeekNumberSunday(d, p) { + return pad(d3Time.utcSunday.count(d3Time.utcYear(d), d), p, 2); + } + + function formatUTCWeekNumberISO(d, p) { + var day = d.getUTCDay(); + d = day >= 4 || day === 0 ? d3Time.utcThursday(d) : d3Time.utcThursday.ceil(d); + return pad(d3Time.utcThursday.count(d3Time.utcYear(d), d) + (d3Time.utcYear(d).getUTCDay() === 4), p, 2); + } + + function formatUTCWeekdayNumberSunday(d) { + return d.getUTCDay(); + } + + function formatUTCWeekNumberMonday(d, p) { + return pad(d3Time.utcMonday.count(d3Time.utcYear(d), d), p, 2); + } + + function formatUTCYear(d, p) { + return pad(d.getUTCFullYear() % 100, p, 2); + } + + function formatUTCFullYear(d, p) { + return pad(d.getUTCFullYear() % 10000, p, 4); + } + + function formatUTCZone() { + return "+0000"; + } + + function formatLiteralPercent() { + return "%"; + } + + function formatUnixTimestamp(d) { + return +d; + } + + function formatUnixTimestampSeconds(d) { + return Math.floor(+d / 1000); + } + + var locale; + + defaultLocale({ + dateTime: "%x, %X", + date: "%-m/%-d/%Y", + time: "%-I:%M:%S %p", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }); + + function defaultLocale(definition) { + locale = formatLocale(definition); + exports.timeFormat = locale.format; + exports.timeParse = locale.parse; + exports.utcFormat = locale.utcFormat; + exports.utcParse = locale.utcParse; + return locale; + } + + var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ"; + + function formatIsoNative(date) { + return date.toISOString(); + } + + var formatIso = Date.prototype.toISOString ? formatIsoNative : exports.utcFormat(isoSpecifier); + + function parseIsoNative(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + } + + var parseIso = +new Date("2000-01-01T00:00:00.000Z") ? parseIsoNative : exports.utcParse(isoSpecifier); + + exports.timeFormatDefaultLocale = defaultLocale; + exports.timeFormatLocale = formatLocale; + exports.isoFormat = formatIso; + exports.isoParse = parseIso; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, { "d3-time": 9 }], 9: [function (require, module, exports) { + // https://d3js.org/d3-time/ Version 1.0.7. Copyright 2017 Mike Bostock. + (function (global, factory) { + (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.d3 = global.d3 || {}); + })(this, function (exports) { + 'use strict'; + + var t0 = new Date(); + var t1 = new Date(); + + function newInterval(floori, offseti, count, field) { + + function interval(date) { + return floori(date = new Date(+date)), date; + } + + interval.floor = interval; + + interval.ceil = function (date) { + return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; + }; + + interval.round = function (date) { + var d0 = interval(date), + d1 = interval.ceil(date); + return date - d0 < d1 - date ? d0 : d1; + }; + + interval.offset = function (date, step) { + return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; + }; + + interval.range = function (start, stop, step) { + var range = []; + start = interval.ceil(start); + step = step == null ? 1 : Math.floor(step); + if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date + do { + range.push(new Date(+start)); + } while ((offseti(start, step), floori(start), start < stop)); + return range; + }; + + interval.filter = function (test) { + return newInterval(function (date) { + if (date >= date) while (floori(date), !test(date)) { + date.setTime(date - 1); + } + }, function (date, step) { + if (date >= date) { + if (step < 0) while (++step <= 0) { + while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty + } else while (--step >= 0) { + while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty + } + } + }); + }; + + if (count) { + interval.count = function (start, end) { + t0.setTime(+start), t1.setTime(+end); + floori(t0), floori(t1); + return Math.floor(count(t0, t1)); + }; + + interval.every = function (step) { + step = Math.floor(step); + return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval : interval.filter(field ? function (d) { + return field(d) % step === 0; + } : function (d) { + return interval.count(0, d) % step === 0; + }); + }; + } + + return interval; + } + + var millisecond = newInterval(function () { + // noop + }, function (date, step) { + date.setTime(+date + step); + }, function (start, end) { + return end - start; + }); + + // An optimized implementation for this simple case. + millisecond.every = function (k) { + k = Math.floor(k); + if (!isFinite(k) || !(k > 0)) return null; + if (!(k > 1)) return millisecond; + return newInterval(function (date) { + date.setTime(Math.floor(date / k) * k); + }, function (date, step) { + date.setTime(+date + step * k); + }, function (start, end) { + return (end - start) / k; + }); + }; + + var milliseconds = millisecond.range; + + var durationSecond = 1e3; + var durationMinute = 6e4; + var durationHour = 36e5; + var durationDay = 864e5; + var durationWeek = 6048e5; + + var second = newInterval(function (date) { + date.setTime(Math.floor(date / durationSecond) * durationSecond); + }, function (date, step) { + date.setTime(+date + step * durationSecond); + }, function (start, end) { + return (end - start) / durationSecond; + }, function (date) { + return date.getUTCSeconds(); + }); + + var seconds = second.range; + + var minute = newInterval(function (date) { + date.setTime(Math.floor(date / durationMinute) * durationMinute); + }, function (date, step) { + date.setTime(+date + step * durationMinute); + }, function (start, end) { + return (end - start) / durationMinute; + }, function (date) { + return date.getMinutes(); + }); + + var minutes = minute.range; + + var hour = newInterval(function (date) { + var offset = date.getTimezoneOffset() * durationMinute % durationHour; + if (offset < 0) offset += durationHour; + date.setTime(Math.floor((+date - offset) / durationHour) * durationHour + offset); + }, function (date, step) { + date.setTime(+date + step * durationHour); + }, function (start, end) { + return (end - start) / durationHour; + }, function (date) { + return date.getHours(); + }); + + var hours = hour.range; + + var day = newInterval(function (date) { + date.setHours(0, 0, 0, 0); + }, function (date, step) { + date.setDate(date.getDate() + step); + }, function (start, end) { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay; + }, function (date) { + return date.getDate() - 1; + }); + + var days = day.range; + + function weekday(i) { + return newInterval(function (date) { + date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); + date.setHours(0, 0, 0, 0); + }, function (date, step) { + date.setDate(date.getDate() + step * 7); + }, function (start, end) { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; + }); + } + + var sunday = weekday(0); + var monday = weekday(1); + var tuesday = weekday(2); + var wednesday = weekday(3); + var thursday = weekday(4); + var friday = weekday(5); + var saturday = weekday(6); + + var sundays = sunday.range; + var mondays = monday.range; + var tuesdays = tuesday.range; + var wednesdays = wednesday.range; + var thursdays = thursday.range; + var fridays = friday.range; + var saturdays = saturday.range; + + var month = newInterval(function (date) { + date.setDate(1); + date.setHours(0, 0, 0, 0); + }, function (date, step) { + date.setMonth(date.getMonth() + step); + }, function (start, end) { + return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; + }, function (date) { + return date.getMonth(); + }); + + var months = month.range; + + var year = newInterval(function (date) { + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); + }, function (date, step) { + date.setFullYear(date.getFullYear() + step); + }, function (start, end) { + return end.getFullYear() - start.getFullYear(); + }, function (date) { + return date.getFullYear(); + }); + + // An optimized implementation for this simple case. + year.every = function (k) { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function (date) { + date.setFullYear(Math.floor(date.getFullYear() / k) * k); + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); + }, function (date, step) { + date.setFullYear(date.getFullYear() + step * k); + }); + }; + + var years = year.range; + + var utcMinute = newInterval(function (date) { + date.setUTCSeconds(0, 0); + }, function (date, step) { + date.setTime(+date + step * durationMinute); + }, function (start, end) { + return (end - start) / durationMinute; + }, function (date) { + return date.getUTCMinutes(); + }); + + var utcMinutes = utcMinute.range; + + var utcHour = newInterval(function (date) { + date.setUTCMinutes(0, 0, 0); + }, function (date, step) { + date.setTime(+date + step * durationHour); + }, function (start, end) { + return (end - start) / durationHour; + }, function (date) { + return date.getUTCHours(); + }); + + var utcHours = utcHour.range; + + var utcDay = newInterval(function (date) { + date.setUTCHours(0, 0, 0, 0); + }, function (date, step) { + date.setUTCDate(date.getUTCDate() + step); + }, function (start, end) { + return (end - start) / durationDay; + }, function (date) { + return date.getUTCDate() - 1; + }); + + var utcDays = utcDay.range; + + function utcWeekday(i) { + return newInterval(function (date) { + date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); + date.setUTCHours(0, 0, 0, 0); + }, function (date, step) { + date.setUTCDate(date.getUTCDate() + step * 7); + }, function (start, end) { + return (end - start) / durationWeek; + }); + } + + var utcSunday = utcWeekday(0); + var utcMonday = utcWeekday(1); + var utcTuesday = utcWeekday(2); + var utcWednesday = utcWeekday(3); + var utcThursday = utcWeekday(4); + var utcFriday = utcWeekday(5); + var utcSaturday = utcWeekday(6); + + var utcSundays = utcSunday.range; + var utcMondays = utcMonday.range; + var utcTuesdays = utcTuesday.range; + var utcWednesdays = utcWednesday.range; + var utcThursdays = utcThursday.range; + var utcFridays = utcFriday.range; + var utcSaturdays = utcSaturday.range; + + var utcMonth = newInterval(function (date) { + date.setUTCDate(1); + date.setUTCHours(0, 0, 0, 0); + }, function (date, step) { + date.setUTCMonth(date.getUTCMonth() + step); + }, function (start, end) { + return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; + }, function (date) { + return date.getUTCMonth(); + }); + + var utcMonths = utcMonth.range; + + var utcYear = newInterval(function (date) { + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + }, function (date, step) { + date.setUTCFullYear(date.getUTCFullYear() + step); + }, function (start, end) { + return end.getUTCFullYear() - start.getUTCFullYear(); + }, function (date) { + return date.getUTCFullYear(); + }); + + // An optimized implementation for this simple case. + utcYear.every = function (k) { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function (date) { + date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + }, function (date, step) { + date.setUTCFullYear(date.getUTCFullYear() + step * k); + }); + }; + + var utcYears = utcYear.range; + + exports.timeInterval = newInterval; + exports.timeMillisecond = millisecond; + exports.timeMilliseconds = milliseconds; + exports.utcMillisecond = millisecond; + exports.utcMilliseconds = milliseconds; + exports.timeSecond = second; + exports.timeSeconds = seconds; + exports.utcSecond = second; + exports.utcSeconds = seconds; + exports.timeMinute = minute; + exports.timeMinutes = minutes; + exports.timeHour = hour; + exports.timeHours = hours; + exports.timeDay = day; + exports.timeDays = days; + exports.timeWeek = sunday; + exports.timeWeeks = sundays; + exports.timeSunday = sunday; + exports.timeSundays = sundays; + exports.timeMonday = monday; + exports.timeMondays = mondays; + exports.timeTuesday = tuesday; + exports.timeTuesdays = tuesdays; + exports.timeWednesday = wednesday; + exports.timeWednesdays = wednesdays; + exports.timeThursday = thursday; + exports.timeThursdays = thursdays; + exports.timeFriday = friday; + exports.timeFridays = fridays; + exports.timeSaturday = saturday; + exports.timeSaturdays = saturdays; + exports.timeMonth = month; + exports.timeMonths = months; + exports.timeYear = year; + exports.timeYears = years; + exports.utcMinute = utcMinute; + exports.utcMinutes = utcMinutes; + exports.utcHour = utcHour; + exports.utcHours = utcHours; + exports.utcDay = utcDay; + exports.utcDays = utcDays; + exports.utcWeek = utcSunday; + exports.utcWeeks = utcSundays; + exports.utcSunday = utcSunday; + exports.utcSundays = utcSundays; + exports.utcMonday = utcMonday; + exports.utcMondays = utcMondays; + exports.utcTuesday = utcTuesday; + exports.utcTuesdays = utcTuesdays; + exports.utcWednesday = utcWednesday; + exports.utcWednesdays = utcWednesdays; + exports.utcThursday = utcThursday; + exports.utcThursdays = utcThursdays; + exports.utcFriday = utcFriday; + exports.utcFridays = utcFridays; + exports.utcSaturday = utcSaturday; + exports.utcSaturdays = utcSaturdays; + exports.utcMonth = utcMonth; + exports.utcMonths = utcMonths; + exports.utcYear = utcYear; + exports.utcYears = utcYears; + + Object.defineProperty(exports, '__esModule', { value: true }); + }); + }, {}], 10: [function (require, module, exports) { + /** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]'; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: + return func.call(thisArg); + case 1: + return func.call(thisArg, args[0]); + case 2: + return func.call(thisArg, args[0], args[1]); + case 3: + return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function (arg) { + return func(transform(arg)); + }; + } + + /** Used for built-in method references. */ + var objectProto = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** Built-in value references. */ + var propertyIsEnumerable = objectProto.propertyIsEnumerable; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeKeys = overArg(Object.keys, Object), + nativeMax = Math.max; + + /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */ + var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf'); + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + // Safari 9 makes `arguments.length` enumerable in strict mode. + var result = isArray(value) || isArguments(value) ? baseTimes(value.length, String) : []; + + var length = result.length, + skipIndexes = !!length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + result.push(key); + } + } + return result; + } + + /** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined && !(key in object)) { + object[key] = value; + } + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + start = nativeMax(start === undefined ? func.length - 1 : start, 0); + return function () { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = array; + return apply(func, this, otherArgs); + }; + } + + /** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ + function copyObject(source, props, object, customizer) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined; + + assignValue(object, key, newValue === undefined ? source[key] : newValue); + } + return object; + } + + /** + * Creates a function like `_.assign`. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ + function createAssigner(assigner) { + return baseRest(function (object, sources) { + var index = -1, + length = sources.length, + customizer = length > 1 ? sources[length - 1] : undefined, + guard = length > 2 ? sources[2] : undefined; + + customizer = assigner.length > 3 && typeof customizer == 'function' ? (length--, customizer) : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + object = Object(object); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, index, customizer); + } + } + return object; + }); + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length; + } + + /** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ + function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index === "undefined" ? "undefined" : _typeof(index); + if (type == 'number' ? isArrayLike(object) && isIndex(index, object.length) : type == 'string' && index in object) { + return eq(object[index], value); + } + return false; + } + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = typeof Ctor == 'function' && Ctor.prototype || objectProto; + + return value === proto; + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || value !== value && other !== other; + } + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return !!value && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && (typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object'; + } + + /** + * Assigns own enumerable string keyed properties of source objects to the + * destination object. Source objects are applied from left to right. + * Subsequent sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object` and is loosely based on + * [`Object.assign`](https://mdn.io/Object/assign). + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assignIn + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assign({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'c': 3 } + */ + var assign = createAssigner(function (object, source) { + if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) { + copyObject(source, keys(source), object); + return; + } + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + assignValue(object, key, source[key]); + } + } + }); + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + module.exports = assign; + }, {}], 11: [function (require, module, exports) { + (function (global) { + /** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + numberTag = '[object Number]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]', + weakMapTag = '[object WeakMap]'; + + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + + /** Used to match `RegExp` flags from their coerced string values. */ + var reFlags = /\w*$/; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** Used to identify `toStringTag` values supported by `_.clone`. */ + var cloneableTags = {}; + cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; + cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = (typeof global === "undefined" ? "undefined" : _typeof(global)) == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = (typeof exports === "undefined" ? "undefined" : _typeof(exports)) == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && (typeof module === "undefined" ? "undefined" : _typeof(module)) == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** + * Adds the key-value `pair` to `map`. + * + * @private + * @param {Object} map The map to modify. + * @param {Array} pair The key-value pair to add. + * @returns {Object} Returns `map`. + */ + function addMapEntry(map, pair) { + // Don't return `map.set` because it's not chainable in IE 11. + map.set(pair[0], pair[1]); + return map; + } + + /** + * Adds `value` to `set`. + * + * @private + * @param {Object} set The set to modify. + * @param {*} value The value to add. + * @returns {Object} Returns `set`. + */ + function addSetEntry(set, value) { + // Don't return `set.add` because it's not chainable in IE 11. + set.add(value); + return set; + } + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + /** + * A specialized version of `_.reduce` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the first element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduce(array, iteratee, accumulator, initAccum) { + var index = -1, + length = array ? array.length : 0; + + if (initAccum && length) { + accumulator = array[++index]; + } + while (++index < length) { + accumulator = iteratee(accumulator, array[index], index, array); + } + return accumulator; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Checks if `value` is a host object in IE < 9. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a host object, else `false`. + */ + function isHostObject(value) { + // Many host objects are `Object` objects that can coerce to strings + // despite having improperly defined `toString` methods. + var result = false; + if (value != null && typeof value.toString != 'function') { + try { + result = !!(value + ''); + } catch (e) {} + } + return result; + } + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function (value, key) { + result[++index] = [key, value]; + }); + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function (arg) { + return func(transform(arg)); + }; + } + + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function (value) { + result[++index] = value; + }); + return result; + } + + /** Used for built-in method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = root['__core-js_shared__']; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = function () { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? 'Symbol(src)_1.' + uid : ''; + }(); + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'); + + /** Built-in value references. */ + var Buffer = moduleExports ? root.Buffer : undefined, + _Symbol = root.Symbol, + Uint8Array = root.Uint8Array, + getPrototype = overArg(Object.getPrototypeOf, Object), + objectCreate = Object.create, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeGetSymbols = Object.getOwnPropertySymbols, + nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeKeys = overArg(Object.keys, Object); + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(root, 'DataView'), + Map = getNative(root, 'Map'), + Promise = getNative(root, 'Promise'), + Set = getNative(root, 'Set'), + WeakMap = getNative(root, 'WeakMap'), + nativeCreate = getNative(Object, 'create'); + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = _Symbol ? _Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = nativeCreate ? nativeCreate(null) : {}; + } + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + return this.has(key) && delete this.__data__[key]; + } + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED ? undefined : result; + } + return hasOwnProperty.call(data, key) ? data[key] : undefined; + } + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); + } + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value; + return this; + } + + // Add methods to `Hash`. + Hash.prototype.clear = hashClear; + Hash.prototype['delete'] = hashDelete; + Hash.prototype.get = hashGet; + Hash.prototype.has = hashHas; + Hash.prototype.set = hashSet; + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + } + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + return true; + } + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return assocIndexOf(this.__data__, key) > -1; + } + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = listCacheClear; + ListCache.prototype['delete'] = listCacheDelete; + ListCache.prototype.get = listCacheGet; + ListCache.prototype.has = listCacheHas; + ListCache.prototype.set = listCacheSet; + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.__data__ = { + 'hash': new Hash(), + 'map': new (Map || ListCache)(), + 'string': new Hash() + }; + } + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + return getMapData(this, key)['delete'](key); + } + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return getMapData(this, key).get(key); + } + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return getMapData(this, key).has(key); + } + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + getMapData(this, key).set(key, value); + return this; + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + this.__data__ = new ListCache(entries); + } + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new ListCache(); + } + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + return this.__data__['delete'](key); + } + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var cache = this.__data__; + if (cache instanceof ListCache) { + var pairs = cache.__data__; + if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) { + pairs.push([key, value]); + return this; + } + cache = this.__data__ = new MapCache(pairs); + } + cache.set(key, value); + return this; + } + + // Add methods to `Stack`. + Stack.prototype.clear = stackClear; + Stack.prototype['delete'] = stackDelete; + Stack.prototype.get = stackGet; + Stack.prototype.has = stackHas; + Stack.prototype.set = stackSet; + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + // Safari 9 makes `arguments.length` enumerable in strict mode. + var result = isArray(value) || isArguments(value) ? baseTimes(value.length, String) : []; + + var length = result.length, + skipIndexes = !!length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + result.push(key); + } + } + return result; + } + + /** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined && !(key in object)) { + object[key] = value; + } + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * The base implementation of `_.assign` without support for multiple sources + * or `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. + */ + function baseAssign(object, source) { + return object && copyObject(source, keys(source), object); + } + + /** + * The base implementation of `_.clone` and `_.cloneDeep` which tracks + * traversed objects. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @param {boolean} [isFull] Specify a clone including symbols. + * @param {Function} [customizer] The function to customize cloning. + * @param {string} [key] The key of `value`. + * @param {Object} [object] The parent object of `value`. + * @param {Object} [stack] Tracks traversed objects and their clone counterparts. + * @returns {*} Returns the cloned value. + */ + function baseClone(value, isDeep, isFull, customizer, key, object, stack) { + var result; + if (customizer) { + result = object ? customizer(value, key, object, stack) : customizer(value); + } + if (result !== undefined) { + return result; + } + if (!isObject(value)) { + return value; + } + var isArr = isArray(value); + if (isArr) { + result = initCloneArray(value); + if (!isDeep) { + return copyArray(value, result); + } + } else { + var tag = getTag(value), + isFunc = tag == funcTag || tag == genTag; + + if (isBuffer(value)) { + return cloneBuffer(value, isDeep); + } + if (tag == objectTag || tag == argsTag || isFunc && !object) { + if (isHostObject(value)) { + return object ? value : {}; + } + result = initCloneObject(isFunc ? {} : value); + if (!isDeep) { + return copySymbols(value, baseAssign(result, value)); + } + } else { + if (!cloneableTags[tag]) { + return object ? value : {}; + } + result = initCloneByTag(value, tag, baseClone, isDeep); + } + } + // Check for circular references and return its corresponding clone. + stack || (stack = new Stack()); + var stacked = stack.get(value); + if (stacked) { + return stacked; + } + stack.set(value, result); + + if (!isArr) { + var props = isFull ? getAllKeys(value) : keys(value); + } + arrayEach(props || value, function (subValue, key) { + if (props) { + key = subValue; + subValue = value[key]; + } + // Recursively populate clone (susceptible to call stack limits). + assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack)); + }); + return result; + } + + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. + */ + function baseCreate(proto) { + return isObject(proto) ? objectCreate(proto) : {}; + } + + /** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); + } + + /** + * The base implementation of `getTag`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + return objectToString.call(value); + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * Creates a clone of `buffer`. + * + * @private + * @param {Buffer} buffer The buffer to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Buffer} Returns the cloned buffer. + */ + function cloneBuffer(buffer, isDeep) { + if (isDeep) { + return buffer.slice(); + } + var result = new buffer.constructor(buffer.length); + buffer.copy(result); + return result; + } + + /** + * Creates a clone of `arrayBuffer`. + * + * @private + * @param {ArrayBuffer} arrayBuffer The array buffer to clone. + * @returns {ArrayBuffer} Returns the cloned array buffer. + */ + function cloneArrayBuffer(arrayBuffer) { + var result = new arrayBuffer.constructor(arrayBuffer.byteLength); + new Uint8Array(result).set(new Uint8Array(arrayBuffer)); + return result; + } + + /** + * Creates a clone of `dataView`. + * + * @private + * @param {Object} dataView The data view to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned data view. + */ + function cloneDataView(dataView, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; + return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); + } + + /** + * Creates a clone of `map`. + * + * @private + * @param {Object} map The map to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned map. + */ + function cloneMap(map, isDeep, cloneFunc) { + var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map); + return arrayReduce(array, addMapEntry, new map.constructor()); + } + + /** + * Creates a clone of `regexp`. + * + * @private + * @param {Object} regexp The regexp to clone. + * @returns {Object} Returns the cloned regexp. + */ + function cloneRegExp(regexp) { + var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); + result.lastIndex = regexp.lastIndex; + return result; + } + + /** + * Creates a clone of `set`. + * + * @private + * @param {Object} set The set to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned set. + */ + function cloneSet(set, isDeep, cloneFunc) { + var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set); + return arrayReduce(array, addSetEntry, new set.constructor()); + } + + /** + * Creates a clone of the `symbol` object. + * + * @private + * @param {Object} symbol The symbol object to clone. + * @returns {Object} Returns the cloned symbol object. + */ + function cloneSymbol(symbol) { + return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; + } + + /** + * Creates a clone of `typedArray`. + * + * @private + * @param {Object} typedArray The typed array to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned typed array. + */ + function cloneTypedArray(typedArray, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; + return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); + } + + /** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ + function copyArray(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; + } + + /** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ + function copyObject(source, props, object, customizer) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined; + + assignValue(object, key, newValue === undefined ? source[key] : newValue); + } + return object; + } + + /** + * Copies own symbol properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbols(source, object) { + return copyObject(source, getSymbols(source), object); + } + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return baseGetAllKeys(object, keys, getSymbols); + } + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map; + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * Creates an array of the own enumerable symbol properties of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray; + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11, + // for data views in Edge < 14, and promises in Node.js. + if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) { + getTag = function getTag(value) { + var result = objectToString.call(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : undefined; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: + return dataViewTag; + case mapCtorString: + return mapTag; + case promiseCtorString: + return promiseTag; + case setCtorString: + return setTag; + case weakMapCtorString: + return weakMapTag; + } + } + return result; + }; + } + + /** + * Initializes an array clone. + * + * @private + * @param {Array} array The array to clone. + * @returns {Array} Returns the initialized clone. + */ + function initCloneArray(array) { + var length = array.length, + result = array.constructor(length); + + // Add properties assigned by `RegExp#exec`. + if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { + result.index = array.index; + result.input = array.input; + } + return result; + } + + /** + * Initializes an object clone. + * + * @private + * @param {Object} object The object to clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneObject(object) { + return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {}; + } + + /** + * Initializes an object clone based on its `toStringTag`. + * + * **Note:** This function only supports cloning values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to clone. + * @param {string} tag The `toStringTag` of the object to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneByTag(object, tag, cloneFunc, isDeep) { + var Ctor = object.constructor; + switch (tag) { + case arrayBufferTag: + return cloneArrayBuffer(object); + + case boolTag: + case dateTag: + return new Ctor(+object); + + case dataViewTag: + return cloneDataView(object, isDeep); + + case float32Tag:case float64Tag: + case int8Tag:case int16Tag:case int32Tag: + case uint8Tag:case uint8ClampedTag:case uint16Tag:case uint32Tag: + return cloneTypedArray(object, isDeep); + + case mapTag: + return cloneMap(object, isDeep, cloneFunc); + + case numberTag: + case stringTag: + return new Ctor(object); + + case regexpTag: + return cloneRegExp(object); + + case setTag: + return cloneSet(object, isDeep, cloneFunc); + + case symbolTag: + return cloneSymbol(object); + } + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length; + } + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && maskSrcKey in func; + } + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = typeof Ctor == 'function' && Ctor.prototype || objectProto; + + return value === proto; + } + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to process. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return func + ''; + } catch (e) {} + } + return ''; + } + + /** + * Creates a shallow clone of `value`. + * + * **Note:** This method is loosely based on the + * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) + * and supports cloning arrays, array buffers, booleans, date objects, maps, + * numbers, `Object` objects, regexes, sets, strings, symbols, and typed + * arrays. The own enumerable properties of `arguments` objects are cloned + * as plain objects. An empty object is returned for uncloneable values such + * as error objects, functions, DOM nodes, and WeakMaps. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to clone. + * @returns {*} Returns the cloned value. + * @see _.cloneDeep + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var shallow = _.clone(objects); + * console.log(shallow[0] === objects[0]); + * // => true + */ + function clone(value) { + return baseClone(value, false, true); + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || value !== value && other !== other; + } + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return !!value && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && (typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object'; + } + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * This method returns a new empty array. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {Array} Returns the new empty array. + * @example + * + * var arrays = _.times(2, _.stubArray); + * + * console.log(arrays); + * // => [[], []] + * + * console.log(arrays[0] === arrays[1]); + * // => false + */ + function stubArray() { + return []; + } + + /** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ + function stubFalse() { + return false; + } + + module.exports = clone; + }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); + }, {}], 12: [function (require, module, exports) { + /** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]'; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function (arg) { + return func(transform(arg)); + }; + } + + /** Used for built-in method references. */ + var objectProto = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** Built-in value references. */ + var propertyIsEnumerable = objectProto.propertyIsEnumerable; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeKeys = overArg(Object.keys, Object); + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + // Safari 9 makes `arguments.length` enumerable in strict mode. + var result = isArray(value) || isArguments(value) ? baseTimes(value.length, String) : []; + + var length = result.length, + skipIndexes = !!length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEach = createBaseEach(baseForOwn); + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = createBaseFor(); + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && baseFor(object, iteratee, keys); + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseEach(eachFunc, fromRight) { + return function (collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while (fromRight ? index-- : ++index < length) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; + } + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function (object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length; + } + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = typeof Ctor == 'function' && Ctor.prototype || objectProto; + + return value === proto; + } + + /** + * Iterates over elements of `collection` and invokes `iteratee` for each element. + * The iteratee is invoked with three arguments: (value, index|key, collection). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * **Note:** As with other "Collections" methods, objects with a "length" + * property are iterated like arrays. To avoid this behavior use `_.forIn` + * or `_.forOwn` for object iteration. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @alias each + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @see _.forEachRight + * @example + * + * _([1, 2]).forEach(function(value) { + * console.log(value); + * }); + * // => Logs `1` then `2`. + * + * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forEach(collection, iteratee) { + var func = isArray(collection) ? arrayEach : baseEach; + return func(collection, typeof iteratee == 'function' ? iteratee : identity); + } + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return !!value && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && (typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object'; + } + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * This method returns the first argument it receives. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Util + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'a': 1 }; + * + * console.log(_.identity(object) === object); + * // => true + */ + function identity(value) { + return value; + } + + module.exports = forEach; + }, {}], 13: [function (require, module, exports) { + (function (global) { + /** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + setTag = '[object Set]', + weakMapTag = '[object WeakMap]'; + + var dataViewTag = '[object DataView]'; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = (typeof global === "undefined" ? "undefined" : _typeof(global)) == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = (typeof exports === "undefined" ? "undefined" : _typeof(exports)) == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && (typeof module === "undefined" ? "undefined" : _typeof(module)) == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Checks if `value` is a host object in IE < 9. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a host object, else `false`. + */ + function isHostObject(value) { + // Many host objects are `Object` objects that can coerce to strings + // despite having improperly defined `toString` methods. + var result = false; + if (value != null && typeof value.toString != 'function') { + try { + result = !!(value + ''); + } catch (e) {} + } + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function (arg) { + return func(transform(arg)); + }; + } + + /** Used for built-in method references. */ + var funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = root['__core-js_shared__']; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = function () { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? 'Symbol(src)_1.' + uid : ''; + }(); + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'); + + /** Built-in value references. */ + var Buffer = moduleExports ? root.Buffer : undefined, + propertyIsEnumerable = objectProto.propertyIsEnumerable; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeKeys = overArg(Object.keys, Object); + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(root, 'DataView'), + Map = getNative(root, 'Map'), + Promise = getNative(root, 'Promise'), + Set = getNative(root, 'Set'), + WeakMap = getNative(root, 'WeakMap'); + + /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */ + var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf'); + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** + * The base implementation of `getTag`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + return objectToString.call(value); + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11, + // for data views in Edge < 14, and promises in Node.js. + if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) { + getTag = function getTag(value) { + var result = objectToString.call(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : undefined; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: + return dataViewTag; + case mapCtorString: + return mapTag; + case promiseCtorString: + return promiseTag; + case setCtorString: + return setTag; + case weakMapCtorString: + return weakMapTag; + } + } + return result; + }; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && maskSrcKey in func; + } + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = typeof Ctor == 'function' && Ctor.prototype || objectProto; + + return value === proto; + } + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to process. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return func + ''; + } catch (e) {} + } + return ''; + } + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (isArrayLike(value) && (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || isBuffer(value) || isArguments(value))) { + return !value.length; + } + var tag = getTag(value); + if (tag == mapTag || tag == setTag) { + return !value.size; + } + if (nonEnumShadows || isPrototype(value)) { + return !nativeKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty.call(value, key)) { + return false; + } + } + return true; + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return !!value && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && (typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object'; + } + + /** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ + function stubFalse() { + return false; + } + + module.exports = isEmpty; + }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); + }, {}], 14: [function (require, module, exports) { + (function (global) { + /** + * Lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright JS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + asyncTag = '[object AsyncFunction]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + numberTag = '[object Number]', + nullTag = '[object Null]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + proxyTag = '[object Proxy]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]', + undefinedTag = '[object Undefined]', + weakMapTag = '[object WeakMap]'; + + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** Used to identify `toStringTag` values of typed arrays. */ + var typedArrayTags = {}; + typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true; + typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = (typeof global === "undefined" ? "undefined" : _typeof(global)) == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = (typeof exports === "undefined" ? "undefined" : _typeof(exports)) == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && (typeof module === "undefined" ? "undefined" : _typeof(module)) == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Detect free variable `process` from Node.js. */ + var freeProcess = moduleExports && freeGlobal.process; + + /** Used to access faster Node.js helpers. */ + var nodeUtil = function () { + try { + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} + }(); + + /* Node.js helper references. */ + var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; + + /** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + /** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ + function baseUnary(func) { + return function (value) { + return func(value); + }; + } + + /** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function cacheHas(cache, key) { + return cache.has(key); + } + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function (value, key) { + result[++index] = [key, value]; + }); + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function (arg) { + return func(transform(arg)); + }; + } + + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function (value) { + result[++index] = value; + }); + return result; + } + + /** Used for built-in method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = root['__core-js_shared__']; + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = function () { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? 'Symbol(src)_1.' + uid : ''; + }(); + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var nativeObjectToString = objectProto.toString; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'); + + /** Built-in value references. */ + var Buffer = moduleExports ? root.Buffer : undefined, + _Symbol2 = root.Symbol, + Uint8Array = root.Uint8Array, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice, + symToStringTag = _Symbol2 ? _Symbol2.toStringTag : undefined; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeGetSymbols = Object.getOwnPropertySymbols, + nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeKeys = overArg(Object.keys, Object); + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(root, 'DataView'), + Map = getNative(root, 'Map'), + Promise = getNative(root, 'Promise'), + Set = getNative(root, 'Set'), + WeakMap = getNative(root, 'WeakMap'), + nativeCreate = getNative(Object, 'create'); + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = _Symbol2 ? _Symbol2.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = nativeCreate ? nativeCreate(null) : {}; + this.size = 0; + } + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED ? undefined : result; + } + return hasOwnProperty.call(data, key) ? data[key] : undefined; + } + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); + } + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value; + return this; + } + + // Add methods to `Hash`. + Hash.prototype.clear = hashClear; + Hash.prototype['delete'] = hashDelete; + Hash.prototype.get = hashGet; + Hash.prototype.has = hashHas; + Hash.prototype.set = hashSet; + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + this.size = 0; + } + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; + } + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return assocIndexOf(this.__data__, key) > -1; + } + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = listCacheClear; + ListCache.prototype['delete'] = listCacheDelete; + ListCache.prototype.get = listCacheGet; + ListCache.prototype.has = listCacheHas; + ListCache.prototype.set = listCacheSet; + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new Hash(), + 'map': new (Map || ListCache)(), + 'string': new Hash() + }; + } + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + var result = getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return getMapData(this, key).get(key); + } + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return getMapData(this, key).has(key); + } + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + var data = getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; + + /** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ + function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new MapCache(); + while (++index < length) { + this.add(values[index]); + } + } + + /** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ + function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; + } + + /** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {number} Returns `true` if `value` is found, else `false`. + */ + function setCacheHas(value) { + return this.__data__.has(value); + } + + // Add methods to `SetCache`. + SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; + SetCache.prototype.has = setCacheHas; + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + var data = this.__data__ = new ListCache(entries); + this.size = data.size; + } + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new ListCache(); + this.size = 0; + } + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; + } + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var data = this.__data__; + if (data instanceof ListCache) { + var pairs = data.__data__; + if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; + } + + // Add methods to `Stack`. + Stack.prototype.clear = stackClear; + Stack.prototype['delete'] = stackDelete; + Stack.prototype.get = stackGet; + Stack.prototype.has = stackHas; + Stack.prototype.set = stackSet; + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + var isArr = isArray(value), + isArg = !isArr && isArguments(value), + isBuff = !isArr && !isArg && isBuffer(value), + isType = !isArr && !isArg && !isBuff && isTypedArray(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + isBuff && (key == 'offset' || key == 'parent') || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset') || + // Skip index properties. + isIndex(key, length)))) { + result.push(key); + } + } + return result; + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); + } + + /** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag; + } + return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value); + } + + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike(value) && baseGetTag(value) == argsTag; + } + + /** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ + function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) { + return value !== value && other !== other; + } + return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); + } + + /** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray(object), + othIsArr = isArray(other), + objTag = objIsArr ? arrayTag : getTag(object), + othTag = othIsArr ? arrayTag : getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer(object)) { + if (!isBuffer(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new Stack()); + return objIsArr || isTypedArray(object) ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG)) { + var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new Stack()); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new Stack()); + return equalObjects(object, other, bitmask, customizer, equalFunc, stack); + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = isFunction(value) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ + function baseIsTypedArray(value) { + return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ + function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(array); + if (stacked && stack.get(other)) { + return stacked == other; + } + var index = -1, + result = true, + seen = bitmask & COMPARE_UNORDERED_FLAG ? new SetCache() : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!arraySome(other, function (othValue, othIndex) { + if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) { + return false; + } + return true; + + case boolTag: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == other + ''; + + case mapTag: + var convert = mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG; + convert || (convert = setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; + } + + /** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + objProps = getAllKeys(object), + objLength = objProps.length, + othProps = getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { + return false; + } + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked && stack.get(other)) { + return stacked == other; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; + } + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return baseGetAllKeys(object, keys, getSymbols); + } + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map; + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ + function getRawTag(value) { + var isOwn = hasOwnProperty.call(value, symToStringTag), + tag = value[symToStringTag]; + + try { + value[symToStringTag] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag] = tag; + } else { + delete value[symToStringTag]; + } + } + return result; + } + + /** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = !nativeGetSymbols ? stubArray : function (object) { + if (object == null) { + return []; + } + object = Object(object); + return arrayFilter(nativeGetSymbols(object), function (symbol) { + return propertyIsEnumerable.call(object, symbol); + }); + }; + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. + if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) { + getTag = function getTag(value) { + var result = baseGetTag(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: + return dataViewTag; + case mapCtorString: + return mapTag; + case promiseCtorString: + return promiseTag; + case setCtorString: + return setTag; + case weakMapCtorString: + return weakMapTag; + } + } + return result; + }; + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length; + } + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && maskSrcKey in func; + } + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = typeof Ctor == 'function' && Ctor.prototype || objectProto; + + return value === proto; + } + + /** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ + function objectToString(value) { + return nativeObjectToString.call(value); + } + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return func + ''; + } catch (e) {} + } + return ''; + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || value !== value && other !== other; + } + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + var isArguments = baseIsArguments(function () { + return arguments; + }()) ? baseIsArguments : function (value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); + }; + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. + * + * **Note:** This method supports comparing arrays, array buffers, booleans, + * date objects, error objects, maps, numbers, `Object` objects, regexes, + * sets, strings, symbols, and typed arrays. `Object` objects are compared + * by their own, not inherited, enumerable properties. Functions and DOM + * nodes are compared by strict equality, i.e. `===`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.isEqual(object, other); + * // => true + * + * object === other; + * // => false + */ + function isEqual(value, other) { + return baseIsEqual(value, other); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + if (!isObject(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = baseGetTag(value); + return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value === "undefined" ? "undefined" : _typeof(value); + return value != null && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return value != null && (typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object'; + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * This method returns a new empty array. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {Array} Returns the new empty array. + * @example + * + * var arrays = _.times(2, _.stubArray); + * + * console.log(arrays); + * // => [[], []] + * + * console.log(arrays[0] === arrays[1]); + * // => false + */ + function stubArray() { + return []; + } + + /** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ + function stubFalse() { + return false; + } + + module.exports = isEqual; + }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); + }, {}], 15: [function (require, module, exports) { + window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; + + /** + * Handles debouncing of events via requestAnimationFrame + * @see http://www.html5rocks.com/en/tutorials/speed/animations/ + * @param {Function} callback The callback to handle whichever event + */ + function Debouncer(callback) { + this.callback = callback; + this.ticking = false; + } + Debouncer.prototype = { + constructor: Debouncer, + + /** + * dispatches the event to the supplied callback + * @private + */ + update: function update() { + this.callback && this.callback(); + this.ticking = false; + }, + + /** + * ensures events don't get stacked + * @private + */ + requestTick: function requestTick() { + if (!this.ticking) { + requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); + this.ticking = true; + } + }, + + /** + * Attach this as the event listeners + */ + handleEvent: function handleEvent() { + this.requestTick(); + } + }; + + module.exports = Debouncer; + }, {}], 16: [function (require, module, exports) { + var isEqual = require('lodash.isequal'), + forEach = require('lodash.foreach'), + isEmpty = require('lodash.isempty'), + cloneObject = require('lodash.clone'), + extendObject = require('lodash.assign'), + debouncer = require("./Debouncer"); + + function Scrllr(options) { + options = extendObject(Scrllr.options, options); + + this.lastKnownScrollY = 0; + this.initialised = false; + this.onScrollCallback = options.onScrollCallback; + } + + Scrllr.prototype = { + constructor: Scrllr, + + init: function init() { + this.debouncer = new debouncer(this.update.bind(this)); + + // defer event registration to handle browser + // potentially restoring previous scroll position + setTimeout(this.attachEvent.bind(this), 100); + + return this; + }, + + attachEvent: function attachEvent() { + if (!this.initialised) { + this.lastKnownScrollY = this.getScrollY(); + this.initialised = true; + + window.addEventListener('scroll', this.debouncer, false); + this.debouncer.handleEvent(); + } + }, + + getScrollY: function getScrollY() { + return window.pageYOffset !== undefined ? window.pageYOffset : window.scrollTop !== undefined ? window.scrollTop : (document.documentElement || document.body.parentNode || document.body).scrollTop; + }, + + update: function update() { + var currentScrollY = this.getScrollY(), + scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up'; + + this.onScrollCallback(currentScrollY); + this.lastKnownScrollY = currentScrollY; + }, + + destroy: function destroy() { + this.initialised = false; + window.removeEventListener('scroll', this.debouncer, false); + } + + }; + + Scrllr.options = { + onScrollCallback: function onScrollCallback() {} + }; + + module.exports = Scrllr; + }, { "./Debouncer": 15, "lodash.assign": 10, "lodash.clone": 11, "lodash.foreach": 12, "lodash.isempty": 13, "lodash.isequal": 14 }], 17: [function (require, module, exports) { + var Scrllr = require("./Scrllr.js"), + Scale = require("d3-scale"), + Interpolator = require("d3-interpolate"), + Ease = require("d3-ease"); + + function ScrollOver(options) { + options = extend(options, ScrollOver.options); + this.PROPERTIES = ['translateX', 'translateY', 'opacity', 'scale']; + this.keyframes = options.keyframes; + } + + ScrollOver.prototype = { + constructor: ScrollOver, + + init: function init() { + var _this = this; + + new Scrllr({ onScrollCallback: update.bind(this) }).init(); + + this.toAnimate = this.keyframes.filter(function (item) { + return item.animate; + }); + this.toReveal = this.keyframes.filter(function (item) { + return item.reveal; + }); + this.toShow = this.keyframes.filter(function (item) { + return item.show; + }); + + this.toAnimate.forEach(function (keyframe) { + if (keyframe) keyframe.animate.forEach(function (property) { + property.scale = _this.createScale(property.property, keyframe.domain, property.range); + }); + }); + + function update(scrollY) { + var _this2 = this; + + this.toAnimate.forEach(function (keyframe) { + if (keyframe) _this2.updateCSSValues(keyframe.element, _this2.calculatePropertyValues(keyframe.animate, scrollY)); + }); + + this.toReveal.forEach(function (keyframe) { + if (keyframe) { + if (scrollY >= keyframe.reveal.when) _this2.updateCSSClass(keyframe.element, keyframe.reveal.className); + } + }); + + this.toShow.forEach(function (keyframe) { + if (keyframe) { + if (scrollY >= keyframe.show.when[0]) _this2.updateCSSClass(keyframe.element, keyframe.show.className); + if (scrollY <= keyframe.show.when[0] || scrollY >= keyframe.show.when[1]) _this2.removeCSSClass(keyframe.element, keyframe.show.className); + } + }); + } + + return this; + }, + + calculatePropertyValues: function calculatePropertyValues(animations, scrollY) { + var _this3 = this; + + var CSSValues = new Object(); + + this.PROPERTIES.forEach(function (propertyName) { + CSSValues[propertyName] = _this3.getDefaultPropertyValue(propertyName); + animations.forEach(function (animation) { + if (animation.property == propertyName) CSSValues[propertyName] = _this3.scaleValue(animation.scale, scrollY); + }); + }); + + return CSSValues; + }, + + scaleValue: function scaleValue(scale, scrollY) { + return scale(scrollY); + }, + + updateCSSValues: function updateCSSValues(element, CSS) { + element.style.transform = 'translate3d(' + CSS.translateX + 'px, ' + CSS.translateY + 'px, 0) scale(' + CSS.scale + ')'; + element.style.opacity = CSS.opacity; + + return element; + }, + + updateCSSClass: function updateCSSClass(element, className) { + element.classList ? element.classList.add(className) : element.className += ' ' + className; + + return element; + }, + + removeCSSClass: function removeCSSClass(element, className) { + if (element.classList) { + element.classList.remove(className); + } else { + element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); + } + + return element; + }, + + getDefaultPropertyValue: function getDefaultPropertyValue(propertyName) { + switch (propertyName) { + case 'translateX': + return 0; + case 'translateY': + return 0; + case 'scale': + return 1; + case 'rotate': + return 0; + case 'opacity': + return 1; + default: + return null; + } + }, + + createScale: function createScale(propertyName, domain, range) { + switch (propertyName) { + case 'translateX': + case 'translateY': + case 'scale': + case 'opacity': + return Scale.scaleLinear().domain(domain).range(range).interpolate(this.easeInterpolate(Ease.easeCubicOut)).clamp(true); + default: + return null; + } + }, + + easeInterpolate: function easeInterpolate(ease) { + return function (a, b) { + var i = Interpolator.interpolate(a, b); + return function (t) { + return Math.round(i(ease(t)) * 100) / 100; + }; + }; + } + + }; + + ScrollOver.options = { + keyframes: {} + + /** + * Helper function for extending objects + */ + };function extend(object /*, objectN ... */) { + if (arguments.length <= 0) { + throw new Error('Missing arguments in extend function'); + } + + var result = object || {}, + key, + i; + + for (i = 1; i < arguments.length; i++) { + var replacement = arguments[i] || {}; + + for (key in replacement) { + // Recurse into object except if the object is a DOM element + if (_typeof(result[key]) === 'object' && !isDOMElement(result[key])) { + result[key] = extend(result[key], replacement[key]); + } else { + result[key] = result[key] || replacement[key]; + } + } + } + + return result; + } + + /** + * Check if object is part of the DOM + * @constructor + * @param {Object} obj element to check + */ + function isDOMElement(obj) { + return obj && typeof window !== 'undefined' && (obj === window || obj.nodeType); + } + + module.exports = ScrollOver; + }, { "./Scrllr.js": 16, "d3-ease": 4, "d3-interpolate": 6, "d3-scale": 7 }], 18: [function (require, module, exports) { + // AnimateScroll.js + // Sunmock Yang Nov. 2015 + + function animateScroll(target, duration, easing, padding, align, onFinish) { + padding = padding ? padding : 0; + var docElem = document.documentElement; // to facilitate minification better + var windowHeight = docElem.clientHeight; + var maxScroll = 'scrollMaxY' in window ? window.scrollMaxY : docElem.scrollHeight - windowHeight; + var currentY = window.pageYOffset; + + var targetY = currentY; + var elementBounds = isNaN(target) ? target.getBoundingClientRect() : 0; + + if (align === "center") { + targetY += isNaN(target) ? elementBounds.top + elementBounds.height / 2 : target; + targetY -= windowHeight / 2; + targetY -= padding; + } else if (align === "bottom") { + targetY += elementBounds.bottom || target; + targetY -= windowHeight; + targetY += padding; + } else { + // top, undefined + targetY += elementBounds.top || target; + targetY -= padding; + } + targetY = Math.max(Math.min(maxScroll, targetY), 0); + + var deltaY = targetY - currentY; + + var obj = { + targetY: targetY, + deltaY: deltaY, + duration: duration ? duration : 0, + easing: easing in animateScroll.Easing ? animateScroll.Easing[easing] : animateScroll.Easing.linear, + onFinish: onFinish, + startTime: Date.now(), + lastY: currentY, + step: animateScroll.step + }; + + window.requestAnimationFrame(obj.step.bind(obj)); + } + + // Taken from gre/easing.js + // https://gist.github.com/gre/1650294 + animateScroll.Easing = { + linear: function linear(t) { + return t; + }, + easeInQuad: function easeInQuad(t) { + return t * t; + }, + easeOutQuad: function easeOutQuad(t) { + return t * (2 - t); + }, + easeInOutQuad: function easeInOutQuad(t) { + return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + }, + easeInCubic: function easeInCubic(t) { + return t * t * t; + }, + easeOutCubic: function easeOutCubic(t) { + return --t * t * t + 1; + }, + easeInOutCubic: function easeInOutCubic(t) { + return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + }, + easeInQuart: function easeInQuart(t) { + return t * t * t * t; + }, + easeOutQuart: function easeOutQuart(t) { + return 1 - --t * t * t * t; + }, + easeInOutQuart: function easeInOutQuart(t) { + return t < .5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; + }, + easeInQuint: function easeInQuint(t) { + return t * t * t * t * t; + }, + easeOutQuint: function easeOutQuint(t) { + return 1 + --t * t * t * t * t; + }, + easeInOutQuint: function easeInOutQuint(t) { + return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; + } + }; + + animateScroll.step = function () { + if (this.lastY !== window.pageYOffset && this.onFinish) { + this.onFinish(); + return; + } + + // Calculate how much time has passed + var t = Math.min((Date.now() - this.startTime) / this.duration, 1); + + // Scroll window amount determined by easing + var y = this.targetY - (1 - this.easing(t)) * this.deltaY; + window.scrollTo(window.scrollX, y); + + // Continue animation as long as duration hasn't surpassed + if (t !== 1) { + this.lastY = window.pageYOffset; + window.requestAnimationFrame(this.step.bind(this)); + } else { + if (this.onFinish) this.onFinish(); + } + }; + + module.exports = animateScroll; + }, {}], 19: [function (require, module, exports) { + var ScrollOver = require("./lib/ScrollOver.js"); + var animateScroll = require("./lib/animatescroll.js"); + + var body = document.querySelectorAll("body")[0]; + // let widget = document.querySelectorAll(".widget-wrap")[0] + // let tagline = document.querySelectorAll(".tagline")[0] + var slideOne = document.querySelectorAll(".slide--one")[0]; + var slideTwo = document.querySelectorAll(".slide--two")[0]; + var sectionOne = document.querySelectorAll(".section--one")[0]; + var sectionTwo = document.querySelectorAll(".section--two")[0]; + var sectionThree = document.querySelectorAll(".section--three")[0]; + var sectionFour = document.querySelectorAll(".section--four")[0]; + // let slideTwo = document.querySelectorAll(".slide--two")[0] + // let slideFive = document.querySelectorAll(".slide--five")[0] + // let phone = document.querySelectorAll(".app-inner")[0] + var scrollTopButton = document.querySelectorAll(".button--scrolltop")[0]; + var scrollToFeatures = document.querySelectorAll(".nav__item-features")[0]; + // + setTimeout(function () { + return body.classList.add("shown"); + }, 400); + // + + scrollTopButton.addEventListener('click', function (event) { + animateScroll(slideOne, 600, "easeInOutCubic", 0); + event.preventDefault(); + }); + + scrollToFeatures.addEventListener('click', function (event) { + animateScroll(slideTwo, 600, "easeInOutCubic", 0); + event.preventDefault(); + }); + + // + // if(document.querySelectorAll(".button--unavailable")[0]) { + // document.querySelectorAll(".button--unavailable")[0].addEventListener('click', function(event){ + // event.preventDefault() + // }) + // } + // + // if(document.querySelectorAll(".button--prtcpt")[0]) { + // document.querySelectorAll(".button--prtcpt")[0].addEventListener('click', function(event){ + // animateScroll(slideTwo, 600, "easeInOutCubic", 0) + // event.preventDefault() + // }) + // } + // + // if(document.querySelectorAll(".nav__item--participate")[0]) { + // document.querySelectorAll(".nav__item--participate")[0].addEventListener('click', function(event){ + // animateScroll(slideTwo, 600, "easeInOutCubic", 0) + // event.preventDefault() + // }) + // } + // + // + new ScrollOver({ + keyframes: [{ + element: sectionOne, + reveal: { + when: 120, + className: "section--shown" + } + }, { + element: sectionTwo, + reveal: { + when: 500, + className: "section--shown" + } + }, { + element: sectionThree, + reveal: { + when: 900, + className: "section--shown" + } + }, { + element: sectionFour, + reveal: { + when: 1280, + className: "section--shown" + } + }] + }).init(); + + /*---Utils---*/ + function addClassToElement(element, className) { + element.classList ? element.classList.add(className) : element.className += ' ' + className; + return element; + } + + function removeClassFromElement(element, className) { + if (element.classList) { + element.classList.remove(className); + } else { + element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); + } + return element; + } + }, { "./lib/ScrollOver.js": 17, "./lib/animatescroll.js": 18 }] }, {}, [19]); \ No newline at end of file diff --git a/static_langing_page/dest/js/mc-validate.js b/static_langing_page/dest/js/mc-validate.js new file mode 100644 index 0000000..6db7958 --- /dev/null +++ b/static_langing_page/dest/js/mc-validate.js @@ -0,0 +1,5 @@ +!function(e,t){"use strict";function n(e){var t=e.length,n=se.type(e);return!se.isWindow(e)&&(!(1!==e.nodeType||!t)||("array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)))}function r(e){var t=we[e]={};return se.each(e.match(le)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(se.acceptData(e)){var a,o,s=se.expando,u="string"==typeof n,l=e.nodeType,c=l?se.cache:e,d=l?e[s]:e[s]&&s;if(d&&c[d]&&(i||c[d].data)||!u||r!==t)return d||(l?e[s]=d=Z.pop()||se.guid++:d=s),c[d]||(c[d]={},l||(c[d].toJSON=se.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[d]=se.extend(c[d],n):c[d].data=se.extend(c[d].data,n)),a=c[d],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[se.camelCase(n)]=r),u?(o=a[n],null==o&&(o=a[se.camelCase(n)])):o=a,o}}function a(e,t,n){if(se.acceptData(e)){var r,i,a,o=e.nodeType,u=o?se.cache:e,l=o?e[se.expando]:se.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){se.isArray(t)?t=t.concat(se.map(t,se.camelCase)):t in r?t=[t]:(t=se.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,a=t.length;a>i;i++)delete r[t[i]];if(!(n?s:se.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(o?se.cleanData([e],!0):se.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function o(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Ce,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r||"false"!==r&&("null"===r?null:+r+""===r?+r:Te.test(r)?se.parseJSON(r):r)}catch(a){}se.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!se.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function d(e,t,n){if(t=t||0,se.isFunction(t))return se.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return se.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=se.grep(e,function(e){return 1===e.nodeType});if(Ie.test(t))return se.filter(t,r,!n);t=se.filter(t,r)}return se.grep(e,function(e){return se.inArray(e,t)>=0===n})}function f(e){var t=$e.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function p(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function m(e){var t=nt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)se._data(n,"globalEval",!t||se._data(t[r],"globalEval"))}function v(e,t){if(1===t.nodeType&&se.hasData(e)){var n,r,i,a=se._data(e),o=se._data(t,a),s=a.events;if(s){delete o.handle,o.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)se.event.add(t,n,s[n][r])}o.data&&(o.data=se.extend({},o.data))}}function y(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!se.support.noCloneEvent&&t[se.expando]){r=se._data(t);for(i in r.events)se.removeEvent(t,i,r.handle);t.removeAttribute(se.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,m(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),se.support.html5Clone&&e.innerHTML&&!se.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ke.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,a=0,o=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!o)for(o=[],r=e.childNodes||e;null!=(i=r[a]);a++)!n||se.nodeName(i,n)?o.push(i):se.merge(o,b(i,n));return n===t||n&&se.nodeName(e,n)?se.merge([e],o):o}function x(e){Ke.test(e.type)&&(e.defaultChecked=e.checked)}function w(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Tt.length;i--;)if(t=Tt[i]+n,t in e)return t;return r}function T(e,t){return e=t||e,"none"===se.css(e,"display")||!se.contains(e.ownerDocument,e)}function C(e,t){for(var n,r=[],i=0,a=e.length;a>i;i++)n=e[i],n.style&&(r[i]=se._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&T(n)&&(r[i]=se._data(n,"olddisplay",N(n.nodeName)))):r[i]||T(n)||se._data(n,"olddisplay",se.css(n,"display")));for(i=0;a>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function k(e,t,n){var r=mt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function F(e,t,n,r,i){for(var a=n===(r?"border":"content")?4:"width"===t?1:0,o=0;4>a;a+=2)"margin"===n&&(o+=se.css(e,n+wt[a],!0,i)),r?("content"===n&&(o-=se.css(e,"padding"+wt[a],!0,i)),"margin"!==n&&(o-=se.css(e,"border"+wt[a]+"Width",!0,i))):(o+=se.css(e,"padding"+wt[a],!0,i),"padding"!==n&&(o+=se.css(e,"border"+wt[a]+"Width",!0,i)));return o}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,a=ut(e),o=se.support.boxSizing&&"border-box"===se.css(e,"boxSizing",!1,a);if(0>=i||null==i){if(i=st(e,t,a),(0>i||null==i)&&(i=e.style[t]),gt.test(i))return i;r=o&&(se.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+F(e,t,n||(o?"border":"content"),r,a)+"px"}function N(e){var t=U,n=yt[e];return n||(n=S(e,t),"none"!==n&&n||(lt=(lt||se(" + +
diff --git a/resources/templates/index.html b/resources/templates/index.html index 6dfa10c..8e9e660 100644 --- a/resources/templates/index.html +++ b/resources/templates/index.html @@ -34,6 +34,16 @@ + + + + + + + + + + +
diff --git a/static_langing_page/index.html b/static_langing_page/index.html index 6dfa10c..9310dac 100644 --- a/static_langing_page/index.html +++ b/static_langing_page/index.html @@ -34,6 +34,16 @@ + + + + + + + + + +
From bb200e8399f4eb14e452e2db13e02c87e7ea7f0e Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Mon, 18 Dec 2017 12:53:50 +0200 Subject: [PATCH 022/100] Automatic gas price Use ethgasstation API for gas price if :auto-gas-price? is true in config. Fixes: #189 --- src/clj/commiteth/eth/core.clj | 25 ++++++++++++++++++-- src/clj/commiteth/util/crypto_fiat_value.clj | 10 ++------ src/clj/commiteth/util/util.clj | 10 +++++++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/clj/commiteth/eth/core.clj b/src/clj/commiteth/eth/core.clj index b04a0e3..c4c8fa5 100644 --- a/src/clj/commiteth/eth/core.clj +++ b/src/clj/commiteth/eth/core.clj @@ -6,19 +6,40 @@ [clojure.string :refer [join]] [clojure.tools.logging :as log] [clojure.string :as str] - [pandect.core :as pandect])) + [pandect.core :as pandect] + [commiteth.util.util :refer [json-api-request]])) (defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545")) (defn eth-account [] (:eth-account env)) (defn eth-password [] (:eth-password env)) (defn gas-estimate-factor [] (env :gas-estimate-factor 1.0)) +(defn auto-gas-price? [] (env :auto-gas-price? false)) -(defn gas-price +(defn eth-gasstation-gas-price [] + (let [data (json-api-request "https://ethgasstation.info/json/ethgasAPI.json") + avg-price (-> (get data "average") + bigint) + avg-price-gwei (/ avg-price (bigint 10))] + (->> (* (bigint (Math/pow 10 9)) avg-price-gwei) ;; for some reason the API returns 10x gwei price + .toBigInteger))) + + +(defn gas-price-from-config [] (-> (:gas-price env 20000000000) ;; 20 gwei default str BigInteger.)) +(defn gas-price + [] + (if (auto-gas-price?) + (try + (eth-gasstation-gas-price) + (catch Throwable t + (log/error "Failed to get gas price with ethgasstation API" t) + (gas-price-from-config))) + (gas-price-from-config))) + (defn eth-rpc [method params] (let [request-id (rand-int 4096) diff --git a/src/clj/commiteth/util/crypto_fiat_value.clj b/src/clj/commiteth/util/crypto_fiat_value.clj index de95b09..07bea89 100644 --- a/src/clj/commiteth/util/crypto_fiat_value.clj +++ b/src/clj/commiteth/util/crypto_fiat_value.clj @@ -1,19 +1,13 @@ (ns commiteth.util.crypto-fiat-value - (:require [clj-http.client :as http] - [mount.core :as mount] + (:require [mount.core :as mount] [clojure.tools.logging :as log] [commiteth.config :refer [env]] - [clojure.data.json :as json])) + [commiteth.util.util :refer [json-api-request]])) (defn fiat-api-provider [] (env :fiat-api-provider :coinmarketcap)) -(defn json-api-request [url] - (->> (http/get url) - (:body) - (json/read-str))) - (defn get-token-usd-price-cryptonator "Get current USD value for a token using cryptonator API" diff --git a/src/clj/commiteth/util/util.clj b/src/clj/commiteth/util/util.clj index 65634c3..f85211e 100644 --- a/src/clj/commiteth/util/util.clj +++ b/src/clj/commiteth/util/util.clj @@ -1,4 +1,7 @@ -(ns commiteth.util.util) +(ns commiteth.util.util + (:require + [clj-http.client :as http] + [clojure.data.json :as json])) (defn eth-decimal->str [n] @@ -6,3 +9,8 @@ (defn usd-decimal->str [n] (format "%.2f" n)) + +(defn json-api-request [url] + (->> (http/get url) + (:body) + (json/read-str))) From 49be307a9d10b07e485a158370dbe2fb11b657b5 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Thu, 21 Dec 2017 11:44:34 +0200 Subject: [PATCH 023/100] Limit top hunters query to 10 Fixes: #196 --- resources/sql/queries.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 074fd57..afa10b7 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -572,7 +572,8 @@ pr.commit_sha = i.commit_sha AND u.id = pr.user_id AND i.payout_receipt IS NOT NULL GROUP BY u.id -ORDER BY total_usd DESC; +ORDER BY total_usd DESC +LIMIT 10; -- :name bounties-activity :? :* From aa4b79988a3c7995b7c16f9682035aba5ef2dbe3 Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Thu, 21 Dec 2017 13:45:06 +0200 Subject: [PATCH 024/100] Top hunters UI tweak --- resources/sql/queries.sql | 4 ++-- src/cljs/commiteth/core.cljs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index afa10b7..e8a3115 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -558,7 +558,7 @@ AND comment_hash = :hash; -- :name top-hunters :? :* --- :doc list of user that have reveived bounty payouts with sum of +-- :doc top 5 list of users that have reveived bounty payouts with sum of -- earnings SELECT u.id AS user_id, @@ -573,7 +573,7 @@ AND u.id = pr.user_id AND i.payout_receipt IS NOT NULL GROUP BY u.id ORDER BY total_usd DESC -LIMIT 10; +LIMIT 5; -- :name bounties-activity :? :* diff --git a/src/cljs/commiteth/core.cljs b/src/cljs/commiteth/core.cljs index 8a90933..45bb1b8 100644 --- a/src/cljs/commiteth/core.cljs +++ b/src/cljs/commiteth/core.cljs @@ -215,7 +215,7 @@ (when (show-top-hunters?) [:div.six.wide.column.computer.only [:div.ui.container.top-hunters - [:h3.top-hunters-header "Top hunters"] + [:h3.top-hunters-header "Top 5 hunters"] [:div.top-hunters-subheader "All time"] [top-hunters]]])]] [footer]]]))) From a9c0b29aae34c61bc3fa11fda11857c3cd2e0b7a Mon Sep 17 00:00:00 2001 From: Noman Date: Wed, 27 Dec 2017 17:59:12 -0500 Subject: [PATCH 025/100] Add Facebook tracking pixel to home page --- static_langing_page/index.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/static_langing_page/index.html b/static_langing_page/index.html index 9310dac..ba31209 100644 --- a/static_langing_page/index.html +++ b/static_langing_page/index.html @@ -290,6 +290,25 @@ } catch (e) {} + + + + + + From 1dc2f95b75f62ebcc4afff7c6d772c1b893f8913 Mon Sep 17 00:00:00 2001 From: Noman Date: Wed, 27 Dec 2017 17:59:29 -0500 Subject: [PATCH 026/100] Add HubSpot tracking code to welcome page --- static_langing_page/welcome.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static_langing_page/welcome.html b/static_langing_page/welcome.html index 96d2c01..7f2c631 100644 --- a/static_langing_page/welcome.html +++ b/static_langing_page/welcome.html @@ -168,6 +168,10 @@ src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1" /> + + + + From 4d8e970262563010a68f926545b395622f74d8ac Mon Sep 17 00:00:00 2001 From: Noman Date: Thu, 28 Dec 2017 02:20:04 -0500 Subject: [PATCH 027/100] Run build script --- resources/templates/index.html | 20 +++++++++++++++++++- resources/templates/welcome.html | 5 ++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/resources/templates/index.html b/resources/templates/index.html index 8e9e660..ba31209 100644 --- a/resources/templates/index.html +++ b/resources/templates/index.html @@ -68,7 +68,6 @@ -
$1M Bounty fund for open source projects @@ -291,6 +290,25 @@ } catch (e) {} + + + + + + diff --git a/resources/templates/welcome.html b/resources/templates/welcome.html index 06cc9be..7f2c631 100644 --- a/resources/templates/welcome.html +++ b/resources/templates/welcome.html @@ -30,7 +30,6 @@ 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-WHLZ2RZ'); - @@ -169,6 +168,10 @@ src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1" /> + + + + From a2e4b52f97151ae79d32f843e53eb8745224c4d9 Mon Sep 17 00:00:00 2001 From: Dmitry Novotochinov Date: Wed, 3 Jan 2018 22:59:19 +0300 Subject: [PATCH 028/100] Suppress stdout logging in dev Log to file only --- env/dev/resources/logback.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/env/dev/resources/logback.xml b/env/dev/resources/logback.xml index f569fce..a4f8cc5 100644 --- a/env/dev/resources/logback.xml +++ b/env/dev/resources/logback.xml @@ -33,7 +33,11 @@ - + + + + + From 7ed71fab9c07ef8d7282f0a7f8da65a53174113e Mon Sep 17 00:00:00 2001 From: Teemu Patja Date: Mon, 8 Jan 2018 12:53:04 +0200 Subject: [PATCH 029/100] Error handling fix --- src/clj/commiteth/scheduler.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/commiteth/scheduler.clj b/src/clj/commiteth/scheduler.clj index fd84030..0c35df3 100644 --- a/src/clj/commiteth/scheduler.clj +++ b/src/clj/commiteth/scheduler.clj @@ -312,12 +312,13 @@ (defn wrap-in-try-catch [func] (try + (func) (catch Throwable t (log/error t)))) (defn run-tasks [tasks] (doall - (map (fn [func] (wrap-in-try-catch (func))) + (map (fn [func] (wrap-in-try-catch func)) tasks))) (defn run-1-min-interval-tasks [time] From 3c77dd297e9f100604d295b6ea2b5e560ea9e844 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Tue, 9 Jan 2018 14:11:35 +0200 Subject: [PATCH 030/100] [FIX #206] Dynamically require commiteth.core in user namespace --- env/dev/clj/user.clj | 9 +++++---- project.clj | 6 +----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/env/dev/clj/user.clj b/env/dev/clj/user.clj index e45e943..b318a86 100644 --- a/env/dev/clj/user.clj +++ b/env/dev/clj/user.clj @@ -1,13 +1,14 @@ (ns user (:require [mount.core :as mount] - [commiteth.figwheel :refer [start-fw stop-fw cljs]] - commiteth.core)) + [commiteth.figwheel :refer [start-fw stop-fw cljs]])) (defn start [] - (mount/start-without #'commiteth.core/repl-server)) + (require 'commiteth.core) + (mount/start-without (ns-resolve 'commiteth.core 'repl-server))) (defn stop [] - (mount/stop-except #'commiteth.core/repl-server)) + (require 'commiteth.core) + (mount/stop-except (ns-resolve 'commiteth.core 'repl-server))) (defn restart [] (stop) diff --git a/project.clj b/project.clj index 9a0ea35..e6af24a 100644 --- a/project.clj +++ b/project.clj @@ -116,10 +116,6 @@ :uberjar-name "commiteth.jar" :source-paths ["env/prod/clj"] :resource-paths ["env/prod/resources"]} - ;; :precomp profile allows to compile classes from commiteth.eth.contracts - ;; namespace before compiling clojure code. Otherwise ClassNotFound exception - ;; will be thrown - :precomp {:target-path "target/base+system+user+dev/" } :dev {:dependencies [[prone "1.1.4"] [ring/ring-mock "0.3.1"] [ring/ring-devel "1.6.2"] @@ -146,7 +142,7 @@ :optimizations :none :pretty-print true}}]} - :prep-tasks ["build-contracts" ["with-profile" "precomp" "javac"]] + :prep-tasks ["build-contracts" "javac"] :doo {:build "test"} :source-paths ["env/dev/clj" "test/clj"] :resource-paths ["env/dev/resources"] From fea0db845ab4ab96f5400e204748768b4960b55a Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 12 Jan 2018 19:47:17 +0200 Subject: [PATCH 032/100] [FIX #156] Add pagination to Bounties and Activities views --- resources/public/icon-forward-gray.svg | 3 + src/cljs/commiteth/activity.cljs | 13 ++-- src/cljs/commiteth/bounties.cljs | 16 ++--- src/cljs/commiteth/common.cljs | 84 ++++++++++++++++++++++++++ src/cljs/commiteth/db.cljs | 2 + src/cljs/commiteth/handlers.cljs | 10 +++ src/cljs/commiteth/subscriptions.cljs | 49 ++++++++++++++- src/less/style.less | 82 +++++++++++++++++++++++++ 8 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 resources/public/icon-forward-gray.svg diff --git a/resources/public/icon-forward-gray.svg b/resources/public/icon-forward-gray.svg new file mode 100644 index 0000000..a80ff29 --- /dev/null +++ b/resources/public/icon-forward-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/cljs/commiteth/activity.cljs b/src/cljs/commiteth/activity.cljs index 36f4162..69b4d34 100644 --- a/src/cljs/commiteth/activity.cljs +++ b/src/cljs/commiteth/activity.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :as rf] [reagent.core :as r] [commiteth.common :refer [moment-timestamp + display-data-page issue-url]])) @@ -56,21 +57,19 @@ -(defn activity-list [activity-items] +(defn activity-list [activity-page-data] [:div.ui.container.activity-container - (if (empty? activity-items) + (if (empty? (:items activity-page-data)) [:div.view-no-data-container [:p "No recent activity yet"]] - (into [:div.ui.items] - (for [item activity-items] - ^{:key item} [activity-item item])))] ) + (display-data-page activity-page-data activity-item :set-activity-page-number))]) (defn activity-page [] - (let [activity-items (rf/subscribe [:activity-feed]) + (let [activity-page-data (rf/subscribe [:activities-page]) activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]])] (fn [] (if @activity-feed-loading? [:div.view-loading-container [:div.ui.active.inverted.dimmer [:div.ui.text.loader.view-loading-label "Loading"]]] - [activity-list @activity-items])))) + [activity-list @activity-page-data])))) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index f7ba984..49bdd75 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -1,6 +1,7 @@ (ns commiteth.bounties (:require [re-frame.core :as rf] [commiteth.common :refer [moment-timestamp + display-data-page issue-url]])) @@ -42,23 +43,22 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-list [open-bounties] +(defn bounties-list [{:keys [items item-count total-count] :as bounty-page-data}] [:div.ui.container.open-bounties-container [:div.open-bounties-header "Bounties"] - (if (empty? open-bounties) + (if (empty? items) [:div.view-no-data-container [:p "No recent activity yet"]] - (into [:div.ui.items] - (for [bounty open-bounties] - [bounty-item bounty])))]) - + [:div [:div.item-counts-label + [:span (str "Showing " item-count " of " total-count)]] + (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) (defn bounties-page [] - (let [open-bounties (rf/subscribe [:open-bounties]) + (let [bounty-page-data (rf/subscribe [:open-bounties-page]) open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] (fn [] (if @open-bounties-loading? [:div.view-loading-container [:div.ui.active.inverted.dimmer [:div.ui.text.loader.view-loading-label "Loading"]]] - [bounties-list @open-bounties])))) + [bounties-list @bounty-page-data])))) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 9d302f0..ce8588b 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -29,3 +29,87 @@ (defn issue-url [owner repo number] (str "https://github.com/" owner "/" repo "/issues/" number)) + +(def items-per-page 20) + +(defn draw-page-numbers [page-number page-count set-page-kw] + "Draw page numbers for the pagination component. + Inserts ellipsis when list is too long, by default + max 6 items are allowed" + (let [draw-page-num-fn (fn [current? i] + ^{:key i} + [:div.rectangle-rounded + (cond-> {} + (not current?) + (assoc :class "grayed-out" + :on-click #(rf/dispatch [set-page-kw i]))) + i]) + max-page-nums 6] + [:div.page-nums-container + (cond (<= page-count max-page-nums) + (for [i (map inc (range page-count))] + (draw-page-num-fn (= i page-number) i)) + (<= page-number (- max-page-nums 3)) + (concat + (for [i (map inc (range (- max-page-nums 2)))] + (draw-page-num-fn (= i page-number) i)) + [^{:key (dec max-page-nums)} + [:div.page-nav-text [:span "..."]]] + [(draw-page-num-fn false page-count)]) + (>= page-number (- page-count (- max-page-nums 4))) + (concat + [(draw-page-num-fn false 1) + ^{:key 2} + [:div.page-nav-text [:span "..."]]] + (for [i (map inc (range (- page-count 4) page-count))] + (draw-page-num-fn (= i page-number) i)) + ) + :else + (concat + [(draw-page-num-fn false 1) + ^{:key 2} [:div.page-nav-text [:span "..."]]] + (for [i [(dec page-number) page-number (inc page-number)]] + (draw-page-num-fn (= i page-number) i)) + [^{:key (dec page-count)} [:div.page-nav-text [:span "..."]] + (draw-page-num-fn false page-count)]))])) + +(defn display-data-page [{:keys [items + item-count + total-count + page-number + page-count]} + draw-item-fn + set-page-kw] + "Draw data items along with pagination controls" + (let [draw-items (fn [] + (into [:div.ui.items] + (for [item items] + ^{:key item} [draw-item-fn item]))) + on-direction-click (fn [forward?] + #(when (or (and (< page-number page-count) + forward?) + (and (< 1 page-number) + (not forward?))) + (rf/dispatch [set-page-kw + (if forward? + (inc page-number) + (dec page-number))]))) + draw-rect (fn [direction] + (let [forward? (= direction :forward)] + [:div.rectangle-rounded + {:on-click (on-direction-click forward?)} + [:img.icon-forward-gray + (cond-> {:src "icon-forward-gray.svg"} + forward? (assoc :class "flip-horizontal"))]]))] + (cond (<= total-count items-per-page) + [draw-items] + :else + [:div + [draw-items] + [:div.page-nav-container + [draw-rect :backward] + [draw-rect :forward] + [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] + [draw-page-numbers page-number page-count set-page-kw]]]))) + + diff --git a/src/cljs/commiteth/db.cljs b/src/cljs/commiteth/db.cljs index 2ee532f..a93c8bc 100644 --- a/src/cljs/commiteth/db.cljs +++ b/src/cljs/commiteth/db.cljs @@ -8,6 +8,8 @@ :activity-feed-loading? false :open-bounties-loading? false :open-bounties [] + :bounty-page-number 1 + :activity-page-number 1 :owner-bounties {} :top-hunters [] :activity-feed []}) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 528c116..1cf2562 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -68,6 +68,16 @@ (fn [db [_ page]] (assoc db :page page))) +(reg-event-db + :set-bounty-page-number + (fn [db [_ page]] + (assoc db :bounty-page-number page))) + +(reg-event-db + :set-activity-page-number + (fn [db [_ page]] + (assoc db :activity-page-number page))) + (reg-event-fx :set-flash-message (fn [{:keys [db]} [_ type text]] diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 6e18eb1..f9cb555 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -1,5 +1,6 @@ (ns commiteth.subscriptions - (:require [re-frame.core :refer [reg-sub]])) + (:require [re-frame.core :refer [reg-sub]] + [commiteth.common :refer [items-per-page]])) (reg-sub :db @@ -33,7 +34,28 @@ (reg-sub :open-bounties (fn [db _] - (:open-bounties db))) + (vec (:open-bounties db)))) + +(reg-sub + :bounty-page-number + (fn [db _] + (:bounty-page-number db))) + +(reg-sub + :open-bounties-page + :<- [:open-bounties] + :<- [:bounty-page-number] + (fn [[open-bounties page-number] _] + (let [total-count (count open-bounties) + start (* (dec page-number) items-per-page) + end (min total-count (+ items-per-page start)) + items (subvec open-bounties start end)] + {:items items + :item-count (count items) + :total-count total-count + :page-number page-number + :page-count (Math/ceil (/ total-count items-per-page))}))) + (reg-sub :owner-bounties @@ -53,7 +75,28 @@ (reg-sub :activity-feed (fn [db _] - (:activity-feed db))) + (vec (:activity-feed db)))) + +(reg-sub + :activity-page-number + (fn [db _] + (:activity-page-number db))) + +(reg-sub + :activities-page + :<- [:activity-feed] + :<- [:activity-page-number] + (fn [[activities page-number] _] + (let [total-count (count activities) + start (* (dec page-number) items-per-page) + end (min total-count (+ items-per-page start)) + items (subvec activities start end)] + {:items items + :item-count (count items) + :total-count total-count + :page-number page-number + :page-count (Math/ceil (/ total-count items-per-page))}))) + (reg-sub :gh-admin-token diff --git a/src/less/style.less b/src/less/style.less index 2c0c2e4..c21473f 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -870,3 +870,85 @@ body { text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; } + +.rectangle-rounded { + width: 45px; + height: 45px; + color: #57a7ed; + /*opacity: 0.2;*/ + border-radius: 22.5px; + /*background-color: #57a7ed;*/ + background-color: rgba(87,167,237,.2); + font-family: "PostGrotesk-Medium"; + font-weight: 500; + font-size: 13px; + display: flex; + margin: 0 6px; + flex: none; + align-items: center; + justify-content: center; +} + +.grayed-out { + color: #8d99a4; + background-color: #f2f5f8; + opacity: 1.0; +} + +.flip-horizontal { + -moz-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); + -ms-filter: fliph; /*IE*/ + filter: fliph; +} + +.pagination-text { + width: 83px; + height: 15px; + font-family: PostGrotesk; + font-size: 15px; + text-align: center; + color: #8d99a4; +} + +.icon-forward-gray { + width: 24px; + height: 24px; + object-fit: contain; +} + +.page-nav-container { + display: flex; + margin: 0 -6px; +} + +.page-nums-container { + display: flex; + margin-left: auto; + justify-content: space-between; +} + +.page-nav-text { + font-family: PostGrotesk-Book; + font-size: 15px; + color: #8d99a4; + + display: flex; + margin: 0 6px; + flex: none; + align-items: center; + justify-content: center; +} + +.item-counts-label { + margin: auto; + font-family: "PostGrotesk-Book"; + font-size: 15px; + color: #8d99a4; + padding-top: 8px; + padding-bottom: 8px; +} + + From ffa0cfa3bb58e14db4fe7d0e48c4bd0796c633ab Mon Sep 17 00:00:00 2001 From: pablodip Date: Tue, 16 Jan 2018 06:55:45 +0100 Subject: [PATCH 033/100] some filter and sort markup --- src/cljs/commiteth/bounties.cljs | 58 +++++++++++++++++---------- src/cljs/commiteth/db.cljs | 3 +- src/cljs/commiteth/subscriptions.cljs | 5 +++ src/less/style.less | 47 ++++++++++++++++++++++ 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index f7ba984..69bef6a 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -5,19 +5,19 @@ (defn bounty-item [bounty] - (let [{avatar-url :repo_owner_avatar_url - owner :repo-owner - repo-name :repo-name - issue-title :issue-title + (let [{avatar-url :repo_owner_avatar_url + owner :repo-owner + repo-name :repo-name + issue-title :issue-title issue-number :issue-number - updated :updated - tokens :tokens - balance-eth :balance-eth - value-usd :value-usd - claim-count :claim-count} bounty - full-repo (str owner "/" repo-name) - repo-url (str "https://github.com/" full-repo) - repo-link [:a {:href repo-url} full-repo] + updated :updated + tokens :tokens + balance-eth :balance-eth + value-usd :value-usd + claim-count :claim-count} bounty + full-repo (str owner "/" repo-name) + repo-url (str "https://github.com/" full-repo) + repo-link [:a {:href repo-url} full-repo] issue-link [:a {:href (issue-url owner repo-name issue-number)} issue-title]] @@ -37,24 +37,38 @@ [:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)] (when (> claim-count 0) [:span.open-claims-label (str claim-count " open claim" - (when (> claim-count 1) "s"))]) ]] + (when (> claim-count 1) "s"))])]] [:div.open-bounty-item-icon [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) (defn bounties-list [open-bounties] - [:div.ui.container.open-bounties-container - [:div.open-bounties-header "Bounties"] - (if (empty? open-bounties) - [:div.view-no-data-container - [:p "No recent activity yet"]] - (into [:div.ui.items] - (for [bounty open-bounties] - [bounty-item bounty])))]) + (let [order (rf/subscribe [:bounties-order])] + [:div.ui.container.open-bounties-container + [:div.open-bounties-header "Bounties"] + [:div.open-bounties-filter-and-sort + [:div.open-bounties-filter + [:a.open-bounties-filter-element {:href "javascript:;"} "Value"] + [:div.open-bounties-filter-tooltip "Lalala"] + [:a.open-bounties-filter-element {:href "javascript:;"} "Type"] + [:a.open-bounties-filter-element {:href "javascript:;"} "Language"] + [:a.open-bounties-filter-element {:href "javascript:;"} "Date"] + [:a.open-bounties-filter-element {:href "javascript:;"} "More"]] + [:div.open-bounties-sort + "Most Recent" + [:div.icon-forward-white-box + [:img + {:src "icon-forward-white.svg"}]]]] + (if (empty? open-bounties) + [:div.view-no-data-container + [:p "No recent activity yet"]] + (into [:div.ui.items] + (for [bounty open-bounties] + [bounty-item bounty])))])) (defn bounties-page [] - (let [open-bounties (rf/subscribe [:open-bounties]) + (let [open-bounties (rf/subscribe [:open-bounties]) open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] (fn [] (if @open-bounties-loading? diff --git a/src/cljs/commiteth/db.cljs b/src/cljs/commiteth/db.cljs index 2ee532f..4a51dbf 100644 --- a/src/cljs/commiteth/db.cljs +++ b/src/cljs/commiteth/db.cljs @@ -10,4 +10,5 @@ :open-bounties [] :owner-bounties {} :top-hunters [] - :activity-feed []}) + :activity-feed [] + :bounties-order ::bounties-order|most-recent}) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 6e18eb1..9228ddc 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -80,3 +80,8 @@ :user-dropdown-open? (fn [db _] (:user-dropdown-open? db))) + +(reg-sub + :bounties-order + (fn [db _] + (:bounties-order db))) diff --git a/src/less/style.less b/src/less/style.less index 2c0c2e4..f9e4a0c 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -413,6 +413,53 @@ font-weight: 500; color: #42505c; } + + .open-bounties-filter-and-sort { + margin-top: 24px; + display: flex; + justify-content: space-between; + } + + .open-bounties-filter { + display: flex; + } + + .open-bounties-filter-element { + position: relative; + font-size: 15px; + font-weight: 500; + line-height: 1.0; + color: #8d99a4; + padding: 8px 12px; + border-radius: 8px; + border: solid 1px rgba(151, 151, 151, 0.2); + margin-right: 10px; + } + + .open-bounties-filter-element:focus { + background-color: #57a7ed; + } + + .open-bounties-filter-tooltip { + position: absolute; + } + + .open-bounties-sort { + font-size: 15px; + font-weight: 500; + line-height: 1.0; + color: #8d99a4; + display: flex; + align-items: center; + } + + .icon-forward-white-box { + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-content: center; + } } .open-bounty-item { From 26c740517bd657d823fc5638e3fbbb17aa0d0506 Mon Sep 17 00:00:00 2001 From: pablodip Date: Tue, 16 Jan 2018 13:05:41 +0100 Subject: [PATCH 034/100] some more sort and filter markup and interface behaviour --- src/cljs/commiteth/bounties.cljs | 55 ++++++++++++++++++++++++-------- src/less/style.less | 49 ++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 69bef6a..6c6c91e 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -1,5 +1,6 @@ (ns commiteth.bounties - (:require [re-frame.core :as rf] + (:require [reagent.core :as r] + [re-frame.core :as rf] [commiteth.common :refer [moment-timestamp issue-url]])) @@ -42,23 +43,51 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) +(defn bounties-filter [name] + (let [open? (r/atom false)] + (fn [name] + [:div.open-bounties-filter-element-container + {:tab-index 0 + :on-blur #(reset! open? false)} + [:div.open-bounties-filter-element + {:on-click #(swap! open? not) + :class (when @open? "open-bounties-filter-element-active")} + name] + (when @open? + [:div.open-bounties-filter-element-tooltip + "TOOLTIP"])]))) + +(defn bounties-filters [] + [:div.open-bounties-filter + [bounties-filter "Value"] + [bounties-filter "Type"] + [bounties-filter "Language"] + [bounties-filter "Date"] + [bounties-filter "More"]]) + +(defn bounties-sort [] + (let [open? (r/atom false)] + (fn [] + [:div.open-bounties-sort + {:tab-index 0 + :on-blur #(reset! open? false)} + [:div.open-bounties-sort-element + {:on-click #(swap! open? not)} + "Most Recent" + [:div.icon-forward-white-box + [:img + {:src "icon-forward-white.svg"}]]] + (when @open? + [:div.open-bounties-sort-element-tooltip + "TOOLTIP"])]))) + (defn bounties-list [open-bounties] (let [order (rf/subscribe [:bounties-order])] [:div.ui.container.open-bounties-container [:div.open-bounties-header "Bounties"] [:div.open-bounties-filter-and-sort - [:div.open-bounties-filter - [:a.open-bounties-filter-element {:href "javascript:;"} "Value"] - [:div.open-bounties-filter-tooltip "Lalala"] - [:a.open-bounties-filter-element {:href "javascript:;"} "Type"] - [:a.open-bounties-filter-element {:href "javascript:;"} "Language"] - [:a.open-bounties-filter-element {:href "javascript:;"} "Date"] - [:a.open-bounties-filter-element {:href "javascript:;"} "More"]] - [:div.open-bounties-sort - "Most Recent" - [:div.icon-forward-white-box - [:img - {:src "icon-forward-white.svg"}]]]] + [bounties-filters] + [bounties-sort]] (if (empty? open-bounties) [:div.view-no-data-container [:p "No recent activity yet"]] diff --git a/src/less/style.less b/src/less/style.less index f9e4a0c..1c9eeef 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -425,7 +425,6 @@ } .open-bounties-filter-element { - position: relative; font-size: 15px; font-weight: 500; line-height: 1.0; @@ -434,25 +433,69 @@ border-radius: 8px; border: solid 1px rgba(151, 151, 151, 0.2); margin-right: 10px; + position: relative; } - .open-bounties-filter-element:focus { + .open-bounties-filter-element:hover { + cursor: pointer; + } + + .open-bounties-filter-element-active { background-color: #57a7ed; + color: #ffffff; } - .open-bounties-filter-tooltip { + .open-bounties-filter-element-container:focus { + outline: none; + } + + .open-bounties-filter-element-tooltip { position: absolute; + margin-top: 12px; + padding: 16px 24px; + border-radius: 10px; + background-color: #ffffff; + box-shadow: 0 15px 12px 0 rgba(161, 174, 182, 0.53), 0 0 38px 0 rgba(0, 0, 0, 0.05); } .open-bounties-sort { + position: relative; + } + + .open-bounties-sort:focus { + outline: none; + } + + .open-bounties-sort-element { + display: flex; font-size: 15px; font-weight: 500; line-height: 1.0; color: #8d99a4; + padding: 8px 12px; display: flex; align-items: center; } + .open-bounties-sort-element:hover { + cursor: pointer; + } + + .open-bounties-sort-element-tooltip { + position: absolute; + right: 0; + //margin-top: 12px; + padding: 16px 24px; + min-width: 200px; + border-radius: 8px; + background-color: #1e3751; + font-family: "PostGrotesk-Medium"; + font-size: 15px; + line-height: 1.0; + text-align: left; + color: #ffffff; + } + .icon-forward-white-box { width: 24px; height: 24px; From 7d7d781f06737b9263680c62ebb715740df880ca Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Tue, 16 Jan 2018 17:48:26 +0200 Subject: [PATCH 035/100] Correct 'Showing' label text; decrease default page size to 15 --- src/cljs/commiteth/bounties.cljs | 11 ++++++++--- src/cljs/commiteth/common.cljs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 49bdd75..26957e3 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :as rf] [commiteth.common :refer [moment-timestamp display-data-page + items-per-page issue-url]])) @@ -43,14 +44,18 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-list [{:keys [items item-count total-count] :as bounty-page-data}] +(defn bounties-list [{:keys [items item-count page-number total-count] + :as bounty-page-data}] [:div.ui.container.open-bounties-container [:div.open-bounties-header "Bounties"] (if (empty? items) [:div.view-no-data-container [:p "No recent activity yet"]] - [:div [:div.item-counts-label - [:span (str "Showing " item-count " of " total-count)]] + [:div + (let [left (inc (* (dec page-number) items-per-page)) + right (dec (+ left item-count))] + [:div.item-counts-label + [:span (str "Showing " left "-" right " of " total-count)]]) (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) (defn bounties-page [] diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index ce8588b..6eed787 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -30,7 +30,7 @@ (defn issue-url [owner repo number] (str "https://github.com/" owner "/" repo "/issues/" number)) -(def items-per-page 20) +(def items-per-page 15) (defn draw-page-numbers [page-number page-count set-page-kw] "Draw page numbers for the pagination component. From 78ab683a28b219598f3eb75c7ccd01edfc524aed Mon Sep 17 00:00:00 2001 From: Chris Hutchinson Date: Wed, 17 Jan 2018 03:13:06 -0600 Subject: [PATCH 036/100] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a961a5d..eb161ec 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Commiteth +# Status Open Bounty Allows you to set bounties for Github issues, paid out in Ether. More information: -http://wiki.status.im/proposals/commiteth/ +https://wiki.status.im/Status_Open_Bounty -Live beta version: +Live production version: https://openbounty.status.im The `master` branch is automatically deployed here. From df68be9ba79c75d85933055c8f9c37eb6fb14266 Mon Sep 17 00:00:00 2001 From: Chris Hutchinson Date: Wed, 17 Jan 2018 03:15:46 -0600 Subject: [PATCH 037/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb161ec..bb7bc5b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Status Open Bounty -Allows you to set bounties for Github issues, paid out in Ether. +Allows you to set bounties for Github issues, paid out in Ether or any ERC-20 token. More information: https://wiki.status.im/Status_Open_Bounty From bdbdc2e2d23dee4f4fc54ba37591992fd8ec333b Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 18 Jan 2018 05:45:42 +0100 Subject: [PATCH 038/100] add some bounty sorting --- src/cljs/commiteth/bounties.cljs | 62 +++++++++++++++------------ src/cljs/commiteth/db.cljs | 23 ++++++---- src/cljs/commiteth/handlers.cljs | 5 +++ src/cljs/commiteth/subscriptions.cljs | 7 +-- src/less/style.less | 12 +++++- 5 files changed, 67 insertions(+), 42 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 6c6c91e..7da81fa 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -2,7 +2,9 @@ (:require [reagent.core :as r] [re-frame.core :as rf] [commiteth.common :refer [moment-timestamp - issue-url]])) + issue-url]] + [commiteth.handlers :as handlers] + [commiteth.db :as db])) (defn bounty-item [bounty] @@ -60,40 +62,44 @@ (defn bounties-filters [] [:div.open-bounties-filter [bounties-filter "Value"] - [bounties-filter "Type"] - [bounties-filter "Language"] + [bounties-filter "Currency"] [bounties-filter "Date"] - [bounties-filter "More"]]) + [bounties-filter "Owner"]]) (defn bounties-sort [] (let [open? (r/atom false)] (fn [] - [:div.open-bounties-sort - {:tab-index 0 - :on-blur #(reset! open? false)} - [:div.open-bounties-sort-element - {:on-click #(swap! open? not)} - "Most Recent" - [:div.icon-forward-white-box - [:img - {:src "icon-forward-white.svg"}]]] - (when @open? - [:div.open-bounties-sort-element-tooltip - "TOOLTIP"])]))) + (let [current-sorting (rf/subscribe [::db/bounty-sorting-type])] + [:div.open-bounties-sort + {:tab-index 0 + :on-blur #(reset! open? false)} + [:div.open-bounties-sort-element + {:on-click #(swap! open? not)} + (db/bounty-sorting-types @current-sorting) + [:div.icon-forward-white-box + [:img + {:src "icon-forward-white.svg"}]]] + (when @open? + [:div.open-bounties-sort-element-tooltip + (for [[sorting-type sorting-name] db/bounty-sorting-types] + [:div.open-bounties-sort-type + {:on-click #(do + (reset! open? false) + (rf/dispatch [::handlers/set-bounty-sorting-type sorting-type]))} + sorting-name])])])))) (defn bounties-list [open-bounties] - (let [order (rf/subscribe [:bounties-order])] - [:div.ui.container.open-bounties-container - [:div.open-bounties-header "Bounties"] - [:div.open-bounties-filter-and-sort - [bounties-filters] - [bounties-sort]] - (if (empty? open-bounties) - [:div.view-no-data-container - [:p "No recent activity yet"]] - (into [:div.ui.items] - (for [bounty open-bounties] - [bounty-item bounty])))])) + [:div.ui.container.open-bounties-container + [:div.open-bounties-header "Bounties"] + [:div.open-bounties-filter-and-sort + [bounties-filters] + [bounties-sort]] + (if (empty? open-bounties) + [:div.view-no-data-container + [:p "No recent activity yet"]] + (into [:div.ui.items] + (for [bounty open-bounties] + [bounty-item bounty])))]) (defn bounties-page [] diff --git a/src/cljs/commiteth/db.cljs b/src/cljs/commiteth/db.cljs index 4a51dbf..e54bcd1 100644 --- a/src/cljs/commiteth/db.cljs +++ b/src/cljs/commiteth/db.cljs @@ -1,14 +1,19 @@ (ns commiteth.db) +(def bounty-sorting-types {::bounty-sorting-type|most-recent "Most recent" + ::bounty-sorting-type|lowest-value "Lowest value" + ::bounty-sorting-type|highest-value "Highest value" + ::bounty-sorting-type|owner "Owner"}) + (def default-db - {:page :bounties - :user nil - :repos-loading? false - :repos {} + {:page :bounties + :user nil + :repos-loading? false + :repos {} :activity-feed-loading? false :open-bounties-loading? false - :open-bounties [] - :owner-bounties {} - :top-hunters [] - :activity-feed [] - :bounties-order ::bounties-order|most-recent}) + :open-bounties [] + :owner-bounties {} + :top-hunters [] + :activity-feed [] + ::bounty-sorting-type ::bounty-sorting-type|most-recent}) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 528c116..b69f1ca 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -453,3 +453,8 @@ (fn [db [_]] (.removeEventListener js/window "click" close-dropdown) (assoc db :user-dropdown-open? false))) + +(reg-event-db + ::set-bounty-sorting-type + (fn [db [_ sorting-type]] + (assoc db ::db/bounty-sorting-type sorting-type))) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 9228ddc..c6d4340 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -1,5 +1,6 @@ (ns commiteth.subscriptions - (:require [re-frame.core :refer [reg-sub]])) + (:require [re-frame.core :refer [reg-sub]] + [commiteth.db :as db])) (reg-sub :db @@ -82,6 +83,6 @@ (:user-dropdown-open? db))) (reg-sub - :bounties-order + ::db/bounty-sorting-type (fn [db _] - (:bounties-order db))) + (::db/bounty-sorting-type db))) diff --git a/src/less/style.less b/src/less/style.less index 1c9eeef..e52f7fa 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -484,8 +484,7 @@ .open-bounties-sort-element-tooltip { position: absolute; right: 0; - //margin-top: 12px; - padding: 16px 24px; + //padding: 16px 0; min-width: 200px; border-radius: 8px; background-color: #1e3751; @@ -496,6 +495,15 @@ color: #ffffff; } + .open-bounties-sort-type { + padding: 19px 24px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .open-bounties-sort-type:hover { + cursor: pointer; + } + .icon-forward-white-box { width: 24px; height: 24px; From 11e4a00ac6c9ed9d7fe66e2f9cf06f0c55a7fb63 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 18 Jan 2018 09:08:37 +0100 Subject: [PATCH 039/100] some filter markup and behaviour --- src/cljs/commiteth/bounties.cljs | 94 ++++++++++++----- src/less/style.less | 169 ++++++++++++++++++++++++++++++- 2 files changed, 235 insertions(+), 28 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 7da81fa..e64e11f 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -45,48 +45,90 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-filter [name] +(defn bounties-filter-tooltip [& content] + [:div.open-bounties-filter-element-tooltip + content]) + +(defn bounties-filter-tooltip-value-input [label tooltip-open?] + [:div.open-bounties-filter-element-tooltip-value-input-container + [:div.:input.open-bounties-filter-element-tooltip-value-input-label + label] + [:input.open-bounties-filter-element-tooltip-value-input + {:type "range" + :min 0 + :max 1000 + :step 10 + :on-focus #(reset! tooltip-open? true)}]]) + +(defn bounties-filter-tooltip-value [tooltip-open?] + [bounties-filter-tooltip + "$0 - $1000+" + [bounties-filter-tooltip-value-input "Min" tooltip-open?] + [bounties-filter-tooltip-value-input "Max" tooltip-open?]]) + +(defn bounties-filter-tooltip-currency [] + [bounties-filter-tooltip "CURRENCY"]) + +(defn bounties-filter-tooltip-date [] + [bounties-filter-tooltip + [:div.open-bounties-filter-list + (for [t ["Last week" "Last month" "Last 3 months"]] + [:div.open-bounties-filter-list-option + t])]]) + +(defn bounties-filter-tooltip-owner [tooltip-open?] + [bounties-filter-tooltip + [:div.open-bounties-filter-list + (for [t ["status-im" "aragon"]] + [:div.open-bounties-filter-list-option-checkbox + [:label + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true)}] + [:div.text t]]])]]) + +(defn bounties-filter [name tooltip] (let [open? (r/atom false)] - (fn [name] + (fn [name tooltip] [:div.open-bounties-filter-element-container {:tab-index 0 + :on-focus #(reset! open? true) :on-blur #(reset! open? false)} [:div.open-bounties-filter-element - {:on-click #(swap! open? not) - :class (when @open? "open-bounties-filter-element-active")} + {:on-mouse-down #(swap! open? not) + :class (when @open? "open-bounties-filter-element-active")} name] (when @open? - [:div.open-bounties-filter-element-tooltip - "TOOLTIP"])]))) + [tooltip open?])]))) (defn bounties-filters [] [:div.open-bounties-filter - [bounties-filter "Value"] - [bounties-filter "Currency"] - [bounties-filter "Date"] - [bounties-filter "Owner"]]) + [bounties-filter "Value" bounties-filter-tooltip-value] + [bounties-filter "Currency" bounties-filter-tooltip-currency] + [bounties-filter "Date" bounties-filter-tooltip-date] + [bounties-filter "Owner" bounties-filter-tooltip-owner]]) (defn bounties-sort [] (let [open? (r/atom false)] (fn [] (let [current-sorting (rf/subscribe [::db/bounty-sorting-type])] [:div.open-bounties-sort - {:tab-index 0 - :on-blur #(reset! open? false)} - [:div.open-bounties-sort-element - {:on-click #(swap! open? not)} - (db/bounty-sorting-types @current-sorting) - [:div.icon-forward-white-box - [:img - {:src "icon-forward-white.svg"}]]] - (when @open? - [:div.open-bounties-sort-element-tooltip - (for [[sorting-type sorting-name] db/bounty-sorting-types] - [:div.open-bounties-sort-type - {:on-click #(do - (reset! open? false) - (rf/dispatch [::handlers/set-bounty-sorting-type sorting-type]))} - sorting-name])])])))) + {:tab-index 0 + :on-blur #(reset! open? false)} + [:div.open-bounties-sort-element + {:on-click #(swap! open? not)} + (db/bounty-sorting-types @current-sorting) + [:div.icon-forward-white-box + [:img + {:src "icon-forward-white.svg"}]]] + (when @open? + [:div.open-bounties-sort-element-tooltip + (for [[sorting-type sorting-name] db/bounty-sorting-types] + [:div.open-bounties-sort-type + {:on-click #(do + (reset! open? false) + (rf/dispatch [::handlers/set-bounty-sorting-type sorting-type]))} + sorting-name])])])))) (defn bounties-list [open-bounties] [:div.ui.container.open-bounties-container diff --git a/src/less/style.less b/src/less/style.less index e52f7fa..4e4b05e 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -425,6 +425,7 @@ } .open-bounties-filter-element { + font-family: "PostGrotesk-Book"; font-size: 15px; font-weight: 500; line-height: 1.0; @@ -451,11 +452,175 @@ .open-bounties-filter-element-tooltip { position: absolute; + min-width: 227px; margin-top: 12px; - padding: 16px 24px; + padding: 24px 16px; border-radius: 10px; background-color: #ffffff; box-shadow: 0 15px 12px 0 rgba(161, 174, 182, 0.53), 0 0 38px 0 rgba(0, 0, 0, 0.05); + font-family: "PostGrotesk-Book"; + font-size: 16px; + line-height: 1.5; + + .open-bounties-filter-element-tooltip-value-input-container { + display: flex; + margin-top: 10px; + } + + .open-bounties-filter-element-tooltip-value-input-label { + width: 60px; + } + + .open-bounties-filter-element-tooltip-value-input { + margin-top: 24px; + width: 288.5px; + height: 4px; + background-color: #55a5ea; + } + + // generated with http://danielstern.ca/range.css/#/ + input[type=range] { + -webkit-appearance: none; + //width: 100%; + margin: 14.5px 0; + } + input[type=range]:focus { + outline: none; + } + input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + cursor: pointer; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + background: #55a5ea; + border-radius: 0px; + border: 0px solid #010101; + } + input[type=range]::-webkit-slider-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 33px; + width: 33px; + border-radius: 50px; + background: #55a5ea; + cursor: pointer; + -webkit-appearance: none; + margin-top: -14.5px; + } + input[type=range]:focus::-webkit-slider-runnable-track { + background: #5aa7eb; + } + input[type=range]::-moz-range-track { + width: 100%; + height: 4px; + cursor: pointer; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + background: #55a5ea; + border-radius: 0px; + border: 0px solid #010101; + } + input[type=range]::-moz-range-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 33px; + width: 33px; + border-radius: 50px; + background: #55a5ea; + cursor: pointer; + } + input[type=range]::-ms-track { + width: 100%; + height: 4px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; + } + input[type=range]::-ms-fill-lower { + background: #50a3e9; + border: 0px solid #010101; + border-radius: 0px; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + } + input[type=range]::-ms-fill-upper { + background: #55a5ea; + border: 0px solid #010101; + border-radius: 0px; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + } + input[type=range]::-ms-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 33px; + width: 33px; + border-radius: 50px; + background: #55a5ea; + cursor: pointer; + height: 4px; + } + input[type=range]:focus::-ms-fill-lower { + background: #55a5ea; + } + input[type=range]:focus::-ms-fill-upper { + background: #5aa7eb; + } + + .open-bounties-filter-list { + display: flex; + flex-direction: column; + align-items: flex-start; + } + + .open-bounties-filter-list-option { + font-family: "PostGrotesk-Book"; + font-size: 15px; + font-weight: 500; + line-height: 1.0; + color: #42505c; + padding: 8px 12px; + border-radius: 8px; + border: solid 1px rgba(151, 151, 151, 0.2); + } + + .open-bounties-filter-list-option:not(:first-child) { + margin-top: 8px; + } + + .open-bounties-filter-list-option:hover { + cursor: pointer; + background-color: #57a7ed; + color: #ffffff; + } + + .open-bounties-filter-list-option-checkbox { + label { + display: flex; + } + + label:hover { + cursor: pointer; + } + + input { + background: green; + transform: scale(1.3); + } + + input:hover { + cursor: pointer; + } + + .text { + font-size: 15px; + font-weight: 500; + line-height: 1.0; + margin-left: 12px; + } + } + + .open-bounties-filter-list-option-checkbox:not(:first-child) { + margin-top: 12px; + } } .open-bounties-sort { @@ -488,7 +653,7 @@ min-width: 200px; border-radius: 8px; background-color: #1e3751; - font-family: "PostGrotesk-Medium"; + font-family: "PostGrotesk-Book"; font-size: 15px; line-height: 1.0; text-align: left; From 646ad8a03843e06611064197c24b43cd77c4873c Mon Sep 17 00:00:00 2001 From: pablodip Date: Fri, 19 Jan 2018 07:51:50 +0100 Subject: [PATCH 040/100] some more filter markup --- src/cljs/commiteth/bounties.cljs | 46 ++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index e64e11f..173d7e5 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -61,35 +61,40 @@ :on-focus #(reset! tooltip-open? true)}]]) (defn bounties-filter-tooltip-value [tooltip-open?] - [bounties-filter-tooltip + [:div "$0 - $1000+" [bounties-filter-tooltip-value-input "Min" tooltip-open?] [bounties-filter-tooltip-value-input "Max" tooltip-open?]]) -(defn bounties-filter-tooltip-currency [] - [bounties-filter-tooltip "CURRENCY"]) +(defn bounties-filter-tooltip-currency [tooltip-open?] + [:div.open-bounties-filter-list + (for [t ["ETH" "SNT"]] + [:div.open-bounties-filter-list-option-checkbox + [:label + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true)}] + [:div.text t]]])]) (defn bounties-filter-tooltip-date [] - [bounties-filter-tooltip - [:div.open-bounties-filter-list - (for [t ["Last week" "Last month" "Last 3 months"]] - [:div.open-bounties-filter-list-option - t])]]) + [:div.open-bounties-filter-list + (for [t ["Last week" "Last month" "Last 3 months"]] + [:div.open-bounties-filter-list-option + t])]) (defn bounties-filter-tooltip-owner [tooltip-open?] - [bounties-filter-tooltip - [:div.open-bounties-filter-list - (for [t ["status-im" "aragon"]] - [:div.open-bounties-filter-list-option-checkbox - [:label - [:input - {:type "checkbox" - :on-focus #(reset! tooltip-open? true)}] - [:div.text t]]])]]) + [:div.open-bounties-filter-list + (for [t ["status-im" "aragon"]] + [:div.open-bounties-filter-list-option-checkbox + [:label + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true)}] + [:div.text t]]])]) -(defn bounties-filter [name tooltip] +(defn bounties-filter [name tooltip-content] (let [open? (r/atom false)] - (fn [name tooltip] + (fn [name tooltip-content] [:div.open-bounties-filter-element-container {:tab-index 0 :on-focus #(reset! open? true) @@ -99,7 +104,8 @@ :class (when @open? "open-bounties-filter-element-active")} name] (when @open? - [tooltip open?])]))) + [bounties-filter-tooltip + [tooltip-content open?]])]))) (defn bounties-filters [] [:div.open-bounties-filter From 03ab5b05398215e08dd0e4859decd709cbfb178a Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 19 Jan 2018 15:41:33 +0200 Subject: [PATCH 041/100] Pagination: add cursor pointer for buttons; scroll to top when changing page --- src/cljs/commiteth/activity.cljs | 1 + src/cljs/commiteth/bounties.cljs | 5 +++-- src/cljs/commiteth/handlers.cljs | 24 ++++++++++++++++++------ src/less/style.less | 1 + 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/cljs/commiteth/activity.cljs b/src/cljs/commiteth/activity.cljs index 69b4d34..0d199dd 100644 --- a/src/cljs/commiteth/activity.cljs +++ b/src/cljs/commiteth/activity.cljs @@ -59,6 +59,7 @@ (defn activity-list [activity-page-data] [:div.ui.container.activity-container + {:id "activity-container"} (if (empty? (:items activity-page-data)) [:div.view-no-data-container [:p "No recent activity yet"]] diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 26957e3..ebe63d2 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -47,15 +47,16 @@ (defn bounties-list [{:keys [items item-count page-number total-count] :as bounty-page-data}] [:div.ui.container.open-bounties-container + {:id "open-bounties-container"} [:div.open-bounties-header "Bounties"] (if (empty? items) [:div.view-no-data-container [:p "No recent activity yet"]] - [:div + [:div (let [left (inc (* (dec page-number) items-per-page)) right (dec (+ left item-count))] [:div.item-counts-label - [:span (str "Showing " left "-" right " of " total-count)]]) + [:span (str "Showing " left "-" right " of " total-count)]]) (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) (defn bounties-page [] diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 1cf2562..f50ad77 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -6,6 +6,7 @@ reg-fx inject-cofx]] [ajax.core :refer [GET POST]] + [clojure.browser.dom :as dom :refer [get-element]] [cuerdas.core :as str] [cljs-web3.core :as web3] [cljs-web3.eth :as web3-eth] @@ -38,6 +39,15 @@ (println "redirecting to" path) (set! (.-pathname js/location) path))) +(reg-fx + :bounty-scroll-pos + (fn [scroll-pos] + (.scrollIntoView (get-element "open-bounties-container")) )) + +(reg-fx + :activity-scroll-pos + (fn [scroll-pos] + (.scrollIntoView (get-element "activity-container")))) (reg-event-fx :initialize-db @@ -68,15 +78,17 @@ (fn [db [_ page]] (assoc db :page page))) -(reg-event-db +(reg-event-fx :set-bounty-page-number - (fn [db [_ page]] - (assoc db :bounty-page-number page))) + (fn [{:keys [db]} [_ page]] + {:db (assoc db :bounty-page-number page) + :bounty-scroll-pos 0})) -(reg-event-db +(reg-event-fx :set-activity-page-number - (fn [db [_ page]] - (assoc db :activity-page-number page))) + (fn [{:keys [db]} [_ page]] + {:db (assoc db :activity-page-number page) + :activity-scroll-pos 0})) (reg-event-fx :set-flash-message diff --git a/src/less/style.less b/src/less/style.less index c21473f..145aca5 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -887,6 +887,7 @@ body { flex: none; align-items: center; justify-content: center; + cursor: pointer; } .grayed-out { From 55cbb713f916af5a810ab7f278b39c610981a0f0 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 19 Jan 2018 21:01:35 +0200 Subject: [PATCH 042/100] Pagination: use React Refs in order to obtain DOM element for scrolling --- src/cljs/commiteth/activity.cljs | 32 ++++++++++++++--------- src/cljs/commiteth/bounties.cljs | 45 ++++++++++++++++++-------------- src/cljs/commiteth/handlers.cljs | 24 +++++------------ 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/cljs/commiteth/activity.cljs b/src/cljs/commiteth/activity.cljs index 0d199dd..d59ed19 100644 --- a/src/cljs/commiteth/activity.cljs +++ b/src/cljs/commiteth/activity.cljs @@ -58,19 +58,25 @@ (defn activity-list [activity-page-data] - [:div.ui.container.activity-container - {:id "activity-container"} - (if (empty? (:items activity-page-data)) - [:div.view-no-data-container - [:p "No recent activity yet"]] - (display-data-page activity-page-data activity-item :set-activity-page-number))]) + (if (empty? (:items activity-page-data)) + [:div.view-no-data-container + [:p "No recent activity yet"]] + (display-data-page activity-page-data activity-item :set-activity-page-number))) (defn activity-page [] (let [activity-page-data (rf/subscribe [:activities-page]) - activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]])] - (fn [] - (if @activity-feed-loading? - [:div.view-loading-container - [:div.ui.active.inverted.dimmer - [:div.ui.text.loader.view-loading-label "Loading"]]] - [activity-list @activity-page-data])))) + activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]]) + container-element (atom nil) + render-fn (fn [] + (if @activity-feed-loading? + [:div.view-loading-container + [:div.ui.active.inverted.dimmer + [:div.ui.text.loader.view-loading-label "Loading"]]] + [:div.ui.container.activity-container + {:ref #(reset! container-element %1)} + [activity-list @activity-page-data]]))] + (r/create-class + {:component-did-update (fn [] + (when @container-element + (.scrollIntoView @container-element))) + :reagent-render render-fn}))) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index ebe63d2..2868da3 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -1,5 +1,6 @@ (ns commiteth.bounties (:require [re-frame.core :as rf] + [reagent.core :as r] [commiteth.common :refer [moment-timestamp display-data-page items-per-page @@ -46,25 +47,31 @@ (defn bounties-list [{:keys [items item-count page-number total-count] :as bounty-page-data}] - [:div.ui.container.open-bounties-container - {:id "open-bounties-container"} - [:div.open-bounties-header "Bounties"] - (if (empty? items) - [:div.view-no-data-container - [:p "No recent activity yet"]] - [:div - (let [left (inc (* (dec page-number) items-per-page)) - right (dec (+ left item-count))] - [:div.item-counts-label - [:span (str "Showing " left "-" right " of " total-count)]]) - (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) + (if (empty? items) + [:div.view-no-data-container + [:p "No recent activity yet"]] + [:div + (let [left (inc (* (dec page-number) items-per-page)) + right (dec (+ left item-count))] + [:div.item-counts-label + [:span (str "Showing " left "-" right " of " total-count)]]) + (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])) (defn bounties-page [] (let [bounty-page-data (rf/subscribe [:open-bounties-page]) - open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] - (fn [] - (if @open-bounties-loading? - [:div.view-loading-container - [:div.ui.active.inverted.dimmer - [:div.ui.text.loader.view-loading-label "Loading"]]] - [bounties-list @bounty-page-data])))) + open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]]) + container-element (atom nil) + render-fn (fn [] + (if @open-bounties-loading? + [:div.view-loading-container + [:div.ui.active.inverted.dimmer + [:div.ui.text.loader.view-loading-label "Loading"]]] + [:div.ui.container.open-bounties-container + {:ref #(reset! container-element %1)} + [:div.open-bounties-header "Bounties"] + [bounties-list @bounty-page-data]]))] + (r/create-class + {:component-did-update (fn [] + (when @container-element + (.scrollIntoView @container-element))) + :reagent-render render-fn}))) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index f50ad77..671dbd8 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -39,16 +39,6 @@ (println "redirecting to" path) (set! (.-pathname js/location) path))) -(reg-fx - :bounty-scroll-pos - (fn [scroll-pos] - (.scrollIntoView (get-element "open-bounties-container")) )) - -(reg-fx - :activity-scroll-pos - (fn [scroll-pos] - (.scrollIntoView (get-element "activity-container")))) - (reg-event-fx :initialize-db [(inject-cofx :store)] @@ -78,17 +68,15 @@ (fn [db [_ page]] (assoc db :page page))) -(reg-event-fx +(reg-event-db :set-bounty-page-number - (fn [{:keys [db]} [_ page]] - {:db (assoc db :bounty-page-number page) - :bounty-scroll-pos 0})) + (fn [db [_ page]] + (assoc db :bounty-page-number page))) -(reg-event-fx +(reg-event-db :set-activity-page-number - (fn [{:keys [db]} [_ page]] - {:db (assoc db :activity-page-number page) - :activity-scroll-pos 0})) + (fn [db [_ page]] + (assoc db :activity-page-number page))) (reg-event-fx :set-flash-message From b2ab0949f6010a650555a9872728912dec383b79 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 19 Jan 2018 21:02:51 +0200 Subject: [PATCH 043/100] handlers: remove require for clojure.browser.dom --- src/cljs/commiteth/handlers.cljs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 671dbd8..b1114fe 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -6,7 +6,6 @@ reg-fx inject-cofx]] [ajax.core :refer [GET POST]] - [clojure.browser.dom :as dom :refer [get-element]] [cuerdas.core :as str] [cljs-web3.core :as web3] [cljs-web3.eth :as web3-eth] From 366148f250c9fda2dad726e35431057331d8c580 Mon Sep 17 00:00:00 2001 From: pablodip Date: Sat, 20 Jan 2018 08:21:59 +0100 Subject: [PATCH 044/100] some behaviour in bounty filters --- src/cljs/commiteth/bounties.cljs | 164 ++++++++++++++++++-------- src/cljs/commiteth/db.cljs | 34 +++--- src/cljs/commiteth/handlers.cljs | 9 +- src/cljs/commiteth/subscriptions.cljs | 9 +- src/cljs/commiteth/ui_model.cljs | 44 +++++++ src/less/style.less | 20 +++- 6 files changed, 211 insertions(+), 69 deletions(-) create mode 100644 src/cljs/commiteth/ui_model.cljs diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 173d7e5..83aa4f3 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -4,7 +4,9 @@ [commiteth.common :refer [moment-timestamp issue-url]] [commiteth.handlers :as handlers] - [commiteth.db :as db])) + [commiteth.db :as db] + [commiteth.ui-model :as ui-model] + [commiteth.subscriptions :as subs])) (defn bounty-item [bounty] @@ -45,102 +47,172 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-filter-tooltip [& content] +(defn bounties-filter-tooltip [content] [:div.open-bounties-filter-element-tooltip content]) -(defn bounties-filter-tooltip-value-input [label tooltip-open?] +(defn bounties-filter-tooltip-value-input [label tooltip-open? opts] [:div.open-bounties-filter-element-tooltip-value-input-container [:div.:input.open-bounties-filter-element-tooltip-value-input-label label] [:input.open-bounties-filter-element-tooltip-value-input - {:type "range" - :min 0 - :max 1000 - :step 10 - :on-focus #(reset! tooltip-open? true)}]]) + {:type "range" + :min (:min opts) + :max (:max opts) + :step (:step opts) + :value (:current-val opts) + :on-change (when-let [f (:on-change-val opts)] + #(-> % .-target .-value int f)) + :on-focus #(reset! tooltip-open? true)}]]) -(defn bounties-filter-tooltip-value [tooltip-open?] - [:div - "$0 - $1000+" - [bounties-filter-tooltip-value-input "Min" tooltip-open?] - [bounties-filter-tooltip-value-input "Max" tooltip-open?]]) +(defn bounties-filter-tooltip-value [current-filter-value tooltip-open?] + (let [default-min 0 + default-max 1000 + common-range-opts {:min default-min :max default-max} + current-min (or (first current-filter-value) default-min) + current-max (or (second current-filter-value) default-max) + on-change-fn (fn [min-val max-val] + (rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|value + [(min min-val (dec default-max)) + (max max-val (inc default-min))]])) + on-min-change-fn (fn [new-min] + (let [new-max (max current-max (inc new-min))] + (on-change-fn new-min new-max))) + on-max-change-fn (fn [new-max] + (let [new-min (min current-min (dec new-max))] + (on-change-fn new-min new-max)))] + [:div + "$0 - $1000+" + [bounties-filter-tooltip-value-input "Min" tooltip-open? (merge common-range-opts + {:current-val current-min + :on-change-val on-min-change-fn})] + [bounties-filter-tooltip-value-input "Max" tooltip-open? (merge common-range-opts + {:current-val current-max + :on-change-val on-max-change-fn})]])) -(defn bounties-filter-tooltip-currency [tooltip-open?] +(defn bounties-filter-tooltip-currency [current-filter-value tooltip-open?] [:div.open-bounties-filter-list (for [t ["ETH" "SNT"]] - [:div.open-bounties-filter-list-option-checkbox - [:label - [:input - {:type "checkbox" - :on-focus #(reset! tooltip-open? true)}] - [:div.text t]]])]) + (let [active? (and current-filter-value (current-filter-value t))] + [:div.open-bounties-filter-list-option-checkbox + [:label + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|currency + (cond + (and active? (= #{t} current-filter-value)) nil + active? (disj current-filter-value t) + :else (into #{t} current-filter-value))])} + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true)}] + [:div.text t]]]))]) -(defn bounties-filter-tooltip-date [] +(defn bounties-filter-tooltip-date [current-filter-value tooltip-open?] [:div.open-bounties-filter-list - (for [t ["Last week" "Last month" "Last 3 months"]] + (for [[option-type option-text] ui-model/bounty-filter-type-date-options-def] + ^{:key (str option-type)} [:div.open-bounties-filter-list-option - t])]) + (merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|date + option-type]) + (reset! tooltip-open? false))} + (when (= option-type current-filter-value) + {:class "active"})) + option-text])]) -(defn bounties-filter-tooltip-owner [tooltip-open?] +(defn bounties-filter-tooltip-owner [current-filter-value tooltip-open?] [:div.open-bounties-filter-list (for [t ["status-im" "aragon"]] - [:div.open-bounties-filter-list-option-checkbox - [:label - [:input - {:type "checkbox" - :on-focus #(reset! tooltip-open? true)}] - [:div.text t]]])]) + (let [active? (and current-filter-value (current-filter-value t))] + [:div.open-bounties-filter-list-option-checkbox + [:label + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|owner + (cond + (and active? (= #{t} current-filter-value)) nil + active? (disj current-filter-value t) + :else (into #{t} current-filter-value))])} + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true) + :checked (when active? "checked")}] + [:div.text t]]]))]) -(defn bounties-filter [name tooltip-content] +(defn- tooltip-view-for-filter-type [filter-type] + (condp = filter-type + ::ui-model/bounty-filter-type|value bounties-filter-tooltip-value + ::ui-model/bounty-filter-type|currency bounties-filter-tooltip-currency + ::ui-model/bounty-filter-type|date bounties-filter-tooltip-date + ::ui-model/bounty-filter-type|owner bounties-filter-tooltip-owner)) + +(defn bounty-filter-view [filter-type current-filter-value] (let [open? (r/atom false)] - (fn [name tooltip-content] + (fn [filter-type current-filter-value] [:div.open-bounties-filter-element-container {:tab-index 0 :on-focus #(reset! open? true) :on-blur #(reset! open? false)} [:div.open-bounties-filter-element {:on-mouse-down #(swap! open? not) - :class (when @open? "open-bounties-filter-element-active")} - name] + :class (when (or current-filter-value @open?) + "open-bounties-filter-element-active")} + [:div.text + (if current-filter-value + (ui-model/bounty-filter-value->short-text filter-type current-filter-value) + (ui-model/bounty-filter-type->name filter-type))] + (when current-filter-value + [:img.remove + {:src "bounty-filter-remove.svg" + :tab-index 0 + :on-focus #(.stopPropagation %) + :on-mouse-down (fn [e] + (.stopPropagation e) + (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) + (reset! open? false))}])] (when @open? [bounties-filter-tooltip - [tooltip-content open?]])]))) + [(tooltip-view-for-filter-type filter-type) current-filter-value open?]])]))) -(defn bounties-filters [] - [:div.open-bounties-filter - [bounties-filter "Value" bounties-filter-tooltip-value] - [bounties-filter "Currency" bounties-filter-tooltip-currency] - [bounties-filter "Date" bounties-filter-tooltip-date] - [bounties-filter "Owner" bounties-filter-tooltip-owner]]) +(defn bounty-filters-view [] + (let [current-filters (rf/subscribe [::subs/open-bounties-filters])] + [:div.open-bounties-filter + ; doall because derefs are not supported in lazy seqs: https://github.com/reagent-project/reagent/issues/18 + (doall + (for [filter-type ui-model/bounty-filter-types] + ^{:key (str filter-type)} + [bounty-filter-view + filter-type + (get @current-filters filter-type)]))])) (defn bounties-sort [] (let [open? (r/atom false)] (fn [] - (let [current-sorting (rf/subscribe [::db/bounty-sorting-type])] + (let [current-sorting (rf/subscribe [::subs/open-bounties-sorting-type])] [:div.open-bounties-sort {:tab-index 0 :on-blur #(reset! open? false)} [:div.open-bounties-sort-element {:on-click #(swap! open? not)} - (db/bounty-sorting-types @current-sorting) + (ui-model/bounty-sorting-types-def @current-sorting) [:div.icon-forward-white-box [:img {:src "icon-forward-white.svg"}]]] (when @open? [:div.open-bounties-sort-element-tooltip - (for [[sorting-type sorting-name] db/bounty-sorting-types] + (for [[sorting-type sorting-name] ui-model/bounty-sorting-types-def] + ^{:key (str sorting-type)} [:div.open-bounties-sort-type {:on-click #(do (reset! open? false) - (rf/dispatch [::handlers/set-bounty-sorting-type sorting-type]))} + (rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))} sorting-name])])])))) (defn bounties-list [open-bounties] [:div.ui.container.open-bounties-container [:div.open-bounties-header "Bounties"] [:div.open-bounties-filter-and-sort - [bounties-filters] + [bounty-filters-view] [bounties-sort]] (if (empty? open-bounties) [:div.view-no-data-container diff --git a/src/cljs/commiteth/db.cljs b/src/cljs/commiteth/db.cljs index e54bcd1..39e8f11 100644 --- a/src/cljs/commiteth/db.cljs +++ b/src/cljs/commiteth/db.cljs @@ -1,19 +1,19 @@ -(ns commiteth.db) - -(def bounty-sorting-types {::bounty-sorting-type|most-recent "Most recent" - ::bounty-sorting-type|lowest-value "Lowest value" - ::bounty-sorting-type|highest-value "Highest value" - ::bounty-sorting-type|owner "Owner"}) +(ns commiteth.db + (:require [commiteth.ui-model :as ui-model])) (def default-db - {:page :bounties - :user nil - :repos-loading? false - :repos {} - :activity-feed-loading? false - :open-bounties-loading? false - :open-bounties [] - :owner-bounties {} - :top-hunters [] - :activity-feed [] - ::bounty-sorting-type ::bounty-sorting-type|most-recent}) + {:page :bounties + :user nil + :repos-loading? false + :repos {} + :activity-feed-loading? false + :open-bounties-loading? false + :open-bounties [] + ::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent + ::open-bounties-filters {::ui-model/bounty-filter-type|value nil + ::ui-model/bounty-filter-type|currency nil + ::ui-model/bounty-filter-type|date nil + ::ui-model/bounty-filter-type|owner nil} + :owner-bounties {} + :top-hunters [] + :activity-feed []}) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index b69f1ca..ef183be 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -455,6 +455,11 @@ (assoc db :user-dropdown-open? false))) (reg-event-db - ::set-bounty-sorting-type + ::set-open-bounties-sorting-type (fn [db [_ sorting-type]] - (assoc db ::db/bounty-sorting-type sorting-type))) + (assoc db ::db/open-bounties-sorting-type sorting-type))) + +(reg-event-db + ::set-open-bounty-filter-type + (fn [db [_ filter-type filter-value]] + (assoc-in db [::db/open-bounties-filters filter-type] filter-value))) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index c6d4340..f9ef6b5 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -83,6 +83,11 @@ (:user-dropdown-open? db))) (reg-sub - ::db/bounty-sorting-type + ::open-bounties-sorting-type (fn [db _] - (::db/bounty-sorting-type db))) + (::db/open-bounties-sorting-type db))) + +(reg-sub + ::open-bounties-filters + (fn [db _] + (::db/open-bounties-filters db))) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs new file mode 100644 index 0000000..e274297 --- /dev/null +++ b/src/cljs/commiteth/ui_model.cljs @@ -0,0 +1,44 @@ +(ns commiteth.ui-model) + +;;;; bounty sorting types + +(def bounty-sorting-types-def {::bounty-sorting-type|most-recent "Most recent" + ::bounty-sorting-type|lowest-value "Lowest value" + ::bounty-sorting-type|highest-value "Highest value" + ::bounty-sorting-type|owner "Owner"}) + +;;;; bounty filter types + +(def bounty-filter-types-def {::bounty-filter-type|value "Value" + ::bounty-filter-type|currency "Currency" + ::bounty-filter-type|date "Date" + ::bounty-filter-type|owner "Owner"}) + +(def bounty-filter-types (keys bounty-filter-types-def)) + +(defn bounty-filter-type->name [filter-type] + (bounty-filter-types-def filter-type)) + +(def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week" + ::bounty-filter-type-date-option|last-month "Last month" + ::bounty-filter-type-date-option|last-3-months "Last 3 months"}) + +(def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) + +(defn bounty-filter-type-date-option->name [option] + (bounty-filter-type-date-options-def option)) + +(defn bounty-filter-value->short-text [filter-type filter-value] + (cond + (= filter-type ::bounty-filter-type|date) + (bounty-filter-type-date-option->name filter-value) + + (#{::bounty-filter-type|owner + ::bounty-filter-type|currency} filter-type) + (str (bounty-filter-type->name filter-type) " (" (count filter-value) ")") + + (= filter-type ::bounty-filter-type|value) + (str "$" (first filter-value) "-$" (second filter-value)) + + :else + (str filter-type " with val " filter-value))) diff --git a/src/less/style.less b/src/less/style.less index 4e4b05e..669db74 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -430,18 +430,29 @@ font-weight: 500; line-height: 1.0; color: #8d99a4; - padding: 8px 12px; + //padding: 8px 12px; border-radius: 8px; border: solid 1px rgba(151, 151, 151, 0.2); margin-right: 10px; position: relative; + display: flex; + + .text { + margin: 8px 12px; + } + + .remove { + margin-left: -6px; + margin-right: 5px; + } } .open-bounties-filter-element:hover { cursor: pointer; } - .open-bounties-filter-element-active { + .open-bounties-filter-element-active + { background-color: #57a7ed; color: #ffffff; } @@ -582,6 +593,11 @@ border: solid 1px rgba(151, 151, 151, 0.2); } + .open-bounties-filter-list-option.active { + background-color: #57a7ed; + color: #ffffff; + } + .open-bounties-filter-list-option:not(:first-child) { margin-top: 8px; } From 4563781192856026ba284f94334d06ee704c727d Mon Sep 17 00:00:00 2001 From: pablodip Date: Sat, 20 Jan 2018 10:49:07 +0100 Subject: [PATCH 045/100] take some filter options from open bounties --- src/cljs/commiteth/bounties.cljs | 64 ++++++++++++++------------- src/cljs/commiteth/subscriptions.cljs | 19 ++++++++ src/less/style.less | 1 + 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 83aa4f3..ffd3162 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -92,21 +92,22 @@ :on-change-val on-max-change-fn})]])) (defn bounties-filter-tooltip-currency [current-filter-value tooltip-open?] - [:div.open-bounties-filter-list - (for [t ["ETH" "SNT"]] - (let [active? (and current-filter-value (current-filter-value t))] - [:div.open-bounties-filter-list-option-checkbox - [:label - {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|currency - (cond - (and active? (= #{t} current-filter-value)) nil - active? (disj current-filter-value t) - :else (into #{t} current-filter-value))])} - [:input - {:type "checkbox" - :on-focus #(reset! tooltip-open? true)}] - [:div.text t]]]))]) + (let [currencies (rf/subscribe [::subs/open-bounties-currencies])] + [:div.open-bounties-filter-list + (for [currency @currencies] + (let [active? (and current-filter-value (current-filter-value currency))] + [:div.open-bounties-filter-list-option-checkbox + [:label + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|currency + (cond + (and active? (= #{currency} current-filter-value)) nil + active? (disj current-filter-value currency) + :else (into #{currency} current-filter-value))])} + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true)}] + [:div.text currency]]]))])) (defn bounties-filter-tooltip-date [current-filter-value tooltip-open?] [:div.open-bounties-filter-list @@ -122,22 +123,23 @@ option-text])]) (defn bounties-filter-tooltip-owner [current-filter-value tooltip-open?] - [:div.open-bounties-filter-list - (for [t ["status-im" "aragon"]] - (let [active? (and current-filter-value (current-filter-value t))] - [:div.open-bounties-filter-list-option-checkbox - [:label - {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|owner - (cond - (and active? (= #{t} current-filter-value)) nil - active? (disj current-filter-value t) - :else (into #{t} current-filter-value))])} - [:input - {:type "checkbox" - :on-focus #(reset! tooltip-open? true) - :checked (when active? "checked")}] - [:div.text t]]]))]) + (let [owners (rf/subscribe [::subs/open-bounties-owners])] + [:div.open-bounties-filter-list + (for [owner @owners] + (let [active? (and current-filter-value (current-filter-value owner))] + [:div.open-bounties-filter-list-option-checkbox + [:label + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|owner + (cond + (and active? (= #{owner} current-filter-value)) nil + active? (disj current-filter-value owner) + :else (into #{owner} current-filter-value))])} + [:input + {:type "checkbox" + :on-focus #(reset! tooltip-open? true) + :checked (when active? "checked")}] + [:div.text owner]]]))])) (defn- tooltip-view-for-filter-type [filter-type] (condp = filter-type diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index f9ef6b5..7ad64f9 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -91,3 +91,22 @@ ::open-bounties-filters (fn [db _] (::db/open-bounties-filters db))) + +(reg-sub + ::open-bounties-owners + :<- [:open-bounties] + (fn [open-bounties _] + (->> open-bounties + (map :repo-owner) + set))) + +(reg-sub + ::open-bounties-currencies + :<- [:open-bounties] + (fn [open-bounties _] + (let [token-ids (->> open-bounties + (map :tokens) + (map keys) + (filter identity) + set)] + (into #{"ETH"} token-ids)))) diff --git a/src/less/style.less b/src/less/style.less index 669db74..20de0c3 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -472,6 +472,7 @@ font-family: "PostGrotesk-Book"; font-size: 16px; line-height: 1.5; + z-index: 999; .open-bounties-filter-element-tooltip-value-input-container { display: flex; From d470b3aea34cf3e53d8c90512fccbf1b67a8a339 Mon Sep 17 00:00:00 2001 From: pablodip Date: Sun, 21 Jan 2018 08:06:09 +0100 Subject: [PATCH 046/100] zindex --- src/less/style.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/less/style.less b/src/less/style.less index 20de0c3..7ef8658 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -675,6 +675,7 @@ line-height: 1.0; text-align: left; color: #ffffff; + z-index: 999; } .open-bounties-sort-type { From 09cd84a89b527809a78f1938bbd6796c06be5109 Mon Sep 17 00:00:00 2001 From: pablodip Date: Sun, 21 Jan 2018 10:27:32 +0100 Subject: [PATCH 047/100] sorting behaviour --- src/clj/commiteth/routes/services.clj | 3 ++- src/cljs/commiteth/bounties.cljs | 8 +++---- src/cljs/commiteth/subscriptions.cljs | 31 +++++++++++++++++-------- src/cljs/commiteth/ui_model.cljs | 33 +++++++++++++++++++++++---- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/clj/commiteth/routes/services.clj b/src/clj/commiteth/routes/services.clj index 82c5633..6a60fec 100644 --- a/src/clj/commiteth/routes/services.clj +++ b/src/clj/commiteth/routes/services.clj @@ -155,7 +155,8 @@ :value_usd :value-usd :claim_count :claim-count :balance_eth :balance-eth - :user_has_address :user-has-address}] + :user_has_address :user-has-address + :created_at :created-at}] (map #(-> % (rename-keys renames) (update :value-usd usd-decimal->str) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index ffd3162..5169598 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -196,19 +196,19 @@ :on-blur #(reset! open? false)} [:div.open-bounties-sort-element {:on-click #(swap! open? not)} - (ui-model/bounty-sorting-types-def @current-sorting) + (ui-model/bounty-sorting-type->name @current-sorting) [:div.icon-forward-white-box [:img {:src "icon-forward-white.svg"}]]] (when @open? [:div.open-bounties-sort-element-tooltip - (for [[sorting-type sorting-name] ui-model/bounty-sorting-types-def] + (for [sorting-type (keys ui-model/bounty-sorting-types-def)] ^{:key (str sorting-type)} [:div.open-bounties-sort-type {:on-click #(do (reset! open? false) (rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))} - sorting-name])])])))) + (ui-model/bounty-sorting-type->name sorting-type)])])])))) (defn bounties-list [open-bounties] [:div.ui.container.open-bounties-container @@ -225,7 +225,7 @@ (defn bounties-page [] - (let [open-bounties (rf/subscribe [:open-bounties]) + (let [open-bounties (rf/subscribe [::subs/filtered-and-sorted-open-bounties]) open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] (fn [] (if @open-bounties-loading? diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 7ad64f9..d91f770 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -1,10 +1,11 @@ (ns commiteth.subscriptions (:require [re-frame.core :refer [reg-sub]] - [commiteth.db :as db])) + [commiteth.db :as db] + [commiteth.ui-model :as ui-model])) (reg-sub - :db - (fn [db _] db)) + :db + (fn [db _] db)) (reg-sub :page @@ -68,17 +69,17 @@ (get-in db path))) (reg-sub - :usage-metrics - (fn [db _] - (:usage-metrics db))) + :usage-metrics + (fn [db _] + (:usage-metrics db))) (reg-sub - :metrics-loading? - (fn [db _] - (:metrics-loading? db))) + :metrics-loading? + (fn [db _] + (:metrics-loading? db))) (reg-sub - :user-dropdown-open? + :user-dropdown-open? (fn [db _] (:user-dropdown-open? db))) @@ -110,3 +111,13 @@ (filter identity) set)] (into #{"ETH"} token-ids)))) + +(reg-sub + ::filtered-and-sorted-open-bounties + :<- [:open-bounties] + :<- [::open-bounties-sorting-type] + (fn [[open-bounties sorting-type] _] + (println "RAW" open-bounties) + (cond->> open-bounties + sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type) + ))) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index e274297..39dc764 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -2,10 +2,35 @@ ;;;; bounty sorting types -(def bounty-sorting-types-def {::bounty-sorting-type|most-recent "Most recent" - ::bounty-sorting-type|lowest-value "Lowest value" - ::bounty-sorting-type|highest-value "Highest value" - ::bounty-sorting-type|owner "Owner"}) +(def bounty-sorting-types-def + {::bounty-sorting-type|most-recent {::bounty-sorting-type.name "Most recent" + ::bounty-sorting-type.sort-key-fn (fn [bounty] + (:created-at bounty)) + ::bounty-sorting-type.sort-comparator-fn compare} + ::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value" + ::bounty-sorting-type.sort-key-fn (fn [bounty] + (:value-usd bounty)) + ::bounty-sorting-type.sort-comparator-fn compare} + ::bounty-sorting-type|highest-value {::bounty-sorting-type.name "Highest value" + ::bounty-sorting-type.sort-key-fn (fn [bounty] + (:value-usd bounty)) + ::bounty-sorting-type.sort-comparator-fn (comp - compare)} + ::bounty-sorting-type|owner {::bounty-sorting-type.name "Owner" + ::bounty-sorting-type.sort-key-fn (fn [bounty] + (:repo-owner bounty)) + ::bounty-sorting-type.sort-comparator-fn compare}}) + +(defn bounty-sorting-type->name [sorting-type] + (-> bounty-sorting-types-def (get sorting-type) ::bounty-sorting-type.name)) + +(defn sort-bounties-by-sorting-type [sorting-type bounties] + (let [keyfn (-> bounty-sorting-types-def + sorting-type + ::bounty-sorting-type.sort-key-fn) + comparator (-> bounty-sorting-types-def + sorting-type + ::bounty-sorting-type.sort-comparator-fn)] + (sort-by keyfn comparator bounties))) ;;;; bounty filter types From d937bf361daff7bc534ab6c69c9cab2643f5eb43 Mon Sep 17 00:00:00 2001 From: pablodip Date: Mon, 22 Jan 2018 12:21:04 +0100 Subject: [PATCH 048/100] fix ui checkbox checked and start filtering --- src/cljs/commiteth/bounties.cljs | 11 ++++--- src/cljs/commiteth/subscriptions.cljs | 10 +++---- src/cljs/commiteth/ui_model.cljs | 42 +++++++++++++++++++++------ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 5169598..cfb5f57 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -95,7 +95,8 @@ (let [currencies (rf/subscribe [::subs/open-bounties-currencies])] [:div.open-bounties-filter-list (for [currency @currencies] - (let [active? (and current-filter-value (current-filter-value currency))] + (let [active? (boolean (and current-filter-value (current-filter-value currency)))] + ^{:key (str currency)} [:div.open-bounties-filter-list-option-checkbox [:label {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type @@ -106,6 +107,7 @@ :else (into #{currency} current-filter-value))])} [:input {:type "checkbox" + :checked active? :on-focus #(reset! tooltip-open? true)}] [:div.text currency]]]))])) @@ -126,7 +128,8 @@ (let [owners (rf/subscribe [::subs/open-bounties-owners])] [:div.open-bounties-filter-list (for [owner @owners] - (let [active? (and current-filter-value (current-filter-value owner))] + (let [active? (boolean (and current-filter-value (current-filter-value owner)))] + ^{:key (str owner)} [:div.open-bounties-filter-list-option-checkbox [:label {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type @@ -137,8 +140,8 @@ :else (into #{owner} current-filter-value))])} [:input {:type "checkbox" - :on-focus #(reset! tooltip-open? true) - :checked (when active? "checked")}] + :checked active? + :on-focus #(reset! tooltip-open? true)}] [:div.text owner]]]))])) (defn- tooltip-view-for-filter-type [filter-type] diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index d91f770..eadb318 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -107,7 +107,7 @@ (fn [open-bounties _] (let [token-ids (->> open-bounties (map :tokens) - (map keys) + (mapcat keys) (filter identity) set)] (into #{"ETH"} token-ids)))) @@ -115,9 +115,9 @@ (reg-sub ::filtered-and-sorted-open-bounties :<- [:open-bounties] + :<- [::open-bounties-filters] :<- [::open-bounties-sorting-type] - (fn [[open-bounties sorting-type] _] - (println "RAW" open-bounties) + (fn [[open-bounties filters sorting-type] _] (cond->> open-bounties - sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type) - ))) + filters (ui-model/filter-bounties filters) + sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type)))) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 39dc764..75f1c6f 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -1,4 +1,5 @@ -(ns commiteth.ui-model) +(ns commiteth.ui-model + (:require [clojure.set :as set])) ;;;; bounty sorting types @@ -24,9 +25,9 @@ (-> bounty-sorting-types-def (get sorting-type) ::bounty-sorting-type.name)) (defn sort-bounties-by-sorting-type [sorting-type bounties] - (let [keyfn (-> bounty-sorting-types-def - sorting-type - ::bounty-sorting-type.sort-key-fn) + (let [keyfn (-> bounty-sorting-types-def + sorting-type + ::bounty-sorting-type.sort-key-fn) comparator (-> bounty-sorting-types-def sorting-type ::bounty-sorting-type.sort-comparator-fn)] @@ -34,15 +35,27 @@ ;;;; bounty filter types -(def bounty-filter-types-def {::bounty-filter-type|value "Value" - ::bounty-filter-type|currency "Currency" - ::bounty-filter-type|date "Date" - ::bounty-filter-type|owner "Owner"}) +(def bounty-filter-types-def + {::bounty-filter-type|value {::bounty-filter-type.name "Value" + ::bounty-filter-type.predicate (fn [filter-value bounty] + true)} + ::bounty-filter-type|currency {::bounty-filter-type.name "Currency" + ::bounty-filter-type.predicate (fn [filter-value bounty] + (and (or (not-any? #{"ETH"} filter-value) + (< 0 (:balance-eth bounty))) + (set/subset? (->> filter-value (remove #{"ETH"}) set) + (-> bounty :tokens keys set))))} + ::bounty-filter-type|date {::bounty-filter-type.name "Date" + ::bounty-filter-type.predicate (fn [filter-value bounty] + true)} + ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" + ::bounty-filter-type.predicate (fn [filter-value bounty] + true)}}) (def bounty-filter-types (keys bounty-filter-types-def)) (defn bounty-filter-type->name [filter-type] - (bounty-filter-types-def filter-type)) + (-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name)) (def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week" ::bounty-filter-type-date-option|last-month "Last month" @@ -67,3 +80,14 @@ :else (str filter-type " with val " filter-value))) + +(defn filter-bounties [filters-by-type bounties] + (let [filter-preds (->> filters-by-type + (remove #(nil? (val %))) + (map (fn [[filter-type filter-value]] + (let [pred (-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.predicate)] + (partial pred filter-value))))) + filters-pred (fn [bounty] + (every? #(% bounty) filter-preds))] + (->> bounties + (filter filters-pred)))) From 326c221988e5dff5ace552deb892233cfe750fe1 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Mon, 22 Jan 2018 13:53:33 +0200 Subject: [PATCH 049/100] [FIX #226] UI changes; refactor React refs usage --- src/cljs/commiteth/activity.cljs | 39 ++++++++++++++------------- src/cljs/commiteth/bounties.cljs | 30 ++++++++++----------- src/cljs/commiteth/common.cljs | 34 ++++++++++++++++------- src/cljs/commiteth/db.cljs | 3 +-- src/cljs/commiteth/handlers.cljs | 17 +++++------- src/cljs/commiteth/subscriptions.cljs | 13 +++------ src/less/style.less | 20 +++++++++++--- 7 files changed, 88 insertions(+), 68 deletions(-) diff --git a/src/cljs/commiteth/activity.cljs b/src/cljs/commiteth/activity.cljs index d59ed19..2e9444e 100644 --- a/src/cljs/commiteth/activity.cljs +++ b/src/cljs/commiteth/activity.cljs @@ -2,7 +2,9 @@ (:require [re-frame.core :as rf] [reagent.core :as r] [commiteth.common :refer [moment-timestamp + items-per-page display-data-page + scroll-div issue-url]])) @@ -55,28 +57,29 @@ (str (subs (str tla) 1) " " balance)])]) [:div.time (moment-timestamp updated)]]]]) - - -(defn activity-list [activity-page-data] +(defn activity-list [{:keys [items item-count page-number total-count] + :as activity-page-data}] (if (empty? (:items activity-page-data)) [:div.view-no-data-container [:p "No recent activity yet"]] - (display-data-page activity-page-data activity-item :set-activity-page-number))) + [:div + (let [left (inc (* (dec page-number) items-per-page)) + right (dec (+ left item-count))] + [:div.item-counts-label + [:span (str "Showing " left "-" right " of " total-count)]]) + (display-data-page activity-page-data activity-item)])) (defn activity-page [] (let [activity-page-data (rf/subscribe [:activities-page]) activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]]) - container-element (atom nil) - render-fn (fn [] - (if @activity-feed-loading? - [:div.view-loading-container - [:div.ui.active.inverted.dimmer - [:div.ui.text.loader.view-loading-label "Loading"]]] - [:div.ui.container.activity-container - {:ref #(reset! container-element %1)} - [activity-list @activity-page-data]]))] - (r/create-class - {:component-did-update (fn [] - (when @container-element - (.scrollIntoView @container-element))) - :reagent-render render-fn}))) + container-element (atom nil)] + (fn [] + (if @activity-feed-loading? + [:div.view-loading-container + [:div.ui.active.inverted.dimmer + [:div.ui.text.loader.view-loading-label "Loading"]]] + [:div.ui.container.activity-container + {:ref #(reset! container-element %1)} + [scroll-div container-element] + [:div.activity-header "Activities"] + [activity-list @activity-page-data]])))) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 2868da3..fdf741d 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -3,6 +3,7 @@ [reagent.core :as r] [commiteth.common :refer [moment-timestamp display-data-page + scroll-div items-per-page issue-url]])) @@ -55,23 +56,20 @@ right (dec (+ left item-count))] [:div.item-counts-label [:span (str "Showing " left "-" right " of " total-count)]]) - (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])) + (display-data-page bounty-page-data bounty-item)])) (defn bounties-page [] (let [bounty-page-data (rf/subscribe [:open-bounties-page]) open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]]) - container-element (atom nil) - render-fn (fn [] - (if @open-bounties-loading? - [:div.view-loading-container - [:div.ui.active.inverted.dimmer - [:div.ui.text.loader.view-loading-label "Loading"]]] - [:div.ui.container.open-bounties-container - {:ref #(reset! container-element %1)} - [:div.open-bounties-header "Bounties"] - [bounties-list @bounty-page-data]]))] - (r/create-class - {:component-did-update (fn [] - (when @container-element - (.scrollIntoView @container-element))) - :reagent-render render-fn}))) + container-element (atom nil)] + (fn [] + (if @open-bounties-loading? + [:div.view-loading-container + [:div.ui.active.inverted.dimmer + [:div.ui.text.loader.view-loading-label "Loading"]]] + [:div.ui.container.open-bounties-container + {:ref #(reset! container-element %1)} + [scroll-div container-element] + [:div.open-bounties-header "Bounties"] + [bounties-list @bounty-page-data]])) + )) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 6eed787..417dea1 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -1,6 +1,7 @@ (ns commiteth.common (:require [reagent.core :as r] [re-frame.core :as rf] + [clojure.string :as str] [cljsjs.moment])) (defn input [val-ratom props] @@ -30,9 +31,22 @@ (defn issue-url [owner repo number] (str "https://github.com/" owner "/" repo "/issues/" number)) +(defn scroll-div [container-element] + "This is an invisible div that exists + for the sole purpose of scrolling the container-element into view + when page-number is updated" + (let [page-number (rf/subscribe [:page-number])] + (r/create-class + {:component-did-update (fn [] + (when @container-element + (.scrollIntoView @container-element))) + :reagent-render (fn [] + [:div {:style {:display "none"}} + @page-number])}))) + (def items-per-page 15) -(defn draw-page-numbers [page-number page-count set-page-kw] +(defn draw-page-numbers [page-number page-count] "Draw page numbers for the pagination component. Inserts ellipsis when list is too long, by default max 6 items are allowed" @@ -41,8 +55,8 @@ [:div.rectangle-rounded (cond-> {} (not current?) - (assoc :class "grayed-out" - :on-click #(rf/dispatch [set-page-kw i]))) + (assoc :class "grayed-out-page-num" + :on-click #(rf/dispatch [:set-page-number i]))) i]) max-page-nums 6] [:div.page-nums-container @@ -78,8 +92,7 @@ total-count page-number page-count]} - draw-item-fn - set-page-kw] + draw-item-fn] "Draw data items along with pagination controls" (let [draw-items (fn [] (into [:div.ui.items] @@ -90,14 +103,17 @@ forward?) (and (< 1 page-number) (not forward?))) - (rf/dispatch [set-page-kw + (rf/dispatch [:set-page-number (if forward? (inc page-number) (dec page-number))]))) draw-rect (fn [direction] - (let [forward? (= direction :forward)] + (let [forward? (= direction :forward) + gray-out? (or (and forward? (= page-number page-count)) + (and (not forward?) (= page-number 1))) ] [:div.rectangle-rounded - {:on-click (on-direction-click forward?)} + (cond-> {:on-click (on-direction-click forward?)} + gray-out? (assoc :class "grayed-out-direction")) [:img.icon-forward-gray (cond-> {:src "icon-forward-gray.svg"} forward? (assoc :class "flip-horizontal"))]]))] @@ -110,6 +126,6 @@ [draw-rect :backward] [draw-rect :forward] [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] - [draw-page-numbers page-number page-count set-page-kw]]]))) + [draw-page-numbers page-number page-count]]]))) diff --git a/src/cljs/commiteth/db.cljs b/src/cljs/commiteth/db.cljs index a93c8bc..76b9a1c 100644 --- a/src/cljs/commiteth/db.cljs +++ b/src/cljs/commiteth/db.cljs @@ -8,8 +8,7 @@ :activity-feed-loading? false :open-bounties-loading? false :open-bounties [] - :bounty-page-number 1 - :activity-page-number 1 + :page-number 1 :owner-bounties {} :top-hunters [] :activity-feed []}) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index b1114fe..077d280 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -63,19 +63,15 @@ (reg-event-db - :set-active-page - (fn [db [_ page]] - (assoc db :page page))) + :set-active-page + (fn [db [_ page]] + (assoc db :page page + :page-number 1))) (reg-event-db - :set-bounty-page-number + :set-page-number (fn [db [_ page]] - (assoc db :bounty-page-number page))) - -(reg-event-db - :set-activity-page-number - (fn [db [_ page]] - (assoc db :activity-page-number page))) + (assoc db :page-number page))) (reg-event-fx :set-flash-message @@ -90,7 +86,6 @@ (fn [db _] (dissoc db :flash-message))) - (defn assoc-in-if-not-empty [m path val] (if (seq val) (assoc-in m path val) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index f9cb555..c1b8952 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -37,14 +37,14 @@ (vec (:open-bounties db)))) (reg-sub - :bounty-page-number + :page-number (fn [db _] - (:bounty-page-number db))) + (:page-number db))) (reg-sub :open-bounties-page :<- [:open-bounties] - :<- [:bounty-page-number] + :<- [:page-number] (fn [[open-bounties page-number] _] (let [total-count (count open-bounties) start (* (dec page-number) items-per-page) @@ -77,15 +77,10 @@ (fn [db _] (vec (:activity-feed db)))) -(reg-sub - :activity-page-number - (fn [db _] - (:activity-page-number db))) - (reg-sub :activities-page :<- [:activity-feed] - :<- [:activity-page-number] + :<- [:page-number] (fn [[activities page-number] _] (let [total-count (count activities) start (* (dec page-number) items-per-page) diff --git a/src/less/style.less b/src/less/style.less index 145aca5..07e3646 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -523,7 +523,7 @@ } } - border: #e7e7e7 solid 0.1em!important; + border-bottom: #eaecee 1px solid !important; box-shadow: none!important; border-radius: 0.3em!important; padding: 0.8em 1em 1.1em!important; @@ -588,9 +588,16 @@ } .activity-container { + background-color: #fff; transform: translate(0, -45px); border-radius: 10px; - + padding: 24px; + .activity-header { + font-family: "PostGrotesk-Medium"; + font-size: 21px; + font-weight: 500; + color: #42505c; + } } .footer-row { @@ -890,12 +897,19 @@ body { cursor: pointer; } -.grayed-out { +.grayed-out-page-num { color: #8d99a4; background-color: #f2f5f8; opacity: 1.0; } +.grayed-out-direction { + color: #8d99a4; + background-color: #f2f5f8; + opacity: 0.4; + cursor: auto +} + .flip-horizontal { -moz-transform: scaleX(-1); -webkit-transform: scaleX(-1); From df8de465320cb50142e6168d6d6d8287f0a105ef Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Mon, 22 Jan 2018 17:42:01 +0200 Subject: [PATCH 050/100] Pagination controls: apply CSS media queries for mobile devices --- src/cljs/commiteth/common.cljs | 5 +++-- src/less/style.less | 27 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 417dea1..8cb213d 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -123,8 +123,9 @@ [:div [draw-items] [:div.page-nav-container - [draw-rect :backward] - [draw-rect :forward] + [:div.page-direction-container + [draw-rect :backward] + [draw-rect :forward]] [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] [draw-page-numbers page-number page-count]]]))) diff --git a/src/less/style.less b/src/less/style.less index 07e3646..d8c078c 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -919,15 +919,6 @@ body { filter: fliph; } -.pagination-text { - width: 83px; - height: 15px; - font-family: PostGrotesk; - font-size: 15px; - text-align: center; - color: #8d99a4; -} - .icon-forward-gray { width: 24px; height: 24px; @@ -937,10 +928,24 @@ body { .page-nav-container { display: flex; margin: 0 -6px; + @media (max-width: 767px) { + align-items: center; + flex-direction: column; + } +} + +.page-direction-container { + display: flex; + @media (max-width: 767px) { + flex-direction: row; + } } .page-nums-container { display: flex; + @media (max-width: 767px) { + display: none; + } margin-left: auto; justify-content: space-between; } @@ -955,6 +960,10 @@ body { flex: none; align-items: center; justify-content: center; + + @media (max-width: 767px) { + margin-top: 12px; + } } .item-counts-label { From 0db8136730cc97177bdefa2d352e1e21ac9a09f4 Mon Sep 17 00:00:00 2001 From: Anton Danchenko Date: Mon, 22 Jan 2018 18:19:05 +0200 Subject: [PATCH 051/100] added local env --- test/end-to-end/pages/thirdparty/github.py | 2 +- test/end-to-end/tests/basetestcase.py | 17 +++++++++++------ test/end-to-end/tests/conftest.py | 4 ++++ test/end-to-end/tests/test_contracts.py | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/test/end-to-end/pages/thirdparty/github.py b/test/end-to-end/pages/thirdparty/github.py index 2585c91..eaf3c84 100644 --- a/test/end-to-end/pages/thirdparty/github.py +++ b/test/end-to-end/pages/thirdparty/github.py @@ -84,7 +84,7 @@ class LabelsButton(BaseButton): class BountyLabel(BaseButton): def __init__(self, driver): super(LabelsButton.BountyLabel, self).__init__(driver) - self.locator = self.Locator.css_selector("[data-name='bounty']") + self.locator = self.Locator.css_selector("[data-name='748942015']") class CrossButton(BaseButton): def __init__(self, driver): diff --git a/test/end-to-end/tests/basetestcase.py b/test/end-to-end/tests/basetestcase.py index 398673b..b658ed1 100644 --- a/test/end-to-end/tests/basetestcase.py +++ b/test/end-to-end/tests/basetestcase.py @@ -39,16 +39,21 @@ class BaseTestCase: desired_caps['captureHtml'] = False return desired_caps + @property + def environment(self): + return pytest.config.getoption('env') + def setup_method(self): self.errors = [] - # options = webdriver.ChromeOptions() - # options.add_argument('--start-fullscreen') - # options.add_extension(path.abspath('/resources/metamask3_12_0.crx')) - - self.driver = webdriver.Remote(self.executor_sauce_lab, - desired_capabilities=self.capabilities_sauce_lab) + if self.environment == 'local': + options = webdriver.ChromeOptions() + options.add_argument('--start-fullscreen') + self.driver = webdriver.Chrome(options=options) + if self.environment == 'sauce': + self.driver = webdriver.Remote(self.executor_sauce_lab, + desired_capabilities=self.capabilities_sauce_lab) self.driver.implicitly_wait(5) def verify_no_errors(self): diff --git a/test/end-to-end/tests/conftest.py b/test/end-to-end/tests/conftest.py index 62f296e..c0abbae 100644 --- a/test/end-to-end/tests/conftest.py +++ b/test/end-to-end/tests/conftest.py @@ -11,6 +11,10 @@ def pytest_addoption(parser): action='store', default=True, help='Display each test step in terminal as plain text: True/False') + parser.addoption('--env', + action='store', + default='sauce', + help='Specify environment, sauce or local') def pytest_configure(config): diff --git a/test/end-to-end/tests/test_contracts.py b/test/end-to-end/tests/test_contracts.py index 68bb4cb..6e617f3 100644 --- a/test/end-to-end/tests/test_contracts.py +++ b/test/end-to-end/tests/test_contracts.py @@ -17,6 +17,6 @@ class TestLogin(BaseTestCase): bounties_page = github.authorize_sob.click() github.install_sob_plugin() assert bounties_page.bounties_header.text == 'Bounties' - assert bounties_page.top_hunters_header.text == 'Top hunters' + assert bounties_page.top_hunters_header.text == 'Top 5 hunters' github.create_new_bounty() github.get_deployed_contract() From 3310a34566920c98869c9a252e24e0c2ae77e18d Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Tue, 23 Jan 2018 12:25:56 +0200 Subject: [PATCH 052/100] Dev fix: add cljs source paths to :dev profile and remove CIDER dep. Additional source paths help to resolve the Piggieback REPL issue. I was getting annoying "No such namespace warnings" for commiteth.* namespaces --- project.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project.clj b/project.clj index e6af24a..3c8bd92 100644 --- a/project.clj +++ b/project.clj @@ -69,7 +69,6 @@ [lein-auto "0.1.2"] [lein-less "1.7.5"] [lein-shell "0.5.0"] - [cider/cider-nrepl "0.15.0-SNAPSHOT"] [lein-sha-version "0.1.1"]] @@ -144,7 +143,7 @@ :prep-tasks ["build-contracts" "javac"] :doo {:build "test"} - :source-paths ["env/dev/clj" "test/clj"] + :source-paths ["env/dev/clj" "test/clj" "src/cljs" "env/dev/cljs"] :resource-paths ["env/dev/resources"] :repl-options {:init-ns user :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} From 12edb1abc60226b05beb8030d0a7ac2436c2ebd0 Mon Sep 17 00:00:00 2001 From: pablodip Date: Tue, 23 Jan 2018 17:48:45 +0100 Subject: [PATCH 053/100] some fixes and behaviours of sort and filter --- src/cljs/commiteth/bounties.cljs | 8 ++++---- src/cljs/commiteth/ui_model.cljs | 26 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index cfb5f57..b4bb03c 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -74,13 +74,13 @@ on-change-fn (fn [min-val max-val] (rf/dispatch [::handlers/set-open-bounty-filter-type ::ui-model/bounty-filter-type|value - [(min min-val (dec default-max)) - (max max-val (inc default-min))]])) + [(min min-val default-max) + (max max-val default-min)]])) on-min-change-fn (fn [new-min] - (let [new-max (max current-max (inc new-min))] + (let [new-max (max current-max (min default-max new-min))] (on-change-fn new-min new-max))) on-max-change-fn (fn [new-max] - (let [new-min (min current-min (dec new-max))] + (let [new-min (min current-min (max default-min new-max))] (on-change-fn new-min new-max)))] [:div "$0 - $1000+" diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 75f1c6f..75e9c3c 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -1,5 +1,8 @@ (ns commiteth.ui-model - (:require [clojure.set :as set])) + (:require [clojure.set :as set] + [cljs-time.core :as t] + [cljs-time.coerce :as t-coerce] + [cljs-time.format :as t-format])) ;;;; bounty sorting types @@ -10,11 +13,11 @@ ::bounty-sorting-type.sort-comparator-fn compare} ::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value" ::bounty-sorting-type.sort-key-fn (fn [bounty] - (:value-usd bounty)) + (js/parseFloat (:value-usd bounty))) ::bounty-sorting-type.sort-comparator-fn compare} ::bounty-sorting-type|highest-value {::bounty-sorting-type.name "Highest value" ::bounty-sorting-type.sort-key-fn (fn [bounty] - (:value-usd bounty)) + (js/parseFloat (:value-usd bounty))) ::bounty-sorting-type.sort-comparator-fn (comp - compare)} ::bounty-sorting-type|owner {::bounty-sorting-type.name "Owner" ::bounty-sorting-type.sort-key-fn (fn [bounty] @@ -38,7 +41,9 @@ (def bounty-filter-types-def {::bounty-filter-type|value {::bounty-filter-type.name "Value" ::bounty-filter-type.predicate (fn [filter-value bounty] - true)} + (let [min-val (first filter-value) + max-val (second filter-value)] + (<= min-val (:value-usd bounty) max-val)))} ::bounty-filter-type|currency {::bounty-filter-type.name "Currency" ::bounty-filter-type.predicate (fn [filter-value bounty] (and (or (not-any? #{"ETH"} filter-value) @@ -47,10 +52,19 @@ (-> bounty :tokens keys set))))} ::bounty-filter-type|date {::bounty-filter-type.name "Date" ::bounty-filter-type.predicate (fn [filter-value bounty] - true)} + (when-let [created-at-inst (:created-at bounty)] + (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long) + filter-from (condp = filter-value + ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) + ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) + ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3))) + interval (t/interval filter-from (t/now))] + (t/within? interval created-at-date))))} ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" ::bounty-filter-type.predicate (fn [filter-value bounty] - true)}}) + (->> filter-value + (some #{(:repo-owner bounty)}) + boolean))}}) (def bounty-filter-types (keys bounty-filter-types-def)) From 27442937cf94f4a50825264978ff5e89b013da6a Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 24 Jan 2018 09:46:51 +0100 Subject: [PATCH 054/100] fix firefox behaviour when removing filters and with checkbox filters --- src/cljs/commiteth/bounties.cljs | 38 ++++++++++++++++++-------------- src/less/style.less | 8 +++++++ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index b4bb03c..c07576d 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -99,12 +99,15 @@ ^{:key (str currency)} [:div.open-bounties-filter-list-option-checkbox [:label - {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|currency - (cond - (and active? (= #{currency} current-filter-value)) nil - active? (disj current-filter-value currency) - :else (into #{currency} current-filter-value))])} + {:on-click #(do (println "label on-click") + (rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|currency + (cond + (and active? (= #{currency} current-filter-value)) nil + active? (disj current-filter-value currency) + :else (into #{currency} current-filter-value))])) + :tab-index 0 + :on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))} [:input {:type "checkbox" :checked active? @@ -137,7 +140,9 @@ (cond (and active? (= #{owner} current-filter-value)) nil active? (disj current-filter-value owner) - :else (into #{owner} current-filter-value))])} + :else (into #{owner} current-filter-value))]) + :tab-index 0 + :on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))} [:input {:type "checkbox" :checked active? @@ -159,7 +164,7 @@ :on-focus #(reset! open? true) :on-blur #(reset! open? false)} [:div.open-bounties-filter-element - {:on-mouse-down #(swap! open? not) + {:on-mouse-down #(do (println "element on-mouse-down") (swap! open? not)) :class (when (or current-filter-value @open?) "open-bounties-filter-element-active")} [:div.text @@ -167,14 +172,15 @@ (ui-model/bounty-filter-value->short-text filter-type current-filter-value) (ui-model/bounty-filter-type->name filter-type))] (when current-filter-value - [:img.remove - {:src "bounty-filter-remove.svg" - :tab-index 0 - :on-focus #(.stopPropagation %) - :on-mouse-down (fn [e] - (.stopPropagation e) - (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) - (reset! open? false))}])] + [:div.remove-container + {:tab-index 0 + :on-focus #(.stopPropagation %)} + [:img.remove + {:src "bounty-filter-remove.svg" + :on-mouse-down (fn [e] + (.stopPropagation e) + (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) + (reset! open? false))}]])] (when @open? [bounties-filter-tooltip [(tooltip-view-for-filter-type filter-type) current-filter-value open?]])]))) diff --git a/src/less/style.less b/src/less/style.less index 7ef8658..68ed6f5 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -441,6 +441,10 @@ margin: 8px 12px; } + .remove-container { + display: flex; + } + .remove { margin-left: -6px; margin-right: 5px; @@ -618,6 +622,10 @@ cursor: pointer; } + label:focus { + outline: none; + } + input { background: green; transform: scale(1.3); From 561232d6e2b90431f1088a8fd4d4ce882e1aa2ba Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 24 Jan 2018 09:49:21 +0100 Subject: [PATCH 055/100] remove printlns and fix chrome remove filter style --- src/cljs/commiteth/bounties.cljs | 15 +++++++-------- src/less/style.less | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index c07576d..a701762 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -99,13 +99,12 @@ ^{:key (str currency)} [:div.open-bounties-filter-list-option-checkbox [:label - {:on-click #(do (println "label on-click") - (rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|currency - (cond - (and active? (= #{currency} current-filter-value)) nil - active? (disj current-filter-value currency) - :else (into #{currency} current-filter-value))])) + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + ::ui-model/bounty-filter-type|currency + (cond + (and active? (= #{currency} current-filter-value)) nil + active? (disj current-filter-value currency) + :else (into #{currency} current-filter-value))]) :tab-index 0 :on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))} [:input @@ -164,7 +163,7 @@ :on-focus #(reset! open? true) :on-blur #(reset! open? false)} [:div.open-bounties-filter-element - {:on-mouse-down #(do (println "element on-mouse-down") (swap! open? not)) + {:on-mouse-down #(swap! open? not) :class (when (or current-filter-value @open?) "open-bounties-filter-element-active")} [:div.text diff --git a/src/less/style.less b/src/less/style.less index 68ed6f5..5977c85 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -616,6 +616,7 @@ .open-bounties-filter-list-option-checkbox { label { display: flex; + align-items: baseline; } label:hover { From 8414d19404fc26a9219e4ec7d7a9f6e92debfb7b Mon Sep 17 00:00:00 2001 From: Tetiana Churikova Date: Wed, 24 Jan 2018 12:58:58 +0200 Subject: [PATCH 056/100] Config support and other little improvements (#231) * Configure work with remote config; add stepa to verify issue title; * transfer settings to global .gitignore * Change channel and repo path * Delete local env by default * change locator for BountyFooters --- .gitignore | 1 - doc/testing.md | 4 +-- test/end-to-end/pages/openbounty/bounties.py | 28 ++++++++++++++++++++ test/end-to-end/pages/openbounty/landing.py | 3 ++- test/end-to-end/pages/thirdparty/github.py | 6 ++++- test/end-to-end/tests/__init__.py | 11 +++++++- test/end-to-end/tests/basetestcase.py | 14 +++++++--- test/end-to-end/tests/config_example.ini | 13 +++++++++ test/end-to-end/tests/test_contracts.py | 24 +++++++++++++++-- 9 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 test/end-to-end/tests/config_example.ini diff --git a/.gitignore b/.gitignore index 0e3eb68..4f4f73c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,3 @@ profiles.clj .idea resources/contracts node_modules -.DS_Store diff --git a/doc/testing.md b/doc/testing.md index a3e59d0..c0ad83f 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -10,7 +10,7 @@ For testing you will need: * a Github account with administrative access to one or more repositories * for approving bounty payouts you will additionally need access to an Ethereum wallet. So far, Mist and [MetaMask](https://metamask.io/) have been used, but anything that provides the web3 javascript interface should work. -The developers can be reached on the `#commiteth` channel in the [Status slack](http://slack.status.im/). +The developers can be reached on the `#openbounty` channel in the [Status slack](http://slack.status.im/). ### Signing up @@ -60,6 +60,6 @@ To remove issue from the Bounties list you can close it in GitHub. ### Reporting bugs -All bugs should be reported as issues in the [CommitETH Github repository](https://github.com/status-im/commiteth/issues). +All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues). Please first check that there is not already a duplicate issue. Issues should contain exact and minimal step-by-step instructions for reproducing the problem. diff --git a/test/end-to-end/pages/openbounty/bounties.py b/test/end-to-end/pages/openbounty/bounties.py index 91415fd..24a47bc 100644 --- a/test/end-to-end/pages/openbounty/bounties.py +++ b/test/end-to-end/pages/openbounty/bounties.py @@ -1,5 +1,6 @@ from pages.base_page import BasePageObject from pages.base_element import * +from tests import test_data class BountiesHeader(BaseText): @@ -15,11 +16,38 @@ class TopHuntersHeader(BaseText): super(TopHuntersHeader, self).__init__(driver) self.locator = self.Locator.css_selector('.top-hunters-header') +class BountyTitles(BaseText): + + def __init__(self, driver): + super(BountyTitles, self).__init__(driver) + self.locator = self.Locator.css_selector('.open-bounty-item-content .header') + + +class BountyItemRows(BaseText): + + def __init__(self, driver): + super(BountyItemRows, self).__init__(driver) + self.locator = self.Locator.css_selector('.open-bounty-item-content .bounty-item-row') + +class BountyFooters(BaseText): + + def __init__(self, driver): + super(BountyFooters, self).__init__(driver) + self.locator = self.Locator.css_selector('.open-bounty-item-content .footer-row') + class BountiesPage(BasePageObject): def __init__(self, driver): super(BountiesPage, self).__init__(driver) + self.driver = driver self.bounties_header = BountiesHeader(self.driver) self.top_hunters_header = TopHuntersHeader(self.driver) + self.bounty_titles = BountyTitles(self.driver) + self.bounty_item_rows = BountyItemRows(self.driver) + self.bounty_footers = BountyFooters(self.driver) + + def get_bounties_page(self): + self.driver.get(test_data.config['Common']['url'] + 'app') + diff --git a/test/end-to-end/pages/openbounty/landing.py b/test/end-to-end/pages/openbounty/landing.py index 7886f90..71f47f1 100644 --- a/test/end-to-end/pages/openbounty/landing.py +++ b/test/end-to-end/pages/openbounty/landing.py @@ -1,5 +1,6 @@ from pages.base_page import BasePageObject from pages.base_element import * +from tests import test_data class LoginButton(BaseButton): @@ -20,4 +21,4 @@ class LandingPage(BasePageObject): self.login_button = LoginButton(self.driver) def get_landing_page(self): - self.driver.get('https://openbounty.status.im:444/') + self.driver.get(test_data.config['Common']['url']) diff --git a/test/end-to-end/pages/thirdparty/github.py b/test/end-to-end/pages/thirdparty/github.py index eaf3c84..0d00335 100644 --- a/test/end-to-end/pages/thirdparty/github.py +++ b/test/end-to-end/pages/thirdparty/github.py @@ -1,6 +1,7 @@ import time, pytest from pages.base_element import * from pages.base_page import BasePageObject +from tests import test_data class EmailEditbox(BaseEditBox): @@ -156,11 +157,14 @@ class GithubPage(BasePageObject): def create_new_bounty(self): self.get_issues_page() self.new_issue_button.click() - self.issue_title_input.send_keys('auto_test_bounty_%s' % self.time_now) + test_data.issue = dict() + test_data.issue['title'] = 'auto_test_bounty_%s' % self.time_now + self.issue_title_input.send_keys(test_data.issue['title']) self.labels_button.click() self.bounty_label.click() self.cross_button.click() self.submit_new_issue_button.click() + return test_data.issue['title'] def get_deployed_contract(self, wait=120): for i in range(wait): diff --git a/test/end-to-end/tests/__init__.py b/test/end-to-end/tests/__init__.py index d9e141d..41ae6be 100644 --- a/test/end-to-end/tests/__init__.py +++ b/test/end-to-end/tests/__init__.py @@ -1,8 +1,17 @@ - +import configparser class TestData(object): def __init__(self): self.test_name = None + self.config = configparser.ConfigParser() + + # define here path to your config.ini file + #example - config_example.ini + + self.config.read('config.ini') + self.base_case_issue = dict() + self.base_case_issue['title'] = 'Very first auto_test_bounty' + test_data = TestData() diff --git a/test/end-to-end/tests/basetestcase.py b/test/end-to-end/tests/basetestcase.py index b658ed1..9ba9409 100644 --- a/test/end-to-end/tests/basetestcase.py +++ b/test/end-to-end/tests/basetestcase.py @@ -46,16 +46,22 @@ class BaseTestCase: def setup_method(self): self.errors = [] + self.cleanup = None if self.environment == 'local': options = webdriver.ChromeOptions() options.add_argument('--start-fullscreen') - self.driver = webdriver.Chrome(options=options) + options.add_extension( + path.abspath(test_data.config['Paths']['tests_absolute'] + 'resources/metamask3_12_0.crx')) + # for chromedriver 2.35 + self.driver = webdriver.Chrome(chrome_options=options) if self.environment == 'sauce': self.driver = webdriver.Remote(self.executor_sauce_lab, desired_capabilities=self.capabilities_sauce_lab) self.driver.implicitly_wait(5) + + def verify_no_errors(self): if self.errors: msg = '' @@ -64,9 +70,9 @@ class BaseTestCase: pytest.fail(msg, pytrace=False) def teardown_method(self): - - remove_application(self.driver) - remove_installation(self.driver) + if self.cleanup: + remove_application(self.driver) + remove_installation(self.driver) try: self.print_sauce_lab_info(self.driver) diff --git a/test/end-to-end/tests/config_example.ini b/test/end-to-end/tests/config_example.ini new file mode 100644 index 0000000..4b52a24 --- /dev/null +++ b/test/end-to-end/tests/config_example.ini @@ -0,0 +1,13 @@ +[Common] +;app URL +url = https://openbounty.status.im:444/ +[Paths] +;AbsolutePath to 'tests' folder +tests_absolute = /usr/dir/open-bounty/test/end-to-end/tests/ +[ORG] +;GitHub credentials for organization +gh_login = login +gh_password = password +;MetaMask password for organization +mm_password = password + diff --git a/test/end-to-end/tests/test_contracts.py b/test/end-to-end/tests/test_contracts.py index 6e617f3..5564b7b 100644 --- a/test/end-to-end/tests/test_contracts.py +++ b/test/end-to-end/tests/test_contracts.py @@ -1,22 +1,42 @@ import pytest from os import environ from pages.openbounty.landing import LandingPage +from pages.openbounty.bounties import BountiesPage from tests.basetestcase import BaseTestCase +from tests import test_data @pytest.mark.sanity class TestLogin(BaseTestCase): def test_deploy_new_contract(self): + self.cleanup = True landing = LandingPage(self.driver) landing.get_landing_page() + + # Sign Up to SOB github = landing.login_button.click() - github.sign_in('anna04test', - 'f@E23D3H15Rd') + github.sign_in(test_data.config['ORG']['gh_login'], + test_data.config['ORG']['gh_password']) assert github.permission_type.text == 'Personal user data' bounties_page = github.authorize_sob.click() + + # SOB Plugin installation and navigate to "Open bounties" github.install_sob_plugin() assert bounties_page.bounties_header.text == 'Bounties' assert bounties_page.top_hunters_header.text == 'Top 5 hunters' + + # Waiting for deployed contract; test_data.issue created here github.create_new_bounty() github.get_deployed_contract() + + # Navigate and check top bounty in "Open bounties" + bounties_page = BountiesPage(self.driver) + bounties_page.get_bounties_page() + titles = bounties_page.bounty_titles.find_elements() + assert titles[0].text == test_data.issue['title'] + + + + + From 41e15937c45f0b0f6dd97d66e9f5f63276de3393 Mon Sep 17 00:00:00 2001 From: Noman Date: Wed, 24 Jan 2018 23:54:45 -0500 Subject: [PATCH 057/100] Ensure Google Analytics using correct ID --- resources/public/dest/img/new-site/icon_rt_black.svg | 2 +- resources/templates/home.html | 2 +- static_langing_page/dest/img/new-site/icon_rt_black.svg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/public/dest/img/new-site/icon_rt_black.svg b/resources/public/dest/img/new-site/icon_rt_black.svg index 835f3d5..7a31f8b 100644 --- a/resources/public/dest/img/new-site/icon_rt_black.svg +++ b/resources/public/dest/img/new-site/icon_rt_black.svg @@ -1 +1 @@ -icon_sl \ No newline at end of file +icon_sl \ No newline at end of file diff --git a/resources/templates/home.html b/resources/templates/home.html index 76e60f3..590156e 100644 --- a/resources/templates/home.html +++ b/resources/templates/home.html @@ -72,7 +72,7 @@ height="0" width="0" style="display:none;visibility:hidden"> (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-79146816-2', 'auto'); + ga('create', 'UA-79146816-1', 'auto'); ga('send', 'pageview'); diff --git a/static_langing_page/dest/img/new-site/icon_rt_black.svg b/static_langing_page/dest/img/new-site/icon_rt_black.svg index 835f3d5..7a31f8b 100644 --- a/static_langing_page/dest/img/new-site/icon_rt_black.svg +++ b/static_langing_page/dest/img/new-site/icon_rt_black.svg @@ -1 +1 @@ -icon_sl \ No newline at end of file +icon_sl \ No newline at end of file From fdec3ad56bc4430d7dbc62f8a28314f9ecff930e Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 08:28:48 +0100 Subject: [PATCH 058/100] abstracting filter type category --- src/cljs/commiteth/bounties.cljs | 103 +++++++++++++------------------ src/cljs/commiteth/ui_model.cljs | 43 ++++++++----- 2 files changed, 70 insertions(+), 76 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index a701762..b034495 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -47,10 +47,6 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-filter-tooltip [content] - [:div.open-bounties-filter-element-tooltip - content]) - (defn bounties-filter-tooltip-value-input [label tooltip-open? opts] [:div.open-bounties-filter-element-tooltip-value-input-container [:div.:input.open-bounties-filter-element-tooltip-value-input-label @@ -65,15 +61,15 @@ #(-> % .-target .-value int f)) :on-focus #(reset! tooltip-open? true)}]]) -(defn bounties-filter-tooltip-value [current-filter-value tooltip-open?] - (let [default-min 0 - default-max 1000 +(defn bounties-filter-tooltip-category-range [filter-type filter-type-def current-filter-value tooltip-open?] + (let [default-min (::ui-model/bounty-filter-type.min-val filter-type-def) + default-max (::ui-model/bounty-filter-type.max-val filter-type-def) common-range-opts {:min default-min :max default-max} current-min (or (first current-filter-value) default-min) current-max (or (second current-filter-value) default-max) on-change-fn (fn [min-val max-val] (rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|value + filter-type [(min min-val default-max) (max max-val default-min)]])) on-min-change-fn (fn [new-min] @@ -91,80 +87,66 @@ {:current-val current-max :on-change-val on-max-change-fn})]])) -(defn bounties-filter-tooltip-currency [current-filter-value tooltip-open?] - (let [currencies (rf/subscribe [::subs/open-bounties-currencies])] - [:div.open-bounties-filter-list - (for [currency @currencies] - (let [active? (boolean (and current-filter-value (current-filter-value currency)))] - ^{:key (str currency)} - [:div.open-bounties-filter-list-option-checkbox - [:label - {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|currency - (cond - (and active? (= #{currency} current-filter-value)) nil - active? (disj current-filter-value currency) - :else (into #{currency} current-filter-value))]) - :tab-index 0 - :on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))} - [:input - {:type "checkbox" - :checked active? - :on-focus #(reset! tooltip-open? true)}] - [:div.text currency]]]))])) - -(defn bounties-filter-tooltip-date [current-filter-value tooltip-open?] +(defn bounties-filter-tooltip-category-single-static-option + [filter-type filter-type-def current-filter-value tooltip-open?] [:div.open-bounties-filter-list - (for [[option-type option-text] ui-model/bounty-filter-type-date-options-def] + (for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)] ^{:key (str option-type)} [:div.open-bounties-filter-list-option (merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|date + filter-type option-type]) (reset! tooltip-open? false))} (when (= option-type current-filter-value) {:class "active"})) option-text])]) -(defn bounties-filter-tooltip-owner [current-filter-value tooltip-open?] - (let [owners (rf/subscribe [::subs/open-bounties-owners])] +(defn bounties-filter-tooltip-category-multiple-dynamic-options + [filter-type filter-type-def current-filter-value tooltip-open?] + (let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subscription-key-for-options filter-type-def)])] [:div.open-bounties-filter-list - (for [owner @owners] - (let [active? (boolean (and current-filter-value (current-filter-value owner)))] - ^{:key (str owner)} + (for [option @options] + (let [active? (boolean (and current-filter-value (current-filter-value option)))] + ^{:key (str option)} [:div.open-bounties-filter-list-option-checkbox [:label - {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type - ::ui-model/bounty-filter-type|owner - (cond - (and active? (= #{owner} current-filter-value)) nil - active? (disj current-filter-value owner) - :else (into #{owner} current-filter-value))]) + {:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type + filter-type + (cond + (and active? (= #{option} current-filter-value)) nil + active? (disj current-filter-value option) + :else (into #{option} current-filter-value))]) :tab-index 0 :on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))} [:input {:type "checkbox" :checked active? :on-focus #(reset! tooltip-open? true)}] - [:div.text owner]]]))])) + [:div.text option]]]))])) -(defn- tooltip-view-for-filter-type [filter-type] - (condp = filter-type - ::ui-model/bounty-filter-type|value bounties-filter-tooltip-value - ::ui-model/bounty-filter-type|currency bounties-filter-tooltip-currency - ::ui-model/bounty-filter-type|date bounties-filter-tooltip-date - ::ui-model/bounty-filter-type|owner bounties-filter-tooltip-owner)) +(defn bounties-filter-tooltip [filter-type filter-type-def current-filter-val tooltip-open?] + [:div.open-bounties-filter-element-tooltip + (let [compo (condp = (::ui-model/bounty-filter-type.category filter-type-def) + ::ui-model/bounty-filter-type-category|single-static-option + bounties-filter-tooltip-category-single-static-option + + ::ui-model/bounty-filter-type-category|multiple-dynamic-options + bounties-filter-tooltip-category-multiple-dynamic-options + + ::ui-model/bounty-filter-type-category|range + bounties-filter-tooltip-category-range)] + (compo filter-type filter-type-def current-filter-val tooltip-open?))]) (defn bounty-filter-view [filter-type current-filter-value] - (let [open? (r/atom false)] + (let [tooltip-open? (r/atom false)] (fn [filter-type current-filter-value] [:div.open-bounties-filter-element-container {:tab-index 0 - :on-focus #(reset! open? true) - :on-blur #(reset! open? false)} + :on-focus #(reset! tooltip-open? true) + :on-blur #(reset! tooltip-open? false)} [:div.open-bounties-filter-element - {:on-mouse-down #(swap! open? not) - :class (when (or current-filter-value @open?) + {:on-mouse-down #(swap! tooltip-open? not) + :class (when (or current-filter-value @tooltip-open?) "open-bounties-filter-element-active")} [:div.text (if current-filter-value @@ -179,10 +161,13 @@ :on-mouse-down (fn [e] (.stopPropagation e) (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) - (reset! open? false))}]])] - (when @open? + (reset! tooltip-open? false))}]])] + (when @tooltip-open? [bounties-filter-tooltip - [(tooltip-view-for-filter-type filter-type) current-filter-value open?]])]))) + filter-type + (ui-model/bounty-filter-types-def filter-type) + current-filter-value + tooltip-open?])]))) (defn bounty-filters-view [] (let [current-filters (rf/subscribe [::subs/open-bounties-filters])] diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 75e9c3c..e85e925 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -38,19 +38,32 @@ ;;;; bounty filter types +(def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week" + ::bounty-filter-type-date-option|last-month "Last month" + ::bounty-filter-type-date-option|last-3-months "Last 3 months"}) + +(def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) + (def bounty-filter-types-def {::bounty-filter-type|value {::bounty-filter-type.name "Value" + ::bounty-filter-type.category ::bounty-filter-type-category|range + ::bounty-filter-type.min-val 0 + ::bounty-filter-type.max-val 1000 ::bounty-filter-type.predicate (fn [filter-value bounty] (let [min-val (first filter-value) max-val (second filter-value)] (<= min-val (:value-usd bounty) max-val)))} - ::bounty-filter-type|currency {::bounty-filter-type.name "Currency" - ::bounty-filter-type.predicate (fn [filter-value bounty] - (and (or (not-any? #{"ETH"} filter-value) - (< 0 (:balance-eth bounty))) - (set/subset? (->> filter-value (remove #{"ETH"}) set) - (-> bounty :tokens keys set))))} + ::bounty-filter-type|currency {::bounty-filter-type.name "Currency" + ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options + ::bounty-filter-type.re-frame-subscription-key-for-options :commiteth.subscriptions/open-bounties-currencies + ::bounty-filter-type.predicate (fn [filter-value bounty] + (and (or (not-any? #{"ETH"} filter-value) + (< 0 (:balance-eth bounty))) + (set/subset? (->> filter-value (remove #{"ETH"}) set) + (-> bounty :tokens keys set))))} ::bounty-filter-type|date {::bounty-filter-type.name "Date" + ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option + ::bounty-filter-type.options bounty-filter-type-date-options-def ::bounty-filter-type.predicate (fn [filter-value bounty] (when-let [created-at-inst (:created-at bounty)] (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long) @@ -60,23 +73,19 @@ ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3))) interval (t/interval filter-from (t/now))] (t/within? interval created-at-date))))} - ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" - ::bounty-filter-type.predicate (fn [filter-value bounty] - (->> filter-value - (some #{(:repo-owner bounty)}) - boolean))}}) + ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" + ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options + ::bounty-filter-type.re-frame-subscription-key-for-options :commiteth.subscriptions/open-bounties-owners + ::bounty-filter-type.predicate (fn [filter-value bounty] + (->> filter-value + (some #{(:repo-owner bounty)}) + boolean))}}) (def bounty-filter-types (keys bounty-filter-types-def)) (defn bounty-filter-type->name [filter-type] (-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name)) -(def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week" - ::bounty-filter-type-date-option|last-month "Last month" - ::bounty-filter-type-date-option|last-3-months "Last 3 months"}) - -(def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) - (defn bounty-filter-type-date-option->name [option] (bounty-filter-type-date-options-def option)) From 834f82609221420296bb98e206b057250c6fae4c Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 08:33:18 +0100 Subject: [PATCH 059/100] disable filter and sort in mobile for now --- src/less/style.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/less/style.less b/src/less/style.less index 5977c85..18d9c5b 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -420,6 +420,12 @@ justify-content: space-between; } + @media (max-width: 767px) { + .open-bounties-filter-and-sort { + display: none; + } + } + .open-bounties-filter { display: flex; } From 4bb81f976bf07d70bf0b8b9229be6407ea631d77 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 09:33:05 +0100 Subject: [PATCH 060/100] pre-predicate-value-processor --- src/cljs/commiteth/ui_model.cljs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index e85e925..fa25f0c 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -61,18 +61,19 @@ (< 0 (:balance-eth bounty))) (set/subset? (->> filter-value (remove #{"ETH"}) set) (-> bounty :tokens keys set))))} - ::bounty-filter-type|date {::bounty-filter-type.name "Date" - ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option - ::bounty-filter-type.options bounty-filter-type-date-options-def - ::bounty-filter-type.predicate (fn [filter-value bounty] - (when-let [created-at-inst (:created-at bounty)] - (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long) - filter-from (condp = filter-value - ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) - ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) - ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3))) - interval (t/interval filter-from (t/now))] - (t/within? interval created-at-date))))} + ::bounty-filter-type|date {::bounty-filter-type.name "Date" + ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option + ::bounty-filter-type.options bounty-filter-type-date-options-def + ::bounty-filter-type.pre-predicate-value-processor (fn [filter-value] + (let [filter-from (condp = filter-value + ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) + ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) + ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))] + (t/interval filter-from (t/now)))) + ::bounty-filter-type.predicate (fn [filter-value-interval bounty] + (when-let [created-at-inst (:created-at bounty)] + (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] + (t/within? filter-value-interval created-at-date))))} ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options ::bounty-filter-type.re-frame-subscription-key-for-options :commiteth.subscriptions/open-bounties-owners @@ -108,7 +109,11 @@ (let [filter-preds (->> filters-by-type (remove #(nil? (val %))) (map (fn [[filter-type filter-value]] - (let [pred (-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.predicate)] + (let [filter-type-def (bounty-filter-types-def filter-type) + pred (::bounty-filter-type.predicate filter-type-def) + pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def) + filter-value (cond-> filter-value + pre-pred-processor (pre-pred-processor filter-value))] (partial pred filter-value))))) filters-pred (fn [bounty] (every? #(% bounty) filter-preds))] From b3b3e47acda14f8cea14a5cb45eb630d3b14ace0 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 09:48:12 +0100 Subject: [PATCH 061/100] change no open bounties message --- src/cljs/commiteth/bounties.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index b034495..b79960b 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -211,7 +211,7 @@ [bounties-sort]] (if (empty? open-bounties) [:div.view-no-data-container - [:p "No recent activity yet"]] + [:p "No matching bounties found."]] (into [:div.ui.items] (for [bounty open-bounties] [bounty-item bounty])))]) From cc1cedbbbd85c88c596b6515e5101e7ed3a97ca8 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 10:06:53 +0100 Subject: [PATCH 062/100] set bounty page 1 when filters or sorting change --- src/cljs/commiteth/handlers.cljs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 1345ade..4367c9a 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -467,9 +467,12 @@ (reg-event-db ::set-open-bounties-sorting-type (fn [db [_ sorting-type]] - (assoc db ::db/open-bounties-sorting-type sorting-type))) + (merge db {::db/open-bounties-sorting-type sorting-type + :bounty-page-number 1}))) (reg-event-db ::set-open-bounty-filter-type (fn [db [_ filter-type filter-value]] - (assoc-in db [::db/open-bounties-filters filter-type] filter-value))) + (-> db + (assoc-in [::db/open-bounties-filters filter-type] filter-value) + (assoc :bounty-page-number 1)))) From fc994102e85203dff573ddea17864e1df37cf8c5 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 10:08:36 +0100 Subject: [PATCH 063/100] add -view suffix and remove old code --- src/cljs/commiteth/bounties.cljs | 67 +++++++------------------------- 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 7d50292..27e9e9e 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -63,7 +63,7 @@ #(-> % .-target .-value int f)) :on-focus #(reset! tooltip-open? true)}]]) -(defn bounties-filter-tooltip-category-range [filter-type filter-type-def current-filter-value tooltip-open?] +(defn bounties-filter-tooltip-category-range-view [filter-type filter-type-def current-filter-value tooltip-open?] (let [default-min (::ui-model/bounty-filter-type.min-val filter-type-def) default-max (::ui-model/bounty-filter-type.max-val filter-type-def) common-range-opts {:min default-min :max default-max} @@ -89,7 +89,7 @@ {:current-val current-max :on-change-val on-max-change-fn})]])) -(defn bounties-filter-tooltip-category-single-static-option +(defn bounties-filter-tooltip-category-single-static-option-view [filter-type filter-type-def current-filter-value tooltip-open?] [:div.open-bounties-filter-list (for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)] @@ -103,7 +103,7 @@ {:class "active"})) option-text])]) -(defn bounties-filter-tooltip-category-multiple-dynamic-options +(defn bounties-filter-tooltip-category-multiple-dynamic-options-view [filter-type filter-type-def current-filter-value tooltip-open?] (let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subscription-key-for-options filter-type-def)])] [:div.open-bounties-filter-list @@ -126,17 +126,17 @@ :on-focus #(reset! tooltip-open? true)}] [:div.text option]]]))])) -(defn bounties-filter-tooltip [filter-type filter-type-def current-filter-val tooltip-open?] +(defn bounties-filter-tooltip-view [filter-type filter-type-def current-filter-val tooltip-open?] [:div.open-bounties-filter-element-tooltip (let [compo (condp = (::ui-model/bounty-filter-type.category filter-type-def) ::ui-model/bounty-filter-type-category|single-static-option - bounties-filter-tooltip-category-single-static-option + bounties-filter-tooltip-category-single-static-option-view ::ui-model/bounty-filter-type-category|multiple-dynamic-options - bounties-filter-tooltip-category-multiple-dynamic-options + bounties-filter-tooltip-category-multiple-dynamic-options-view ::ui-model/bounty-filter-type-category|range - bounties-filter-tooltip-category-range)] + bounties-filter-tooltip-category-range-view)] (compo filter-type filter-type-def current-filter-val tooltip-open?))]) (defn bounty-filter-view [filter-type current-filter-value] @@ -165,7 +165,7 @@ (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) (reset! tooltip-open? false))}]])] (when @tooltip-open? - [bounties-filter-tooltip + [bounties-filter-tooltip-view filter-type (ui-model/bounty-filter-types-def filter-type) current-filter-value @@ -182,7 +182,7 @@ filter-type (get @current-filters filter-type)]))])) -(defn bounties-sort [] +(defn bounties-sort-view [] (let [open? (r/atom false)] (fn [] (let [current-sorting (rf/subscribe [::subs/open-bounties-sorting-type])] @@ -205,38 +205,25 @@ (rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))} (ui-model/bounty-sorting-type->name sorting-type)])])])))) -;(defn bounties-list [open-bounties] -; [:div.ui.container.open-bounties-container -; [:div.open-bounties-header "Bounties"] -; [:div.open-bounties-filter-and-sort -; [bounty-filters-view] -; [bounties-sort]] -; (if (empty? open-bounties) -; [:div.view-no-data-container -; [:p "No matching bounties found."]] -; (into [:div.ui.items] -; (for [bounty open-bounties] -; [bounty-item bounty])))]) - (defn bounties-list [{:keys [items item-count page-number total-count] - :as bounty-page-data}] + :as bounty-page-data}] [:div.ui.container.open-bounties-container [:div.open-bounties-header "Bounties"] [:div.open-bounties-filter-and-sort [bounty-filters-view] - [bounties-sort]] + [bounties-sort-view]] (if (empty? items) [:div.view-no-data-container [:p "No matching bounties found."]] [:div - (let [left (inc (* (dec page-number) items-per-page)) + (let [left (inc (* (dec page-number) items-per-page)) right (dec (+ left item-count))] [:div.item-counts-label [:span (str "Showing " left "-" right " of " total-count)]]) (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) (defn bounties-page [] - (let [bounty-page-data (rf/subscribe [:open-bounties-page]) + (let [bounty-page-data (rf/subscribe [:open-bounties-page]) open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] (fn [] (if @open-bounties-loading? @@ -244,31 +231,3 @@ [:div.ui.active.inverted.dimmer [:div.ui.text.loader.view-loading-label "Loading"]]] [bounties-list @bounty-page-data])))) -; -;(defn bounties-page [] -; (let [open-bounties (rf/subscribe [::subs/filtered-and-sorted-open-bounties]) -;======= -;(defn bounties-list [{:keys [items item-count page-number total-count] -; :as bounty-page-data}] -; [:div.ui.container.open-bounties-container -; [:div.open-bounties-header "Bounties"] -; (if (empty? items) -; [:div.view-no-data-container -; [:p "No recent activity yet"]] -; [:div -; (let [left (inc (* (dec page-number) items-per-page)) -; right (dec (+ left item-count))] -; [:div.item-counts-label -; [:span (str "Showing " left "-" right " of " total-count)]]) -; (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])]) -; -;(defn bounties-page [] -; (let [bounty-page-data (rf/subscribe [:open-bounties-page]) -;>>>>>>> status/develop -; open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])] -; (fn [] -; (if @open-bounties-loading? -; [:div.view-loading-container -; [:div.ui.active.inverted.dimmer -; [:div.ui.text.loader.view-loading-label "Loading"]]] -; [bounties-list @bounty-page-data])))) From aabdff10fda655c13574fc97aea18eae7ac5cbb4 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 10:34:32 +0100 Subject: [PATCH 064/100] use multimethod in bounties-filter-tooltip-view --- src/cljs/commiteth/bounties.cljs | 47 +++++++++++++------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 27e9e9e..3964f04 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -49,7 +49,7 @@ [:div.ui.tiny.circular.image [:img {:src avatar-url}]]]])) -(defn bounties-filter-tooltip-value-input [label tooltip-open? opts] +(defn bounties-filter-tooltip-value-input-view [label tooltip-open? opts] [:div.open-bounties-filter-element-tooltip-value-input-container [:div.:input.open-bounties-filter-element-tooltip-value-input-label label] @@ -63,7 +63,10 @@ #(-> % .-target .-value int f)) :on-focus #(reset! tooltip-open? true)}]]) -(defn bounties-filter-tooltip-category-range-view [filter-type filter-type-def current-filter-value tooltip-open?] +(defmulti bounties-filter-tooltip-view #(-> %2 ::ui-model/bounty-filter-type.category)) + +(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|range + [filter-type filter-type-def current-filter-value tooltip-open?] (let [default-min (::ui-model/bounty-filter-type.min-val filter-type-def) default-max (::ui-model/bounty-filter-type.max-val filter-type-def) common-range-opts {:min default-min :max default-max} @@ -82,14 +85,14 @@ (on-change-fn new-min new-max)))] [:div "$0 - $1000+" - [bounties-filter-tooltip-value-input "Min" tooltip-open? (merge common-range-opts - {:current-val current-min - :on-change-val on-min-change-fn})] - [bounties-filter-tooltip-value-input "Max" tooltip-open? (merge common-range-opts - {:current-val current-max - :on-change-val on-max-change-fn})]])) + [bounties-filter-tooltip-value-input-view "Min" tooltip-open? (merge common-range-opts + {:current-val current-min + :on-change-val on-min-change-fn})] + [bounties-filter-tooltip-value-input-view "Max" tooltip-open? (merge common-range-opts + {:current-val current-max + :on-change-val on-max-change-fn})]])) -(defn bounties-filter-tooltip-category-single-static-option-view +(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|single-static-option [filter-type filter-type-def current-filter-value tooltip-open?] [:div.open-bounties-filter-list (for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)] @@ -103,7 +106,7 @@ {:class "active"})) option-text])]) -(defn bounties-filter-tooltip-category-multiple-dynamic-options-view +(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|multiple-dynamic-options [filter-type filter-type-def current-filter-value tooltip-open?] (let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subscription-key-for-options filter-type-def)])] [:div.open-bounties-filter-list @@ -126,19 +129,6 @@ :on-focus #(reset! tooltip-open? true)}] [:div.text option]]]))])) -(defn bounties-filter-tooltip-view [filter-type filter-type-def current-filter-val tooltip-open?] - [:div.open-bounties-filter-element-tooltip - (let [compo (condp = (::ui-model/bounty-filter-type.category filter-type-def) - ::ui-model/bounty-filter-type-category|single-static-option - bounties-filter-tooltip-category-single-static-option-view - - ::ui-model/bounty-filter-type-category|multiple-dynamic-options - bounties-filter-tooltip-category-multiple-dynamic-options-view - - ::ui-model/bounty-filter-type-category|range - bounties-filter-tooltip-category-range-view)] - (compo filter-type filter-type-def current-filter-val tooltip-open?))]) - (defn bounty-filter-view [filter-type current-filter-value] (let [tooltip-open? (r/atom false)] (fn [filter-type current-filter-value] @@ -165,11 +155,12 @@ (rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil]) (reset! tooltip-open? false))}]])] (when @tooltip-open? - [bounties-filter-tooltip-view - filter-type - (ui-model/bounty-filter-types-def filter-type) - current-filter-value - tooltip-open?])]))) + [:div.open-bounties-filter-element-tooltip + [bounties-filter-tooltip-view + filter-type + (ui-model/bounty-filter-types-def filter-type) + current-filter-value + tooltip-open?]])]))) (defn bounty-filters-view [] (let [current-filters (rf/subscribe [::subs/open-bounties-filters])] From 6786411299e94e2c23f0067402ac19c4a3e99ac0 Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 25 Jan 2018 11:08:50 +0100 Subject: [PATCH 065/100] add created_at to queries --- resources/sql/queries.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index e8a3115..1fa529c 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -422,6 +422,7 @@ SELECT i.payout_hash AS payout_hash, i.payout_receipt AS payout_receipt, i.updated AS updated, + i.created_at AS created_at, r.owner AS repo_owner, r.owner_avatar_url AS repo_owner_avatar_url, r.repo AS repo_name, From 5c163773b49977713ec8d9b122470e2ceeed3e63 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Thu, 25 Jan 2018 13:14:51 +0200 Subject: [PATCH 066/100] Add config-related docs; add cookbook.md for common issue troubleshooting --- README.md | 42 +++++++++++++++++++++++++++++++++++++----- doc/cookbook.md | 18 ++++++++++++++++++ doc/testing.md | 8 ++------ 3 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 doc/cookbook.md diff --git a/README.md b/README.md index bb7bc5b..597831f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Live production version: https://openbounty.status.im The `master` branch is automatically deployed here. - Live testnet (Ropsten) version: https://openbounty.status.im:444 The `develop` branch is automatically deployed here. @@ -20,7 +19,6 @@ The `develop` branch is automatically deployed here. You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. ### PostgreSQL -<<<<<<< HEAD Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up: @@ -39,7 +37,6 @@ lein figwheel lein less auto ``` -======= Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up: @@ -56,9 +53,40 @@ Solidity compiler [0.4.15](https://github.com/ethereum/solidity/releases/tag/v0. Web3j [2.3.0](https://github.com/web3j/web3j/releases/tag/v2.3.0) is required and the command line tools need to be in $PATH. -## Running +## Application config -Make sure `env/dev/resources/config.edn` is correctly populated. +Make sure that `env/dev/resources/config.edn` is correctly populated. Description of config fields is given below: + +Key | Description +--- | --- +dev | Currently specifies whether Swagger UI endpoints should be added to routes +port | HTTP port for the Ring web app +nrepl-port | nREPL port for development +jdbc-database-url | PostgreSQL database URL. For instance, URL to local db would be `jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth` +server-address | URL and port of local server that can be resolved from public internet. It will be used as a redirect URI during GitHub OAuth authorization process +eth-account | Ethereum account ID for the bot +eth-password | Ethereum account password for the bot +eth-rpc-url | RPC URL to Ethereum node, e.g. Geth. Either local or remote +eth-wallet-file | Location of wallet file. If Geth is run with the parameters as given below, it will reside under `$HOME/.ropsten/keystore` +tokenreg-base-format | Should be set to `:status` +github-client-id | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps +github-client-secret | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps +github-user | GitHub username for bot account. It is used for posting bounty comments +github-password | GitHub password for bot account +webhook-secret | Secret string to be used when creating a GitHub App +user-whitelist | Set of GitHub user/org IDs to be whitelisted. E.g. `#{"status-im" "your_org"}` +testnet-token-data | Token data map, useful if there are Geth connectivity problems + +## GitHub integration +Open Bounty uses both OAuth App and GitHub App integration. + +### OAuth App +Follow the steps [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). Specify the value of `:server-address` as "Homepage URL", and `:server-address` + `/callback` as "Authorization callback URL". Be sure to copy Client ID and Client Secret values to `config.edn`. + +### GitHub App +Follow the steps [here](https://developer.github.com/apps/building-github-apps/creating-a-github-app/). Be sure to specify `:server-address` + `/webhook-app` as "Webhook URL", and `:webhook-secret` as "Webhook Secret". + +## Running Lauch a local geth node with the bot account unlocked: @@ -141,6 +169,10 @@ Landing page is static and different CSS and JS due to time constraints. This copies over necessary artifacts to `resources` dir. + +### Troubleshooting +See the [Cookbook](doc/cookbook.md). + ## License Licensed under the [Affero General Public License v3.0](https://github.com/status-im/commiteth/blob/master/LICENSE.md) diff --git a/doc/cookbook.md b/doc/cookbook.md new file mode 100644 index 0000000..e76ad48 --- /dev/null +++ b/doc/cookbook.md @@ -0,0 +1,18 @@ +# Cookbook + +Here some common tasks/issues are listed along with their solutions. + +## Change config and restart the service + - ssh to the host running commiteth + - go to `/opt/commiteth` for prod or `/opt/commiteth-test` for test environment. + - there edit `config.edn` + - restart the service with `sudo service commiteth-test restart` + +## Manually add a GitHub repo to SOB app +Sometimes SOB will not + - connect to Postgres instance. Get DB name and username/password from respective `config.edn` (see previous answer). These are set in `:jdbc-database-url` field. + - execute the query, e.g. +``` +insert into repositories(repo_id,user_id,owner,repo,hook_id,state,hook_secret,owner_avatar_url) values(116971984,447328, 'aragon', 'aragon-monthly', 0, 2, '', 'https://avatars1.githubusercontent.com/u/24612534?v=4'); +``` +Note that the value for repo_id field is GitHub's internal repo ID. One way of obtaining it is navigating to `api.github.com/repos/:owner/:repo_name`, e.g. `https://api.github.com/repos/aragon/aragon-monthly`. diff --git a/doc/testing.md b/doc/testing.md index c0ad83f..91f955a 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -29,13 +29,9 @@ You should now see `Bounties`, `Activity`, `Repositories` and `Manage Payouts` t ### Creating bounty issues -Before you can create bounties, you need to have administrative access to one or more GitHub repositories. These can be either in the scope of your personal user account or in the scope of a Github orgnazation. +Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to https://github.com/apps/status-open-bounty-app-test (or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos. -* click the `Repositories` tab -* click on the button `Enable Github Account` -* If you have 1 or more Organisation repositories then grant Organisation access to each of them by clicking on the button `Grant` -* grant Status Open Bounty the needed addtional permissions for managing repository webhooks, adding and modifying comments by clicking on the button `Authorize status-open-bounty` -* now you should see your repositories on the `Repositories` tab, click `Add` on one. If your account isn't whitelisted you will see instructions how to request an access. If your account is whitelisted then new `bounty` label will become available in the GitHub repository's labels and a new webhook should now exist for the repository. +* Request for your account to be whitelisted. Contact [Riot](https://chat.status.im) for more information * now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait` * once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code From bb71d830a88fa82c78443a98d494eda550e49e60 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Thu, 25 Jan 2018 13:43:15 +0200 Subject: [PATCH 067/100] Add page-num-active style disabling pointer cursor --- src/cljs/commiteth/common.cljs | 12 ++++++------ src/less/style.less | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 8cb213d..25b47b3 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -48,15 +48,15 @@ (defn draw-page-numbers [page-number page-count] "Draw page numbers for the pagination component. - Inserts ellipsis when list is too long, by default - max 6 items are allowed" + Inserts ellipsis when list is too long, by default + max 6 items are allowed" (let [draw-page-num-fn (fn [current? i] ^{:key i} [:div.rectangle-rounded - (cond-> {} - (not current?) - (assoc :class "grayed-out-page-num" - :on-click #(rf/dispatch [:set-page-number i]))) + (if current? + {:class "page-num-active"} + {:class "grayed-out-page-num" + :on-click #(rf/dispatch [:set-page-number i])}) i]) max-page-nums 6] [:div.page-nums-container diff --git a/src/less/style.less b/src/less/style.less index d8c078c..063f7bb 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -897,6 +897,10 @@ body { cursor: pointer; } +.page-num-active { + cursor: auto; +} + .grayed-out-page-num { color: #8d99a4; background-color: #f2f5f8; From dbffabffe0db5f45e7d668884689db56e4c0fdf6 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Thu, 25 Jan 2018 17:04:00 +0200 Subject: [PATCH 068/100] Remove scroll-div; pass container element ref to on-click handlers --- src/cljs/commiteth/activity.cljs | 9 ++++----- src/cljs/commiteth/bounties.cljs | 9 ++++----- src/cljs/commiteth/common.cljs | 29 ++++++++++------------------- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/cljs/commiteth/activity.cljs b/src/cljs/commiteth/activity.cljs index 2e9444e..887b8c7 100644 --- a/src/cljs/commiteth/activity.cljs +++ b/src/cljs/commiteth/activity.cljs @@ -4,7 +4,6 @@ [commiteth.common :refer [moment-timestamp items-per-page display-data-page - scroll-div issue-url]])) @@ -58,7 +57,8 @@ [:div.time (moment-timestamp updated)]]]]) (defn activity-list [{:keys [items item-count page-number total-count] - :as activity-page-data}] + :as activity-page-data} + container-element] (if (empty? (:items activity-page-data)) [:div.view-no-data-container [:p "No recent activity yet"]] @@ -67,7 +67,7 @@ right (dec (+ left item-count))] [:div.item-counts-label [:span (str "Showing " left "-" right " of " total-count)]]) - (display-data-page activity-page-data activity-item)])) + (display-data-page activity-page-data activity-item container-element)])) (defn activity-page [] (let [activity-page-data (rf/subscribe [:activities-page]) @@ -80,6 +80,5 @@ [:div.ui.text.loader.view-loading-label "Loading"]]] [:div.ui.container.activity-container {:ref #(reset! container-element %1)} - [scroll-div container-element] [:div.activity-header "Activities"] - [activity-list @activity-page-data]])))) + [activity-list @activity-page-data container-element]])))) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index fdf741d..bfa73a4 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -3,7 +3,6 @@ [reagent.core :as r] [commiteth.common :refer [moment-timestamp display-data-page - scroll-div items-per-page issue-url]])) @@ -47,7 +46,8 @@ [:img {:src avatar-url}]]]])) (defn bounties-list [{:keys [items item-count page-number total-count] - :as bounty-page-data}] + :as bounty-page-data} + container-element] (if (empty? items) [:div.view-no-data-container [:p "No recent activity yet"]] @@ -56,7 +56,7 @@ right (dec (+ left item-count))] [:div.item-counts-label [:span (str "Showing " left "-" right " of " total-count)]]) - (display-data-page bounty-page-data bounty-item)])) + (display-data-page bounty-page-data bounty-item container-element)])) (defn bounties-page [] (let [bounty-page-data (rf/subscribe [:open-bounties-page]) @@ -69,7 +69,6 @@ [:div.ui.text.loader.view-loading-label "Loading"]]] [:div.ui.container.open-bounties-container {:ref #(reset! container-element %1)} - [scroll-div container-element] [:div.open-bounties-header "Bounties"] - [bounties-list @bounty-page-data]])) + [bounties-list @bounty-page-data container-element]])) )) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 25b47b3..f89e5e1 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -31,22 +31,9 @@ (defn issue-url [owner repo number] (str "https://github.com/" owner "/" repo "/issues/" number)) -(defn scroll-div [container-element] - "This is an invisible div that exists - for the sole purpose of scrolling the container-element into view - when page-number is updated" - (let [page-number (rf/subscribe [:page-number])] - (r/create-class - {:component-did-update (fn [] - (when @container-element - (.scrollIntoView @container-element))) - :reagent-render (fn [] - [:div {:style {:display "none"}} - @page-number])}))) - (def items-per-page 15) -(defn draw-page-numbers [page-number page-count] +(defn draw-page-numbers [page-number page-count container-element] "Draw page numbers for the pagination component. Inserts ellipsis when list is too long, by default max 6 items are allowed" @@ -56,7 +43,9 @@ (if current? {:class "page-num-active"} {:class "grayed-out-page-num" - :on-click #(rf/dispatch [:set-page-number i])}) + :on-click #(do + (rf/dispatch [:set-page-number i]) + (.scrollIntoView @container-element))}) i]) max-page-nums 6] [:div.page-nums-container @@ -92,7 +81,8 @@ total-count page-number page-count]} - draw-item-fn] + draw-item-fn + container-element] "Draw data items along with pagination controls" (let [draw-items (fn [] (into [:div.ui.items] @@ -106,11 +96,12 @@ (rf/dispatch [:set-page-number (if forward? (inc page-number) - (dec page-number))]))) + (dec page-number))]) + (.scrollIntoView @container-element))) draw-rect (fn [direction] (let [forward? (= direction :forward) gray-out? (or (and forward? (= page-number page-count)) - (and (not forward?) (= page-number 1))) ] + (and (not forward?) (= page-number 1)))] [:div.rectangle-rounded (cond-> {:on-click (on-direction-click forward?)} gray-out? (assoc :class "grayed-out-direction")) @@ -127,6 +118,6 @@ [draw-rect :backward] [draw-rect :forward]] [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] - [draw-page-numbers page-number page-count]]]))) + [draw-page-numbers page-number page-count container-element]]]))) From 3813e191508a48d1a74c885a7fc19087cb4f6994 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 26 Jan 2018 12:46:50 +0200 Subject: [PATCH 069/100] Add check for container-element --- src/cljs/commiteth/common.cljs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index f89e5e1..1213395 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -45,7 +45,8 @@ {:class "grayed-out-page-num" :on-click #(do (rf/dispatch [:set-page-number i]) - (.scrollIntoView @container-element))}) + (when @container-element + (.scrollIntoView @container-element)))}) i]) max-page-nums 6] [:div.page-nums-container @@ -97,7 +98,8 @@ (if forward? (inc page-number) (dec page-number))]) - (.scrollIntoView @container-element))) + (when @container-element + (.scrollIntoView @container-element)))) draw-rect (fn [direction] (let [forward? (= direction :forward) gray-out? (or (and forward? (= page-number page-count)) From b10fee6ff1649d43d7a56e82ec680c368c155d11 Mon Sep 17 00:00:00 2001 From: Churikova Tetiana Date: Fri, 26 Jan 2018 13:36:08 +0200 Subject: [PATCH 070/100] Add end-to-end setup doc --- doc/testing.md | 8 ++++++++ test/Jenkinsfile | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/Jenkinsfile diff --git a/doc/testing.md b/doc/testing.md index c0ad83f..3ac6a9f 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -63,3 +63,11 @@ To remove issue from the Bounties list you can close it in GitHub. All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues). Please first check that there is not already a duplicate issue. Issues should contain exact and minimal step-by-step instructions for reproducing the problem. + +### Status Open Bounty end-to-end tests + +Framework for testing located in: `open-bounty/test/end-to-end` + +Full installation and configuring manual: [Status Open Bounty end-to-end tests](https://wiki.status.im/Status_Open_Bounty_end-to-end_tests) + +Currently supports local and Jenkins environment running (you can find example of JenkinsFile in `open-bounty/test` ) diff --git a/test/Jenkinsfile b/test/Jenkinsfile new file mode 100644 index 0000000..8845432 --- /dev/null +++ b/test/Jenkinsfile @@ -0,0 +1,13 @@ +# `Jenkinsfile` is a groovy script DSL for defining CI/CD workflows for Jenkins (end-to-end autotests) + +node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') { + checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]]) + configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) { + try {sh 'cd test/end-to-end/tests && python3 -m pytest -m sanity --build=$BUILD_NAME -v -n 1' + } + finally { + saucePublisher() + junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/*.xml' } + } + } +} \ No newline at end of file From 2f2431792ae6a4af8c9040902168d1848375b0af Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 26 Jan 2018 15:54:35 +0200 Subject: [PATCH 071/100] Revert CIDER removal and update to recent version --- project.clj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 3c8bd92..5f822e7 100644 --- a/project.clj +++ b/project.clj @@ -69,6 +69,7 @@ [lein-auto "0.1.2"] [lein-less "1.7.5"] [lein-shell "0.5.0"] + [cider/cider-nrepl "0.15.0-SNAPSHOT"] [lein-sha-version "0.1.1"]] @@ -143,7 +144,7 @@ :prep-tasks ["build-contracts" "javac"] :doo {:build "test"} - :source-paths ["env/dev/clj" "test/clj" "src/cljs" "env/dev/cljs"] + :source-paths ["env/dev/clj" "test/clj"] :resource-paths ["env/dev/resources"] :repl-options {:init-ns user :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} @@ -167,3 +168,4 @@ :output-to "resources/public/js/compiled/devcards.js" :output-dir "resources/public/js/compiled/out" :source-map-timestamp true}}}}}}) + From 3f883a36c1ed99555fe101347e82566cc70eff12 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 26 Jan 2018 19:24:25 +0200 Subject: [PATCH 072/100] Remove empty line --- project.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/project.clj b/project.clj index 5f822e7..e6af24a 100644 --- a/project.clj +++ b/project.clj @@ -168,4 +168,3 @@ :output-to "resources/public/js/compiled/devcards.js" :output-dir "resources/public/js/compiled/out" :source-map-timestamp true}}}}}}) - From 3c4413b160af025056e8554e0f012ed18212b5ff Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Mon, 29 Jan 2018 19:07:54 +0200 Subject: [PATCH 073/100] [FIX #187] Add dummy value to Metamask dropdown; do not automatically preselect first item in the list; convert addresses to lower-case --- src/cljs/commiteth/common.cljs | 17 +++++++++-------- src/cljs/commiteth/update_address.cljs | 14 ++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 1213395..bc44638 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -10,18 +10,19 @@ (merge props {:type "text" :value @val-ratom :on-change #(reset! val-ratom (-> % .-target .-value))})])) - (defn dropdown [props title val-ratom items] (fn [] - (if (= 1 (count items)) - (reset! val-ratom (first items))) [:select.ui.basic.selection.dropdown (merge props {:on-change - #(reset! val-ratom (-> % .-target .-value))}) - (doall (for [item items] - ^{:key item} [:option - {:value item} - item]))])) + #(let [selected-value (-> % .-target .-value)] + (when (not= selected-value title) + (reset! val-ratom selected-value))) + :default-value (if (contains? (set items) @val-ratom) + @val-ratom + title)}) + (conj (doall (for [item items] + ^{:key item} [:option {:value item} item])) + ^{:key title} [:option {:value title :disabled true} title])])) (defn moment-timestamp [time] (let [now (.now js/Date.) diff --git a/src/cljs/commiteth/update_address.cljs b/src/cljs/commiteth/update_address.cljs index 6134bde..41364c6 100644 --- a/src/cljs/commiteth/update_address.cljs +++ b/src/cljs/commiteth/update_address.cljs @@ -3,6 +3,7 @@ [commiteth.common :refer [input dropdown]] [reagent.core :as r] [reagent.crypt :as crypt] + [clojure.string :as str] [cljs-web3.eth :as web3-eth])) @@ -10,7 +11,9 @@ (let [db (rf/subscribe [:db]) user (rf/subscribe [:user]) updating-address (rf/subscribe [:get-in [:updating-address]]) - address (r/atom @(rf/subscribe [:get-in [:user :address]]))] + address (-> @(rf/subscribe [:get-in [:user :address]]) + str/lower-case + r/atom)] (fn [] (let [web3 (:web3 @db) web3-accounts (when web3 @@ -22,13 +25,12 @@ [:p "Insert your Ethereum address in hex format."] [:div.field (if-not (empty? web3-accounts) - [dropdown {:class "address-input"} "Select address" + [dropdown {:class "address-input"} + "Select address" address - (vec - (for [acc web3-accounts] - acc))] + (map str/lower-case web3-accounts)] [:div.ui.input.address-input - [input address {:placeholder "0x0000000000000000000000000000000000000000" + [input address {:placeholder "0x0000000000000000000000000000000000000000" :auto-complete "off" :auto-correct "off" :spell-check "false" From c5fdad5b8e86f980d271e5f57595f5361ac12733 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Mon, 29 Jan 2018 19:36:49 +0200 Subject: [PATCH 074/100] Fix assoc-in in save-user-address handler --- src/cljs/commiteth/handlers.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 077d280..382d7fb 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -324,7 +324,7 @@ :http {:method POST :url "/api/user/address" :on-success #(do - (dispatch [:assoc-in [:user [:address] address]]) + (dispatch [:assoc-in [:user :address] address]) (dispatch [:set-flash-message :success "Address saved"])) From a52f5f2b20e446670c414f033979457528df65ca Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Mon, 29 Jan 2018 20:18:16 +0200 Subject: [PATCH 075/100] Move selection behaviour and option list population to dropdown component --- src/cljs/commiteth/common.cljs | 31 ++++++++++++++++---------- src/cljs/commiteth/update_address.cljs | 5 ++--- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index bc44638..a613925 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -10,19 +10,26 @@ (merge props {:type "text" :value @val-ratom :on-change #(reset! val-ratom (-> % .-target .-value))})])) + (defn dropdown [props title val-ratom items] - (fn [] - [:select.ui.basic.selection.dropdown - (merge props {:on-change - #(let [selected-value (-> % .-target .-value)] - (when (not= selected-value title) - (reset! val-ratom selected-value))) - :default-value (if (contains? (set items) @val-ratom) - @val-ratom - title)}) - (conj (doall (for [item items] - ^{:key item} [:option {:value item} item])) - ^{:key title} [:option {:value title :disabled true} title])])) + "If val-ratom is set, preselect it in the dropdown. + Add value of val-ratom if it's missing from items list. + Otherwise, prepend title as a disabled option" + (let [items (cond-> items + (and @val-ratom + (not (contains? (set items) @val-ratom))) + (conj @val-ratom) + (not @val-ratom) + (conj title))] + (fn [] + [:select.ui.basic.selection.dropdown + (merge props {:on-change + #(reset! val-ratom (-> % .-target .-value)) + :default-value (or @val-ratom title)}) + (doall (for [item items] + ^{:key item} [:option {:value item + :disabled (= item title)} + item]))]))) (defn moment-timestamp [time] (let [now (.now js/Date.) diff --git a/src/cljs/commiteth/update_address.cljs b/src/cljs/commiteth/update_address.cljs index 41364c6..9261671 100644 --- a/src/cljs/commiteth/update_address.cljs +++ b/src/cljs/commiteth/update_address.cljs @@ -11,9 +11,8 @@ (let [db (rf/subscribe [:db]) user (rf/subscribe [:user]) updating-address (rf/subscribe [:get-in [:updating-address]]) - address (-> @(rf/subscribe [:get-in [:user :address]]) - str/lower-case - r/atom)] + address (r/atom (some-> @(rf/subscribe [:get-in [:user :address]]) + str/lower-case))] (fn [] (let [web3 (:web3 @db) web3-accounts (when web3 From 7bf00ce6c1e8fd321a6ee1b5366db1379460971c Mon Sep 17 00:00:00 2001 From: Churikova Tetiana Date: Tue, 30 Jan 2018 18:25:46 +0200 Subject: [PATCH 076/100] fixed Jenkinsfile for Jenkins --- test/Jenkinsfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/Jenkinsfile b/test/Jenkinsfile index 8845432..8169c6f 100644 --- a/test/Jenkinsfile +++ b/test/Jenkinsfile @@ -1,5 +1,3 @@ -# `Jenkinsfile` is a groovy script DSL for defining CI/CD workflows for Jenkins (end-to-end autotests) - node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') { checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]]) configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) { From 9b40abc1a1b88728b257f62a053920fb7b19944f Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 11:43:39 +0100 Subject: [PATCH 077/100] added missing svgs --- resources/public/bounty-filter-remove.svg | 6 ++++++ resources/public/icon-forward-white.svg | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 resources/public/bounty-filter-remove.svg create mode 100644 resources/public/icon-forward-white.svg diff --git a/resources/public/bounty-filter-remove.svg b/resources/public/bounty-filter-remove.svg new file mode 100644 index 0000000..b4e091f --- /dev/null +++ b/resources/public/bounty-filter-remove.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/public/icon-forward-white.svg b/resources/public/icon-forward-white.svg new file mode 100644 index 0000000..9842677 --- /dev/null +++ b/resources/public/icon-forward-white.svg @@ -0,0 +1,3 @@ + + + From fa5bdef66ae23813875ea3dab291b1958bf0c1d4 Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 11:51:55 +0100 Subject: [PATCH 078/100] a few improvements and comments in filter-bounties --- src/cljs/commiteth/subscriptions.cljs | 4 ++-- src/cljs/commiteth/ui_model.cljs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 03be998..d7a1818 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -162,6 +162,6 @@ :<- [::open-bounties-sorting-type] (fn [[open-bounties filters sorting-type] _] (cond->> open-bounties - filters (ui-model/filter-bounties filters) + true (ui-model/filter-bounties filters) sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type) - filter vec))) + true vec))) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index fa25f0c..cfa0210 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -107,15 +107,16 @@ (defn filter-bounties [filters-by-type bounties] (let [filter-preds (->> filters-by-type + ; used `nil?` because a valid filter value can be `false` (remove #(nil? (val %))) (map (fn [[filter-type filter-value]] (let [filter-type-def (bounty-filter-types-def filter-type) pred (::bounty-filter-type.predicate filter-type-def) pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def) filter-value (cond-> filter-value - pre-pred-processor (pre-pred-processor filter-value))] + pre-pred-processor pre-pred-processor)] (partial pred filter-value))))) filters-pred (fn [bounty] (every? #(% bounty) filter-preds))] - (->> bounties - (filter filters-pred)))) + (cond->> bounties + (not-empty filter-preds) (filter filters-pred)))) From 65cea5d521eb86bf2f95e442dd62e322a3d0d692 Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 12:01:24 +0100 Subject: [PATCH 079/100] reformat bounty-filter-types-def --- src/cljs/commiteth/bounties.cljs | 2 +- src/cljs/commiteth/ui_model.cljs | 79 +++++++++++++++++--------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 3964f04..645958a 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -108,7 +108,7 @@ (defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|multiple-dynamic-options [filter-type filter-type-def current-filter-value tooltip-open?] - (let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subscription-key-for-options filter-type-def)])] + (let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subs-key-for-options filter-type-def)])] [:div.open-bounties-filter-list (for [option @options] (let [active? (boolean (and current-filter-value (current-filter-value option)))] diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index cfa0210..039920d 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -45,42 +45,49 @@ (def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) (def bounty-filter-types-def - {::bounty-filter-type|value {::bounty-filter-type.name "Value" - ::bounty-filter-type.category ::bounty-filter-type-category|range - ::bounty-filter-type.min-val 0 - ::bounty-filter-type.max-val 1000 - ::bounty-filter-type.predicate (fn [filter-value bounty] - (let [min-val (first filter-value) - max-val (second filter-value)] - (<= min-val (:value-usd bounty) max-val)))} - ::bounty-filter-type|currency {::bounty-filter-type.name "Currency" - ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options - ::bounty-filter-type.re-frame-subscription-key-for-options :commiteth.subscriptions/open-bounties-currencies - ::bounty-filter-type.predicate (fn [filter-value bounty] - (and (or (not-any? #{"ETH"} filter-value) - (< 0 (:balance-eth bounty))) - (set/subset? (->> filter-value (remove #{"ETH"}) set) - (-> bounty :tokens keys set))))} - ::bounty-filter-type|date {::bounty-filter-type.name "Date" - ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option - ::bounty-filter-type.options bounty-filter-type-date-options-def - ::bounty-filter-type.pre-predicate-value-processor (fn [filter-value] - (let [filter-from (condp = filter-value - ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) - ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) - ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))] - (t/interval filter-from (t/now)))) - ::bounty-filter-type.predicate (fn [filter-value-interval bounty] - (when-let [created-at-inst (:created-at bounty)] - (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] - (t/within? filter-value-interval created-at-date))))} - ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" - ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options - ::bounty-filter-type.re-frame-subscription-key-for-options :commiteth.subscriptions/open-bounties-owners - ::bounty-filter-type.predicate (fn [filter-value bounty] - (->> filter-value - (some #{(:repo-owner bounty)}) - boolean))}}) + {::bounty-filter-type|value + {::bounty-filter-type.name "Value" + ::bounty-filter-type.category ::bounty-filter-type-category|range + ::bounty-filter-type.min-val 0 + ::bounty-filter-type.max-val 1000 + ::bounty-filter-type.predicate (fn [filter-value bounty] + (let [min-val (first filter-value) + max-val (second filter-value)] + (<= min-val (:value-usd bounty) max-val)))} + + ::bounty-filter-type|currency + {::bounty-filter-type.name "Currency" + ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options + ::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-currencies + ::bounty-filter-type.predicate (fn [filter-value bounty] + (and (or (not-any? #{"ETH"} filter-value) + (< 0 (:balance-eth bounty))) + (set/subset? (->> filter-value (remove #{"ETH"}) set) + (-> bounty :tokens keys set))))} + + ::bounty-filter-type|date + {::bounty-filter-type.name "Date" + ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option + ::bounty-filter-type.options bounty-filter-type-date-options-def + ::bounty-filter-type.pre-predicate-value-processor (fn [filter-value] + (let [filter-from (condp = filter-value + ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) + ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) + ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))] + (t/interval filter-from (t/now)))) + ::bounty-filter-type.predicate (fn [filter-value-interval bounty] + (when-let [created-at-inst (:created-at bounty)] + (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] + (t/within? filter-value-interval created-at-date))))} + + ::bounty-filter-type|owner + {::bounty-filter-type.name "Owner" + ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options + ::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-owners + ::bounty-filter-type.predicate (fn [filter-value bounty] + (->> filter-value + (some #{(:repo-owner bounty)}) + boolean))}}) (def bounty-filter-types (keys bounty-filter-types-def)) From 15768318cf7f5ef53bbcc57d5db1b34004adbf9f Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 12:04:33 +0100 Subject: [PATCH 080/100] reset sorting and filters when navigating page --- src/cljs/commiteth/handlers.cljs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 4367c9a..554e6d6 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -11,7 +11,8 @@ [cljs-web3.eth :as web3-eth] [akiroz.re-frame.storage :as rf-storage - :refer [reg-co-fx!]])) + :refer [reg-co-fx!]] + [commiteth.ui-model :as ui-model])) (rf-storage/reg-co-fx! :commiteth-sob {:fx :store @@ -66,7 +67,9 @@ (reg-event-db :set-active-page (fn [db [_ page]] - (assoc db :page page))) + (assoc db :page page + ::db/open-bounties-filters {} + ::db/open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent))) (reg-event-db :set-bounty-page-number From faff5327978b32cbbe4e7e1fe63a37943e16ceba Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 12:45:40 +0100 Subject: [PATCH 081/100] making some lines shorter --- src/cljs/commiteth/ui_model.cljs | 48 +++++++++++++++++++------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 039920d..9fab8c8 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -44,6 +44,19 @@ (def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) +(def bounty-filter-type-date-pre-predicate-value-processor + (fn [filter-value] + (let [filter-from (condp = filter-value + ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) + ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) + ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))] + (t/interval filter-from (t/now))))) +(def bounty-filter-type-date-predicate + (fn [filter-value-interval bounty] + (when-let [created-at-inst (:created-at bounty)] + (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] + (t/within? filter-value-interval created-at-date))))) + (def bounty-filter-types-def {::bounty-filter-type|value {::bounty-filter-type.name "Value" @@ -69,16 +82,8 @@ {::bounty-filter-type.name "Date" ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option ::bounty-filter-type.options bounty-filter-type-date-options-def - ::bounty-filter-type.pre-predicate-value-processor (fn [filter-value] - (let [filter-from (condp = filter-value - ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) - ::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1)) - ::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))] - (t/interval filter-from (t/now)))) - ::bounty-filter-type.predicate (fn [filter-value-interval bounty] - (when-let [created-at-inst (:created-at bounty)] - (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] - (t/within? filter-value-interval created-at-date))))} + ::bounty-filter-type.pre-predicate-value-processor bounty-filter-type-date-pre-predicate-value-processor + ::bounty-filter-type.predicate bounty-filter-type-date-predicate} ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" @@ -112,17 +117,20 @@ :else (str filter-type " with val " filter-value))) +(defn- bounty-filter-values-by-type->predicates [filters-by-type] + (->> filters-by-type + ; used `nil?` because a valid filter value can be `false` + (remove #(nil? (val %))) + (map (fn [[filter-type filter-value]] + (let [filter-type-def (bounty-filter-types-def filter-type) + pred (::bounty-filter-type.predicate filter-type-def) + pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def) + filter-value (cond-> filter-value + pre-pred-processor pre-pred-processor)] + (partial pred filter-value)))))) + (defn filter-bounties [filters-by-type bounties] - (let [filter-preds (->> filters-by-type - ; used `nil?` because a valid filter value can be `false` - (remove #(nil? (val %))) - (map (fn [[filter-type filter-value]] - (let [filter-type-def (bounty-filter-types-def filter-type) - pred (::bounty-filter-type.predicate filter-type-def) - pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def) - filter-value (cond-> filter-value - pre-pred-processor pre-pred-processor)] - (partial pred filter-value))))) + (let [filter-preds (bounty-filter-values-by-type->predicates filters-by-type) filters-pred (fn [bounty] (every? #(% bounty) filter-preds))] (cond->> bounties From 35ad4e48d07e9fc7b7eaadedd45df9e5a5848c6b Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 15:21:34 +0100 Subject: [PATCH 082/100] add filter for claims --- src/cljs/commiteth/ui_model.cljs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 9fab8c8..0a99444 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -44,6 +44,9 @@ (def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def)) +(defn bounty-filter-type-date-option->name [option] + (bounty-filter-type-date-options-def option)) + (def bounty-filter-type-date-pre-predicate-value-processor (fn [filter-value] (let [filter-from (condp = filter-value @@ -57,6 +60,13 @@ (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] (t/within? filter-value-interval created-at-date))))) +(def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "With no claims"}) + +(def bounty-filter-type-claims-options (keys bounty-filter-type-claims-options-def)) + +(defn bounty-filter-type-claims-option->name [option] + (bounty-filter-type-claims-options-def option)) + (def bounty-filter-types-def {::bounty-filter-type|value {::bounty-filter-type.name "Value" @@ -92,16 +102,22 @@ ::bounty-filter-type.predicate (fn [filter-value bounty] (->> filter-value (some #{(:repo-owner bounty)}) - boolean))}}) + boolean))} + + ::bounty-filter-type|claims + {::bounty-filter-type.name "Claims" + ::bounty-filter-type.category ::bounty-filter-type-category|single-static-option + ::bounty-filter-type.options bounty-filter-type-claims-options-def + ::bounty-filter-type.predicate (fn [filter-value bounty] + (condp = filter-value + ::bounty-filter-type-claims-option|no-claims + (<= 0 (:claim-count bounty))))}}) (def bounty-filter-types (keys bounty-filter-types-def)) (defn bounty-filter-type->name [filter-type] (-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name)) -(defn bounty-filter-type-date-option->name [option] - (bounty-filter-type-date-options-def option)) - (defn bounty-filter-value->short-text [filter-type filter-value] (cond (= filter-type ::bounty-filter-type|date) @@ -114,6 +130,9 @@ (= filter-type ::bounty-filter-type|value) (str "$" (first filter-value) "-$" (second filter-value)) + (= filter-type ::bounty-filter-type|claims) + (bounty-filter-type-claims-option->name filter-value) + :else (str filter-type " with val " filter-value))) From eef57a3046547a457cfcd3adbc0abab7859eab43 Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 16:06:01 +0100 Subject: [PATCH 083/100] add some docstrings --- src/cljs/commiteth/ui_model.cljs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 0a99444..2efba9c 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -48,6 +48,9 @@ (bounty-filter-type-date-options-def option)) (def bounty-filter-type-date-pre-predicate-value-processor + "It converts an option of the filter type date to a cljs-time interval in which + that option is valid, so that you can check `cljs-time.core.within?` against that + interval and know if a `cljs-time` date is valid for that filter type date option." (fn [filter-value] (let [filter-from (condp = filter-value ::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1)) @@ -137,6 +140,11 @@ (str filter-type " with val " filter-value))) (defn- bounty-filter-values-by-type->predicates [filters-by-type] + "It receives a map with filter types as keys and filter values as values and + returns a lazy seq of predicates, one for each pair of filter type and value. + Those predicate can receive a bounty and tell whether that bounty passes + the filter type with that filter value. It removes filter types with a `nil` + filter value." (->> filters-by-type ; used `nil?` because a valid filter value can be `false` (remove #(nil? (val %))) From 489260aaf4e67c6ceb4d5a41da648aeeba375938 Mon Sep 17 00:00:00 2001 From: pablodip Date: Wed, 31 Jan 2018 18:51:44 +0100 Subject: [PATCH 084/100] change <= for < --- src/cljs/commiteth/ui_model.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 2efba9c..db3234d 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -114,7 +114,7 @@ ::bounty-filter-type.predicate (fn [filter-value bounty] (condp = filter-value ::bounty-filter-type-claims-option|no-claims - (<= 0 (:claim-count bounty))))}}) + (= 0 (:claim-count bounty))))}}) (def bounty-filter-types (keys bounty-filter-types-def)) From 9e9d0f6d4d9a4e5e0fc09451b7dac651b46ea223 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Wed, 31 Jan 2018 17:48:26 +0200 Subject: [PATCH 085/100] Change dropdown component: remove doall, use into instead of conj --- src/cljs/commiteth/common.cljs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index a613925..0b0898f 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -15,21 +15,21 @@ "If val-ratom is set, preselect it in the dropdown. Add value of val-ratom if it's missing from items list. Otherwise, prepend title as a disabled option" - (let [items (cond-> items + (let [items (cond->> items (and @val-ratom (not (contains? (set items) @val-ratom))) - (conj @val-ratom) + (into [@val-ratom]) (not @val-ratom) - (conj title))] + (into [title]))] (fn [] [:select.ui.basic.selection.dropdown (merge props {:on-change #(reset! val-ratom (-> % .-target .-value)) :default-value (or @val-ratom title)}) - (doall (for [item items] + (for [item items] ^{:key item} [:option {:value item :disabled (= item title)} - item]))]))) + item])]))) (defn moment-timestamp [time] (let [now (.now js/Date.) From 50036b10dd6f296aa17da7dd93d5f25cc2f40c86 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Thu, 1 Feb 2018 15:40:54 +0200 Subject: [PATCH 086/100] Address dropdown: move items list construction to invocation point --- src/cljs/commiteth/common.cljs | 27 ++++++++++---------------- src/cljs/commiteth/update_address.cljs | 19 ++++++++++++++---- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 0b0898f..4399156 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -13,23 +13,16 @@ (defn dropdown [props title val-ratom items] "If val-ratom is set, preselect it in the dropdown. - Add value of val-ratom if it's missing from items list. - Otherwise, prepend title as a disabled option" - (let [items (cond->> items - (and @val-ratom - (not (contains? (set items) @val-ratom))) - (into [@val-ratom]) - (not @val-ratom) - (into [title]))] - (fn [] - [:select.ui.basic.selection.dropdown - (merge props {:on-change - #(reset! val-ratom (-> % .-target .-value)) - :default-value (or @val-ratom title)}) - (for [item items] - ^{:key item} [:option {:value item - :disabled (= item title)} - item])]))) + Otherwise, prepend title as a disabled option." + (fn [] + [:select.ui.basic.selection.dropdown + (merge props {:on-change + #(reset! val-ratom (-> % .-target .-value)) + :default-value (or @val-ratom title)}) + (for [item items] + ^{:key item} [:option {:value item + :disabled (= item title)} + item])])) (defn moment-timestamp [time] (let [now (.now js/Date.) diff --git a/src/cljs/commiteth/update_address.cljs b/src/cljs/commiteth/update_address.cljs index 9261671..60e298f 100644 --- a/src/cljs/commiteth/update_address.cljs +++ b/src/cljs/commiteth/update_address.cljs @@ -24,10 +24,21 @@ [:p "Insert your Ethereum address in hex format."] [:div.field (if-not (empty? web3-accounts) - [dropdown {:class "address-input"} - "Select address" - address - (map str/lower-case web3-accounts)] + ; Add value of address if it's missing from items list. + ; If address is empty, add title + (let [accounts (map str/lower-case web3-accounts) + addr @address + title "Select address" + items (cond->> accounts + (and addr + (not (contains? (set accounts) addr))) + (into [addr]) + (not addr) + (into [title]))] + [dropdown {:class "address-input"} + title + address + items]) [:div.ui.input.address-input [input address {:placeholder "0x0000000000000000000000000000000000000000" :auto-complete "off" From 8a75d5d68c6bf778a10702af60e7badc28eca4f5 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Thu, 1 Feb 2018 16:32:10 +0200 Subject: [PATCH 087/100] Address dropdown: Apply case-insensitive comparison only during component setup --- src/cljs/commiteth/update_address.cljs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cljs/commiteth/update_address.cljs b/src/cljs/commiteth/update_address.cljs index 60e298f..14ceaa1 100644 --- a/src/cljs/commiteth/update_address.cljs +++ b/src/cljs/commiteth/update_address.cljs @@ -11,8 +11,7 @@ (let [db (rf/subscribe [:db]) user (rf/subscribe [:user]) updating-address (rf/subscribe [:get-in [:updating-address]]) - address (r/atom (some-> @(rf/subscribe [:get-in [:user :address]]) - str/lower-case))] + address (r/atom @(rf/subscribe [:get-in [:user :address]]))] (fn [] (let [web3 (:web3 @db) web3-accounts (when web3 @@ -29,12 +28,14 @@ (let [accounts (map str/lower-case web3-accounts) addr @address title "Select address" - items (cond->> accounts - (and addr - (not (contains? (set accounts) addr))) - (into [addr]) - (not addr) - (into [title]))] + addr-not-in-web3? (and addr (as-> web3-accounts acc + (map str/lower-case acc) + (set acc) + (contains? acc addr) + (not acc))) + items (cond->> web3-accounts + addr-not-in-web3? (into [addr]) + (not addr) (into [title]))] [dropdown {:class "address-input"} title address From a3e42fe61f81f1722de092a80f109af57e6426bf Mon Sep 17 00:00:00 2001 From: pablodip Date: Thu, 1 Feb 2018 15:56:15 +0100 Subject: [PATCH 088/100] some filtering and sorting fixes --- src/cljs/commiteth/bounties.cljs | 2 +- src/cljs/commiteth/handlers.cljs | 4 ++-- src/cljs/commiteth/subscriptions.cljs | 9 ++++++++- src/cljs/commiteth/ui_model.cljs | 7 ++++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 55e2c29..5d7600c 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -84,7 +84,7 @@ (let [new-min (min current-min (max default-min new-max))] (on-change-fn new-min new-max)))] [:div - "$0 - $1000+" + (::ui-model/bounty-filter.type.header filter-type-def) [bounties-filter-tooltip-value-input-view "Min" tooltip-open? (merge common-range-opts {:current-val current-min :on-change-val on-min-change-fn})] diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index da58177..eb61374 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -465,11 +465,11 @@ ::set-open-bounties-sorting-type (fn [db [_ sorting-type]] (merge db {::db/open-bounties-sorting-type sorting-type - :bounty-page-number 1}))) + :page-number 1}))) (reg-event-db ::set-open-bounty-filter-type (fn [db [_ filter-type filter-value]] (-> db (assoc-in [::db/open-bounties-filters filter-type] filter-value) - (assoc :bounty-page-number 1)))) + (assoc :page-number 1)))) diff --git a/src/cljs/commiteth/subscriptions.cljs b/src/cljs/commiteth/subscriptions.cljs index 5598142..bb2a82a 100644 --- a/src/cljs/commiteth/subscriptions.cljs +++ b/src/cljs/commiteth/subscriptions.cljs @@ -2,7 +2,8 @@ (:require [re-frame.core :refer [reg-sub]] [commiteth.db :as db] [commiteth.ui-model :as ui-model] - [commiteth.common :refer [items-per-page]])) + [commiteth.common :refer [items-per-page]] + [clojure.string :as string])) (reg-sub :db @@ -139,6 +140,12 @@ (map :repo-owner) set))) +(reg-sub + ::open-bounties-owners-sorted + :<- [::open-bounties-owners] + (fn [owners _] + (sort-by string/lower-case owners))) + (reg-sub ::open-bounties-currencies :<- [:open-bounties] diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index db3234d..2b4a3f3 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -63,7 +63,7 @@ (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] (t/within? filter-value-interval created-at-date))))) -(def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "With no claims"}) +(def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "Not claimed yet"}) (def bounty-filter-type-claims-options (keys bounty-filter-type-claims-options-def)) @@ -75,7 +75,8 @@ {::bounty-filter-type.name "Value" ::bounty-filter-type.category ::bounty-filter-type-category|range ::bounty-filter-type.min-val 0 - ::bounty-filter-type.max-val 1000 + ::bounty-filter-type.max-val 10000 + ::bounty-filter.type.header "$0 - $10000+" ::bounty-filter-type.predicate (fn [filter-value bounty] (let [min-val (first filter-value) max-val (second filter-value)] @@ -101,7 +102,7 @@ ::bounty-filter-type|owner {::bounty-filter-type.name "Owner" ::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options - ::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-owners + ::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-owners-sorted ::bounty-filter-type.predicate (fn [filter-value bounty] (->> filter-value (some #{(:repo-owner bounty)}) From 3686004dc2182d890d01d0108ad93f1d090e9f86 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Fri, 2 Feb 2018 13:23:26 +0200 Subject: [PATCH 089/100] Include wkhtmltopdf reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 597831f..0124750 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The `develop` branch is automatically deployed here. ## Prerequisites -You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. +You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. Also, make sure that you have [wkhtmltoimage](https://wkhtmltopdf.org/downloads.html) available in your PATH. On macOS, it can be installed via `brew cask install wkhtmltopdf`. ### PostgreSQL From e3a9d07add7ce132a0cc05781a12e17f28f3d60f Mon Sep 17 00:00:00 2001 From: pablodip Date: Fri, 2 Feb 2018 23:39:49 +0100 Subject: [PATCH 090/100] fix filter single-static-option when clicking with touchpad in mac --- src/cljs/commiteth/bounties.cljs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index 5d7600c..af1b2c7 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -98,10 +98,12 @@ (for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)] ^{:key (str option-type)} [:div.open-bounties-filter-list-option - (merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type - filter-type - option-type]) - (reset! tooltip-open? false))} + (merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type + filter-type + option-type]) + (reset! tooltip-open? false)) + :tab-index 0 + :on-focus #(reset! tooltip-open? true)} (when (= option-type current-filter-value) {:class "active"})) option-text])]) From 1b9151e1e1a38b08dc70d40a3483a7847fa84497 Mon Sep 17 00:00:00 2001 From: pablodip Date: Fri, 2 Feb 2018 23:46:09 +0100 Subject: [PATCH 091/100] use updated-at in filtering and sorting --- resources/sql/queries.sql | 1 - src/clj/commiteth/routes/services.clj | 3 +-- src/cljs/commiteth/ui_model.cljs | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 1fa529c..e8a3115 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -422,7 +422,6 @@ SELECT i.payout_hash AS payout_hash, i.payout_receipt AS payout_receipt, i.updated AS updated, - i.created_at AS created_at, r.owner AS repo_owner, r.owner_avatar_url AS repo_owner_avatar_url, r.repo AS repo_name, diff --git a/src/clj/commiteth/routes/services.clj b/src/clj/commiteth/routes/services.clj index 6a60fec..82c5633 100644 --- a/src/clj/commiteth/routes/services.clj +++ b/src/clj/commiteth/routes/services.clj @@ -155,8 +155,7 @@ :value_usd :value-usd :claim_count :claim-count :balance_eth :balance-eth - :user_has_address :user-has-address - :created_at :created-at}] + :user_has_address :user-has-address}] (map #(-> % (rename-keys renames) (update :value-usd usd-decimal->str) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 2b4a3f3..46824a1 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -9,7 +9,7 @@ (def bounty-sorting-types-def {::bounty-sorting-type|most-recent {::bounty-sorting-type.name "Most recent" ::bounty-sorting-type.sort-key-fn (fn [bounty] - (:created-at bounty)) + (:updated-at bounty)) ::bounty-sorting-type.sort-comparator-fn compare} ::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value" ::bounty-sorting-type.sort-key-fn (fn [bounty] @@ -59,9 +59,9 @@ (t/interval filter-from (t/now))))) (def bounty-filter-type-date-predicate (fn [filter-value-interval bounty] - (when-let [created-at-inst (:created-at bounty)] - (let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)] - (t/within? filter-value-interval created-at-date))))) + (when-let [date-inst (:updated-at bounty)] + (let [date (-> date-inst inst-ms t-coerce/from-long)] + (t/within? filter-value-interval date))))) (def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "Not claimed yet"}) From bc72fd165d0b98dcb8a7a0c9eca4ab58a1d058fb Mon Sep 17 00:00:00 2001 From: pablodip Date: Sat, 3 Feb 2018 00:07:23 +0100 Subject: [PATCH 092/100] limit height of filter tooltips and add scroll to them if exceeded --- src/cljs/commiteth/bounties.cljs | 2 +- src/less/style.less | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cljs/commiteth/bounties.cljs b/src/cljs/commiteth/bounties.cljs index af1b2c7..34137bb 100644 --- a/src/cljs/commiteth/bounties.cljs +++ b/src/cljs/commiteth/bounties.cljs @@ -83,7 +83,7 @@ on-max-change-fn (fn [new-max] (let [new-min (min current-min (max default-min new-max))] (on-change-fn new-min new-max)))] - [:div + [:div.open-bounties-filter-list (::ui-model/bounty-filter.type.header filter-type-def) [bounties-filter-tooltip-value-input-view "Min" tooltip-open? (merge common-range-opts {:current-val current-min diff --git a/src/less/style.less b/src/less/style.less index 0827245..6fb2407 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -475,7 +475,6 @@ position: absolute; min-width: 227px; margin-top: 12px; - padding: 24px 16px; border-radius: 10px; background-color: #ffffff; box-shadow: 0 15px 12px 0 rgba(161, 174, 182, 0.53), 0 0 38px 0 rgba(0, 0, 0, 0.05); @@ -483,6 +482,8 @@ font-size: 16px; line-height: 1.5; z-index: 999; + max-height: 300px; + overflow: scroll; .open-bounties-filter-element-tooltip-value-input-container { display: flex; @@ -591,6 +592,7 @@ display: flex; flex-direction: column; align-items: flex-start; + margin: 24px 16px; } .open-bounties-filter-list-option { From 7fad2dc03cf83aa1a0d633c1fff70e4feeb231ad Mon Sep 17 00:00:00 2001 From: pablodip Date: Sat, 3 Feb 2018 09:47:06 +0100 Subject: [PATCH 093/100] support for unversioned config files and some missing config keys in the dev base config file --- .gitignore | 4 ++++ env/dev/resources/config.edn | 4 +++- project.clj | 9 ++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 4f4f73c..39c20db 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ profiles.clj .idea resources/contracts node_modules +/config-prod.edn +/config-dev.edn +/config-test.edn +/src/java diff --git a/env/dev/resources/config.edn b/env/dev/resources/config.edn index 1ee9264..012811d 100644 --- a/env/dev/resources/config.edn +++ b/env/dev/resources/config.edn @@ -11,7 +11,7 @@ :eth-password "XXX" ;; RPC URL to ethereum node to be used - :eth-rpc-url "http://localhost:8547" + :eth-rpc-url "http://localhost:8545" :eth-wallet-file "/some/location" ;; address of token registry to be used @@ -43,5 +43,7 @@ ;; needeed when :hubspot-contact-create-enabled :hubspot-api-key "xxxxxxx-xxxx-x-xxxx-xxxx" + :user-whitelist #{} + ;; used for blacklisting tokens from token registry data :token-blacklist #{}} diff --git a/project.clj b/project.clj index e6af24a..0678470 100644 --- a/project.clj +++ b/project.clj @@ -94,7 +94,8 @@ :profiles - {:uberjar {:omit-source true + {:uberjar {:jvm-opts ["-server" "-Dconf=config-prod.edn"] + :omit-source true :prep-tasks ["build-contracts" "javac" "compile" ["cljsbuild" "once" "min"] ["less" "once"]] :cljsbuild {:builds @@ -116,7 +117,8 @@ :uberjar-name "commiteth.jar" :source-paths ["env/prod/clj"] :resource-paths ["env/prod/resources"]} - :dev {:dependencies [[prone "1.1.4"] + :dev {:jvm-opts ["-server" "-Dconf=config-dev.edn"] + :dependencies [[prone "1.1.4"] [ring/ring-mock "0.3.1"] [ring/ring-devel "1.6.2"] [pjstadig/humane-test-output "0.8.3"] @@ -150,7 +152,8 @@ :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} :injections [(require 'pjstadig.humane-test-output) (pjstadig.humane-test-output/activate!)]} - :test {:resource-paths ["env/dev/resources" "env/test/resources"] + :test {:jvm-opts ["-server" "-Dconf=config-test.edn"] + :resource-paths ["env/dev/resources" "env/test/resources"] :dependencies [[devcards "0.2.4"]] :cljsbuild {:builds From fe9125202f3c2110bde8df3f3fd5e47ec6900b4a Mon Sep 17 00:00:00 2001 From: pablodip Date: Sat, 3 Feb 2018 09:51:06 +0100 Subject: [PATCH 094/100] adapt docs to unversioned config files --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 597831f..aee9657 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Web3j [2.3.0](https://github.com/web3j/web3j/releases/tag/v2.3.0) is required an ## Application config -Make sure that `env/dev/resources/config.edn` is correctly populated. Description of config fields is given below: +Make sure to create `/config-dev.edn` and populate it correctly, which is based on `env/dev/resources/config.edn`. Description of config fields is given below: Key | Description --- | --- @@ -81,7 +81,7 @@ testnet-token-data | Token data map, useful if there are Geth connectivity probl Open Bounty uses both OAuth App and GitHub App integration. ### OAuth App -Follow the steps [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). Specify the value of `:server-address` as "Homepage URL", and `:server-address` + `/callback` as "Authorization callback URL". Be sure to copy Client ID and Client Secret values to `config.edn`. +Follow the steps [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). Specify the value of `:server-address` as "Homepage URL", and `:server-address` + `/callback` as "Authorization callback URL". Be sure to copy Client ID and Client Secret values in the config file. ### GitHub App Follow the steps [here](https://developer.github.com/apps/building-github-apps/creating-a-github-app/). Be sure to specify `:server-address` + `/webhook-app` as "Webhook URL", and `:webhook-secret` as "Webhook Secret". From 52f95b82543bc1f72ec0c83033a3dc2be918521d Mon Sep 17 00:00:00 2001 From: pablodip Date: Mon, 5 Feb 2018 08:48:22 +0100 Subject: [PATCH 095/100] fix some default values in dev env --- env/dev/resources/config.edn | 8 +++++--- src/clj/commiteth/eth/multisig_wallet.clj | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/env/dev/resources/config.edn b/env/dev/resources/config.edn index 012811d..571a6ae 100644 --- a/env/dev/resources/config.edn +++ b/env/dev/resources/config.edn @@ -15,12 +15,14 @@ :eth-wallet-file "/some/location" ;; address of token registry to be used - :tokenreg-addr "0x..." + ;; this is the default value for ropsten + :tokenreg-addr "0x7d127a3e3b5e72cd8f15e7dee650abe4fcced2b9" ;; format of tokenreg records' base field, possible values :status, :parity - :tokenreg-base-format :parity + :tokenreg-base-format :status ;; address of factory contract used for deploying bounty contracts - :contract-factory-addr "0x..." + ;; this is the default value for ropsten + :contract-factory-addr "0x3B9A3c062Bdb640b5039C0cCda4157737d732F95" ;; commiteth-test-tpatja :github-client-id "CLIENT ID" diff --git a/src/clj/commiteth/eth/multisig_wallet.clj b/src/clj/commiteth/eth/multisig_wallet.clj index 67bbb06..f5393ab 100644 --- a/src/clj/commiteth/eth/multisig_wallet.clj +++ b/src/clj/commiteth/eth/multisig_wallet.clj @@ -29,7 +29,7 @@ :confirmation (eth/event-sig->topic-id "Confirmation(address,uint256)")}) (defn factory-contract-addr [] - (env :contract-factory-addr "0x47F56FD26EEeCda4FdF5DB5843De1fe75D2A64A6")) + (env :contract-factory-addr)) (defn tokenreg-base-format ;; status tokenreg uses eg :base 18, while parity uses :base 1000000000000 From 0df0ef6ef2de35393b6801d6f5a6681e422dff31 Mon Sep 17 00:00:00 2001 From: pablodip Date: Mon, 5 Feb 2018 16:26:48 +0100 Subject: [PATCH 096/100] change :update-at by :update and sort direction fixing --- src/cljs/commiteth/ui_model.cljs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cljs/commiteth/ui_model.cljs b/src/cljs/commiteth/ui_model.cljs index 46824a1..fd7d41c 100644 --- a/src/cljs/commiteth/ui_model.cljs +++ b/src/cljs/commiteth/ui_model.cljs @@ -9,8 +9,8 @@ (def bounty-sorting-types-def {::bounty-sorting-type|most-recent {::bounty-sorting-type.name "Most recent" ::bounty-sorting-type.sort-key-fn (fn [bounty] - (:updated-at bounty)) - ::bounty-sorting-type.sort-comparator-fn compare} + (:updated bounty)) + ::bounty-sorting-type.sort-comparator-fn (comp - compare)} ::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value" ::bounty-sorting-type.sort-key-fn (fn [bounty] (js/parseFloat (:value-usd bounty))) @@ -59,7 +59,7 @@ (t/interval filter-from (t/now))))) (def bounty-filter-type-date-predicate (fn [filter-value-interval bounty] - (when-let [date-inst (:updated-at bounty)] + (when-let [date-inst (:updated bounty)] (let [date (-> date-inst inst-ms t-coerce/from-long)] (t/within? filter-value-interval date))))) From 3d454c9f8d0c4fa08d3680e8dba93e72b854168f Mon Sep 17 00:00:00 2001 From: Ivan Borovkov <555475+v2nek@users.noreply.github.com> Date: Tue, 6 Feb 2018 12:18:31 +0200 Subject: [PATCH 097/100] jenkinsfile v1 (#229) * jenkinsfile v1 * call another job to roll out --- .dockerignore | 3 +++ Dockerfile | 30 ++++++++++++++++++++++++++++++ Jenkinsfile | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Jenkinsfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f10a6b0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +docker-compose.yml +Dockerfile +Jenkinsfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ce2947a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM clojure as builder + +WORKDIR /tmp + +RUN wget -O /usr/local/bin/solc https://github.com/ethereum/solidity/releases/download/v0.4.15/solc-static-linux +RUN chmod +x /usr/local/bin/solc + +RUN wget https://github.com/web3j/web3j/releases/download/v2.3.0/web3j-2.3.0.tar +RUN tar -xf web3j-2.3.0.tar +RUN cp -r web3j-2.3.0/* /usr/local/ + + +COPY . /usr/src/app +WORKDIR /usr/src/app + +ENV LEIN_SNAPSHOTS_IN_RELEASE=1 + + +RUN lein less once +RUN lein uberjar + + +FROM clojure +WORKDIR /root/ + +COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar . + +CMD [""] +ENTRYPOINT ["/usr/bin/java", "-Duser.timezone=UTC", "-Dconf=config-test.edn", "-jar", "/root/commiteth.jar"] + diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ac6c4bf --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,35 @@ +#!/usr/bin/env groovy + +node('linux') { +checkout scm + +def dockerreponame = "statusim/openbounty-app" + + try { + stage('Build & push') { + + GIT_COMMIT_HASH = sh (script: "git rev-parse --short HEAD | tr -d '\n'", returnStdout: true) + + docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-statusvan') { + def openbountyApp = docker.build("${dockerreponame}:${env.BUILD_NUMBER}") + openbountyApp.push("${env.BRANCH_NAME}") + if (env.BRANCH_NAME == 'develop') { + openbountyApp.push("${dockerreponame}:develop") + } else if (env.BRANCH_NAME == 'master') { + openbountyApp.push("${dockerreponame}:master") + } else { + println "Not named branch have no custom tag" + } + } + + } + + stage('Deploy') { + build job: 'status-openbounty/openbounty-cluster', parameters: [[$class: 'StringParameterValue', name: 'DEPLOY_ENVIRONMENT', value: "dev"], [$class: 'StringParameterValue', name: 'BRANCH', value: env.BRANCH_NAME]] + } + + } catch (e) { + // slackSend color: 'bad', message: REPO + ":" + BRANCH_NAME + ' failed to build. ' + env.BUILD_URL + throw e + } +} \ No newline at end of file From 9f1f04c3d694093b838f899d1ca5ca1a5b1ce8ba Mon Sep 17 00:00:00 2001 From: Ivan Borovkov <555475+v2nek@users.noreply.github.com> Date: Tue, 6 Feb 2018 12:29:17 +0200 Subject: [PATCH 098/100] fix named branches in jenkinsfile (#249) --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ac6c4bf..ec6b860 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,9 +14,9 @@ def dockerreponame = "statusim/openbounty-app" def openbountyApp = docker.build("${dockerreponame}:${env.BUILD_NUMBER}") openbountyApp.push("${env.BRANCH_NAME}") if (env.BRANCH_NAME == 'develop') { - openbountyApp.push("${dockerreponame}:develop") + openbountyApp.push("develop") } else if (env.BRANCH_NAME == 'master') { - openbountyApp.push("${dockerreponame}:master") + openbountyApp.push("master") } else { println "Not named branch have no custom tag" } From 2ca563ab1a0de79728736e1eba10855ede4ec858 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Tue, 6 Feb 2018 16:31:46 +0200 Subject: [PATCH 099/100] Revert "[FIX #187] Fix address dropdown in My Payment Details" --- src/cljs/commiteth/common.cljs | 15 +++++++-------- src/cljs/commiteth/handlers.cljs | 2 +- src/cljs/commiteth/update_address.cljs | 25 ++++++------------------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/cljs/commiteth/common.cljs b/src/cljs/commiteth/common.cljs index 4399156..1213395 100644 --- a/src/cljs/commiteth/common.cljs +++ b/src/cljs/commiteth/common.cljs @@ -12,17 +12,16 @@ :on-change #(reset! val-ratom (-> % .-target .-value))})])) (defn dropdown [props title val-ratom items] - "If val-ratom is set, preselect it in the dropdown. - Otherwise, prepend title as a disabled option." (fn [] + (if (= 1 (count items)) + (reset! val-ratom (first items))) [:select.ui.basic.selection.dropdown (merge props {:on-change - #(reset! val-ratom (-> % .-target .-value)) - :default-value (or @val-ratom title)}) - (for [item items] - ^{:key item} [:option {:value item - :disabled (= item title)} - item])])) + #(reset! val-ratom (-> % .-target .-value))}) + (doall (for [item items] + ^{:key item} [:option + {:value item} + item]))])) (defn moment-timestamp [time] (let [now (.now js/Date.) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index ed677d7..eb61374 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -327,7 +327,7 @@ :http {:method POST :url "/api/user/address" :on-success #(do - (dispatch [:assoc-in [:user :address] address]) + (dispatch [:assoc-in [:user [:address] address]]) (dispatch [:set-flash-message :success "Address saved"])) diff --git a/src/cljs/commiteth/update_address.cljs b/src/cljs/commiteth/update_address.cljs index 14ceaa1..6134bde 100644 --- a/src/cljs/commiteth/update_address.cljs +++ b/src/cljs/commiteth/update_address.cljs @@ -3,7 +3,6 @@ [commiteth.common :refer [input dropdown]] [reagent.core :as r] [reagent.crypt :as crypt] - [clojure.string :as str] [cljs-web3.eth :as web3-eth])) @@ -23,25 +22,13 @@ [:p "Insert your Ethereum address in hex format."] [:div.field (if-not (empty? web3-accounts) - ; Add value of address if it's missing from items list. - ; If address is empty, add title - (let [accounts (map str/lower-case web3-accounts) - addr @address - title "Select address" - addr-not-in-web3? (and addr (as-> web3-accounts acc - (map str/lower-case acc) - (set acc) - (contains? acc addr) - (not acc))) - items (cond->> web3-accounts - addr-not-in-web3? (into [addr]) - (not addr) (into [title]))] - [dropdown {:class "address-input"} - title - address - items]) + [dropdown {:class "address-input"} "Select address" + address + (vec + (for [acc web3-accounts] + acc))] [:div.ui.input.address-input - [input address {:placeholder "0x0000000000000000000000000000000000000000" + [input address {:placeholder "0x0000000000000000000000000000000000000000" :auto-complete "off" :auto-correct "off" :spell-check "false" From 560cd6a9643ab3e8070c62ca7e0a58d9a6a82fc4 Mon Sep 17 00:00:00 2001 From: Vitaliy Vlasov Date: Tue, 6 Feb 2018 16:45:15 +0200 Subject: [PATCH 100/100] [FIX #154] Increase padding for open-claims-label --- src/less/style.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/less/style.less b/src/less/style.less index 6fb2407..49c38ce 100644 --- a/src/less/style.less +++ b/src/less/style.less @@ -794,7 +794,7 @@ } .open-claims-label { - padding-left: 5px; + padding-left: 15px; font-size: 15px; color: #57a7ed; }