2017-06-08_509bba0/509bba0_unpacked_with_node_modules/discord_app/components/PrivateChannelRecipientsInvite.js
2022-07-26 10:06:20 -07:00

404 lines
13 KiB
JavaScript
Executable file

/* @flow */
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ReactDOM from 'react-dom';
import lodash from 'lodash';
import Flux from '../lib/flux';
import classNames from 'classnames';
import Popout from './common/Popout';
import SearchBar from './common/SearchBar';
import Avatar from './common/Avatar';
import LazyScroller, {Themes} from './common/LazyScroller';
import DiscordTag from './common/DiscordTag';
import Checkbox from './common/Checkbox';
import PrivateChannelRecipientsInviteStore from '../stores/PrivateChannelRecipientsInviteStore';
import UserSettingsStore from '../stores/UserSettingsStore';
import PresenceStore from '../stores/PresenceStore';
import RelationshipStore from '../stores/RelationshipStore';
import UserStore from '../stores/UserStore';
import CallStore from '../stores/CallStore';
import PopoutStore from '../stores/PopoutStore';
import PrivateChannelRecipientsInviteActionCreators from '../actions/PrivateChannelRecipientsInviteActionCreators';
import ChannelActionCreators from '../actions/ChannelActionCreators';
import RelationshipActionCreators from '../actions/RelationshipActionCreators';
import CallActionCreators from '../actions/CallActionCreators';
import {HeaderToolbarButton} from './HeaderToolbar';
import {
ChannelTypes,
ThemeTypes,
Colors,
MAX_GROUP_DM_PARTICIPANTS,
PopoutCallers,
ComponentActions,
} from '../Constants';
import i18n from '../i18n';
import type ChannelRecord from '../records/ChannelRecord';
import AnalyticsUtils from '../utils/AnalyticsUtils';
import '../styles/private_channel_recipients_invite.styl';
type PrivateChannelRecipientsInviteButtonProps = {
channel: ?ChannelRecord,
tooltip?: string,
};
const POPOUT_ID = 'PrivateChannelRecipientsInvitePopout';
const ROW_HEIGHT = 38;
const BUTTON_REF = 'BUTTON_REF';
const SEARCH_REF = 'SEARCH_REF';
const PrivateChannelRecipientsInvitePopout = React.createClass({
mixins: [Flux.LazyStoreListenerMixin(PrivateChannelRecipientsInviteStore), PureRenderMixin],
getInitialState() {
return this.getStateFromStores();
},
getStateFromStores() {
return PrivateChannelRecipientsInviteStore.getState();
},
componentDidMount() {
const {channel} = this.props;
if (channel != null && channel.isDM() && !RelationshipStore.isFriend(channel.getRecipientId())) {
AnalyticsUtils.track('open_popout', {
type: 'Not Friend',
source: this.props.caller === PopoutCallers.GROUP_DM ? 'Group DM' : 'Call',
});
}
},
render() {
const {hasFriends, results, selectedRow, selectedUsers} = this.state;
const {channel} = this.props;
const theme = UserSettingsStore.theme;
let notFriends = false;
let isPartyFull = false;
if (channel != null) {
const {recipients} = channel;
notFriends = channel.isDM() && !RelationshipStore.isFriend(recipients[0]);
isPartyFull = recipients.length + 1 >= MAX_GROUP_DM_PARTICIPANTS;
}
const hideSearchBar = !hasFriends || notFriends;
let rows = [];
let footer;
let title = i18n.Messages.GROUP_DM_HEADER;
let subtitle;
// Are you friends with this user
if (notFriends) {
const user = UserStore.getUser(channel.recipients[0]);
let text;
if (this.props.caller === PopoutCallers.CALL) {
title = i18n.Messages.START_CALL;
text = i18n.Messages.CALL_INVITE_NOT_FRIENDS;
} else {
title = i18n.Messages.GROUP_DM_ADD_FRIENDS;
text = i18n.Messages.GROUP_DM_INVITE_NOT_FRIENDS;
}
rows = (
<div className="error-state not-friends">
<div className="icon" />
<div className="text">
{text.format({username: user.username})}
</div>
<div className="btn" onClick={() => this.handleAddFriend(user)}>
{i18n.Messages.ADD_FRIEND_BUTTON}
</div>
</div>
);
} else if (!hasFriends) {
// No friends added
rows = (
<div className="error-state no-friends">
<div className="icon" />
<div className="text">{i18n.Messages.GROUP_DM_INVITE_NO_FRIENDS}</div>
</div>
);
} else {
if (results == null) {
throw new Error('[App]PrivateChannelRecipientsInvitePopout.render(): assert failed: results != null');
}
if (results.length > 0) {
rows = results.map(this.renderRow);
}
// Party full
if (isPartyFull) {
rows = (
<div className="error-state party-full">
<div className="icon" />
<div className="text">{i18n.Messages.GROUP_DM_INVITE_FULL_MAIN}</div>
<div className="text">{i18n.Messages.GROUP_DM_INVITE_FULL_SUB}</div>
</div>
);
} else {
// No results or friends remaining
if (rows.length === 0) {
rows = (
<div className="error-state search-results">
<div className="icon" />
<div className="text">{i18n.Messages.GROUP_DM_INVITE_EMPTY}</div>
</div>
);
} else {
// for light theme we don't want to use a theme, so we show the purple scroll bar.
// for dark theme we do want to show the theme though.
rows = (
<LazyScroller
ref="scroller"
elementHeight={ROW_HEIGHT}
theme={theme === ThemeTypes.LIGHT ? Themes.LIGHT : Themes.DARK}
backgroundColor={theme === ThemeTypes.LIGHT ? undefined : Colors.CHANNELS_GREY}
keyboardScroll={true}>
{rows}
</LazyScroller>
);
}
const existingUsersCount = (channel == null ? 0 : channel.recipients.length) + 1; // + self
const remaining = MAX_GROUP_DM_PARTICIPANTS - selectedUsers.length - existingUsersCount;
const disabled =
selectedUsers.length == 0 || selectedUsers.length + existingUsersCount > MAX_GROUP_DM_PARTICIPANTS;
let buttonText = i18n.Messages.GROUP_DM_ADD_MEMBERS.format({number: selectedUsers.length});
if (channel == null || channel.type === ChannelTypes.DM) {
buttonText = i18n.Messages.CREATE_GROUP_DM;
}
if (remaining <= 0) {
subtitle = (
<div className={classNames('subtitle', {' subtitle-warning': remaining < 0})}>
{i18n.Messages.GROUP_DM_INVITE_FULL_SUB}
</div>
);
} else {
subtitle = (
<div className="subtitle">
{i18n.Messages.GROUP_DM_INVITE_REMAINING.format({number: remaining})}
</div>
);
}
footer = (
<div className="footer">
<button type="button" disabled={disabled} onClick={this.handleInviteUsers}>{buttonText}</button>
</div>
);
}
}
return (
<div className="private-channel-recipients-invite themed-popout">
<div className="header">
<div className="title">{title}</div>
{subtitle}
{hideSearchBar
? null
: <SearchBar
ref={SEARCH_REF}
query={this.state.query}
selectedSection={0}
selectedRow={selectedRow}
sections={[results ? results.length : 0]}
tags={selectedUsers.map(user => user.username)}
onSelect={this.handleSelect}
onSelectionChange={this.handleSelectionChange}
onQueryChange={this.handleQueryChange}
onRemoveTag={this.handleRemoveUser}
light={theme === ThemeTypes.LIGHT}
placeholder={selectedUsers.length === 0 ? i18n.Messages.GROUP_DM_SEARCH_PLACEHOLDER : null}
disabled={isPartyFull}
autoFocus={true}
/>}
</div>
<div className="body">
{rows}
</div>
{footer}
</div>
);
},
// Utils
handleAddFriend(user) {
this.props.onClose();
RelationshipActionCreators.sendRequest(user.tag, {
location: this.props.caller === PopoutCallers.GROUP_DM ? 'Group DM' : 'Call',
});
},
handleSelect(selectedSection, selectedRow, e) {
if (selectedSection == null) {
PrivateChannelRecipientsInviteActionCreators.clear();
} else {
const results = this.state.results;
if (results == null) {
throw new Error('[App]PrivateChannelRecipientsInvitePopout.handleSelect(): assert failed: results != null');
}
const user = results[selectedRow];
const added = this.state.selectedUsers.indexOf(user) > -1;
this.handleClick(e, !added, user);
}
},
handleSelectionChange(section, row) {
PrivateChannelRecipientsInviteActionCreators.select(row);
const scroller = this.refs.scroller;
if (scroller) {
scroller.scrollIntoViewRect(row * ROW_HEIGHT, row * ROW_HEIGHT + ROW_HEIGHT);
}
},
handleQueryChange(query) {
PrivateChannelRecipientsInviteActionCreators.search(query);
},
handleRemoveUser(index) {
PrivateChannelRecipientsInviteActionCreators.removeUser(this.state.selectedUsers[index]);
},
handleClick(e, added, user) {
if (added) {
PrivateChannelRecipientsInviteActionCreators.addUser(user);
if (this.state.query.length > 0) {
PrivateChannelRecipientsInviteActionCreators.clear();
}
} else {
PrivateChannelRecipientsInviteActionCreators.removeUser(user);
}
},
handleInviteUsers() {
const {channel} = this.props;
const {selectedUsers} = this.state;
if (channel != null) {
// invite users to current group chat
const callActive = CallStore.isCallActive(channel.id);
const selectedUserIds = selectedUsers.map(user => user.id);
ChannelActionCreators.addRecipients(channel.id, selectedUserIds).then(newChannelId => {
if (callActive) {
CallActionCreators.ring(newChannelId, selectedUserIds);
}
});
} else {
if (selectedUsers.length == 1) {
// open DM with selected user
ChannelActionCreators.openPrivateChannel(UserStore.getCurrentUser().id, selectedUsers[0].id);
} else {
// create a group chat with selected users
const selectedUserIds = selectedUsers.map(user => user.id);
ChannelActionCreators.openPrivateChannel(UserStore.getCurrentUser().id, selectedUserIds);
}
}
this.props.onClose();
},
renderRow(user, i) {
const checked = this.state.selectedUsers.indexOf(user) > -1;
return (
<div
key={user.id}
onClick={evt => this.handleClick(evt, !checked, user)}
className={classNames('friend', {selected: i === this.state.selectedRow})}>
<Avatar user={user} status={PresenceStore.getStatus(user.id)} />
<DiscordTag user={user} />
<Checkbox className="checkbox" checked={checked} readOnly />
</div>
);
},
});
class PrivateChannelRecipientsInviteButton extends React.Component {
popoutCaller: string;
constructor(props: PrivateChannelRecipientsInviteButtonProps) {
super(props);
this.popoutCaller = PopoutCallers.GROUP_DM;
lodash.bindAll(this, ['renderPopout']);
}
componentDidMount() {
this.maybeForceOpen();
}
componentDidUpdate(prevProps: PrivateChannelRecipientsInviteButtonProps) {
if (prevProps.channel === this.props.channel) return;
this.maybeForceOpen();
}
render() {
const {channel} = this.props;
const iconLight = channel != null
? require('../images/header_icons/ic_person_add_grey_24px.svg')
: require('../images/header_icons/ic_compose_grey_24px.svg');
const iconDark = channel != null
? require('../images/icon-header-add-members-dark.svg')
: require('../images/ic_header_compose_dark_24px.svg');
const button = <HeaderToolbarButton tooltip={this.props.tooltip} iconLight={iconLight} iconDark={iconDark} />;
return (
<Popout
ref={BUTTON_REF}
closeOnScroll={false}
render={this.renderPopout}
position={Popout.TOP_RIGHT}
shadow={false}
offsetX={5}
uniqueId={POPOUT_ID}
animationType="none"
subscribeTo={ComponentActions.TOGGLE_DM_CREATE}>
{button}
</Popout>
);
}
renderPopout(props: $PropsOf<Popout>) {
return <PrivateChannelRecipientsInvitePopout {...props} channel={this.props.channel} caller={this.popoutCaller} />;
}
maybeForceOpen() {
// Force open the popout if this channel has no recipients.
if (
this.props.channel != null &&
this.props.channel.type === ChannelTypes.GROUP_DM &&
this.props.channel.recipients.length === 0 &&
!PopoutStore.isOpen(POPOUT_ID)
) {
const elem = ReactDOM.findDOMNode(this.refs[BUTTON_REF]);
if (!(elem instanceof HTMLElement)) {
throw new Error('elem is not instanceof HTMLElement');
}
elem.click();
}
}
// Triggered by Channel.js when the call icon button is clicked.
openFromDisabledCall() {
this.popoutCaller = PopoutCallers.CALL;
const elem = ReactDOM.findDOMNode(this.refs[BUTTON_REF]);
if (!(elem instanceof HTMLElement)) {
throw new Error('elem is not instanceof HTMLElement');
}
elem.click();
setImmediate(() => (this.popoutCaller = PopoutCallers.GROUP_DM));
}
}
export default PrivateChannelRecipientsInviteButton;
// WEBPACK FOOTER //
// ./discord_app/components/PrivateChannelRecipientsInvite.js