From d71352b7819b5b0b1543c8a28c9f672ae68a3866 Mon Sep 17 00:00:00 2001 From: "Michael Bradley, Jr" Date: Thu, 4 Apr 2019 12:44:55 -0500 Subject: [PATCH] feat(@cockpit): implement pagination for contracts Display five contracts per page in the dashboard. Display ten contracts per page in the contracts explorer and deployment page. Sort contracts by name. In the future we can implement an option to sort by block number and index within a block by calculating and including that information as part of the server-side api response (based on a contract's txhash). Remove unnecessary contract filtering in the components since the containers take care of it. Make use of `listenToContracts` / `stopContracts` in DeploymentContainer. --- .../embark-ui/src/components/Contracts.js | 67 +++++----- .../src/components/ContractsDeployment.js | 86 ++++++------ .../embark-ui/src/components/ContractsList.js | 67 +++++----- .../src/containers/ContractsContainer.js | 105 ++++++++++++--- .../src/containers/DeploymentContainer.js | 124 ++++++++++++++---- .../embark-ui/src/containers/HomeContainer.js | 2 +- packages/embark-ui/src/reducers/index.js | 8 ++ 7 files changed, 310 insertions(+), 149 deletions(-) diff --git a/packages/embark-ui/src/components/Contracts.js b/packages/embark-ui/src/components/Contracts.js index 514a7eeb0..6f03c5032 100644 --- a/packages/embark-ui/src/components/Contracts.js +++ b/packages/embark-ui/src/components/Contracts.js @@ -1,12 +1,13 @@ -import PropTypes from "prop-types"; import React from 'react'; import {Row, Col, Card, CardHeader, CardBody} from "reactstrap"; import {Link} from 'react-router-dom'; +import PropTypes from "prop-types"; +import Pagination from './Pagination'; import {formatContractForDisplay} from '../utils/presentation'; import CardTitleIdenticon from './CardTitleIdenticon'; -const Contracts = ({contracts, title = "Contracts"}) => ( +const Contracts = ({contracts, changePage, currentPage, numberOfPages, title = "Contracts"}) => ( @@ -14,36 +15,37 @@ const Contracts = ({contracts, title = "Contracts"}) => (

{title}

- { - contracts - .filter(contract => !contract.silent) - .map((contract, key) => { - const contractDisplay = formatContractForDisplay(contract); - if (!contractDisplay) { - return ''; - } + {!contracts.length && "No contracts to display"} + {contracts + .map((contract, key) => { + const contractDisplay = formatContractForDisplay(contract); + if (!contractDisplay) { + return ''; + } - return ( -
- - {contract.className} - - - - Address -
{contract.address}
- - - State -
- {contractDisplay.state} -
- -
-
- ) - }) - } + return ( +
+ + + {contract.className} + + + + + Address +
{contract.address}
+ + + State +
+ {contractDisplay.state} +
+ +
+
+ ) + })} + {numberOfPages > 1 && }
@@ -52,6 +54,9 @@ const Contracts = ({contracts, title = "Contracts"}) => ( Contracts.propTypes = { contracts: PropTypes.array, + changePage: PropTypes.func, + currentPage: PropTypes.number, + numberOfPages: PropTypes.number, title: PropTypes.string }; diff --git a/packages/embark-ui/src/components/ContractsDeployment.js b/packages/embark-ui/src/components/ContractsDeployment.js index c63c1420c..95de19a90 100644 --- a/packages/embark-ui/src/components/ContractsDeployment.js +++ b/packages/embark-ui/src/components/ContractsDeployment.js @@ -1,19 +1,18 @@ -import PropTypes from "prop-types"; import React from 'react'; import FontAwesomeIcon from 'react-fontawesome'; -import { - Row, - Col, - FormGroup, - Input, - Label, - UncontrolledTooltip, - Button, - Card, - CardHeader, - CardTitle, - CardBody -} from 'reactstrap'; +import {Row, + Col, + FormGroup, + Input, + Label, + UncontrolledTooltip, + Button, + Card, + CardHeader, + CardTitle, + CardBody} from 'reactstrap'; +import PropTypes from "prop-types"; +import Pagination from './Pagination'; import classNames from 'classnames'; import {DEPLOYMENT_PIPELINES} from '../constants'; import Description from './Description'; @@ -326,39 +325,49 @@ class ContractsDeployment extends React.Component { } render() { + const props = this.props; + const {changePage, currentPage, numberOfPages} = props; return ( - - - - {this.props.contracts.filter(contract => (contract.code || contract.deploy) && !contract.silent) - .sort((a, b) => a.index - b.index).map((contract, index) => { + + + + + {!props.contracts.length && "No contracts to display"} + {props.contracts + .map((contract, index) => { contract.deployIndex = index; - return ( this.toggleContractOverview(contract)} - {...this.props} />); - } - )} - - {this.isContractOverviewOpen() && - - - -

{this.state.currentContractOverview.className} - Overview

- -
-
+ return ( + this.toggleContractOverview(contract)} + {...props} /> + ); + })} - } -
+ {this.isContractOverviewOpen() && + + + +

{this.state.currentContractOverview.className} - Overview

+ +
+
+ + } +
+ {numberOfPages > 1 && } + ) } } - ContractsDeployment.propTypes = { contracts: PropTypes.array, + changePage: PropTypes.func, + currentPage: PropTypes.number, + numberOfPages: PropTypes.number, deploymentPipeline: PropTypes.oneOfType([ PropTypes.object, PropTypes.string @@ -373,4 +382,3 @@ ContractsDeployment.propTypes = { }; export default ContractsDeployment; - diff --git a/packages/embark-ui/src/components/ContractsList.js b/packages/embark-ui/src/components/ContractsList.js index f8058ed5f..3e803685d 100644 --- a/packages/embark-ui/src/components/ContractsList.js +++ b/packages/embark-ui/src/components/ContractsList.js @@ -1,42 +1,49 @@ -import PropTypes from "prop-types"; import React from 'react'; import {Table} from "reactstrap"; import {Link} from 'react-router-dom'; +import PropTypes from "prop-types"; +import Pagination from './Pagination'; import {formatContractForDisplay} from '../utils/presentation'; -const ContractsList = ({contracts}) => ( - - - - - - - - - - { - contracts - .filter(contract => !contract.silent) - .map((contract) => { - const contractDisplay = formatContractForDisplay(contract); - if (!contractDisplay) { - return null; - } - return ( - - - - - - ); - }) - } - -
NameAddressState
{contract.className}{contractDisplay.address}{contractDisplay.state}
+const ContractsList = ({contracts, changePage, currentPage, numberOfPages}) => ( + + {!contracts.length && "No contracts to display"} + + + + + + + + + + { + contracts + .map((contract) => { + const contractDisplay = formatContractForDisplay(contract); + if (!contractDisplay) { + return null; + } + return ( + + + + + + ); + }) + } + +
NameAddressState
{contract.className}{contractDisplay.address}{contractDisplay.state}
+ {numberOfPages > 1 && } +
); ContractsList.propTypes = { contracts: PropTypes.array, + changePage: PropTypes.func, + currentPage: PropTypes.number, + numberOfPages: PropTypes.number }; export default ContractsList; diff --git a/packages/embark-ui/src/containers/ContractsContainer.js b/packages/embark-ui/src/containers/ContractsContainer.js index 97a7c51aa..36e71889b 100644 --- a/packages/embark-ui/src/containers/ContractsContainer.js +++ b/packages/embark-ui/src/containers/ContractsContainer.js @@ -1,19 +1,24 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; -import { - listenToContracts as listenToContractsAction, - stopContracts as stopContractsAction, - contracts as contractsAction -} from "../actions"; - +import {contracts as contractsAction, + listenToContracts, + stopContracts} from "../actions"; import Contracts from '../components/Contracts'; import ContractsList from '../components/ContractsList'; -import DataWrapper from "../components/DataWrapper"; -import PageHead from "../components/PageHead"; import {getContracts} from "../reducers/selectors"; +import PageHead from "../components/PageHead"; + +const MAX_CONTRACTS = 10; class ContractsContainer extends Component { + constructor(props) { + super(props); + + this.numContractsToDisplay = this.props.numContractsToDisplay || MAX_CONTRACTS; + this.state = {currentPage: 1}; + } + componentDidMount() { this.props.fetchContracts(); this.props.listenToContracts(); @@ -23,14 +28,72 @@ class ContractsContainer extends Component { this.props.stopContracts(); } + get numberOfContracts() { + if (this._numberOfContracts === undefined) { + this._numberOfContracts = this.props.contracts + .filter(contract => !contract.silent) + .length; + } + return this._numberOfContracts; + } + + get numberOfPages() { + if (this._numberOfPages === undefined) { + this._numberOfPages = Math.ceil( + this.numberOfContracts / this.numContractsToDisplay + ); + } + return this._numberOfPages; + } + + resetNums() { + this._numberOfContracts = undefined; + this._numberOfPages = undefined; + } + + changePage(newPage) { + if (newPage <= 0) { + newPage = 1; + } else if (newPage > this.numberOfPages) { + newPage = this.numberOfPages; + } + this.setState({ currentPage: newPage }); + this.props.fetchContracts(); + } + + get currentContracts() { + let offset = 0; + return this.props.contracts + .filter((contract, arrIndex) => { + if (contract.silent) { + offset++; + return false + }; + const index = ( + (arrIndex + 1 - offset) - + (this.numContractsToDisplay * (this.state.currentPage - 1)) + ); + return index <= this.numContractsToDisplay && index > 0; + }); + } + render() { + this.resetNums(); + let ContractsComp; + if (this.props.mode === "detail") { + ContractsComp = Contracts + } else if (this.props.mode === "list") { + ContractsComp = ContractsList + } return ( - {this.props.updatePageHeader && } - 0} {...this.props} render={({contracts}) => { - if (this.props.mode === "list") return ; - if (this.props.mode === "detail") return ; - }} /> + {this.props.updatePageHeader && + } + this.changePage(newPage)} + currentPage={this.state.currentPage} /> ); } @@ -40,15 +103,17 @@ function mapStateToProps(state) { return { contracts: getContracts(state), error: state.errorMessage, - loading: state.loading}; + loading: state.loading + }; } ContractsContainer.propTypes = { + contracts: PropTypes.array, + fetchContracts: PropTypes.func, + numContractsToDisplay: PropTypes.number, listenToContracts: PropTypes.func, stopContracts: PropTypes.func, - contracts: PropTypes.array, fiddleContracts: PropTypes.array, - fetchContracts: PropTypes.func, mode: PropTypes.string, updatePageHeader: PropTypes.bool }; @@ -59,9 +124,9 @@ ContractsContainer.defaultProps = { } export default connect( - mapStateToProps,{ - listenToContracts: listenToContractsAction, - stopContracts: stopContractsAction, - fetchContracts: contractsAction.request + mapStateToProps, { + fetchContracts: contractsAction.request, + listenToContracts, + stopContracts, } )(ContractsContainer); diff --git a/packages/embark-ui/src/containers/DeploymentContainer.js b/packages/embark-ui/src/containers/DeploymentContainer.js index e515f6049..8d4ca6287 100644 --- a/packages/embark-ui/src/containers/DeploymentContainer.js +++ b/packages/embark-ui/src/containers/DeploymentContainer.js @@ -1,44 +1,107 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; -import { - contracts as contractsAction, - web3Deploy as web3DeployAction, - web3EstimateGas as web3EstimateGasAction, - updateDeploymentPipeline} from "../actions"; - +import {contracts as contractsAction, + listenToContracts, + stopContracts, + web3Deploy as web3DeployAction, + web3EstimateGas as web3EstimateGasAction, + updateDeploymentPipeline} from "../actions"; import ContractsDeployment from '../components/ContractsDeployment'; -import DataWrapper from "../components/DataWrapper"; +import {getContracts, + getDeploymentPipeline, + getWeb3, + getWeb3GasEstimates, + getWeb3Deployments, + getWeb3ContractsDeployed} from "../reducers/selectors"; import PageHead from '../components/PageHead'; -import { - getContracts, - getDeploymentPipeline, - getWeb3, - getWeb3GasEstimates, - getWeb3Deployments, - getWeb3ContractsDeployed -} from "../reducers/selectors"; + +const MAX_CONTRACTS = 10; class DeploymentContainer extends Component { + constructor(props) { + super(props); + + this.numContractsToDisplay = this.props.numContractsToDisplay || MAX_CONTRACTS; + this.state = {currentPage: 1}; + } + componentDidMount() { this.props.fetchContracts(); + this.props.listenToContracts(); + } + + componentWillUnmount() { + this.props.stopContracts(); + } + + get numberOfContracts() { + if (this._numberOfContracts === undefined) { + this._numberOfContracts = this.props.contracts + .filter(contract => (contract.code || contract.deploy) && !contract.silent) + .length; + } + return this._numberOfContracts; + } + + get numberOfPages() { + if (this._numberOfPages === undefined) { + this._numberOfPages = Math.ceil( + this.numberOfContracts / this.numContractsToDisplay + ); + } + return this._numberOfPages; + } + + resetNums() { + this._numberOfContracts = undefined; + this._numberOfPages = undefined; + } + + changePage(newPage) { + if (newPage <= 0) { + newPage = 1; + } else if (newPage > this.numberOfPages) { + newPage = this.numberOfPages; + } + this.setState({ currentPage: newPage }); + this.props.fetchContracts(); + } + + get currentContracts() { + let offset = 0; + return this.props.contracts + .filter((contract, arrIndex) => { + if (!(contract.code || contract.deploy) || contract.silent) { + offset++; + return false + }; + const index = ( + (arrIndex + 1 - offset) - + (this.numContractsToDisplay * (this.state.currentPage - 1)) + ); + return index <= this.numContractsToDisplay && index > 0; + }); } render() { + this.resetNums(); return ( - - 0} {...this.props} render={() => ( - - )} /> + + this.changePage(newPage)} + currentPage={this.state.currentPage} /> ); } @@ -59,11 +122,14 @@ function mapStateToProps(state) { DeploymentContainer.propTypes = { contracts: PropTypes.array, + fetchContracts: PropTypes.func, + numContractsToDisplay: PropTypes.number, + listenToContracts: PropTypes.func, + stopContracts: PropTypes.func, deploymentPipeline: PropTypes.oneOfType([ PropTypes.object, PropTypes.string ]), - fetchContracts: PropTypes.func, updateDeploymentPipeline: PropTypes.func, web3: PropTypes.object, web3Deploy: PropTypes.func, @@ -75,6 +141,8 @@ DeploymentContainer.propTypes = { export default connect( mapStateToProps, { fetchContracts: contractsAction.request, + listenToContracts, + stopContracts, web3Deploy: web3DeployAction.request, web3EstimateGas: web3EstimateGasAction.request, updateDeploymentPipeline: updateDeploymentPipeline diff --git a/packages/embark-ui/src/containers/HomeContainer.js b/packages/embark-ui/src/containers/HomeContainer.js index b0316a52b..c77ea4685 100644 --- a/packages/embark-ui/src/containers/HomeContainer.js +++ b/packages/embark-ui/src/containers/HomeContainer.js @@ -76,7 +76,7 @@ class HomeContainer extends Component { Deployed Contracts
- +
diff --git a/packages/embark-ui/src/reducers/index.js b/packages/embark-ui/src/reducers/index.js index feeea9b4b..9b91b5ac2 100644 --- a/packages/embark-ui/src/reducers/index.js +++ b/packages/embark-ui/src/reducers/index.js @@ -50,6 +50,14 @@ const sorter = { blocksFull: function(a, b) { return b.number - a.number; }, + contracts: function (a, b) { + const aName = a.className || ''; + const bName = b.className || ''; + if (!(aName || bName)) return 0; + if (!aName) return 1; + if (!bName) return -1; + return aName < bName ? -1 : aName > bName ? 1 : 0 + }, transactions: function(a, b) { return ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex); },