249 lines
8 KiB
JavaScript
Executable file
249 lines
8 KiB
JavaScript
Executable file
import React from 'react';
|
|
import lodash from 'lodash';
|
|
import Flux from '../../lib/flux';
|
|
import i18n from '../../i18n';
|
|
import RegexUtils from '../../utils/RegexUtils';
|
|
|
|
import StreamerModeStore from '../../stores/StreamerModeStore';
|
|
import UserStore from '../../stores/UserStore';
|
|
import UserSettingsStore from '../../stores/UserSettingsStore';
|
|
import GuildSettingsStore from '../../stores/GuildSettingsStore';
|
|
|
|
import GuildSettingsActionCreators from '../../actions/GuildSettingsActionCreators';
|
|
import GuildActionCreators from '../../actions/GuildActionCreators';
|
|
import ModalActionCreators from '../../actions/ModalActionCreators';
|
|
|
|
import {LazyContentScroller} from '../common/StandardSidebarView';
|
|
import ContextMenu from '../common/ContextMenu';
|
|
import UserContextMenu from '../contextmenus/UserContextMenu';
|
|
import {Tags as FormTitleTags} from '../../uikit/form/FormTitle';
|
|
import Avatar from '../../uikit/Avatar';
|
|
import Button from '../../uikit/Button';
|
|
import FormText, {Types as FormTextTypes} from '../../uikit/form/FormText';
|
|
import EmptyState, {EmptyStateText, EmptyStateImage} from '../../uikit/EmptyState';
|
|
import Flex from '../../uikit/Flex';
|
|
import FormSection from '../../uikit/form/FormSection';
|
|
import FormDivider from '../../uikit/form/FormDivider';
|
|
import Modal from '../../uikit/Modal';
|
|
import SearchBar from '../../uikit/SearchBar';
|
|
import Spinner from '../../uikit/Spinner';
|
|
import Text from '../../uikit/Text';
|
|
|
|
import {GuildSettingsSections, ContextMenuTypes} from '../../Constants';
|
|
|
|
import './GuildSettingsBans.styl';
|
|
import '../../styles/shared/hover_card.styl';
|
|
|
|
class BannedUserModal extends React.PureComponent {
|
|
render() {
|
|
const {user, ban, theme, onRevoke, onClose} = this.props;
|
|
|
|
return (
|
|
<Modal className="guild-settings-banned-user-modal" theme={theme}>
|
|
<Modal.Content className="content user-select-text">
|
|
<div>
|
|
<Flex justify={Flex.Justify.CENTER}>
|
|
<Text color={Text.Colors.PRIMARY} className="user-username">{user.toString()}</Text>
|
|
<Text className="user-discrim">#{user.discriminator}</Text>
|
|
</Flex>
|
|
<Text className="reason-header margin-top-40" size={Text.Sizes.SMALL}>
|
|
{i18n.Messages.BAN_REASON}
|
|
</Text>
|
|
<Text className="reason">
|
|
{ban.reason || i18n.Messages.NO_BAN_REASON}
|
|
</Text>
|
|
</div>
|
|
</Modal.Content>
|
|
<Modal.Footer className="footer">
|
|
<Flex justify={Flex.Justify.BETWEEN}>
|
|
<Button className="revoke-btn" onClick={onRevoke} look={Button.Looks.LINK} color={Button.Colors.RED}>
|
|
{i18n.Messages.REVOKE_BAN}
|
|
</Button>
|
|
<Button onClick={onClose}>{i18n.Messages.DONE}</Button>
|
|
</Flex>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
);
|
|
}
|
|
}
|
|
|
|
const BannedUserModalConnected = Flux.connectStores([UserSettingsStore], () => ({
|
|
theme: UserSettingsStore.theme,
|
|
}))(BannedUserModal);
|
|
|
|
class BannedUser extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
(this: any).handleRevokeBan = this.handleRevokeBan.bind(this);
|
|
(this: any).handleShowModal = this.handleShowModal.bind(this);
|
|
(this: any).handleCloseModal = this.handleCloseModal.bind(this);
|
|
(this: any).handleContextMenu = this.handleContextMenu.bind(this);
|
|
}
|
|
|
|
render() {
|
|
const {user, hideDiscriminator} = this.props;
|
|
|
|
return (
|
|
<Flex
|
|
className="guild-settings-banned-user ui-hover-card"
|
|
align={Flex.Align.CENTER}
|
|
onClick={this.handleShowModal}
|
|
onContextMenu={this.handleContextMenu}>
|
|
<Avatar className="banned-user-avatar" src={user.getAvatarURL()} />
|
|
<Flex.Child basis={100} className="margin-reset">
|
|
<div className="username">
|
|
{user.toString()}
|
|
{!hideDiscriminator && <span className="discrim">#{user.discriminator}</span>}
|
|
</div>
|
|
</Flex.Child>
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
handleShowModal() {
|
|
const {user, hideDiscriminator, ban} = this.props;
|
|
ModalActionCreators.push(props =>
|
|
<BannedUserModalConnected
|
|
{...props}
|
|
ban={ban}
|
|
user={user}
|
|
hideDiscriminator={hideDiscriminator}
|
|
onRevoke={this.handleRevokeBan}
|
|
onClose={this.handleCloseModal}
|
|
/>
|
|
);
|
|
}
|
|
|
|
handleCloseModal() {
|
|
ModalActionCreators.pop();
|
|
}
|
|
|
|
handleRevokeBan() {
|
|
const {guildId, user} = this.props;
|
|
GuildActionCreators.unbanUser(guildId, user.id).then(ModalActionCreators.pop);
|
|
}
|
|
|
|
handleContextMenu(e: Event) {
|
|
ContextMenu.openContextMenu(e, props =>
|
|
<UserContextMenu {...props} type={ContextMenuTypes.USER_BANS} user={this.props.user} />
|
|
);
|
|
}
|
|
}
|
|
|
|
class GuildSettingsBans extends React.PureComponent {
|
|
makeFilter() {
|
|
const {searchQuery} = this.props;
|
|
if (searchQuery == null || searchQuery.length === 0) {
|
|
return () => true;
|
|
} else {
|
|
const regex = new RegExp(`^${RegexUtils.escape(searchQuery)}`, 'i');
|
|
return user => user != null && regex.test(user.username);
|
|
}
|
|
}
|
|
|
|
renderHeader() {
|
|
const {bans, theme, searchQuery} = this.props;
|
|
|
|
let emptyState;
|
|
if (bans == null) {
|
|
emptyState = null;
|
|
} else if (lodash.isEmpty(bans)) {
|
|
emptyState = (
|
|
<EmptyState theme={theme} className="margin-top-40">
|
|
<EmptyStateImage
|
|
darkSrc={require('../../images/empties/empty_server_settings_bans_dark.svg')}
|
|
lightSrc={require('../../images/empties/empty_server_settings_bans_light.svg')}
|
|
width={256}
|
|
height={212}
|
|
/>
|
|
<EmptyStateText note={i18n.Messages.BANS_NO_USERS_BANNED} style={{maxWidth: 300}}>
|
|
{i18n.Messages.NO_BANS}
|
|
</EmptyStateText>
|
|
</EmptyState>
|
|
);
|
|
}
|
|
const totalBans = lodash(bans || {}).keys().value().length;
|
|
return (
|
|
<FormSection tag={FormTitleTags.H2} title={i18n.Messages.BANS_HEADER.format({bans: totalBans})}>
|
|
<Flex justify={Flex.Justify.BETWEEN}>
|
|
<FormText type={FormTextTypes.DESCRIPTION} className="margin-bottom-20">
|
|
{i18n.Messages.BANS_HINT.format({onModerationClick: this.handleModerationClick})}
|
|
</FormText>
|
|
{emptyState == null &&
|
|
<SearchBar
|
|
query={searchQuery}
|
|
placeholder={i18n.Messages.BANS_SEARCH_PLACEHOLDER}
|
|
onChange={this.handleQueryChange}
|
|
onClear={this.handleQueryClear}
|
|
/>}
|
|
</Flex>
|
|
<FormDivider style={{marginBottom: -1}} />
|
|
{emptyState}
|
|
</FormSection>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const {bans, guildId, streamerMode, theme} = this.props;
|
|
|
|
let bansInner;
|
|
if (bans == null) {
|
|
bansInner = [<Spinner key="spinner" className="margin-top-20" type={Spinner.Type.SPINNING_CIRCLE} />];
|
|
} else {
|
|
bansInner = lodash(bans)
|
|
.keys()
|
|
.map(UserStore.getUser)
|
|
.filter(this.makeFilter())
|
|
.sortBy(user => user.username.toLowerCase())
|
|
.map(user =>
|
|
<BannedUser
|
|
user={user}
|
|
ban={bans[user.id]}
|
|
hideDiscriminator={streamerMode}
|
|
guildId={guildId}
|
|
key={user.id}
|
|
/>
|
|
)
|
|
.value();
|
|
}
|
|
|
|
return (
|
|
<LazyContentScroller theme={theme} header={this.renderHeader()} elementHeight={56}>
|
|
{bansInner}
|
|
</LazyContentScroller>
|
|
);
|
|
}
|
|
|
|
// Handlers
|
|
|
|
handleModerationClick() {
|
|
GuildSettingsActionCreators.setSection(GuildSettingsSections.MODERATION);
|
|
}
|
|
|
|
handleQueryChange(query) {
|
|
GuildSettingsActionCreators.setSearchQuery(query);
|
|
}
|
|
|
|
handleQueryClear() {
|
|
GuildSettingsActionCreators.setSearchQuery('');
|
|
}
|
|
}
|
|
|
|
const WrappedGuildSettingsBans = Flux.connectStores([GuildSettingsStore, UserSettingsStore], () => {
|
|
const {bans, guild, searchQuery} = GuildSettingsStore.getProps();
|
|
return {
|
|
searchQuery: searchQuery || '',
|
|
bans,
|
|
guildId: guild.id,
|
|
theme: UserSettingsStore.theme,
|
|
streamerMode: StreamerModeStore.enabled,
|
|
};
|
|
})(GuildSettingsBans);
|
|
|
|
export default WrappedGuildSettingsBans;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/guild_settings/GuildSettingsBans.js
|