import PropTypes from "prop-types"; import { defineMessages, injectIntl, FormattedMessage } from "react-intl"; import classNames from "classnames"; import ImmutablePropTypes from "react-immutable-proptypes"; import ImmutablePureComponent from "react-immutable-pure-component"; import escapeTextContentForBrowser from "escape-html"; import spring from "react-motion/lib/spring"; import { Icon } from "flavours/glitch/components/icon"; import emojify from "flavours/glitch/features/emoji/emoji"; import Motion from "flavours/glitch/features/ui/util/optional_motion"; import { RelativeTimestamp } from "./relative_timestamp"; const messages = defineMessages({ closed: { id: "poll.closed", defaultMessage: "Closed", }, voted: { id: "poll.voted", defaultMessage: "You voted for this answer", }, votes: { id: "poll.votes", defaultMessage: "{votes, plural, one {# vote} other {# votes}}", }, }); const makeEmojiMap = record => record.get("emojis").reduce((obj, emoji) => { obj[`:${emoji.get("shortcode")}:`] = emoji.toJS(); return obj; }, {}); class Poll extends ImmutablePureComponent { static contextTypes = { identity: PropTypes.object, }; static propTypes = { poll: ImmutablePropTypes.map, lang: PropTypes.string, intl: PropTypes.object.isRequired, disabled: PropTypes.bool, refresh: PropTypes.func, onVote: PropTypes.func, }; state = { selected: {}, expired: null, }; static getDerivedStateFromProps (props, state) { const { poll } = props; const expires_at = poll.get("expires_at"); const expired = poll.get("expired") || expires_at !== null && (new Date(expires_at)).getTime() < Date.now(); return (expired === state.expired) ? null : { expired }; } componentDidMount () { this._setupTimer(); } componentDidUpdate () { this._setupTimer(); } componentWillUnmount () { clearTimeout(this._timer); } _setupTimer () { const { poll } = this.props; clearTimeout(this._timer); if (!this.state.expired) { const delay = (new Date(poll.get("expires_at"))).getTime() - Date.now(); this._timer = setTimeout(() => { this.setState({ expired: true }); }, delay); } } _toggleOption = value => { if (this.props.poll.get("multiple")) { const tmp = { ...this.state.selected }; if (tmp[value]) { delete tmp[value]; } else { tmp[value] = true; } this.setState({ selected: tmp }); } else { const tmp = {}; tmp[value] = true; this.setState({ selected: tmp }); } }; handleOptionChange = ({ target: { value } }) => { this._toggleOption(value); }; handleOptionKeyPress = (e) => { if (e.key === "Enter" || e.key === " ") { this._toggleOption(e.target.getAttribute("data-index")); e.stopPropagation(); e.preventDefault(); } }; handleVote = () => { if (this.props.disabled) { return; } this.props.onVote(Object.keys(this.state.selected)); }; handleRefresh = () => { if (this.props.disabled) { return; } this.props.refresh(); }; handleReveal = () => { this.setState({ revealed: true }); }; renderOption (option, optionIndex, showResults) { const { poll, lang, disabled, intl } = this.props; const pollVotesCount = poll.get("voters_count") || poll.get("votes_count"); const percent = pollVotesCount === 0 ? 0 : (option.get("votes_count") / pollVotesCount) * 100; const leading = poll.get("options").filterNot(other => other.get("title") === option.get("title")).every(other => option.get("votes_count") >= other.get("votes_count")); const active = !!this.state.selected[`${optionIndex}`]; const voted = option.get("voted") || (poll.get("own_votes") && poll.get("own_votes").includes(optionIndex)); const title = option.getIn(["translation", "title"]) || option.get("title"); let titleHtml = option.getIn(["translation", "titleHtml"]) || option.get("titleHtml"); if (!titleHtml) { const emojiMap = makeEmojiMap(poll); titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap); } return (