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

391 lines
12 KiB
JavaScript
Executable file

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Flux from '../lib/flux';
import TransitionGroup from '../lib/transitions/TransitionGroup';
import Animated from '../lib/animated';
import i18n from '../i18n';
import {Link} from 'react-router';
import lodash from 'lodash';
import classNames from 'classnames';
import Scroller from '../uikit/Scroller';
import LazyScroller from './common/LazyScroller';
import Avatar from './common/Avatar';
import Badge from './common/Badge';
import ConfirmModal from './ConfirmModal';
import UserStore from '../stores/UserStore';
import PresenceStore from '../stores/PresenceStore';
import RelationshipStore from '../stores/RelationshipStore';
import FriendSuggestionStore from '../stores/FriendSuggestionStore';
import UserContextMenu from './contextmenus/UserContextMenu';
import GroupDMContextMenu from './contextmenus/GroupDMContextMenu';
import ContextMenu from './common/ContextMenu';
import PrivateChannelsMixin from '../mixins/PrivateChannelsMixin';
import DelayedSelectionStore from '../stores/DelayedSelectionStore';
import DimensionStore from '../stores/DimensionStore';
import ChannelActionCreators from '../actions/ChannelActionCreators';
import MessageActionCreators from '../actions/MessageActionCreators';
import ModalActionCreators from '../actions/ModalActionCreators';
import DimensionActionCreators from '../actions/DimensionActionCreators';
import {show as quickSwitcherShow, search as quickSwitcherSearch} from '../actions/QuickSwitcherActionCreators';
import TypingStore from '../stores/TypingStore';
import {renderActivity, isStreaming} from '../utils/ActivityUtils';
import TutorialIndicator from './common/TutorialIndicator';
import SearchBar from './common/SearchBar';
import '../styles/private_channels.styl';
import {ME, Routes, ContextMenuTypes, ChannelTypes, Colors, ComponentActions} from '../Constants';
const SCROLLER_REF = 'SCROLLER_DMS';
// The combined height of all elements preceeding the DMs
const TO_START_OF_DMS = 104;
const LAZY_RENDER_THRESHOLD = 30;
const DM_HEIGHT = 42;
const NoPrivateChannels = () => <div className="no-private-channels" />;
const FriendsButton = React.createClass({
mixins: [Flux.StoreListenerMixin(RelationshipStore, FriendSuggestionStore)],
getStateFromStores() {
return {
badge: RelationshipStore.getPendingCount() + FriendSuggestionStore.getSuggestionCount(),
};
},
render() {
return (
<div className={classNames('channel btn-friends', {selected: this.props.selected})}>
<Link to={Routes.FRIENDS}>
<div className="icon-friends" />
<div className="channel-name">{i18n.Messages.FRIENDS}</div>
<Badge value={this.state.badge} />
</Link>
</div>
);
},
});
function createPrivateChannel(displayName, stores = [], getStateFromStores = () => {}) {
return React.createClass({
displayName,
mixins: [Flux.StoreListenerMixin(...stores), PureRenderMixin],
getInitialState() {
return {
animated: new Animated.Value(1),
};
},
getStateFromStores() {
return getStateFromStores(this.props.channel);
},
getDefaultProps() {
return {
selected: false,
};
},
componentWillEnter(callback) {
this.state.animated.setValue(0);
Animated.timing(this.state.animated, {
toValue: 1,
duration: 200,
}).start(callback);
},
componentWillLeave(callback) {
Animated.timing(this.state.animated, {
toValue: 0,
duration: 200,
}).start(callback);
},
handleClose(e) {
if (e != null) {
e.preventDefault();
e.stopPropagation();
}
ChannelActionCreators.closePrivateChannel(this.props.channel.id, this.props.selected);
},
handleContextMenu(e) {
const {channel, selected} = this.props;
const {user} = this.state;
if (channel.type === ChannelTypes.GROUP_DM) {
ContextMenu.openContextMenu(
e,
props => <GroupDMContextMenu {...props} channelId={channel.id} selected={selected} />,
{noBlurEvent: true}
);
} else {
ContextMenu.openContextMenu(e, props =>
<UserContextMenu
{...props}
type={ContextMenuTypes.USER_PRIVATE_CHANNELS}
user={user}
channelId={channel.id}
selected={selected}
/>
);
}
},
handleLeaveGroup(e) {
e.preventDefault();
e.stopPropagation();
const name = this.props.channel.toString();
let header = i18n.Messages.LEAVE_GROUP_DM_TITLE.format({name});
let body = i18n.Messages.LEAVE_GROUP_DM_BODY.format({name});
if (this.props.channel.isManaged()) {
header = i18n.Messages.LEAVE_GROUP_DM_MANAGED_TITLE.format({name});
body = i18n.Messages.LEAVE_GROUP_DM_MANAGED_BODY.format({name});
}
ModalActionCreators.push(props => {
return (
<ConfirmModal
header={header}
confirmText={i18n.Messages.LEAVE_GROUP_DM}
cancelText={i18n.Messages.CANCEL}
onConfirm={this.handleClose}
{...props}>
<p>{body}</p>
</ConfirmModal>
);
});
},
handleMouseDown() {
MessageActionCreators.prefetchMessages(ME, this.props.channel.id);
},
render() {
const {channel} = this.props;
const {user, status, activity, typing} = this.state;
let channelName = <span className="channel-name">{channel.toString()}</span>;
if (activity) {
channelName = (
<div className="channel-name">
{channel.toString()}
<div className="channel-activity">{renderActivity(activity)}</div>
</div>
);
}
const classes = {
channel: true,
selected: this.props.selected,
private: true,
};
const style = {
height: this.state.animated.interpolate({
inputRange: [0, 1],
outputRange: [0, 42],
}),
opacity: this.state.animated,
};
let avatar;
let onClick;
if (channel.type === ChannelTypes.GROUP_DM) {
avatar = <Avatar channel={channel} size="small" />;
channelName = (
<div className="channel-name">
{channel.toString()}
<div className="channel-activity">
{i18n.Messages.MEMBERS_HEADER.format({members: channel.recipients.length + 1})}
</div>
</div>
);
onClick = this.handleLeaveGroup;
} else {
avatar = <Avatar user={user} size="small" status={status} streaming={isStreaming(activity)} typing={typing} />;
onClick = this.handleClose;
}
return (
<Animated.div
className={classNames(classes)}
style={style}
key={channel.id}
ref={channel.id}
onContextMenu={this.handleContextMenu}
onMouseDown={this.handleMouseDown}>
<Link to={Routes.CHANNEL(ME, channel.id)}>
{avatar}
{channelName}
<button className="close" onClick={onClick} />
</Link>
</Animated.div>
);
},
});
}
const DirectMessage = createPrivateChannel('DirectMessage', [UserStore, PresenceStore, TypingStore], channel => {
const user = UserStore.getUser(channel.getRecipientId());
return {
user,
status: PresenceStore.getStatus(user.id),
activity: PresenceStore.getActivity(user.id),
typing: TypingStore.isTyping(channel.id, channel.getRecipientId()),
};
});
const GroupDirectMessage = createPrivateChannel('GroupDirectMessage');
const DirectMessages = React.createClass({
mixins: [
Flux.StoreListenerMixin(DelayedSelectionStore, DimensionStore, ...PrivateChannelsMixin.getPrivateChannelsStores()),
PrivateChannelsMixin,
PureRenderMixin,
],
getInitialState() {
return {
initialized: false,
};
},
getStateFromStores() {
const {scrollTo, scrollTop} = DimensionStore.getGuildDimensions(ME);
return {
selectedChannelId: DelayedSelectionStore.getChannelId(ME),
scrollTo,
scrollTop,
...this.getPrivateChannelsState(),
};
},
getSubscriptions() {
return {
[ComponentActions.SCROLLTO_CHANNEL]: ({channel, guild}) => {
if (guild !== ME) {
return;
}
this.setState({
scrollTo: {guild, channel},
});
},
};
},
componentDidMount() {
this.calculateScrollState({});
},
componentDidUpdate(prevProps, prevState) {
this.calculateScrollState(prevState);
},
calculateScrollState(prevState) {
if (this.state.privateChannelIds.length && !this.state.initialized) {
if (this.state.scrollTo) {
this.scrollToChannel(this.state.scrollTo);
DimensionActionCreators.clearChannelListScrollTo(ME);
} else if (this.state.scrollTop || this.state.scrollTop === 0) {
this.refs[SCROLLER_REF].scrollTo(this.state.scrollTop);
} else if (this.state.selectedChannelId) {
this.scrollToChannel(this.state.selectedChannelId);
}
this.setState({initialized: true});
} else if (this.state.privateChannelIds.length) {
if (this.state.scrollTo) {
this.scrollToChannel(this.state.scrollTo);
DimensionActionCreators.clearChannelListScrollTo(ME);
} else if (this.state.selectedChannelId !== prevState.selectedChannelId) {
this.scrollToChannel(this.state.selectedChannelId);
}
}
},
scrollToChannel(channelId, animated) {
const channelIndex = this.state.privateChannelIds.indexOf(channelId);
let scrollTo = 0;
if (channelId !== undefined && channelIndex === -1) {
return;
} else if (channelIndex !== -1) {
scrollTo += TO_START_OF_DMS + DM_HEIGHT * channelIndex;
}
this.refs[SCROLLER_REF].scrollIntoViewRect(scrollTo, scrollTo + DM_HEIGHT, animated);
},
render() {
const selectedChannelId = this.state.selectedChannelId;
let children = [
<FriendsButton key="friends-button" selected={selectedChannelId == null} />,
<header key={i18n.Messages.DIRECT_MESSAGES}>{i18n.Messages.DIRECT_MESSAGES}</header>,
];
this.state.privateChannelIds.forEach(channelId => {
const channel = this.state.channels[channelId];
if (channel.type === ChannelTypes.GROUP_DM) {
children.push(
<GroupDirectMessage key={channel.id} selected={selectedChannelId === channel.id} channel={channel} />
);
} else {
children.push(<DirectMessage key={channel.id} selected={selectedChannelId === channel.id} channel={channel} />);
}
});
const scrollerProps = {
theme: LazyScroller.Themes.GHOST_HAIRLINE,
fade: true,
ref: SCROLLER_REF,
onScroll: lodash.throttle(this.handleScroll, 100),
};
let ScrollerComponent = Scroller;
if (children.length === 2) {
children.push(<NoPrivateChannels key="no-private-channels" />);
scrollerProps.style = {paddingTop: 20};
} else if (children.length > LAZY_RENDER_THRESHOLD) {
ScrollerComponent = LazyScroller;
scrollerProps.elementHeight = DM_HEIGHT;
scrollerProps.batchEndMultiplier = 1.3;
scrollerProps.track = false;
scrollerProps.backgroundColor = Colors.PRIMARY_630;
scrollerProps.paddingTop = 20;
scrollerProps.paddingBottom = 20;
} else {
scrollerProps.style = {paddingTop: 20};
children = <TransitionGroup transitionAppear={false}>{children}</TransitionGroup>;
}
return (
<ScrollerComponent {...scrollerProps}>
{children}
</ScrollerComponent>
);
},
handleScroll() {
DimensionActionCreators.updateChannelListScroll(ME, this.refs[SCROLLER_REF].getScrollerNode().scrollTop);
},
});
class PrivateChannels extends React.PureComponent {
handleQueryChange(query) {
quickSwitcherShow('DM_SEARCH');
quickSwitcherSearch(`@${query}`);
}
render() {
return (
<div className={classNames('private-channels')}>
<TutorialIndicator tutorialId="direct-messages" position={TutorialIndicator.RIGHT} offsetY={-18} offsetX={-52}>
<SearchBar onQueryChange={this.handleQueryChange} />
</TutorialIndicator>
<DirectMessages />
</div>
);
}
}
export default PrivateChannels;
// WEBPACK FOOTER //
// ./discord_app/components/PrivateChannels.js