diff options
author | Starfall <us@starfall.systems> | 2023-04-14 19:22:47 -0500 |
---|---|---|
committer | Starfall <us@starfall.systems> | 2023-04-14 19:22:47 -0500 |
commit | 4fe1689de43f4404eb9530fcfbcbfb26d6c1c13a (patch) | |
tree | 6811b845bb7f4966b10dcefa3dea404246f161c7 /app/javascript/mastodon/components/poll.js | |
parent | 65c1e53a32cabcdbb7bca57002bb0f6acdebe07e (diff) | |
parent | bed63f6dae0879ac840066b031229e0d139089cd (diff) |
Diffstat (limited to 'app/javascript/mastodon/components/poll.js')
-rw-r--r-- | app/javascript/mastodon/components/poll.js | 233 |
1 files changed, 0 insertions, 233 deletions
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js deleted file mode 100644 index 3e643168e..000000000 --- a/app/javascript/mastodon/components/poll.js +++ /dev/null @@ -1,233 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; -import Motion from 'mastodon/features/ui/util/optional_motion'; -import spring from 'react-motion/lib/spring'; -import escapeTextContentForBrowser from 'escape-html'; -import emojify from 'mastodon/features/emoji/emoji'; -import RelativeTimestamp from './relative_timestamp'; -import Icon from 'mastodon/components/icon'; - -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; -}, {}); - -export default @injectIntl -class Poll extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - - static propTypes = { - poll: ImmutablePropTypes.map, - intl: PropTypes.object.isRequired, - disabled: PropTypes.bool, - refresh: PropTypes.func, - onVote: PropTypes.func, - }; - - state = { - selected: {}, - expired: null, - }; - - static getDerivedStateFromProps (props, state) { - const { poll, intl } = props; - const expires_at = poll.get('expires_at'); - const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now(); - return (expired === state.expired) ? null : { expired }; - } - - componentDidMount () { - this._setupTimer(); - } - - componentDidUpdate () { - this._setupTimer(); - } - - componentWillUnmount () { - clearTimeout(this._timer); - } - - _setupTimer () { - const { poll, intl } = this.props; - clearTimeout(this._timer); - if (!this.state.expired) { - const delay = (new Date(poll.get('expires_at'))).getTime() - intl.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(); - }; - - renderOption (option, optionIndex, showResults) { - const { poll, 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)); - - let titleEmojified = option.get('title_emojified'); - if (!titleEmojified) { - const emojiMap = makeEmojiMap(poll); - titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap); - } - - return ( - <li key={option.get('title')}> - <label className={classNames('poll__option', { selectable: !showResults })}> - <input - name='vote-options' - type={poll.get('multiple') ? 'checkbox' : 'radio'} - value={optionIndex} - checked={active} - onChange={this.handleOptionChange} - disabled={disabled} - /> - - {!showResults && ( - <span - className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} - tabIndex='0' - role={poll.get('multiple') ? 'checkbox' : 'radio'} - onKeyPress={this.handleOptionKeyPress} - aria-checked={active} - aria-label={option.get('title')} - data-index={optionIndex} - /> - )} - {showResults && ( - <span - className='poll__number' - title={intl.formatMessage(messages.votes, { - votes: option.get('votes_count'), - })} - > - {Math.round(percent)}% - </span> - )} - - <span - className='poll__option__text translate' - dangerouslySetInnerHTML={{ __html: titleEmojified }} - /> - - {!!voted && <span className='poll__voted'> - <Icon id='check' className='poll__voted__mark' title={intl.formatMessage(messages.voted)} /> - </span>} - </label> - - {showResults && ( - <Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}> - {({ width }) => - <span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} /> - } - </Motion> - )} - </li> - ); - } - - render () { - const { poll, intl } = this.props; - const { expired } = this.state; - - if (!poll) { - return null; - } - - const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />; - const showResults = poll.get('voted') || expired; - const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item); - - let votesCount = null; - - if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) { - votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />; - } else { - votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />; - } - - return ( - <div className='poll'> - <ul> - {poll.get('options').map((option, i) => this.renderOption(option, i, showResults))} - </ul> - - <div className='poll__footer'> - {!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} - {showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>} - {votesCount} - {poll.get('expires_at') && <span> · {timeRemaining}</span>} - </div> - </div> - ); - } - -} |