356 lines
11 KiB
JavaScript
Executable file
356 lines
11 KiB
JavaScript
Executable file
import React from 'react';
|
|
import classNames from 'classnames';
|
|
import Flux from '../lib/flux';
|
|
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
|
import RegionSelect from './common/RegionSelect';
|
|
import GuildIconUploader from './common/GuildIconUploader';
|
|
import CreateGuildModalStore from '../stores/CreateGuildModalStore';
|
|
import CreateGuildModalActionCreators from '../actions/CreateGuildModalActionCreators';
|
|
import i18n from '../i18n';
|
|
import '../styles/create_guild.styl';
|
|
import InstantInviteActionCreators from '../actions/InstantInviteActionCreators';
|
|
import InviteStore from '../stores/InviteStore';
|
|
import Animated from '../lib/animated';
|
|
import {InviteStates, CreateGuildModalScreens, Routes} from '../Constants';
|
|
|
|
function createAnimationMixin(reverse = false, duration = 250, easing = Animated.Easing.inOut(Animated.Easing.cubic)) {
|
|
return {
|
|
getInitialState() {
|
|
return {
|
|
translateX: new Animated.Value(0),
|
|
};
|
|
},
|
|
|
|
componentDidAppear() {
|
|
this.state.translateX.setValue(1);
|
|
},
|
|
|
|
componentWillEnter(callback) {
|
|
Animated.timing(this.state.translateX, {toValue: 1, duration, easing}).start(callback);
|
|
},
|
|
|
|
componentWillLeave(callback) {
|
|
Animated.timing(this.state.translateX, {toValue: 0, duration, easing}).start(callback);
|
|
},
|
|
|
|
getAnimatedStyle() {
|
|
return Animated.accelerate({
|
|
transform: [
|
|
{
|
|
translateX: this.state.translateX.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [reverse ? '-100%' : '100%', '0%'],
|
|
}),
|
|
},
|
|
],
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
const JoinGuildModal = React.createClass({
|
|
mixins: [Flux.StoreListenerMixin(InviteStore), createAnimationMixin()],
|
|
|
|
getInitialState() {
|
|
return {
|
|
code: null,
|
|
submitting: false,
|
|
hasError: false,
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
const code = this.state ? this.state.code : null;
|
|
return {
|
|
invite: InviteStore.getInvite(code) || {},
|
|
};
|
|
},
|
|
|
|
componentWillUpdate(nextProps, nextState) {
|
|
if (nextState.invite != null && this.state.invite !== nextState.invite) {
|
|
if (nextState.invite.state === InviteStates.RESOLVED) {
|
|
InstantInviteActionCreators.acceptInvite(nextState.invite.code, 'Join a Server Modal', () => {
|
|
CreateGuildModalActionCreators.close();
|
|
InstantInviteActionCreators.transitionToInviteChannel(
|
|
nextState.invite.guild.id,
|
|
nextState.invite.channel.id,
|
|
nextState.invite.channel.type
|
|
);
|
|
});
|
|
} else if (nextState.invite.state === InviteStates.EXPIRED) {
|
|
this.setState({submitting: false, hasError: true});
|
|
}
|
|
}
|
|
},
|
|
|
|
render() {
|
|
const isInviteInvalid = this.state.hasError;
|
|
//this.state.invite && this.state.invite.state === InviteStates.EXPIRED;
|
|
|
|
const errorMessage = isInviteInvalid
|
|
? <span className="error">({i18n.Messages.INSTANT_INVITE_EXPIRED})</span>
|
|
: null;
|
|
|
|
let host = process.env.INVITE_HOST;
|
|
let path = '/';
|
|
if (host == null) {
|
|
host = location.host;
|
|
path = Routes.INVITE('');
|
|
}
|
|
const examplePrefix = `${location.protocol}//${host}${path}`;
|
|
|
|
return (
|
|
<Animated.form className="form join-server" onSubmit={this.handleSubmit} style={this.getAnimatedStyle()}>
|
|
<div className="form-inner">
|
|
<h5>{i18n.Messages.JOIN_SERVER_TITLE}</h5>
|
|
<div className={classNames({'control-group': true})}>
|
|
<div className="instructions">
|
|
<p>{i18n.Messages.JOIN_SERVER_DESCRIPTION}</p>
|
|
<p className="sample-link">{examplePrefix}qJq5C</p>
|
|
<p className="sample-link">{examplePrefix}discord-developers</p>
|
|
<p className="sample-link">qJq5C</p>
|
|
</div>
|
|
</div>
|
|
<div className="link-container control-group">
|
|
<input
|
|
className={classNames({error: isInviteInvalid})}
|
|
type="text"
|
|
ref="invite"
|
|
onChange={this.handleTyped}
|
|
autoFocus
|
|
/>
|
|
<label htmlFor="invite">{i18n.Messages.FORM_LABEL_INSTANT_INVITE} {errorMessage}</label>
|
|
</div>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn btn-default" onClick={this.props.onCancel}>
|
|
{i18n.Messages.BACK}
|
|
</button>
|
|
<button type="submit" className="btn btn-primary" disabled={this.state.submitting}>
|
|
{i18n.Messages.JOIN}
|
|
</button>
|
|
</div>
|
|
</Animated.form>
|
|
);
|
|
},
|
|
|
|
handleTyped() {
|
|
if (this.state.hasError) {
|
|
this.setState({hasError: false});
|
|
}
|
|
},
|
|
|
|
handleSubmit(e) {
|
|
e.preventDefault();
|
|
const inviteString = this.refs['invite'].value.trim();
|
|
if (inviteString === '') {
|
|
this.setState({hasError: true});
|
|
return;
|
|
}
|
|
|
|
const segments = inviteString.split('/');
|
|
const inviteCode = segments[segments.length - 1];
|
|
InstantInviteActionCreators.resolveInvite(inviteCode, 'Join Guild Modal');
|
|
this.setState({code: inviteCode, submitting: true});
|
|
},
|
|
});
|
|
|
|
const CreateGuildModal = React.createClass({
|
|
mixins: [createAnimationMixin()],
|
|
|
|
componentDidMount() {
|
|
if (this.props.guild.name.length === 0) {
|
|
this.refs['name'].focus();
|
|
}
|
|
},
|
|
|
|
render() {
|
|
const hasError = field => this.props.errors && this.props.errors[field] != null;
|
|
|
|
const renderError = field => {
|
|
if (hasError(field)) {
|
|
return <span className="error">({this.props.errors[field]})</span>;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Animated.form className="form create-guild" onSubmit={this.handleSubmit} style={this.getAnimatedStyle()}>
|
|
<div className="form-inner">
|
|
<h5>{i18n.Messages.CREATE_SERVER_TITLE}</h5>
|
|
<p>{i18n.Messages.CREATE_SERVER_DESCRIPTION.format()}</p>
|
|
<ul className="guild-form">
|
|
<li>
|
|
<div className={classNames({'control-group': true, error: hasError('name')})}>
|
|
<label htmlFor="guild-name">{i18n.Messages.FORM_LABEL_SERVER_NAME} {renderError('name')}</label>
|
|
<input
|
|
id="guild-name"
|
|
ref="name"
|
|
type="text"
|
|
onChange={this.handleNameChange}
|
|
defaultValue={this.props.guild.name}
|
|
placeholder={i18n.Messages.FORM_PLACEHOLDER_SERVER_NAME}
|
|
/>
|
|
</div>
|
|
<div className="control-group">
|
|
<label htmlFor="guild-region">{i18n.Messages.FORM_LABEL_SERVER_REGION}</label>
|
|
<RegionSelect
|
|
id="guild-region"
|
|
ref="region"
|
|
value={this.props.guild.region}
|
|
onChange={this.handleRegionChange}
|
|
/>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<GuildIconUploader
|
|
name={this.props.guild.name}
|
|
icon={this.props.guild.icon}
|
|
onChange={this.handleIconChange}
|
|
/>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn btn-default" onClick={this.props.onCancel}>
|
|
{i18n.Messages.BACK}
|
|
</button>
|
|
<button type="submit" className="btn btn-primary" disabled={this.props.submitting}>
|
|
{i18n.Messages.CREATE}
|
|
</button>
|
|
</div>
|
|
</Animated.form>
|
|
);
|
|
},
|
|
|
|
// Handlers
|
|
|
|
handleNameChange(e) {
|
|
CreateGuildModalActionCreators.updateTemporaryGuild({name: e.currentTarget.value});
|
|
},
|
|
|
|
handleRegionChange(region) {
|
|
CreateGuildModalActionCreators.updateTemporaryGuild({region});
|
|
},
|
|
|
|
handleIconChange(icon) {
|
|
CreateGuildModalActionCreators.updateTemporaryGuild({icon});
|
|
},
|
|
|
|
handleSubmit(e) {
|
|
e.preventDefault();
|
|
CreateGuildModalActionCreators.createGuild(this.props.guild.name, this.props.guild.region, this.props.guild.icon);
|
|
},
|
|
});
|
|
|
|
const GuildActionButton = React.createClass({
|
|
render() {
|
|
let header;
|
|
let body;
|
|
let cta;
|
|
switch (this.props.type) {
|
|
case 'create':
|
|
header = i18n.Messages.CREATE;
|
|
body = i18n.Messages.CREATE_SERVER_BUTTON_BODY;
|
|
cta = i18n.Messages.CREATE_SERVER_BUTTON_CTA;
|
|
break;
|
|
case 'join':
|
|
header = i18n.Messages.JOIN;
|
|
body = i18n.Messages.JOIN_SERVER_BUTTON_BODY;
|
|
cta = i18n.Messages.JOIN_SERVER_BUTTON_CTA;
|
|
break;
|
|
}
|
|
return (
|
|
<div className={`action ${this.props.type}`} onMouseEnter={this.props.onMouseEnter} onClick={this.props.onClick}>
|
|
<div className="action-header">{header}</div>
|
|
<div className="action-body">{body}</div>
|
|
<div className="action-icon" />
|
|
<button className="btn btn-primary" type="button" onClick={this.props.onClick}>{cta}</button>
|
|
</div>
|
|
);
|
|
},
|
|
});
|
|
|
|
const CreateOrJoinGuildModal = React.createClass({
|
|
mixins: [createAnimationMixin(true)],
|
|
|
|
render() {
|
|
return (
|
|
<Animated.div className="form create-or-join" style={this.getAnimatedStyle()}>
|
|
<div className="form-inner">
|
|
<header>{i18n.Messages.CREATE_OR_JOIN_MODAL_HEADER}</header>
|
|
<div className="actions">
|
|
<GuildActionButton
|
|
type="create"
|
|
onClick={this.handleClick.bind(this, CreateGuildModalScreens.CreateGuild)}
|
|
/>
|
|
<GuildActionButton type="join" onClick={this.handleClick.bind(this, CreateGuildModalScreens.JoinGuild)} />
|
|
<div className="or">or</div>
|
|
</div>
|
|
</div>
|
|
</Animated.div>
|
|
);
|
|
},
|
|
|
|
handleClick(selectedScreen) {
|
|
this.props.onClick(selectedScreen);
|
|
},
|
|
});
|
|
|
|
const CreateOrJoinGuildContainer = React.createClass({
|
|
statics: {
|
|
modalConfig: {
|
|
store: CreateGuildModalStore,
|
|
},
|
|
},
|
|
|
|
propTypes: {
|
|
errors: React.PropTypes.object.isRequired,
|
|
submitting: React.PropTypes.bool.isRequired,
|
|
},
|
|
|
|
close() {
|
|
CreateGuildModalActionCreators.close();
|
|
},
|
|
|
|
handleNavigateScreen(selectedScreen) {
|
|
CreateGuildModalActionCreators.setScreen(selectedScreen);
|
|
},
|
|
|
|
render() {
|
|
let body;
|
|
if (this.props.screen === CreateGuildModalScreens.Choose) {
|
|
body = <CreateOrJoinGuildModal key={CreateGuildModalScreens.Choose} onClick={this.handleNavigateScreen} />;
|
|
} else if (this.props.screen === CreateGuildModalScreens.CreateGuild) {
|
|
body = (
|
|
<CreateGuildModal
|
|
key={CreateGuildModalScreens.CreateGuild}
|
|
onCancel={() => this.handleNavigateScreen(CreateGuildModalScreens.Choose)}
|
|
{...this.props}
|
|
/>
|
|
);
|
|
} else if (this.props.screen === CreateGuildModalScreens.JoinGuild) {
|
|
body = (
|
|
<JoinGuildModal
|
|
key={CreateGuildModalScreens.JoinGuild}
|
|
onCancel={() => this.handleNavigateScreen(CreateGuildModalScreens.Choose)}
|
|
{...this.props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<TransitionGroup className="create-guild-container" component="div">
|
|
{body}
|
|
</TransitionGroup>
|
|
);
|
|
},
|
|
});
|
|
|
|
export default CreateOrJoinGuildContainer;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/CreateGuildModal.js
|