This commit is contained in:
mgabdev
2020-03-06 23:53:28 -05:00
parent da3d0c3462
commit 557c6470f5
41 changed files with 1181 additions and 1106 deletions

View File

@@ -1,29 +1,34 @@
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 escapeTextContentForBrowser from 'escape-html';
import spring from 'react-motion/lib/spring';
import Motion from '../../features/ui/util/optional_motion';
import { vote, fetchPoll } from '../../actions/polls';
import emojify from '../emoji/emoji';
import RelativeTimestamp from '../relative_timestamp';
import Button from '../button';
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import classNames from 'classnames/bind'
import escapeTextContentForBrowser from 'escape-html'
import spring from 'react-motion/lib/spring'
import Motion from '../../features/ui/util/optional_motion'
import { vote } from '../../actions/polls'
import emojify from '../emoji/emoji'
import RelativeTimestamp from '../relative_timestamp'
import Button from '../button'
import DotTextSeperator from '../dot_text_seperator'
import Text from '../text'
const cx = classNames.bind(_s)
const mapStateToProps = (state, { pollId }) => ({
poll: state.getIn(['polls', pollId]),
});
})
const messages = defineMessages({
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
vote: { id: 'poll.vote', defaultMessage: 'Vote' },
refresh: { id: 'poll.refresh', defaultMessage: 'Refresh' },
});
})
const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
return obj;
}, {});
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS()
return obj
}, {})
export default
@connect(mapStateToProps)
@@ -35,71 +40,91 @@ class Poll extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func,
disabled: PropTypes.bool,
};
}
state = {
selected: {},
};
}
handleOptionChange = e => {
const { target: { value } } = e;
const { target: { value } } = e
if (this.props.poll.get('multiple')) {
const tmp = { ...this.state.selected };
const tmp = { ...this.state.selected }
if (tmp[value]) {
delete tmp[value];
delete tmp[value]
} else {
tmp[value] = true;
tmp[value] = true
}
this.setState({ selected: tmp });
this.setState({ selected: tmp })
} else {
const tmp = {};
tmp[value] = true;
this.setState({ selected: tmp });
const tmp = {}
tmp[value] = true
this.setState({ selected: tmp })
}
};
}
handleVote = () => {
if (this.props.disabled) return;
if (this.props.disabled) return
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)));
};
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)))
}
handleRefresh = () => {
if (this.props.disabled) return;
renderOption(option, optionIndex) {
const { poll, disabled } = this.props
const { selected } = this.state
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 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 = !!selected[`${optionIndex}`]
const showResults = poll.get('voted') || poll.get('expired')
const multiple = poll.get('multiple')
this.props.dispatch(fetchPoll(this.props.poll.get('id')));
};
renderOption (option, optionIndex) {
const { poll, disabled } = this.props;
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 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 showResults = poll.get('voted') || poll.get('expired');
const multiple = poll.get('multiple');
let titleEmojified = option.get('title_emojified');
let titleEmojified = option.get('title_emojified')
if (!titleEmojified) {
const emojiMap = makeEmojiMap(poll);
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
const emojiMap = makeEmojiMap(poll)
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap)
}
const chartClasses = classNames('poll__chart', {
'poll__chart--leading': leading,
});
const chartClasses = cx({
default: 1,
positionAbsolute: 1,
top0: 1,
left0: 1,
radiusSmall: 1,
height100PC: 1,
backgroundSubtle2: !leading,
backgroundColorBrandLight: leading,
})
const textClasses = classNames('poll__text', {
selectable: !showResults,
});
const inputClasses = classNames('poll__input', {
const inputClasses = cx('poll__input', {
'poll__input--checkbox': multiple,
'poll__input--active': active,
});
})
const listItemClasses = cx({
default: 1,
flexRow: 1,
paddingVertical10PX: showResults,
marginBottom10PX: 1,
border1PX: !showResults,
borderColorSecondary: !showResults,
circle: !showResults,
cursorPointer: !showResults,
backgroundSubtle_onHover: !showResults,
backgroundSubtle: !showResults && active,
})
const textContainerClasses = cx({
default: 1,
width100PC: 1,
paddingHorizontal15PX: 1,
paddingVertical10PX: !showResults,
cursorPointer: !showResults,
alignItemsCenter: !showResults,
})
return (
<li className='poll-item' key={option.get('title')}>
<li className={listItemClasses} key={option.get('title')}>
{
showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
@@ -110,72 +135,99 @@ class Poll extends ImmutablePureComponent {
)
}
<label className={textClasses}>
<input
name='vote-options'
type={multiple ? 'checkbox' : 'radio'}
value={optionIndex}
checked={active}
onChange={this.handleOptionChange}
disabled={disabled}
/>
<label className={textContainerClasses}>
<Text
size='medium'
color='primary'
weight={leading ? 'bold' : 'normal'}
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
>
{
!showResults &&
<input
name='vote-options'
type={multiple ? 'checkbox' : 'radio'}
value={optionIndex}
checked={active}
onChange={this.handleOptionChange}
disabled={disabled}
className={[_s.default, _s.marginRight10PX].join(' ')}
/>
}
{!showResults && <span className={inputClasses} />}
{showResults && <span className='poll-item__number'>{Math.round(percent)}%</span>}
{
!showResults && <span className={inputClasses} />
}
<span className='poll-item__text' dangerouslySetInnerHTML={{ __html: titleEmojified }} />
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
{
showResults &&
<span className={_s.marginLeftAuto}>
{Math.round(percent)}%
</span>
}
</Text>
</label>
</li>
);
)
}
render () {
const { poll, intl } = this.props;
render() {
const { poll, intl } = this.props
if (!poll) return null;
if (!poll) return null
const timeRemaining = poll.get('expired') ?
intl.formatMessage(messages.closed)
: <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
const showResults = poll.get('voted') || poll.get('expired');
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
: <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />
const showResults = poll.get('voted') || poll.get('expired')
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item)
return (
<div className='poll'>
<ul className='poll__list'>
{poll.get('options').map((option, i) => this.renderOption(option, i))}
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ul className={[_s.default, _s.listStyleNone].join(' ')}>
{
poll.get('options').map((option, i) => this.renderOption(option, i))
}
</ul>
<div className='poll__footer'>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
{
!showResults &&
<Button className='poll__button' disabled={disabled} onClick={this.handleVote} secondary>
{intl.formatMessage(messages.vote)}
<Button
narrow
className={_s.marginRight10PX}
disabled={disabled}
onClick={this.handleVote}
>
<Text color='inherit' size='small' className={_s.paddingHorizontal10PX}>
{intl.formatMessage(messages.vote)}
</Text>
</Button>
}
{
showResults && !this.props.disabled &&
<span>
<button className='poll__link' onClick={this.handleRefresh}>
{intl.formatMessage(messages.refresh)}
</button>
&nbsp;·&nbsp;
</span>
}
<FormattedMessage
id='poll.total_votes'
defaultMessage='{count, plural, one {# vote} other {# votes}}'
values={{
count: poll.get('votes_count'),
}}
/>
{
poll.get('expires_at') &&
<span> · {timeRemaining}</span>
}
<Text color='secondary'>
<FormattedMessage
id='poll.total_votes'
defaultMessage='{count, plural, one {# vote} other {# votes}}'
values={{
count: poll.get('votes_count'),
}}
/>
{
poll.get('expires_at') &&
<Fragment>
<DotTextSeperator />
<Text color='secondary' className={_s.marginLeft5PX}>
{timeRemaining}
</Text>
</Fragment>
}
</Text>
</div>
</div>
);
)
}
}