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

560 lines
16 KiB
JavaScript
Executable file

/* @flow */
import React from 'react';
import Flux from '../../lib/flux';
import lodash from 'lodash';
import i18n from '../../i18n';
import classNames from 'classnames';
import Tooltip from '../common/Tooltip';
import GuildRole from '../common/GuildRole';
import PermissionUtils from '../../utils/PermissionUtils';
import HelpdeskUtils from '../../utils/HelpdeskUtils';
import GuildSettingsStore from '../../stores/GuildSettingsStore';
import UserStore from '../../stores/UserStore';
import UserSettingsStore from '../../stores/UserSettingsStore';
import GuildSettingsRolesStore from '../../stores/GuildSettingsRolesStore';
import GuildSettingsActionCreators from '../../actions/GuildSettingsActionCreators';
import GuildActionCreators from '../../actions/GuildActionCreators';
import AlertActionCreators from '../../actions/AlertActionCreators';
import {
init,
updateRoleSort,
updateRolePermissions,
toggleRoleSettings,
updateRoleName,
updateRoleColor,
saveRoleSettings,
} from '../../actions/GuildSettingsRolesActionCreators';
import Layout from '../../uikit/Layout';
import {makeDragItem, makeDragList} from '../../../discord_common/js/containers/makeDraggable';
import TabBar, {TabBarHeader} from '../../uikit/TabBar';
import ColorPicker from '../common/ColorPicker';
import FormSection from '../../uikit/form/FormSection';
import FormItem from '../../uikit/form/FormItem';
import FormDivider from '../../uikit/form/FormDivider';
import FormText, {Types} from '../../uikit/form/FormText';
import FormTitle, {Tags} from '../../uikit/form/FormTitle';
import TextInput from '../../uikit/TextInput';
import SwitchItem from '../../uikit/SwitchItem';
import SettingsNotice from '../common/SettingsNotice';
import PermissionsForm from '../common/PermissionsForm';
import Flex from '../../uikit/Flex';
import Button from '../../uikit/Button';
import Card, {Types as CardTypes} from '../../uikit/Card';
import NotAllowedIcon from '../../uikit/icons/NotAllowedIcon';
import type {GuildRole as GuildRoleType} from '../../flow/Server';
import {ROLE_COLORS, DEFAULT_ROLE_COLOR, FormStates, ThemeTypes} from '../../Constants';
import './GuildSettingsRoles.styl';
const DEFAULT_STYLE = {
paddingTop: 60,
paddingBottom: 80,
paddingLeft: 20,
paddingRight: 20,
color: '#fff',
};
function wrapTooltip(children, text) {
return (
<Tooltip type={Tooltip.ERROR} delay={500} position={Tooltip.TOP} text={text}>
<div className="not-allowed">{children}</div>
</Tooltip>
);
}
export const GuildSettingsRolesNotice = Flux.connectStores([GuildSettingsStore, GuildSettingsRolesStore], () => {
const {guild} = GuildSettingsStore.getProps();
const formState = GuildSettingsRolesStore.formState;
return {
guild,
submitting: formState === FormStates.SUBMITTING,
onReset() {
init();
},
onSave() {
const {id: guildId} = guild;
const roles = GuildSettingsRolesStore.editedRoleIds.map(id => GuildSettingsRolesStore.getRole(id));
const deltas = GuildSettingsRolesStore.getSortDeltas();
saveRoleSettings(guildId, roles, deltas);
},
};
})(SettingsNotice);
const GuildRoleDraggable = makeDragItem('ITEM')(GuildRole);
const TabBarDraggable = makeDragList('ITEM')(TabBar);
type SpecItem = {
title: string,
description?: string,
flag: number,
};
type Spec = {
title: string,
permissions: Array<SpecItem>,
};
class GuildRoleSettings extends React.PureComponent {
generalSpec: Spec;
textSpec: Spec;
voiceSpec: Spec;
constructor(props) {
super(props);
this.generalSpec = PermissionUtils.generateGuildGeneralPermissionSpec();
this.textSpec = PermissionUtils.generateGuildTextPermissionSpec();
this.voiceSpec = PermissionUtils.generateGuildVoicePermissionSpec();
lodash.bindAll(this, [
'handleNameChange',
'handleColorChange',
'handleDeleteRole',
'handlePermissionChange',
'handlePermissionRender',
]);
}
handleNameChange(name) {
const {id} = this.props.role;
updateRoleName(id, name);
}
handleColorChange(color) {
const {role} = this.props;
const {id} = role;
// No changes, do nothing
if (role.color === color || (color === DEFAULT_ROLE_COLOR && role.color === 0)) {
return;
}
// The way the backend understands default is by setting 0
if (color === DEFAULT_ROLE_COLOR) {
color = 0;
}
updateRoleColor(id, color);
}
handleSettingChange(key, {target: {checked: value}}) {
let {hoist, mentionable, id} = this.props.role;
if (key === 'hoist') {
hoist = value;
} else {
mentionable = value;
}
toggleRoleSettings(id, hoist, mentionable);
}
handlePermissionChange(flag, allow) {
const {id} = this.props.role;
updateRolePermissions(id, flag, allow);
}
handleDeleteRole() {
const {guild: {id: guildId}, role: {id: roleId, name}} = this.props;
AlertActionCreators.show({
title: i18n.Messages.SETTINGS_ROLES_DELETE_TITLE,
body: i18n.Messages.SETTINGS_ROLES_DELETE_BODY.format({name}),
cancelText: i18n.Messages.CANCEL,
onConfirm: () => GuildActionCreators.deleteRole(guildId, roleId),
iconUrl: require('../../images/icon-alert-alert.svg'),
});
}
renderName() {
const {role, locked, everyone} = this.props;
return (
<FormItem title={i18n.Messages.FORM_LABEL_ROLE_NAME} className="margin-bottom-20">
<TextInput
disabled={locked || everyone}
style={{color: role.colorString}}
value={role.name}
onChange={this.handleNameChange}
/>
</FormItem>
);
}
renderColorPicker() {
const {role, locked, everyone} = this.props;
let colorPicker = (
<ColorPicker
defaultColor={DEFAULT_ROLE_COLOR}
colors={ROLE_COLORS}
value={role.color}
disabled={locked || everyone}
onChange={this.handleColorChange}
/>
);
if (locked || everyone) {
colorPicker = wrapTooltip(colorPicker, i18n.Messages.FORM_LABEL_DISABLED_FOR_EVERYONE);
}
return (
<FormItem title={i18n.Messages.FORM_LABEL_ROLE_COLOR} className="margin-bottom-40">
{colorPicker}
</FormItem>
);
}
renderDisabledIndicator(disabledMessage: string) {
return (
<Tooltip text={disabledMessage} position={Tooltip.TOP} type={Tooltip.ERROR}>
<span>
<NotAllowedIcon className="not-allowed-icon" width={18} height={18} />
</span>
</Tooltip>
);
}
renderRoleSettings() {
const {role, locked} = this.props;
const disabledMessage = this.handlePermissionRender();
const disabled = !!(locked || disabledMessage);
const disabledIcon = typeof disabledMessage === 'string' ? this.renderDisabledIndicator(disabledMessage) : null;
const className = classNames({'role-permission-title-disabled': disabledIcon != null});
return (
<FormSection className="margin-bottom-40">
<FormTitle tag={Tags.H5} className="margin-bottom-20">
{i18n.Messages.FORM_LABEL_ROLE_SETTINGS}
</FormTitle>
<SwitchItem
className={classNames('permission-item', 'margin-bottom-20', {disabled})}
disabled={disabled}
onChange={this.handleSettingChange.bind(this, 'hoist')}
value={role.hoist}>
{disabledIcon}
<span className={className}>
{i18n.Messages.FORM_LABEL_HOIST_DESCRIPTION}
</span>
</SwitchItem>
<SwitchItem
disabled={disabled}
onChange={this.handleSettingChange.bind(this, 'mentionable')}
value={role.mentionable}>
{disabledIcon}
<span className={className}>
{i18n.Messages.FORM_LABEL_MENTIONABLE.format()}
</span>
</SwitchItem>
</FormSection>
);
}
renderDeleteButton() {
const {locked, role, everyone} = this.props;
if (locked || role.managed || everyone) {
return null;
}
return (
<Button look={Button.Looks.OUTLINED} color={Button.Colors.RED} onClick={this.handleDeleteRole}>
{i18n.Messages.DELETE} {role.name}
</Button>
);
}
renderManagedNote() {
const {role} = this.props;
if (role.managed) {
return (
<Card type={CardTypes.WARNING} className="padded margin-bottom-20">
{i18n.Messages.MANAGED_ROLE_EXPLAINATION}
</Card>
);
}
}
handlePermissionRender(flag) {
const {locked, guild, currentUser, everyone, role} = this.props;
const highestRole = PermissionUtils.getHighestRole(guild, currentUser.id);
const isMineLocked = locked && role === highestRole;
if (isMineLocked) {
return i18n.Messages.HELP_ROLE_LOCKED_MINE;
} else if (locked) {
return i18n.Messages.HELP_ROLE_LOCKED;
} else if (flag == null && everyone) {
return i18n.Messages.FORM_LABEL_DISABLED_FOR_EVERYONE;
} else if (flag != null && !PermissionUtils.can(flag, currentUser, guild)) {
return i18n.Messages.HELP_MISSING_PERMISSION;
} else if (
flag != null &&
!PermissionUtils.can(flag, currentUser, guild, null, {
[role.id]: {...role, permissions: role.permissions ^ (role.permissions & flag)},
})
) {
// eslint-disable-line max-len
return i18n.Messages.HELP_SINGULAR_PERMISSION;
} else {
return false;
}
}
render() {
const {locked, role: {permissions}} = this.props;
return (
<FormSection>
{this.renderManagedNote()}
{this.renderName()}
{this.renderColorPicker()}
<FormDivider className="margin-bottom-40" />
{this.renderRoleSettings()}
<PermissionsForm
spec={this.generalSpec}
permissions={permissions}
locked={locked}
onChange={this.handlePermissionChange}
className="margin-bottom-40"
permissionRender={this.handlePermissionRender}
/>
<PermissionsForm
spec={this.textSpec}
permissions={permissions}
locked={locked}
onChange={this.handlePermissionChange}
className="margin-bottom-40"
permissionRender={this.handlePermissionRender}
/>
<PermissionsForm
spec={this.voiceSpec}
permissions={permissions}
locked={locked}
onChange={this.handlePermissionChange}
permissionRender={this.handlePermissionRender}
/>
{this.renderDeleteButton()}
</FormSection>
);
}
}
type State = {
dragging: ?string,
roles: ?Array<GuildRoleType>,
};
class GuildSettingsRoles extends React.PureComponent {
state: State;
constructor(props) {
super(props);
lodash.bindAll(this, [
'handleSelectRole',
'handleDragStart',
'handleDrop',
'handleHover',
'handleCancel',
'handleDragLeave',
'handleCreateRole',
]);
// NOTE: Internal state is ONLY used during a drag operation
this.state = {
dragging: null,
roles: null,
};
}
getSelectedRole() {
return GuildSettingsRolesStore.getRole(this.props.selectedRoleId);
}
handleCreateRole() {
GuildActionCreators.createRole(this.props.guild.id);
}
handleSelectRole(roleId) {
GuildSettingsActionCreators.selectRole(roleId);
}
handleDragStart(dragging) {
const {roles} = this.props;
this.setState({
dragging,
roles: [...roles],
});
}
handleDragLeave() {
const {roles} = this.props;
this.setState({roles: [...roles]});
}
handleDrop() {
if (this.state.roles == null) {
return;
}
updateRoleSort(this.state.roles.map(({id}) => id));
this.setState({dragging: null, roles: null});
}
handleHover(selectedId: string, draggedId: string) {
if (this.state.roles == null) {
return;
}
const roles = [...this.state.roles];
const dragged = roles.find(({id}) => draggedId === id);
const selected = roles.find(({id}) => selectedId === id);
if (dragged == null || selected == null) {
return;
}
const draggedIndex = roles.indexOf(dragged);
let selectedIndex = roles.indexOf(selected);
const direction = selectedIndex - draggedIndex > 0 ? 1 : 0;
roles.splice(draggedIndex, 1);
selectedIndex = roles.indexOf(selected);
roles.splice(selectedIndex + direction, 0, dragged);
this.setState({roles});
}
handleCancel() {
this.setState({dragging: null, roles: null});
}
renderRoleList() {
const {currentUser, guild, selectedRoleId, theme} = this.props;
let {roles} = this.props;
const highestRole = PermissionUtils.getHighestRole(guild, currentUser.id);
// If we are dragging, then we should use the internal state
if (this.state.roles) {
roles = this.state.roles;
}
return roles.map(role => {
const locked = !PermissionUtils.isRoleHigher(guild, currentUser.id, highestRole, role);
const isMineLocked = locked && role === highestRole;
const {id, colorString, name} = role;
let lockTooltip;
if (isMineLocked) {
lockTooltip = i18n.Messages.HELP_ROLE_LOCKED_MINE;
} else if (locked) {
lockTooltip = i18n.Messages.HELP_ROLE_LOCKED;
}
// Locked and @everyone is not draggable
if (locked || isMineLocked || id === guild.id) {
return (
<GuildRole
key={`${selectedRoleId}-${id}`}
theme={theme}
id={id}
locked={locked}
lockTooltip={lockTooltip}
color={colorString}>
{name}
</GuildRole>
);
}
return (
<GuildRoleDraggable
key={`${selectedRoleId}-${id}`}
theme={theme}
dragId={id}
id={id}
locked={locked}
lockTooltip={lockTooltip}
color={colorString}>
{name}
</GuildRoleDraggable>
);
});
}
renderRoleInfo() {
return (
<div>
<div className="ui-tab-bar-separator margin-top-20" style={{marginBottom: 14}} />
<FormText type={Types.DESCRIPTION} className="margin-bottom-20">
{i18n.Messages.FORM_LABEL_ROLES_PRO_TIP_DESCRIPTION}
</FormText>
<FormText type={Types.DESCRIPTION}>
<a href={HelpdeskUtils.getArticleURL(206029707)} target="_blank">
{i18n.Messages.PERMISSION_HELPDESK}
</a>
</FormText>
</div>
);
}
renderRoles() {
const {selectedRoleId, theme} = this.props;
const {dragging} = this.state;
const iconSrc = this.props.theme === ThemeTypes.DARK
? require('../../images/ic_roles_add_primary_400_16px.svg')
: require('../../images/ic_roles_add_primary_300_16px.svg');
return (
<Layout.Sidebar style={{...DEFAULT_STYLE, overflow: 'hidden'}} scrollable theme={theme}>
<TabBarDraggable
onItemSelect={this.handleSelectRole}
selectedItem={dragging || selectedRoleId}
onDragStart={this.handleDragStart}
onHover={this.handleHover}
onDrop={this.handleDrop}
onCancel={this.handleCancel}
onDragLeave={this.handleDragLeave}>
<TabBarHeader>
<Flex className="sidebar-header" onClick={this.handleCreateRole} justify={Flex.Justify.BETWEEN}>
{i18n.Messages.ROLES}
<img className="add-role-icon" src={iconSrc} />
</Flex>
</TabBarHeader>
{this.renderRoleList()}
{this.renderRoleInfo()}
</TabBarDraggable>
</Layout.Sidebar>
);
}
renderRoleSettings() {
const {guild, currentUser} = this.props;
const role = this.getSelectedRole();
const highestRole = PermissionUtils.getHighestRole(guild, currentUser.id);
const locked = !PermissionUtils.isRoleHigher(guild, currentUser.id, highestRole, role);
const everyone = role != null && role.id === guild.id;
return (
<Layout.Content style={{...DEFAULT_STYLE, paddingRight: 0}} grow={0}>
<GuildRoleSettings guild={guild} role={role} locked={locked} everyone={everyone} currentUser={currentUser} />
</Layout.Content>
);
}
render() {
return (
<Layout className="guild-settings-roles">
{this.renderRoles()}
{this.renderRoleSettings()}
</Layout>
);
}
}
export default Flux.connectStores([GuildSettingsStore, UserStore, UserSettingsStore, GuildSettingsRolesStore], () => {
const {guild, selectedRoleId} = GuildSettingsStore.getProps();
const currentUser = UserStore.getCurrentUser();
const roles = GuildSettingsRolesStore.roles;
return {
currentUser,
guild,
selectedRoleId,
roles,
theme: UserSettingsStore.theme,
};
})(GuildSettingsRoles);
// WEBPACK FOOTER //
// ./discord_app/components/guild_settings/GuildSettingsRoles.js