371 lines
11 KiB
JavaScript
Executable File
371 lines
11 KiB
JavaScript
Executable File
/* @flow */
|
|
|
|
import React from 'react';
|
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
import Animated from '../lib/animated';
|
|
import Select from 'react-select';
|
|
import i18n from '../i18n';
|
|
import InstantInvite from './InstantInvite';
|
|
import Flux from '../lib/flux';
|
|
import AnalyticsUtils from '../utils/AnalyticsUtils';
|
|
import InstantInviteActionCreators from '../actions/InstantInviteActionCreators';
|
|
import InstantInviteStore from '../stores/InstantInviteStore';
|
|
import StreamerModeStore from '../stores/StreamerModeStore';
|
|
import InstantInviteUtils from '../utils/InstantInviteUtils';
|
|
import GuildChannelStore from '../stores/views/GuildChannelStore';
|
|
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
|
import Checkbox from './common/Checkbox';
|
|
import InvitePeriodExperimentStore from '../stores/experiments/InvitePeriodExperimentStore';
|
|
import createExperimentContainer from './experiments/ExperimentContainer';
|
|
import Spinner from './common/Spinner';
|
|
import Tooltip from './common/Tooltip';
|
|
import {ChannelTypes, ExperimentTypes, ExperimentBuckets} from '../Constants';
|
|
import '../styles/instant_invite_modal.styl';
|
|
|
|
const LOCATION = 'Invite Modal';
|
|
const MAX_AGE_OPTIONS = InstantInviteUtils.getMaxAgeOptions;
|
|
const MAX_USES_OPTIONS = InstantInviteUtils.getMaxUsesOptions;
|
|
|
|
function createAnimationMixin(reverse = false, duration = 250, easing = Animated.Easing.inOut(Animated.Easing.cubic)) {
|
|
return {
|
|
getInitialState() {
|
|
return {
|
|
opacity: new Animated.Value(0),
|
|
};
|
|
},
|
|
|
|
componentDidAppear() {
|
|
this.state.opacity.setValue(1);
|
|
},
|
|
|
|
componentWillEnter(callback) {
|
|
Animated.timing(this.state.opacity, {toValue: 1, duration, easing}).start(callback);
|
|
},
|
|
|
|
componentWillLeave(callback) {
|
|
Animated.timing(this.state.opacity, {toValue: 0, duration, easing}).start(callback);
|
|
},
|
|
|
|
getAnimatedStyle() {
|
|
return {
|
|
opacity: this.state.opacity,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
const BasicInstantInviteModal = React.createClass({
|
|
mixins: [createAnimationMixin()],
|
|
|
|
render() {
|
|
const {invite, guild, hide, maxAge, handleExpiresChange, showAdvanced} = this.props;
|
|
|
|
let doesNotExpire = false;
|
|
let instantInvite;
|
|
let expireText;
|
|
if (!invite) {
|
|
instantInvite = <Spinner />;
|
|
} else {
|
|
instantInvite = (
|
|
<InstantInvite code={invite.code} copyText={i18n.Messages.COPY} hidden={hide} location={LOCATION} />
|
|
);
|
|
|
|
doesNotExpire = maxAge == MAX_AGE_OPTIONS[0].value;
|
|
if (doesNotExpire) {
|
|
expireText = i18n.Messages.INVITE_LINKS_NEVER_EXPIRES;
|
|
} else if (maxAge == MAX_AGE_OPTIONS[1].value) {
|
|
expireText = i18n.Messages.INVITE_LINKS_EXPIRE_AFTER_BY_DEFAULT;
|
|
} else {
|
|
expireText = i18n.Messages.INVITE_LINKS_EXPIRE_AFTER_1_DAY;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Animated.form id="instant-invite-modal" className="form" style={this.getAnimatedStyle()}>
|
|
<div className="form-header">
|
|
<header>
|
|
{i18n.Messages.INVITE_TO_SERVER_NAME.format({guildName: guild.name})}
|
|
</header>
|
|
<div className="blurb">
|
|
{i18n.Messages.SHARE_INVITE_LINK_FOR_ACCESS}
|
|
</div>
|
|
</div>
|
|
<div className="form-inner">
|
|
{instantInvite}
|
|
<div className="expire-text">{expireText}</div>
|
|
|
|
</div>
|
|
<div className="form-actions">
|
|
<Checkbox checked={doesNotExpire} onChange={handleExpiresChange}>
|
|
{i18n.Messages.SET_INVITE_LINK_NEVER_EXPIRE}
|
|
</Checkbox>
|
|
<Tooltip text={i18n.Messages.LINK_SETTINGS}>
|
|
<button className="advanced" onClick={showAdvanced} />
|
|
</Tooltip>
|
|
</div>
|
|
</Animated.form>
|
|
);
|
|
},
|
|
});
|
|
|
|
const AdvancedInstantInviteModal = React.createClass({
|
|
mixins: [createAnimationMixin(true)],
|
|
|
|
render() {
|
|
const {
|
|
maxAge,
|
|
maxUses,
|
|
temporary,
|
|
handleTemporaryChange,
|
|
handleGenerateNewLink,
|
|
hideAdvanced,
|
|
handleMaxUsesChange,
|
|
handleExpireAfterChange,
|
|
} = this.props;
|
|
const maxAgeValue = MAX_AGE_OPTIONS.find(age => age.value == maxAge);
|
|
const maxUsesValue = MAX_USES_OPTIONS.find(uses => uses.value == maxUses);
|
|
|
|
return (
|
|
<Animated.form id="instant-invite-modal-advanced" className="form" style={this.getAnimatedStyle()}>
|
|
<div className="form-inner">
|
|
<div className="control-groups">
|
|
<div className="control-group">
|
|
<label>{i18n.Messages.EXPIRE_AFTER}</label>
|
|
<Select
|
|
value={maxAgeValue}
|
|
clearable={false}
|
|
searchable={false}
|
|
options={MAX_AGE_OPTIONS}
|
|
onChange={handleExpireAfterChange}
|
|
/>
|
|
</div>
|
|
|
|
<div className="control-group">
|
|
<label>{i18n.Messages.MAX_NUMBER_OF_USES}</label>
|
|
<Select
|
|
value={maxUsesValue}
|
|
clearable={false}
|
|
searchable={false}
|
|
options={MAX_USES_OPTIONS}
|
|
onChange={handleMaxUsesChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="control-group">
|
|
<Checkbox checked={temporary} onChange={handleTemporaryChange}>
|
|
{i18n.Messages.GRANT_TEMPORARY_MEMBERSHIP}
|
|
</Checkbox>
|
|
<div className="help-text">
|
|
{i18n.Messages.TEMPORARY_MEMBERSHIP_EXPLANATION}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn btn-default" onClick={hideAdvanced}>
|
|
{i18n.Messages.CANCEL}
|
|
</button>
|
|
<button type="submit" className="btn btn-primary" onClick={handleGenerateNewLink}>
|
|
{i18n.Messages.GENERATE_A_NEW_LINK}
|
|
</button>
|
|
</div>
|
|
</Animated.form>
|
|
);
|
|
},
|
|
});
|
|
|
|
const InstantInviteModal = React.createClass({
|
|
mixins: [Flux.LazyStoreListenerMixin(InstantInviteStore, StreamerModeStore), PureRenderMixin],
|
|
|
|
getInitialState() {
|
|
return {
|
|
showAdvanced: false,
|
|
maxUses: MAX_USES_OPTIONS[0].value,
|
|
temporary: false,
|
|
channel: this.getChannel(),
|
|
...this.getStateFromStores(),
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
const invite = InstantInviteStore.getInvite(this.getChannel().id);
|
|
const maxAge = invite ? invite.maxAge : MAX_AGE_OPTIONS[1].value;
|
|
const savedMaxAge = maxAge == MAX_USES_OPTIONS[0].value ? MAX_AGE_OPTIONS[1].value : MAX_AGE_OPTIONS[0].value;
|
|
|
|
return {
|
|
invite,
|
|
maxAge,
|
|
savedMaxAge,
|
|
hide: StreamerModeStore.hideInstantInvites,
|
|
};
|
|
},
|
|
|
|
componentDidMount() {
|
|
const validate = this.state.invite ? this.state.invite.code : null;
|
|
const experimentBucket = this.props.experiment ? this.props.experiment.bucket : 0;
|
|
|
|
const maxAge = (experimentBucket => {
|
|
switch (experimentBucket) {
|
|
case ExperimentBuckets.TREATMENT_1:
|
|
case ExperimentBuckets.TREATMENT_2:
|
|
return MAX_AGE_OPTIONS[5].value; // 1 Day
|
|
default:
|
|
return MAX_AGE_OPTIONS[1].value;
|
|
}
|
|
})(experimentBucket);
|
|
|
|
InstantInviteActionCreators.createInvite(
|
|
this.state.channel.id,
|
|
{
|
|
validate,
|
|
max_age: maxAge, // eslint-disable-line camelcase
|
|
},
|
|
'InstantInviteModal Mount'
|
|
);
|
|
|
|
let {source} = this.props;
|
|
if (source != null) {
|
|
if (window.inviteButtonSource) {
|
|
source = window.inviteButtonSource;
|
|
window.inviteButtonSource = undefined;
|
|
}
|
|
|
|
AnalyticsUtils.track('open_popout', {
|
|
Type: 'Instant Invite Modal',
|
|
Source: source,
|
|
});
|
|
}
|
|
},
|
|
|
|
getChannel() {
|
|
const {channel, guild} = this.props;
|
|
|
|
if (channel) {
|
|
return channel;
|
|
}
|
|
if (this.state && this.state.channel) {
|
|
return this.state.channel;
|
|
}
|
|
|
|
const channels = GuildChannelStore.getChannels(guild.id)[ChannelTypes.GUILD_TEXT];
|
|
return channels.find(channel => channel.channel.id == guild.id).channel;
|
|
},
|
|
|
|
handleExpiresChange() {
|
|
this.createInvite(this.state.savedMaxAge, this.state.maxUses, this.state.temporary, 'InstantInviteModal');
|
|
|
|
this.setState({
|
|
maxAge: this.state.savedMaxAge,
|
|
savedMaxAge: this.state.maxAge,
|
|
});
|
|
},
|
|
|
|
handleExpireAfterChange(value: string) {
|
|
this.setState({
|
|
maxAge: value,
|
|
savedMaxAge: value == MAX_AGE_OPTIONS[0].value ? MAX_AGE_OPTIONS[1].value : MAX_AGE_OPTIONS[0].value,
|
|
});
|
|
},
|
|
|
|
handleMaxUsesChange(value: string) {
|
|
this.setState({
|
|
maxUses: value,
|
|
});
|
|
},
|
|
|
|
handleTemporaryChange(e: Event) {
|
|
this.setState({
|
|
// $FlowFixMe
|
|
temporary: e.currentTarget.checked,
|
|
});
|
|
},
|
|
|
|
showAdvanced(e: Event) {
|
|
e.preventDefault();
|
|
|
|
this.setState({
|
|
showAdvanced: true,
|
|
});
|
|
},
|
|
|
|
hideAdvanced(e: Event) {
|
|
e.preventDefault();
|
|
|
|
this.setState({
|
|
showAdvanced: false,
|
|
});
|
|
},
|
|
|
|
handleGenerateNewLink(e: Event) {
|
|
e.preventDefault();
|
|
|
|
this.createInvite(this.state.maxAge, this.state.maxUses, this.state.temporary, 'InstantInviteModal Regenerate');
|
|
this.setState({showAdvanced: false});
|
|
},
|
|
|
|
createInvite(maxAge: string | number, maxUses: string | number, temporary: boolean) {
|
|
InstantInviteActionCreators.createInvite(
|
|
this.state.channel.id,
|
|
{
|
|
/* eslint-disable camelcase */
|
|
max_age: parseInt(maxAge, 10),
|
|
max_uses: parseInt(maxUses, 10),
|
|
/* eslint-enable camelcase */
|
|
temporary: temporary,
|
|
},
|
|
'InstantInviteModal'
|
|
);
|
|
},
|
|
|
|
render() {
|
|
const {showAdvanced, maxAge, maxUses, temporary, hide, invite} = this.state;
|
|
const {guild} = this.props;
|
|
|
|
const content = showAdvanced
|
|
? <AdvancedInstantInviteModal
|
|
maxAge={maxAge}
|
|
maxUses={maxUses}
|
|
temporary={temporary}
|
|
handleTemporaryChange={this.handleTemporaryChange}
|
|
handleGenerateNewLink={this.handleGenerateNewLink}
|
|
handleMaxUsesChange={this.handleMaxUsesChange}
|
|
handleExpireAfterChange={this.handleExpireAfterChange}
|
|
hideAdvanced={this.hideAdvanced}
|
|
key="instant-invite-modal"
|
|
/>
|
|
: <BasicInstantInviteModal
|
|
invite={invite}
|
|
guild={guild}
|
|
hide={hide}
|
|
maxAge={maxAge}
|
|
handleExpiresChange={this.handleExpiresChange}
|
|
showAdvanced={this.showAdvanced}
|
|
key="instant-invite-modal-advanced"
|
|
/>;
|
|
|
|
return (
|
|
<TransitionGroup className="instant-invite-modal theme-light" component="div">
|
|
{content}
|
|
</TransitionGroup>
|
|
);
|
|
},
|
|
});
|
|
|
|
const renderComponent = (props, experimentDescriptor) => {
|
|
return <InstantInviteModal {...props} experiment={experimentDescriptor} />;
|
|
};
|
|
|
|
export default createExperimentContainer(InvitePeriodExperimentStore, {
|
|
[ExperimentTypes.NONE]: props => <InstantInviteModal {...props} />,
|
|
|
|
[ExperimentTypes.USER]: {
|
|
[ExperimentBuckets.CONTROL]: renderComponent,
|
|
[ExperimentBuckets.TREATMENT_1]: renderComponent,
|
|
[ExperimentBuckets.TREATMENT_2]: renderComponent,
|
|
},
|
|
});
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/InstantInviteModal.js
|