613 lines
21 KiB
JavaScript
Executable file
613 lines
21 KiB
JavaScript
Executable file
import React from 'react';
|
|
import {Link} from 'react-router';
|
|
import Helmet from 'react-helmet';
|
|
import classNames from 'classnames';
|
|
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
|
import lodash from 'lodash';
|
|
import Flux from '../lib/flux';
|
|
import Platforms from '../lib/Platforms';
|
|
import Badge from './common/Badge';
|
|
import Avatar from './common/Avatar';
|
|
import GuildIcon from './common/GuildIcon';
|
|
import TabBar, {TabBarItem, TabBarSeparator, TabBarTypes} from './common/TabBar';
|
|
import Scroller from './common/Scroller';
|
|
import Tooltip from './common/Tooltip';
|
|
import DiscordTag from './common/DiscordTag';
|
|
import ContextMenu from './common/ContextMenu';
|
|
import ConnectAccountButton from './common/ConnectAccountButton';
|
|
import DownloadApps from './DownloadApps';
|
|
import UserContextMenu from './contextmenus/UserContextMenu';
|
|
import ConfirmModal from './ConfirmModal';
|
|
import HeaderToolbar from './HeaderToolbar';
|
|
import PrivateChannelRecipientsInviteButton from './PrivateChannelRecipientsInvite';
|
|
import AddFriendInput from './AddFriendInput';
|
|
import FriendsStore from '../stores/views/FriendsStore';
|
|
import FriendSuggestionStore from '../stores/FriendSuggestionStore';
|
|
import UserStore from '../stores/UserStore';
|
|
import ChannelStore from '../stores/ChannelStore';
|
|
import UserSettingsStore from '../stores/UserSettingsStore';
|
|
import StreamerModeStore from '../stores/StreamerModeStore';
|
|
import ConnectedAccountsStore from '../stores/ConnectedAccountsStore';
|
|
import DetectedPlatformAccountsStore from '../stores/DetectedPlatformAccountsStore';
|
|
import VideoCallExperimentStore from '../stores/experiments/VideoCallExperimentStore';
|
|
import RouterUtils from '../utils/RouterUtils';
|
|
import NativeUtils from '../utils/NativeUtils';
|
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
import AudioActionCreators from '../actions/AudioActionCreators';
|
|
import FriendsActionCreators from '../actions/FriendsActionCreators';
|
|
import RelationshipActionCreators from '../actions/RelationshipActionCreators';
|
|
import ChannelActionCreators from '../actions/ChannelActionCreators';
|
|
import ModalActionCreators from '../actions/ModalActionCreators';
|
|
import UserProfileModalActionCreators from '../actions/UserProfileModalActionCreators';
|
|
import FriendSuggestionActionCreators from '../actions/FriendSuggestionActionCreators';
|
|
import UserSettingsModalActionCreators from '../actions/UserSettingsModalActionCreators';
|
|
import ExperimentStore from '../stores/ExperimentStore';
|
|
import {getStatusText} from '../utils/FriendsUtils';
|
|
import {renderActivity, isStreaming} from '../utils/ActivityUtils';
|
|
import i18n from '../i18n';
|
|
import Animated from '../lib/animated';
|
|
import '../styles/friends.styl';
|
|
import {
|
|
StatusTypes,
|
|
FriendsSections,
|
|
ME,
|
|
RelationshipTypes,
|
|
MAX_MUTUAL_GUILDS,
|
|
Routes,
|
|
ContextMenuTypes,
|
|
ChannelTypes,
|
|
ThemeTypes,
|
|
UserSettingsSections,
|
|
ExperimentTypes,
|
|
} from '../Constants';
|
|
|
|
const FriendsEmptyState = React.createClass({
|
|
mixins: [PureRenderMixin, Flux.StoreListenerMixin(ConnectedAccountsStore)],
|
|
|
|
getInitialState() {
|
|
return {
|
|
opacity: new Animated.Value(1),
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
hasAccounts: ConnectedAccountsStore.getUnsafeAccounts().length > 0,
|
|
};
|
|
},
|
|
|
|
componentWillEnter(callback) {
|
|
this.state.opacity.setValue(0);
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 1,
|
|
duration: 250,
|
|
}).start(callback);
|
|
},
|
|
|
|
componentWillLeave(callback) {
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 0,
|
|
duration: 250,
|
|
}).start(callback);
|
|
},
|
|
|
|
handleDownloadApp() {
|
|
ModalActionCreators.push(DownloadApps, {source: 'Suggestions'});
|
|
},
|
|
|
|
handleConnectAccounts() {
|
|
UserSettingsModalActionCreators.open(UserSettingsSections.CONNECTIONS);
|
|
},
|
|
|
|
render() {
|
|
const {type, onClick} = this.props;
|
|
const {hasAccounts} = this.state;
|
|
const style = {opacity: this.state.opacity};
|
|
switch (type) {
|
|
case FriendsSections.ADD_FRIEND:
|
|
let description;
|
|
let button;
|
|
let className = 'section-add-friend-no-connection';
|
|
if (!hasAccounts && !NativeUtils.embedded) {
|
|
description = i18n.Messages.FRIENDS_EMPTY_STATE_ADD_FRIEND_DOWNLOAD;
|
|
button = (
|
|
<button className="btn" onClick={this.handleDownloadApp}>
|
|
{i18n.Messages.DOWNLOAD}
|
|
</button>
|
|
);
|
|
} else if (!hasAccounts && NativeUtils.embedded) {
|
|
description = i18n.Messages.FRIENDS_EMPTY_STATE_ADD_FRIEND_NO_ACCOUNTS;
|
|
button = (
|
|
<button className="btn" onClick={this.handleConnectAccounts}>
|
|
{i18n.Messages.CONNECT_ACCOUNTS}
|
|
</button>
|
|
);
|
|
} else {
|
|
className = 'section-add-friend-no-suggestion';
|
|
description = i18n.Messages.FRIENDS_EMPTY_STATE_ADD_FRIEND_NO_SUGGESTION;
|
|
}
|
|
return (
|
|
<Animated.div className={`friends-empty ${className}`} style={style}>
|
|
<Helmet title="Discord" />
|
|
<div className="empty-state-image" />
|
|
<p>{description}</p>
|
|
{button}
|
|
</Animated.div>
|
|
);
|
|
case FriendsSections.ALL:
|
|
return (
|
|
<Animated.div className="friends-empty section-all" style={style}>
|
|
<Helmet title="Discord" />
|
|
<div className="empty-state-image" />
|
|
<p>{i18n.Messages.FRIENDS_EMPTY_STATE_ALL}</p>
|
|
<button className="btn" onClick={onClick}>
|
|
{i18n.Messages.ADD_FRIEND}
|
|
</button>
|
|
</Animated.div>
|
|
);
|
|
case FriendsSections.ONLINE:
|
|
return (
|
|
<Animated.div className="friends-empty section-online" style={style}>
|
|
<Helmet title="Discord" />
|
|
<div className="empty-state-image" />
|
|
<p>{i18n.Messages.FRIENDS_EMPTY_STATE_ONLINE}</p>
|
|
</Animated.div>
|
|
);
|
|
case FriendsSections.PENDING:
|
|
return (
|
|
<Animated.div className="friends-empty section-pending" style={style}>
|
|
<Helmet title="Discord" />
|
|
<div className="empty-state-image" />
|
|
<p>{i18n.Messages.FRIENDS_EMPTY_STATE_PENDING}</p>
|
|
</Animated.div>
|
|
);
|
|
case FriendsSections.BLOCKED:
|
|
return (
|
|
<Animated.div className="friends-empty section-blocked" style={style}>
|
|
<Helmet title="Discord" />
|
|
<div className="empty-state-image" />
|
|
<p>{i18n.Messages.FRIENDS_EMPTY_STATE_BLOCKED}</p>
|
|
</Animated.div>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
});
|
|
|
|
const MutualGuild = ({guild}) =>
|
|
<Tooltip text={guild.toString()}>
|
|
<Link to={Routes.GUILD(guild.id)} onClick={e => e.stopPropagation()}>
|
|
<GuildIcon guild={guild} textScale={0.8} />
|
|
</Link>
|
|
</Tooltip>;
|
|
|
|
const MoreMutualGuildsButton = ({num, ...props}) => <div className="more-mutual-guilds-btn" {...props}>+{num}</div>;
|
|
|
|
const FriendsColumn = props => <div className={`friends-column friends-column-${props.column}`}>{props.children}</div>;
|
|
|
|
const FriendsColumnSeparator = () => <div className="friends-column-separator" />;
|
|
|
|
const RowMixin = {
|
|
getInitialState() {
|
|
return {
|
|
height: new Animated.Value(62),
|
|
opacity: new Animated.Value(1),
|
|
};
|
|
},
|
|
|
|
componentWillEnter(callback) {
|
|
this.state.opacity.setValue(0);
|
|
this.state.height.setValue(0);
|
|
Animated.parallel([
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 1,
|
|
duration: 200,
|
|
}),
|
|
Animated.timing(this.state.height, {
|
|
toValue: 62,
|
|
duration: 200,
|
|
}),
|
|
]).start(callback);
|
|
},
|
|
|
|
componentWillLeave(callback) {
|
|
Animated.parallel([
|
|
Animated.timing(this.state.opacity, {
|
|
toValue: 0,
|
|
duration: 200,
|
|
}),
|
|
Animated.timing(this.state.height, {
|
|
toValue: 0,
|
|
duration: 200,
|
|
}),
|
|
]).start(callback);
|
|
},
|
|
};
|
|
|
|
function Action({type, tooltip, onClick}) {
|
|
const action = onClick != null
|
|
? <div className={`friends-action friends-action-${type}`} onClick={onClick} />
|
|
: <div className={`friends-action friends-action-${type} disabled`} />;
|
|
return <Tooltip text={tooltip}>{action}</Tooltip>;
|
|
}
|
|
|
|
const FriendSuggestionRow = React.createClass({
|
|
mixins: [RowMixin, PureRenderMixin, Flux.StoreListenerMixin(UserSettingsStore, StreamerModeStore)],
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
theme: UserSettingsStore.theme,
|
|
hide: StreamerModeStore.hidePersonalInformation,
|
|
};
|
|
},
|
|
|
|
handleOpenProfile(e) {
|
|
e.stopPropagation();
|
|
const {user} = this.props;
|
|
UserProfileModalActionCreators.open(user.id);
|
|
},
|
|
|
|
handleAddRelationship(e) {
|
|
e.stopPropagation();
|
|
const {user} = this.props;
|
|
RelationshipActionCreators.addRelationship(user.id, {location: 'Friend Suggestion'});
|
|
},
|
|
|
|
handleIgnoreFriendSuggestion(e) {
|
|
e.stopPropagation();
|
|
const {user} = this.props;
|
|
FriendSuggestionActionCreators.ignore(user.id);
|
|
},
|
|
|
|
render() {
|
|
const {user, reasons} = this.props;
|
|
const {hide, theme} = this.state;
|
|
|
|
let platforms;
|
|
if (!hide) {
|
|
platforms = reasons.map(({name, platform_type: platformType}, i) => {
|
|
const platform = Platforms.get(platformType);
|
|
return (
|
|
<div key={i} className="friends-suggestion-platform">
|
|
<img
|
|
className="friends-suggestion-platform-icon"
|
|
src={theme === ThemeTypes.DARK ? platform.icon.white : platform.icon.grey}
|
|
/>
|
|
<span className="friends-suggestion-platform-name">{name}</span>
|
|
</div>
|
|
);
|
|
});
|
|
}
|
|
|
|
return (
|
|
<Animated.div
|
|
className="friends-row"
|
|
onClick={this.handleOpenProfile}
|
|
style={{height: this.state.height, opacity: this.state.opacity}}>
|
|
<div className="friends-suggestion-inner">
|
|
<Avatar user={user} size="large" />
|
|
<div className="friends-suggestion-info">
|
|
<DiscordTag user={user} />
|
|
<div className="friends-suggestion-platforms">{platforms}</div>
|
|
</div>
|
|
</div>
|
|
<div className="friends-column friends-column-actions friends-column-actions-visible">
|
|
<Action type="add" tooltip={i18n.Messages.SEND_FRIEND_REQUEST} onClick={this.handleAddRelationship} />
|
|
<Action
|
|
type="ignore"
|
|
tooltip={i18n.Messages.FRIEND_REQUEST_IGNORE}
|
|
onClick={this.handleIgnoreFriendSuggestion}
|
|
/>
|
|
</div>
|
|
</Animated.div>
|
|
);
|
|
},
|
|
});
|
|
|
|
const FriendRow = React.createClass({
|
|
mixins: [RowMixin, PureRenderMixin],
|
|
|
|
getInitialState() {
|
|
// FIXME: Quick hack to enable video button without using an experiment
|
|
// wrapper because it breakes the component transitions
|
|
let video = false;
|
|
const override = ExperimentStore.getOverrideExperimentDescriptor(VideoCallExperimentStore.getExperimentId());
|
|
if (override != null && override.type === ExperimentTypes.DEVELOPER) {
|
|
video = true;
|
|
}
|
|
return {video};
|
|
},
|
|
|
|
handleOpenProfile(e) {
|
|
e.stopPropagation();
|
|
UserProfileModalActionCreators.open(this.props.user.id);
|
|
},
|
|
|
|
handleOpenPrivateChannel(e) {
|
|
e.stopPropagation();
|
|
const {user} = this.props;
|
|
const channel = lodash.find(
|
|
ChannelStore.getChannels(),
|
|
channel => channel.type == ChannelTypes.DM && channel.getRecipientId() === user.id
|
|
);
|
|
if (channel != null) {
|
|
RouterUtils.transitionTo(Routes.CHANNEL(ME, channel.id));
|
|
} else {
|
|
ChannelActionCreators.openPrivateChannel(UserStore.getCurrentUser().id, user.id);
|
|
}
|
|
},
|
|
|
|
handleRemoveFriend(e) {
|
|
e.stopPropagation();
|
|
ModalActionCreators.push(props => {
|
|
return (
|
|
<ConfirmModal
|
|
header={i18n.Messages.REMOVE_FRIEND_TITLE.format({name: this.props.user.toString()})}
|
|
confirmText={i18n.Messages.REMOVE_FRIEND}
|
|
cancelText={i18n.Messages.CANCEL}
|
|
onConfirm={this.handleRemoveRelationship}
|
|
{...props}>
|
|
<p>{i18n.Messages.REMOVE_FRIEND_BODY.format({name: this.props.user.toString()})}</p>
|
|
</ConfirmModal>
|
|
);
|
|
});
|
|
},
|
|
|
|
handleAddRelationship(e) {
|
|
e.stopPropagation();
|
|
const {user} = this.props;
|
|
RelationshipActionCreators.addRelationship(user.id, {location: 'Friends'});
|
|
},
|
|
|
|
handleRemoveRelationship(e) {
|
|
e && e.stopPropagation();
|
|
RelationshipActionCreators.removeRelationship(this.props.user.id, {location: 'Friends'});
|
|
},
|
|
|
|
handleContextMenu(event, user) {
|
|
ContextMenu.openContextMenu(event, props =>
|
|
<UserContextMenu {...props} type={ContextMenuTypes.USER_FRIEND_LIST} user={user} />
|
|
);
|
|
},
|
|
|
|
handleVideoCall(e) {
|
|
AudioActionCreators.setVideoEnabled(true);
|
|
this.handleVoiceCall(e);
|
|
},
|
|
|
|
handleVoiceCall(e) {
|
|
e.stopPropagation();
|
|
ChannelActionCreators.openPrivateChannel(UserStore.getCurrentUser().id, this.props.user.id, true);
|
|
},
|
|
|
|
render() {
|
|
const {user, type, mutualGuilds, mutualGuildsLength} = this.props;
|
|
|
|
const actions = [];
|
|
const addAction = (type, tooltip, onClick = null) => {
|
|
actions.push(<Action key={actions.length} type={type} tooltip={tooltip} onClick={onClick} />);
|
|
};
|
|
|
|
let status = this.props.status;
|
|
let statusText = renderActivity(this.props.activity) || getStatusText(this.props.status);
|
|
let actionsVisible = true;
|
|
switch (type) {
|
|
case RelationshipTypes.FRIEND:
|
|
actionsVisible = false;
|
|
if (this.state.video) {
|
|
addAction('video-call', i18n.Messages.START_VIDEO_CALL, this.handleVideoCall);
|
|
}
|
|
addAction('voice-call', i18n.Messages.START_VOICE_CALL, this.handleVoiceCall);
|
|
addAction('remove', i18n.Messages.REMOVE_FRIEND, this.handleRemoveFriend);
|
|
break;
|
|
case RelationshipTypes.BLOCKED:
|
|
status = StatusTypes.UNKNOWN;
|
|
statusText = i18n.Messages.BLOCKED;
|
|
addAction('remove', i18n.Messages.UNBLOCK, this.handleRemoveRelationship);
|
|
break;
|
|
case RelationshipTypes.PENDING_INCOMING:
|
|
status = status === StatusTypes.OFFLINE ? StatusTypes.UNKNOWN : status;
|
|
statusText = i18n.Messages.INCOMING_FRIEND_REQUEST;
|
|
addAction('accept', i18n.Messages.FRIEND_REQUEST_ACCEPT, this.handleAddRelationship);
|
|
addAction('ignore', i18n.Messages.FRIEND_REQUEST_IGNORE, this.handleRemoveRelationship);
|
|
break;
|
|
case RelationshipTypes.PENDING_OUTGOING:
|
|
actionsVisible = false;
|
|
status = status === StatusTypes.OFFLINE ? StatusTypes.UNKNOWN : status;
|
|
statusText = i18n.Messages.OUTGOING_FRIEND_REQUEST;
|
|
addAction('cancel', i18n.Messages.FRIEND_REQUEST_CANCEL, this.handleRemoveRelationship);
|
|
break;
|
|
}
|
|
|
|
let moreMutualGuildsButton;
|
|
if (mutualGuildsLength > MAX_MUTUAL_GUILDS) {
|
|
moreMutualGuildsButton = (
|
|
<MoreMutualGuildsButton num={mutualGuildsLength - MAX_MUTUAL_GUILDS} onClick={this.handleOpenProfile} />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Animated.div
|
|
className="friends-row"
|
|
onClick={this.handleOpenPrivateChannel}
|
|
onContextMenu={e => this.handleContextMenu(e, user)}
|
|
style={{height: this.state.height, opacity: this.state.opacity}}>
|
|
<div className="friends-column friends-column-name">
|
|
<Avatar user={user} />
|
|
<DiscordTag user={user} />
|
|
</div>
|
|
<div className="friends-column friends-column-status">
|
|
<span className={`status status-${isStreaming(this.props.activity) ? 'streaming' : status}`} />
|
|
<span className="status-text">{statusText}</span>
|
|
</div>
|
|
<div className="friends-column friends-column-guilds">
|
|
{mutualGuilds.map(guild => <MutualGuild key={guild.id} guild={guild} />)}
|
|
{moreMutualGuildsButton}
|
|
</div>
|
|
<div
|
|
className={classNames('friends-column friends-column-actions', {
|
|
'friends-column-actions-visible': actionsVisible,
|
|
})}>
|
|
{actions}
|
|
</div>
|
|
</Animated.div>
|
|
);
|
|
},
|
|
});
|
|
|
|
const FriendsTableHeader = () =>
|
|
<div className="friends-table-header">
|
|
<FriendsColumn column="name">{i18n.Messages.FRIENDS_COLUMN_NAME}</FriendsColumn>
|
|
<FriendsColumnSeparator />
|
|
<FriendsColumn column="status">{i18n.Messages.FRIENDS_COLUMN_STATUS}</FriendsColumn>
|
|
<FriendsColumnSeparator />
|
|
<FriendsColumn column="guilds">{i18n.Messages.MUTUAL_GUILDS}</FriendsColumn>
|
|
<FriendsColumnSeparator />
|
|
<FriendsColumn column="actions" />
|
|
</div>;
|
|
|
|
const AddFriendHeader = React.createClass({
|
|
mixins: [PureRenderMixin, Flux.StoreListenerMixin(DetectedPlatformAccountsStore)],
|
|
|
|
getInitialState() {
|
|
return {
|
|
addFriendError: null,
|
|
addFriendSuccess: null,
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
availablePlatformTypes: DetectedPlatformAccountsStore.getAvailablePlatformTypes(),
|
|
};
|
|
},
|
|
|
|
handleAddFriendSubmit(error, success) {
|
|
this.setState({addFriendError: error, addFriendSuccess: success});
|
|
},
|
|
|
|
render() {
|
|
const {availablePlatformTypes, addFriendSuccess, addFriendError} = this.state;
|
|
|
|
let connectAccounts;
|
|
if (availablePlatformTypes.length > 0) {
|
|
connectAccounts = (
|
|
<div className="connect-accounts">
|
|
<h2>{i18n.Messages.CONNECT_ACCOUNTS_TITLE}</h2>
|
|
<h3>{i18n.Messages.CONNECT_ACCOUNTS_DESCRIPTION}</h3>
|
|
<div className="connect-account-btn-group">
|
|
{availablePlatformTypes
|
|
.slice(0, 4)
|
|
.map(type => <ConnectAccountButton key={type} type={type} location="Friends" />)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
let success = false;
|
|
let error = false;
|
|
let description = i18n.Messages.ADD_FRIEND_DESCRIPTION;
|
|
if (addFriendSuccess != null) {
|
|
success = true;
|
|
description = addFriendSuccess;
|
|
} else if (addFriendError != null) {
|
|
error = true;
|
|
description = addFriendError;
|
|
}
|
|
|
|
return (
|
|
<div className="friend-table-add-wrapper">
|
|
<div className="friend-table-add-header">
|
|
<div className="friends-table-add">
|
|
<h2>{i18n.Messages.ADD_FRIEND}</h2>
|
|
<div className={classNames('friend-table-add-description', {success, error})}>{description}</div>
|
|
<AddFriendInput onSubmit={this.handleAddFriendSubmit} />
|
|
</div>
|
|
{connectAccounts}
|
|
</div>
|
|
<div className="friend-table-suggestions-header">
|
|
<h2>{i18n.Messages.FRIEND_SUGGESTIONS}</h2>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
});
|
|
|
|
const Friends = React.createClass({
|
|
mixins: [PureRenderMixin, Flux.StoreListenerMixin(FriendsStore, FriendSuggestionStore)],
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
suggestionCount: FriendSuggestionStore.getSuggestionCount(),
|
|
suggestions: FriendSuggestionStore.getSuggestions(),
|
|
...FriendsStore.getState(),
|
|
};
|
|
},
|
|
|
|
handleItemSelect(section) {
|
|
FriendsActionCreators.setSection(section);
|
|
},
|
|
|
|
handleAddFriend() {
|
|
FriendsActionCreators.setSection(FriendsSections.ADD_FRIEND);
|
|
},
|
|
|
|
render() {
|
|
let rows;
|
|
let tableHeader;
|
|
if (this.state.section === FriendsSections.ADD_FRIEND) {
|
|
rows = this.state.suggestions.map(props => <FriendSuggestionRow {...props} />);
|
|
tableHeader = <AddFriendHeader />;
|
|
} else {
|
|
rows = this.state.rows.filter(this.state.section).map(props => <FriendRow {...props.toJS()} />);
|
|
tableHeader = <FriendsTableHeader />;
|
|
}
|
|
|
|
let emptyState;
|
|
if (rows.length === 0) {
|
|
emptyState = (
|
|
<FriendsEmptyState key={this.state.section} type={this.state.section} onClick={this.handleAddFriend} />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div id="friends">
|
|
<div className="friends-header">
|
|
<TabBar selectedItem={this.state.section} type={TabBarTypes.UNIQUE} onItemSelect={this.handleItemSelect}>
|
|
<TabBarItem className="tab-bar-item-primary" key={FriendsSections.ADD_FRIEND}>
|
|
{i18n.Messages.FRIENDS_SECTION_ADD_FRIEND} <Badge value={this.state.suggestionCount} />
|
|
</TabBarItem>
|
|
<TabBarSeparator />
|
|
<TabBarItem key={FriendsSections.ALL}>{i18n.Messages.FRIENDS_SECTION_ALL}</TabBarItem>
|
|
<TabBarItem key={FriendsSections.ONLINE}>{i18n.Messages.FRIENDS_SECTION_ONLINE}</TabBarItem>
|
|
<TabBarItem key={FriendsSections.PENDING}>
|
|
{i18n.Messages.FRIENDS_SECTION_PENDING} <Badge value={this.state.pendingCount} />
|
|
</TabBarItem>
|
|
<TabBarSeparator />
|
|
<TabBarItem key={FriendsSections.BLOCKED}>{i18n.Messages.BLOCKED}</TabBarItem>
|
|
</TabBar>
|
|
<HeaderToolbar>
|
|
<PrivateChannelRecipientsInviteButton tooltip={i18n.Messages.NEW_GROUP_DM} />
|
|
</HeaderToolbar>
|
|
</div>
|
|
<div className="friends-table">
|
|
{tableHeader}
|
|
<Scroller className="friends-table-body">
|
|
<TransitionGroup transitionAppear={false}>
|
|
{rows}
|
|
{emptyState}
|
|
</TransitionGroup>
|
|
</Scroller>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
});
|
|
|
|
export default Friends;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/Friends.js
|