279 lines
9.1 KiB
JavaScript
Raw Normal View History

2018-09-25 20:45:33 -04:00
import React, { Fragment, Component, PureComponent } from 'react';
import { toString } from 'lodash';
2018-07-12 15:54:13 -04:00
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
2018-06-27 12:32:11 -04:00
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
2018-06-27 14:40:45 -04:00
import CardActions from '@material-ui/core/CardActions';
2018-06-27 12:32:11 -04:00
import Button from '@material-ui/core/Button';
2018-07-12 15:54:13 -04:00
import Dialog from '@material-ui/core/Dialog';
import Slide from '@material-ui/core/Slide';
2018-06-27 12:32:11 -04:00
import Typography from '@material-ui/core/Typography';
2018-06-27 14:40:45 -04:00
import Slider from '@material-ui/lab/Slider';
2018-06-27 20:45:31 -04:00
import PollManager from 'Embark/contracts/PollManager';
2018-07-12 15:54:13 -04:00
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
2018-06-28 16:22:11 -04:00
import web3 from "Embark/web3"
import CircularProgress from '@material-ui/core/CircularProgress';
import { withStyles } from '@material-ui/core/styles';
import { VotingContext } from '../../context';
import rlp from 'rlp';
2018-06-27 12:32:11 -04:00
const styles = {
card: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
thumb: {
width: '24px',
height: '24px'
2018-07-12 15:54:13 -04:00
},
appBar: {
position: 'relative',
},
flex: {
flex: 1,
},
};
2018-07-12 15:54:13 -04:00
function Transition(props) {
return <Slide direction="up" {...props} />;
};
2018-07-12 15:31:47 -04:00
const getIdeaFromStr = str => {
const match = str.match(/\(([^)]+)\)/)
if (match) return match[1].toLowerCase();
return match;
}
2018-06-29 14:39:07 -04:00
const sortingFn = {
MOST_VOTES: (a, b) => b._qvResults - a._qvResults,
MOST_VOTERS: (a, b) => b._voters - a._voters,
NEWEST_ADDED: (a, b) => b._startBlock - a._startBlock,
ENDING_SOONEST: (a, b) => a._endBlock - b._endBlock
};
2018-06-30 08:01:51 -04:00
class Poll extends PureComponent {
2018-06-27 20:45:31 -04:00
constructor(props){
super(props);
2018-09-25 20:45:33 -04:00
this.state = {
t: 0,
value: props.votes,
originalVotes: {}, // TODO: props.votes
2018-09-25 20:45:33 -04:00
balance: 0,
isSubmitting: false,
open: false,
votes: {
}
};
}
updateVotes = i => numVotes => {
const votes = this.state.votes;
votes[i] = numVotes;
this.setState({votes, t: new Date()});
2018-06-27 20:45:31 -04:00
}
2018-06-27 14:40:45 -04:00
2018-07-12 15:54:13 -04:00
handleClickOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
2018-06-27 20:45:31 -04:00
handleClick = (event) => {
event.preventDefault();
this.setState({isSubmitting: true});
const { vote, poll, unvote } = PollManager.methods;
const { updatePoll, idPoll } = this.props;
const { votes } = this.state;
2018-06-28 16:22:11 -04:00
const { toWei } = web3.utils;
2018-06-27 20:45:31 -04:00
const ballots = Object.values(votes).map(el => el * el);
const balance4Voting = ballots.reduce((prev, curr) => prev + curr, 0);
const toSend = balance4Voting == 0 ? unvote(idPoll) : vote(idPoll, ballots);
2018-06-27 20:45:31 -04:00
toSend.estimateGas()
.then(gasEstimated => {
console.log("voting gas estimated: " + gasEstimated);
return toSend.send({gas: gasEstimated + 100000});
})
.then(res => {
console.log('sucess:', res);
this.setState({ isSubmitting: false, originalVotes: Object.assign({}, votes)});
return updatePoll(idPoll);
})
.catch(res => {
2018-07-01 16:07:44 -04:00
console.log('fail:', res, res.messsage);
this.setState({ error: res.message })
})
.finally(() => {
this.setState({isSubmitting: false});
});
2018-06-27 20:45:31 -04:00
}
2018-06-27 14:40:45 -04:00
render(){
const {
_description,
_voters,
_qvResults,
_results,
_canVote,
balance,
2018-07-12 15:31:47 -04:00
classes,
ideaSites,
_numBallots,
} = this.props;
2018-09-25 20:45:33 -04:00
const { originalVotes, isSubmitting, error, votes } = this.state;
2018-07-01 15:46:39 -04:00
const cantVote = balance == 0 || !_canVote;
const disableVote = cantVote || isSubmitting;
const originalVotesQty = Object.values(originalVotes).reduce((x,y) => x+y, 0);
const votesQty = Object.values(votes).reduce((x,y) => x+y, 0);
const buttonText = originalVotesQty != 0 && originalVotesQty != votesQty ? 'Change Vote' : 'Vote';
// Extracting description
const decodedDesc = rlp.decode(_description);
const title = decodedDesc[0].toString();
const ballots = decodedDesc[1];
const idea = getIdeaFromStr(title);
const ideaSite = ideaSites && ideaSites.filter(site => site.includes(idea));
2018-09-25 20:45:33 -04:00
// Calculating votes availables
const maxVotes = Math.floor(Math.sqrt(balance));
const maxValuesForBallots = {};
let votedSNT = 0;
for(let i = 0; i < ballots.length; i++){
if(votes[i] == undefined){
votes[i] = 0;
maxValuesForBallots[i] = 0;
} else {
votedSNT += votes[i]*votes[i];
}
}
for(let i = 0; i < ballots.length; i++){
maxValuesForBallots[i] = Math.floor(Math.sqrt(balance - votedSNT + votes[i]*votes[i])); // votes[i] // Math.floor(Math.sqrt(balance - (votedSNT*votedSNT) + (votes[i]*votes[i])));
}
2018-06-27 14:40:45 -04:00
return (
<Card>
<CardContent>
2018-09-25 20:45:33 -04:00
<Typography variant="title">{title}</Typography>
2018-06-27 14:40:45 -04:00
<Typography variant="subheading" color="textSecondary">
2018-09-25 20:45:33 -04:00
<b>Total:</b> {_voters} voters. {_qvResults} votes ({(_results)} SNT)
2018-06-27 20:45:31 -04:00
</Typography>
<Typography variant="subheading" color="textSecondary">
2018-09-25 20:45:33 -04:00
<b>SNT available for voting:</b> {(balance - votedSNT).toFixed(2)} of {(parseFloat(balance).toFixed(2))} SNT
2018-06-27 14:40:45 -04:00
</Typography>
2018-09-25 20:45:33 -04:00
{ _numBallots > 0 &&
ballots.map((opt, i) => {
return <div key={i}>
<Typography variant="display1">{opt.toString()}</Typography>
{!cantVote }
<BallotSlider classes={classes} votes={votes[0]} maxVotes={maxVotes} maxVotesAvailable={maxValuesForBallots[i]} updateVotes={this.updateVotes(i)} />
2018-09-25 20:45:33 -04:00
</div>
})
}
2018-07-01 15:46:39 -04:00
{cantVote && <Typography variant="body2" color="error">
2018-07-02 18:09:03 -04:00
{balance == 0 && <span>Voting disabled for proposals made when there was no SNT in the account</span>}
2018-07-01 13:46:25 -04:00
{balance != 0 && !_canVote && <span>You can not vote on this poll</span>}
</Typography>}
2018-07-01 16:07:44 -04:00
{error && <Typography variant="body2" color="error">{error}</Typography>}
2018-09-25 20:45:33 -04:00
2018-07-13 09:56:35 -04:00
{ideaSite && ideaSite.length > 0 && <Typography onClick={this.handleClickOpen} variant="subheading" color="primary">{ideaSite}</Typography>}
2018-07-12 15:54:13 -04:00
{ideaSite && <Dialog
fullScreen
open={this.state.open}
onClose={this.handleClose}
TransitionComponent={Transition}
>
<AppBar className={classes.appBar} onClick={this.handleClose}>
<Toolbar>
<IconButton color="inherit" aria-label="Close">
<CloseIcon />
</IconButton>
<Typography variant="title" color="inherit" className={classes.flex}>
close
</Typography>
</Toolbar>
</AppBar>
2018-07-13 08:10:50 -04:00
<div
style={{ overflow: "auto", height: '100%', width: '100%', position: "fixed", top: 0, left: 0, zIndex: 1, overflowScrolling: "touch", WebkitOverflowScrolling: "touch" }}
>
<iframe
className="contentIframe"
frameBorder="0"
src={ideaSite[0]}
style={{ height: "100%", width: "100%" }}
height="100%"
width="100%"
>
</iframe>
</div>
2018-07-12 15:54:13 -04:00
</Dialog>}
2018-06-27 14:40:45 -04:00
</CardContent>
2018-07-01 15:46:39 -04:00
{!cantVote && <CardActions className={classes.card}>
2018-09-25 20:45:33 -04:00
{isSubmitting ? <CircularProgress /> : <Button variant="contained" disabled={disableVote} color="primary" onClick={this.handleClick}>{buttonText}</Button>}
</CardActions>}
2018-06-27 14:40:45 -04:00
</Card>
)
}
}
2018-06-27 12:32:11 -04:00
2018-06-27 20:45:31 -04:00
const PollsList = ({ classes }) => (
<VotingContext.Consumer>
2018-07-12 15:31:47 -04:00
{({ updatePoll, rawPolls, pollOrder, appendToPoll, ideaSites }) =>
<Fragment>
{rawPolls
.sort(sortingFn[pollOrder])
2018-09-25 20:45:33 -04:00
.map((poll, i) => <Poll key={poll.idPoll} classes={classes} appendToPoll={appendToPoll} updatePoll={updatePoll} ideaSites={ideaSites} {...poll} />)}
</Fragment>
}
</VotingContext.Consumer>
2018-06-27 12:32:11 -04:00
)
2018-09-25 20:45:33 -04:00
class BallotSlider extends Component {
constructor(props){
super(props);
this.state = {
value: props.votes || 0
}
}
handleChange = (event, value) => {
if(value > this.props.maxVotesAvailable){
value = this.props.maxVotesAvailable;
}
this.setState({value});
this.props.updateVotes(value);
};
render(){
const {maxVotes, maxVotesAvailable, classes} = this.props;
2018-09-25 20:45:33 -04:00
const {value} = this.state;
const nextVote = value + 1;
return <Fragment>
<Slider classes={{ thumb: classes.thumb }} style={{ width: '95%' }} value={value} min={0} max={maxVotes} step={1} onChange={this.handleChange} />
2018-09-25 20:45:33 -04:00
<b>Votes: {value} ({value * value} SNT)</b>
{ nextVote <= maxVotesAvailable ? <small>- Additional vote will cost {nextVote*nextVote - value*value} SNT</small> : <small>- Not enough balance available to buy additional votes</small> }
</Fragment>
}
}
export default withStyles(styles)(PollsList);