Add files
This commit is contained in:
commit
bb80829159
18195 changed files with 2122994 additions and 0 deletions
678
509bba0_unpacked/discord_app/uikit/AuditLog.js
Executable file
678
509bba0_unpacked/discord_app/uikit/AuditLog.js
Executable file
|
@ -0,0 +1,678 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
import Avatar from './Avatar';
|
||||
import ExpandIcon from './icons/ExpandIcon';
|
||||
import Flex from './Flex';
|
||||
import type UserRecord from '../records/UserRecord';
|
||||
import i18n from '../i18n';
|
||||
import {
|
||||
AuditLogTargetTypes,
|
||||
AuditLogChangeKeys,
|
||||
AuditLogActionTypes,
|
||||
AuditLogActions,
|
||||
MFALevels,
|
||||
VerificationLevels,
|
||||
UserNotificationSettings,
|
||||
ChannelTypes,
|
||||
GuildExplicitContentFilterTypes,
|
||||
Permissions,
|
||||
} from '../Constants';
|
||||
import './AuditLog.styl';
|
||||
import '../styles/round_remove_button.styl';
|
||||
|
||||
type Change = {
|
||||
key: string,
|
||||
oldValue: ?any,
|
||||
newValue: any,
|
||||
};
|
||||
|
||||
type AuditLogType = {
|
||||
id: string,
|
||||
actionType: string,
|
||||
action: number,
|
||||
targetType: string,
|
||||
targetId: ?string,
|
||||
target: any,
|
||||
userId: string,
|
||||
user: UserRecord,
|
||||
changes: ?Array<Change>,
|
||||
timestampStart: moment,
|
||||
timestampEnd: moment,
|
||||
options: any,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
log: AuditLogType,
|
||||
className: ?string,
|
||||
expanded: boolean,
|
||||
onHeaderClick: Function,
|
||||
onContentClick: Function,
|
||||
onUserContextMenu: Function,
|
||||
onChannelContextMenu: Function,
|
||||
onTargetContextMenu: Function,
|
||||
};
|
||||
|
||||
const DEFAULT_FOR_STRINGS_KEY = -1;
|
||||
|
||||
function getNullableOldValueString(hasNoOldValue, hasOldValue) {
|
||||
return change => (change.oldValue == null ? hasNoOldValue : hasOldValue);
|
||||
}
|
||||
|
||||
function getNullableNewValueString(hasNoNewValue, hasNewValue) {
|
||||
return change => (change.newValue == null ? hasNoNewValue : hasNewValue);
|
||||
}
|
||||
|
||||
function getNullableNewOrOldValueString(hasBoth, hasNoOldValue, hasNoNewValue, hasNeither) {
|
||||
return change => {
|
||||
if (change.newValue != null && change.oldValue != null) {
|
||||
return hasBoth;
|
||||
} else if (change.newValue != null) {
|
||||
return hasNoOldValue;
|
||||
} else if (change.oldValue != null) {
|
||||
return hasNoNewValue;
|
||||
}
|
||||
return hasNeither;
|
||||
};
|
||||
}
|
||||
|
||||
const GuildChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.NAME]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_NAME_CHANGE,
|
||||
[AuditLogChangeKeys.ICON_HASH]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_ICON_HASH_CHANGE,
|
||||
[AuditLogChangeKeys.SPLASH_HASH]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_SPLASH_HASH_CHANGE,
|
||||
[AuditLogChangeKeys.OWNER_ID]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_OWNER_ID_CHANGE,
|
||||
[AuditLogChangeKeys.REGION]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_REGION_CHANGE,
|
||||
[AuditLogChangeKeys.AFK_CHANNEL_ID]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_AFK_CHANNEL_ID_CHANGE,
|
||||
[AuditLogChangeKeys.AFK_TIMEOUT]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_AFK_TIMEOUT_CHANGE,
|
||||
[AuditLogChangeKeys.MFA_LEVEL]: {
|
||||
[MFALevels.NONE]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_MFA_LEVEL_DISABLED,
|
||||
[MFALevels.ELEVATED]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_MFA_LEVEL_ENABLED,
|
||||
},
|
||||
[AuditLogChangeKeys.WIDGET_ENABLED]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_WIDGET_ENABLED,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_WIDGET_DISABLED,
|
||||
},
|
||||
[AuditLogChangeKeys.WIDGET_CHANNEL_ID]: getNullableNewValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_WIDGET_CHANNEL_ID_DELETE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_WIDGET_CHANNEL_ID_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.VERIFICATION_LEVEL]: {
|
||||
[VerificationLevels.NONE]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VERIFICATION_LEVEL_CHANGE_NONE,
|
||||
[VerificationLevels.LOW]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VERIFICATION_LEVEL_CHANGE_LOW,
|
||||
[VerificationLevels.MEDIUM]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VERIFICATION_LEVEL_CHANGE_MEDIUM,
|
||||
[VerificationLevels.HIGH]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VERIFICATION_LEVEL_CHANGE_HIGH,
|
||||
},
|
||||
[AuditLogChangeKeys.DEFAULT_MESSAGE_NOTIFICATIONS]: {
|
||||
[UserNotificationSettings.ALL_MESSAGES]:
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_DEFAULT_MESSAGE_NOTIFICATIONS_CHANGE_ALL_MESSAGES,
|
||||
[UserNotificationSettings.ONLY_MENTIONS]:
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_DEFAULT_MESSAGE_NOTIFICATIONS_CHANGE_ONLY_MENTIONS,
|
||||
},
|
||||
[AuditLogChangeKeys.VANITY_URL_CODE]: getNullableNewValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VANITY_URL_CODE_DELETE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_VANITY_URL_CODE_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.EXPLICIT_CONTENT_FILTER]: {
|
||||
[GuildExplicitContentFilterTypes.DISABLED]:
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_EXPLICIT_CONTENT_FILTER_DISABLE,
|
||||
[GuildExplicitContentFilterTypes.MEMBERS_WITHOUT_ROLES]:
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_EXPLICIT_CONTENT_FILTER_MEMBERS_WITHOUT_ROLES,
|
||||
[GuildExplicitContentFilterTypes.ALL_MEMBERS]:
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_EXPLICIT_CONTENT_FILTER_ALL_MEMBERS,
|
||||
},
|
||||
});
|
||||
|
||||
const ChannelChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.NAME]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_NAME_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_NAME_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.POSITION]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_POSITION_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_POSITION_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.TOPIC]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_TOPIC_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_TOPIC_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.BITRATE]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_BITRATE_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_BITRATE_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.PERMISSIONS_GRANTED]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_PERMISSION_OVERRIDES_GRANTED,
|
||||
[AuditLogChangeKeys.PERMISSIONS_DENIED]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_PERMISSION_OVERRIDES_DENIED,
|
||||
});
|
||||
|
||||
const UserChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.NICK]: getNullableNewOrOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_NICK_CHANGE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_NICK_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_NICK_DELETE
|
||||
),
|
||||
[AuditLogChangeKeys.DEAF]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_DEAF_ON,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_DEAF_OFF,
|
||||
},
|
||||
[AuditLogChangeKeys.MUTE]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_MUTE_ON,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_MUTE_OFF,
|
||||
},
|
||||
[AuditLogChangeKeys.ROLES_REMOVE]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_ROLES_REMOVE,
|
||||
[AuditLogChangeKeys.ROLES_ADD]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_ROLES_ADD,
|
||||
[AuditLogChangeKeys.REASON]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_REASON,
|
||||
[AuditLogChangeKeys.PRUNE_DELETE_DAYS]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_PRUNE_DELETE_DAYS,
|
||||
});
|
||||
|
||||
const RoleChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.NAME]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_NAME_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_NAME_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.PERMISSIONS_GRANTED]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_PERMISSIONS_GRANTED,
|
||||
[AuditLogChangeKeys.PERMISSIONS_DENIED]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_PERMISSIONS_DENIED,
|
||||
[AuditLogChangeKeys.COLOR]: {
|
||||
'#000000': i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_COLOR_NONE,
|
||||
[DEFAULT_FOR_STRINGS_KEY]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_COLOR,
|
||||
},
|
||||
[AuditLogChangeKeys.HOIST]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_HOIST_ON,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_HOIST_OFF,
|
||||
},
|
||||
[AuditLogChangeKeys.MENTIONABLE]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_MENTIONABLE_ON,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_MENTIONABLE_OFF,
|
||||
},
|
||||
});
|
||||
|
||||
const InviteChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.CODE]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_CODE_CREATE,
|
||||
[AuditLogChangeKeys.CHANNEL_ID]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_CHANNEL_CREATE,
|
||||
[AuditLogChangeKeys.MAX_USES]: {
|
||||
[0]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_MAX_USES_CREATE_INFINITE,
|
||||
[DEFAULT_FOR_STRINGS_KEY]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_MAX_USES_CREATE,
|
||||
},
|
||||
[AuditLogChangeKeys.MAX_AGE]: {
|
||||
[0]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_MAX_AGE_CREATE_INFINITE,
|
||||
[DEFAULT_FOR_STRINGS_KEY]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_MAX_AGE_CREATE,
|
||||
},
|
||||
[AuditLogChangeKeys.TEMPORARY]: {
|
||||
true: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_TEMPORARY_ON,
|
||||
false: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_TEMPORARY_OFF,
|
||||
},
|
||||
});
|
||||
|
||||
const WebhookChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.CHANNEL_ID]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_CHANNEL_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_CHANNEL_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.NAME]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_NAME_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_NAME_CHANGE
|
||||
),
|
||||
[AuditLogChangeKeys.AVATAR_HASH]: i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_AVATAR,
|
||||
});
|
||||
|
||||
const EmojiChangeStrings = () => ({
|
||||
[AuditLogChangeKeys.NAME]: getNullableOldValueString(
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_EMOJI_NAME_CREATE,
|
||||
i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_EMOJI_NAME_CHANGE
|
||||
),
|
||||
});
|
||||
|
||||
const NotRenderedChanges = {
|
||||
[AuditLogTargetTypes.CHANNEL]: {
|
||||
[AuditLogChangeKeys.TYPE]: true,
|
||||
[AuditLogChangeKeys.ID]: true,
|
||||
},
|
||||
[AuditLogTargetTypes.INVITE]: {
|
||||
[AuditLogChangeKeys.INVITER_ID]: true,
|
||||
[AuditLogChangeKeys.USES]: true,
|
||||
},
|
||||
[AuditLogTargetTypes.WEBHOOK]: {
|
||||
[AuditLogChangeKeys.TYPE]: true,
|
||||
[AuditLogChangeKeys.APPLICATION_ID]: true,
|
||||
},
|
||||
};
|
||||
|
||||
function shouldNotRenderChangeDetail(log: AuditLogType, change: Change): boolean {
|
||||
const notRenderedTarget = NotRenderedChanges[log.targetType];
|
||||
return notRenderedTarget != null && notRenderedTarget[change.key] === true;
|
||||
}
|
||||
|
||||
function getChangeTitle(log) {
|
||||
switch (log.action) {
|
||||
case AuditLogActions.GUILD_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_GUILD_UPDATE;
|
||||
|
||||
case AuditLogActions.CHANNEL_CREATE:
|
||||
const typeChange = log.changes ? log.changes.find(change => change.key === AuditLogChangeKeys.TYPE) : null;
|
||||
if (typeChange == null) {
|
||||
throw new Error('[AuditLog] Could not find type change for channel create');
|
||||
}
|
||||
switch (typeChange.newValue) {
|
||||
case ChannelTypes.GUILD_VOICE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_VOICE_CREATE;
|
||||
default:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_TEXT_CREATE;
|
||||
}
|
||||
case AuditLogActions.CHANNEL_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_UPDATE;
|
||||
case AuditLogActions.CHANNEL_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_DELETE;
|
||||
case AuditLogActions.CHANNEL_OVERWRITE_CREATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_OVERWRITE_CREATE;
|
||||
case AuditLogActions.CHANNEL_OVERWRITE_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_OVERWRITE_UPDATE;
|
||||
case AuditLogActions.CHANNEL_OVERWRITE_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_CHANNEL_OVERWRITE_DELETE;
|
||||
|
||||
case AuditLogActions.MEMBER_KICK:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_KICK;
|
||||
case AuditLogActions.MEMBER_PRUNE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_PRUNE;
|
||||
case AuditLogActions.MEMBER_BAN_ADD:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_BAN_ADD;
|
||||
case AuditLogActions.MEMBER_BAN_REMOVE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_BAN_REMOVE;
|
||||
case AuditLogActions.MEMBER_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_UPDATE;
|
||||
case AuditLogActions.MEMBER_ROLE_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MEMBER_ROLE_UPDATE;
|
||||
|
||||
case AuditLogActions.ROLE_CREATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_CREATE;
|
||||
case AuditLogActions.ROLE_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_UPDATE;
|
||||
case AuditLogActions.ROLE_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_ROLE_DELETE;
|
||||
|
||||
case AuditLogActions.INVITE_CREATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_CREATE;
|
||||
case AuditLogActions.INVITE_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_UPDATE;
|
||||
case AuditLogActions.INVITE_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_INVITE_DELETE;
|
||||
|
||||
case AuditLogActions.WEBHOOK_CREATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_CREATE;
|
||||
case AuditLogActions.WEBHOOK_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_UPDATE;
|
||||
case AuditLogActions.WEBHOOK_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_WEBHOOK_DELETE;
|
||||
|
||||
case AuditLogActions.EMOJI_CREATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_EMOJI_CREATE;
|
||||
case AuditLogActions.EMOJI_UPDATE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_EMOJI_UPDATE;
|
||||
case AuditLogActions.EMOJI_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_EMOJI_DELETE;
|
||||
|
||||
case AuditLogActions.MESSAGE_DELETE:
|
||||
return i18n.Messages.GUILD_SETTINGS_AUDIT_LOG_MESSAGE_DELETE;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getStringForPermission(permission: number, log): ?string {
|
||||
switch (permission) {
|
||||
case Permissions.CREATE_INSTANT_INVITE:
|
||||
return i18n.Messages.CREATE_INSTANT_INVITE;
|
||||
case Permissions.KICK_MEMBERS:
|
||||
return i18n.Messages.KICK_MEMBERS;
|
||||
case Permissions.BAN_MEMBERS:
|
||||
return i18n.Messages.BAN_MEMBERS;
|
||||
case Permissions.ADMINISTRATOR:
|
||||
return i18n.Messages.ADMINISTRATOR;
|
||||
case Permissions.MANAGE_CHANNELS: {
|
||||
if (log.targetType === AuditLogTargetTypes.CHANNEL) {
|
||||
return i18n.Messages.MANAGE_CHANNEL;
|
||||
}
|
||||
|
||||
return i18n.Messages.MANAGE_CHANNELS;
|
||||
}
|
||||
case Permissions.MANAGE_GUILD:
|
||||
return i18n.Messages.MANAGE_SERVER;
|
||||
case Permissions.CHANGE_NICKNAME:
|
||||
return i18n.Messages.CHANGE_NICKNAME;
|
||||
case Permissions.MANAGE_NICKNAMES:
|
||||
return i18n.Messages.MANAGE_NICKNAMES;
|
||||
case Permissions.MANAGE_ROLES:
|
||||
return i18n.Messages.MANAGE_ROLES;
|
||||
case Permissions.MANAGE_WEBHOOKS:
|
||||
return i18n.Messages.MANAGE_WEBHOOKS;
|
||||
case Permissions.MANAGE_EMOJIS:
|
||||
return i18n.Messages.MANAGE_EMOJIS;
|
||||
case Permissions.VIEW_AUDIT_LOG:
|
||||
return i18n.Messages.VIEW_AUDIT_LOG;
|
||||
case Permissions.READ_MESSAGES:
|
||||
return i18n.Messages.READ_MESSAGES;
|
||||
case Permissions.SEND_MESSAGES:
|
||||
return i18n.Messages.SEND_MESSAGES;
|
||||
case Permissions.SEND_TSS_MESSAGES:
|
||||
return i18n.Messages.SEND_TTS_MESSAGES;
|
||||
case Permissions.MANAGE_MESSAGES:
|
||||
return i18n.Messages.MANAGE_MESSAGES;
|
||||
case Permissions.EMBED_LINKS:
|
||||
return i18n.Messages.EMBED_LINKS;
|
||||
case Permissions.ATTACH_FILES:
|
||||
return i18n.Messages.ATTACH_FILES;
|
||||
case Permissions.READ_MESSAGE_HISTORY:
|
||||
return i18n.Messages.READ_MESSAGE_HISTORY;
|
||||
case Permissions.MENTION_EVERYONE:
|
||||
return i18n.Messages.MENTION_EVERYONE;
|
||||
case Permissions.USE_EXTERNAL_EMOJIS:
|
||||
return i18n.Messages.USE_EXTERNAL_EMOJIS;
|
||||
case Permissions.ADD_REACTIONS:
|
||||
return i18n.Messages.ADD_REACTIONS;
|
||||
case Permissions.CONNECT:
|
||||
return i18n.Messages.CONNECT;
|
||||
case Permissions.SPEAK:
|
||||
return i18n.Messages.SPEAK;
|
||||
case Permissions.MUTE_MEMBERS:
|
||||
return i18n.Messages.MUTE_MEMBERS;
|
||||
case Permissions.DEAFEN_MEMBERS:
|
||||
return i18n.Messages.DEAFEN_MEMBERS;
|
||||
case Permissions.MOVE_MEMBERS:
|
||||
return i18n.Messages.MOVE_MEMBERS;
|
||||
case Permissions.USE_VAD:
|
||||
return i18n.Messages.USE_VAD;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTargetClass(targetType: string, action: number) {
|
||||
if (action === AuditLogActions.MESSAGE_DELETE) {
|
||||
return 'target-message';
|
||||
}
|
||||
return {
|
||||
[AuditLogTargetTypes.ALL]: 'target-all',
|
||||
[AuditLogTargetTypes.GUILD]: 'target-guild',
|
||||
[AuditLogTargetTypes.CHANNEL]: 'target-channel',
|
||||
[AuditLogTargetTypes.USER]: 'target-member',
|
||||
[AuditLogTargetTypes.ROLE]: 'target-role',
|
||||
[AuditLogTargetTypes.INVITE]: 'target-invite',
|
||||
[AuditLogTargetTypes.WEBHOOK]: 'target-webhook',
|
||||
[AuditLogTargetTypes.EMOJI]: 'target-emoji',
|
||||
}[targetType];
|
||||
}
|
||||
|
||||
const TYPE_CLASSES = {
|
||||
[AuditLogActionTypes.CREATE]: 'type-create',
|
||||
[AuditLogActionTypes.UPDATE]: 'type-update',
|
||||
[AuditLogActionTypes.DELETE]: 'type-delete',
|
||||
};
|
||||
|
||||
export class AuditLogIcon extends React.PureComponent {
|
||||
props: {
|
||||
actionType: string,
|
||||
targetType: string,
|
||||
themeOverride?: ?string,
|
||||
action: number,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {actionType, targetType, action, themeOverride} = this.props;
|
||||
const themeOverrideClass = themeOverride != null ? `theme-override-${themeOverride}` : null;
|
||||
const classes = classNames(
|
||||
'ui-audit-log-icon',
|
||||
TYPE_CLASSES[actionType],
|
||||
getTargetClass(targetType, action),
|
||||
themeOverrideClass
|
||||
);
|
||||
return <div className={classes} />;
|
||||
}
|
||||
}
|
||||
|
||||
class UserHook extends React.PureComponent {
|
||||
render() {
|
||||
const {user, onContextMenu} = this.props;
|
||||
return (
|
||||
<span onContextMenu={onContextMenu} className="user-hook">
|
||||
<span className="username">{user.username}</span>
|
||||
<span className="discrim">#{user.discriminator}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AuditLog extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
hasChangesToRender(): boolean {
|
||||
const {log} = this.props;
|
||||
const {changes} = log;
|
||||
|
||||
if (
|
||||
log.actionType === AuditLogActionTypes.DELETE &&
|
||||
log.action !== AuditLogActions.MEMBER_BAN_ADD &&
|
||||
log.action !== AuditLogActions.MEMBER_KICK &&
|
||||
log.action !== AuditLogActions.MEMBER_PRUNE
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return changes != null && changes.some(change => !shouldNotRenderChangeDetail(log, change));
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
const {log, onUserContextMenu, onTargetContextMenu, onChannelContextMenu} = this.props;
|
||||
const {user, target, options} = log;
|
||||
const title = getChangeTitle(log);
|
||||
return title != null
|
||||
? <div className="overflow-ellipsis">
|
||||
{title.format({
|
||||
user,
|
||||
target,
|
||||
userHook: (text, key) => {
|
||||
return <UserHook user={log.user} onContextMenu={onUserContextMenu} key={key} />;
|
||||
},
|
||||
targetHook: (text, key) => {
|
||||
if (log.targetType === AuditLogTargetTypes.USER) {
|
||||
return <UserHook user={log.target} onContextMenu={onTargetContextMenu} key={key} />;
|
||||
}
|
||||
|
||||
return <span onContextMenu={onTargetContextMenu} className="target-hook" key={key}>{text}</span>;
|
||||
},
|
||||
count: options.count,
|
||||
channel: options.channel ? options.channel.toString(true) : null,
|
||||
channelHook: (text, key) => {
|
||||
return <span key={key} onContextMenu={onChannelContextMenu} className="channel-hook">{text}</span>;
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
: null;
|
||||
}
|
||||
|
||||
renderRoleUpdate({newValue}: {newValue: Array<any>}) {
|
||||
if (Array.isArray(newValue)) {
|
||||
return newValue.map(role => <div className="sub-list-item" key={role.id}>{role.name}</div>);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderPermissionUpdate({newValue}: {newValue: Array<any>}) {
|
||||
if (Array.isArray(newValue)) {
|
||||
return newValue.map(permission =>
|
||||
<div className="sub-list-item" key={permission}>{getStringForPermission(permission, this.props.log)}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderChangeDetails(strings: {[key: string]: any}) {
|
||||
const {log, onContentClick} = this.props;
|
||||
if (log.changes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let changeNumber = 0;
|
||||
const changes = log.changes.map((change, i) => {
|
||||
const {oldValue, newValue} = change;
|
||||
|
||||
let subChanges = null;
|
||||
if (log.action === AuditLogActions.MEMBER_ROLE_UPDATE) {
|
||||
subChanges = this.renderRoleUpdate(change);
|
||||
} else if (
|
||||
log.targetType === AuditLogTargetTypes.ROLE ||
|
||||
log.action === AuditLogActions.CHANNEL_OVERWRITE_CREATE ||
|
||||
log.action === AuditLogActions.CHANNEL_OVERWRITE_UPDATE
|
||||
) {
|
||||
subChanges = this.renderPermissionUpdate(change);
|
||||
}
|
||||
|
||||
let changeStr = strings[change.key];
|
||||
|
||||
if (shouldNotRenderChangeDetail(log, change)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof changeStr === 'function') {
|
||||
changeStr = changeStr(change);
|
||||
}
|
||||
|
||||
if (changeStr != null && typeof changeStr === 'object') {
|
||||
if (changeStr.format == null) {
|
||||
const maybeChangeStr = changeStr[change.newValue];
|
||||
|
||||
if (maybeChangeStr == null && changeStr[DEFAULT_FOR_STRINGS_KEY] != null) {
|
||||
changeStr = changeStr[DEFAULT_FOR_STRINGS_KEY];
|
||||
} else if (maybeChangeStr != null) {
|
||||
changeStr = maybeChangeStr;
|
||||
}
|
||||
}
|
||||
|
||||
if (changeStr != null && changeStr.format != null) {
|
||||
changeStr = changeStr.format({
|
||||
user: log.user,
|
||||
target: log.target,
|
||||
oldValue,
|
||||
newValue,
|
||||
count: Array.isArray(newValue) ? newValue.length : null,
|
||||
subtarget: log.options.subtarget ? log.options.subtarget : null,
|
||||
newColorHook: (_, key) => {
|
||||
return <div className="color-hook" style={{backgroundColor: change.newValue}} key={key} />;
|
||||
},
|
||||
oldColorHook: (_, key) => {
|
||||
return <div className="color-hook" style={{backgroundColor: change.oldValue}} key={key} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!changeStr) {
|
||||
console.warn('No change string for', change);
|
||||
return null;
|
||||
}
|
||||
|
||||
const prefixClasses = classNames('prefix', TYPE_CLASSES[log.actionType]);
|
||||
changeNumber++;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className="detail"
|
||||
style={{
|
||||
position: 'relative',
|
||||
top: 1,
|
||||
}}
|
||||
key={i}>
|
||||
<Flex className={prefixClasses} grow={0}>
|
||||
<div className="number">{changeNumber < 10 ? '0' : null}{changeNumber}</div>
|
||||
<div className="dash">—</div>
|
||||
</Flex>
|
||||
<Flex className="change" direction={Flex.Direction.VERTICAL}>
|
||||
<div className="change-str">{changeStr}</div>
|
||||
{subChanges != null ? <div>{subChanges}</div> : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex onClick={onContentClick} className="ui-audit-log-change-details" direction={Flex.Direction.VERTICAL}>
|
||||
{changes}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
renderChangeSummary() {
|
||||
const {expanded, log} = this.props;
|
||||
|
||||
if (expanded) {
|
||||
switch (log.targetType) {
|
||||
case AuditLogTargetTypes.GUILD:
|
||||
return this.renderChangeDetails(GuildChangeStrings());
|
||||
case AuditLogTargetTypes.CHANNEL:
|
||||
return this.renderChangeDetails(ChannelChangeStrings());
|
||||
case AuditLogTargetTypes.USER:
|
||||
return this.renderChangeDetails(UserChangeStrings());
|
||||
case AuditLogTargetTypes.ROLE:
|
||||
return this.renderChangeDetails(RoleChangeStrings());
|
||||
case AuditLogTargetTypes.INVITE:
|
||||
return this.renderChangeDetails(InviteChangeStrings());
|
||||
case AuditLogTargetTypes.WEBHOOK:
|
||||
return this.renderChangeDetails(WebhookChangeStrings());
|
||||
case AuditLogTargetTypes.EMOJI:
|
||||
return this.renderChangeDetails(EmojiChangeStrings());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {log, className, expanded, onHeaderClick} = this.props;
|
||||
|
||||
const hasChangesToRender = this.hasChangesToRender();
|
||||
|
||||
const classes = classNames('ui-audit-log', className, {
|
||||
'ui-audit-log-clickable': hasChangesToRender,
|
||||
'ui-audit-log-expanded': expanded,
|
||||
});
|
||||
|
||||
const timestampStartStr = log.timestampStart.calendar();
|
||||
const timestampEndStar = log.timestampEnd.calendar();
|
||||
let timestamp;
|
||||
if (timestampStartStr === timestampEndStar) {
|
||||
timestamp = <div className="timestamp">{timestampStartStr}</div>;
|
||||
} else {
|
||||
timestamp = <div className="timestamp">{timestampStartStr}—{timestampEndStar}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction={Flex.Direction.VERTICAL} className={classes}>
|
||||
<Flex
|
||||
className="header"
|
||||
direction={Flex.Direction.HORIZONTAL}
|
||||
align={Flex.Align.CENTER}
|
||||
onClick={hasChangesToRender && onHeaderClick}>
|
||||
<AuditLogIcon action={log.action} actionType={log.actionType} targetType={log.targetType} />
|
||||
<Avatar className="audit-log-avatar" src={log.user.getAvatarURL()} />
|
||||
<Flex className="title-time-wrap" direction={Flex.Direction.VERTICAL}>
|
||||
<Flex.Child grow={1}>
|
||||
{this.renderTitle()}
|
||||
</Flex.Child>
|
||||
{timestamp}
|
||||
</Flex>
|
||||
{hasChangesToRender ? <ExpandIcon expanded={expanded} /> : null}
|
||||
</Flex>
|
||||
{expanded ? <div className="divider" /> : null}
|
||||
{this.renderChangeSummary()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AuditLog;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/AuditLog.js
|
47
509bba0_unpacked/discord_app/uikit/Avatar.js
Executable file
47
509bba0_unpacked/discord_app/uikit/Avatar.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Avatar.mod.css';
|
||||
|
||||
export const AvatarSizes = {
|
||||
PROFILE: 'profile',
|
||||
XXLARGE: 'xxlarge',
|
||||
XLARGE: 'xlarge',
|
||||
LARGE: 'large',
|
||||
SMALL: 'small',
|
||||
XSMALL: 'xsmall',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
size: string,
|
||||
src: any,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Avatar PureComponent
|
||||
*/
|
||||
class Avatar extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
static Sizes = AvatarSizes;
|
||||
static defaultProps = {
|
||||
size: AvatarSizes.LARGE,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {size, src, className} = this.props;
|
||||
const style = {
|
||||
backgroundImage: `url('${src}')`,
|
||||
};
|
||||
return <div className={classNames(styles.avatar, styles[size], className)} style={style} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Avatar;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Avatar.js
|
9
509bba0_unpacked/discord_app/uikit/Avatar.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Avatar.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"avatar":"avatar-1BXaQj","profile":"profile-3z9uol","xxlarge":"xxlarge-1arjqQ","xlarge":"xlarge-2lb-HO","large":"large-3yh-62","small":"small-TEeAkX","xsmall":"xsmall-2rXiD4"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Avatar.mod.css
|
||||
// module id = 2086
|
||||
// module chunks = 2
|
30
509bba0_unpacked/discord_app/uikit/Badge.js
Executable file
30
509bba0_unpacked/discord_app/uikit/Badge.js
Executable file
|
@ -0,0 +1,30 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import styles from './Badge.mod.css';
|
||||
|
||||
type Props = {
|
||||
value: ?number,
|
||||
};
|
||||
|
||||
class Badge extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
value: 0,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {value} = this.props;
|
||||
if (value != null && value > 0) {
|
||||
return <div className={styles.wrapper}>{value}</div>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Badge;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Badge.js
|
9
509bba0_unpacked/discord_app/uikit/Badge.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Badge.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"wrapper":"wrapper-2xO9RX"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Badge.mod.css
|
||||
// module id = 2087
|
||||
// module chunks = 2
|
20
509bba0_unpacked/discord_app/uikit/BotTag.js
Executable file
20
509bba0_unpacked/discord_app/uikit/BotTag.js
Executable file
|
@ -0,0 +1,20 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Styles from './BotTag.mod.css';
|
||||
|
||||
type Props = {
|
||||
invertColor?: boolean,
|
||||
};
|
||||
|
||||
const BotTag = ({invertColor = false}: Props) =>
|
||||
<span className={invertColor ? Styles.botTagInvert : Styles.botTagRegular}>
|
||||
BOT
|
||||
</span>;
|
||||
|
||||
export default BotTag;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/BotTag.js
|
9
509bba0_unpacked/discord_app/uikit/BotTag.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/BotTag.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"botTag":"botTag-1OwMgs","botTagRegular":"botTagRegular-288-ZL botTag-1OwMgs","botTagInvert":"botTagInvert-gorWR_ botTag-1OwMgs"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/BotTag.mod.css
|
||||
// module id = 2088
|
||||
// module chunks = 2
|
160
509bba0_unpacked/discord_app/uikit/Button.js
Executable file
160
509bba0_unpacked/discord_app/uikit/Button.js
Executable file
|
@ -0,0 +1,160 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Styles from './Button.mod.css';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
|
||||
export const ButtonLooks = {
|
||||
FILLED: 'filled',
|
||||
INVERTED: 'inverted',
|
||||
OUTLINED: 'outlined',
|
||||
GHOST: 'ghost',
|
||||
LINK: 'link',
|
||||
};
|
||||
|
||||
export const ButtonColors = {
|
||||
BRAND: 'brand',
|
||||
GREY: 'grey',
|
||||
RED: 'red',
|
||||
GREEN: 'green',
|
||||
YELLOW: 'yellow',
|
||||
PRIMARY: 'primary',
|
||||
LINK: 'link',
|
||||
WHITE: 'white',
|
||||
TRANSPARENT: 'transparent',
|
||||
};
|
||||
|
||||
export const ButtonSizes = {
|
||||
SMALL: 'small',
|
||||
MEDIUM: 'medium',
|
||||
LARGE: 'large',
|
||||
XLARGE: 'xlarge',
|
||||
MIN: 'min',
|
||||
MAX: 'max',
|
||||
ICON: 'icon',
|
||||
};
|
||||
|
||||
const Modes = {
|
||||
DEFAULT: 'Default',
|
||||
DISABLED: 'Disabled',
|
||||
SUBMITTING: 'Submitting',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
look: string,
|
||||
color: string,
|
||||
size: string,
|
||||
grow: boolean,
|
||||
disabled: boolean,
|
||||
submitting: boolean,
|
||||
type?: string,
|
||||
style?: Object,
|
||||
className?: string,
|
||||
onClick?: (e: MouseEvent) => void,
|
||||
onMouseDown?: (e: MouseEvent) => void,
|
||||
onMouseUp?: (e: MouseEvent) => void,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
class Button extends React.PureComponent {
|
||||
static Looks = ButtonLooks;
|
||||
static Colors = ButtonColors;
|
||||
static Sizes = ButtonSizes;
|
||||
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
look: ButtonLooks.FILLED,
|
||||
color: ButtonColors.BRAND,
|
||||
size: ButtonSizes.MEDIUM,
|
||||
grow: true,
|
||||
disabled: false,
|
||||
submitting: false,
|
||||
type: 'button',
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).getMode = this.getMode.bind(this);
|
||||
(this: any).renderSpinner = this.renderSpinner.bind(this);
|
||||
}
|
||||
|
||||
getMode() {
|
||||
const {submitting, disabled} = this.props;
|
||||
|
||||
if (disabled) {
|
||||
return Modes.DISABLED;
|
||||
}
|
||||
if (submitting) {
|
||||
return Modes.SUBMITTING;
|
||||
}
|
||||
|
||||
return Modes.DEFAULT;
|
||||
}
|
||||
|
||||
renderSpinner() {
|
||||
const {look, color, disabled, submitting} = this.props;
|
||||
|
||||
if (!submitting || disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const itemClass = getClass(Styles, 'spinnerItem', color, look);
|
||||
return (
|
||||
<span className={Styles.spinner}>
|
||||
<span className={Styles.spinnerInner}>
|
||||
<span className={itemClass} />
|
||||
<span className={itemClass} />
|
||||
<span className={itemClass} />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
look,
|
||||
color,
|
||||
size,
|
||||
grow,
|
||||
disabled,
|
||||
submitting,
|
||||
type,
|
||||
style,
|
||||
className,
|
||||
onClick,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
children,
|
||||
} = this.props;
|
||||
const mode = this.getMode();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={disabled || submitting ? undefined : onClick}
|
||||
onMouseUp={disabled ? undefined : onMouseUp}
|
||||
onMouseDown={disabled ? undefined : onMouseDown}
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
className={classNames(
|
||||
getClass(Styles, 'button', color, look, mode),
|
||||
getClass(Styles, size, grow ? 'Grow' : null),
|
||||
className
|
||||
)}>
|
||||
<div className={classNames(getClass(Styles, 'contents', mode), getClass(Styles, 'contents', look))}>
|
||||
{children}
|
||||
</div>
|
||||
{this.renderSpinner()}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Button;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Button.js
|
9
509bba0_unpacked/discord_app/uikit/Button.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Button.mod.css
Executable file
File diff suppressed because one or more lines are too long
59
509bba0_unpacked/discord_app/uikit/ButtonGroup.js
Executable file
59
509bba0_unpacked/discord_app/uikit/ButtonGroup.js
Executable file
|
@ -0,0 +1,59 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './ButtonGroup.styl';
|
||||
|
||||
export type ButtonType = {
|
||||
content: string | React$Element,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
onClick: Function,
|
||||
};
|
||||
|
||||
type ButtonGroupProps = {
|
||||
buttons: Array<ButtonType>,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
};
|
||||
|
||||
type ButtonProps = {
|
||||
data: ButtonType,
|
||||
disabled?: boolean,
|
||||
};
|
||||
|
||||
const Button = ({data, disabled}: ButtonProps) => {
|
||||
const {content, className, onClick, disabled: buttonDisabled} = data;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames('ui-button-group-item', className)}
|
||||
onClick={onClick}
|
||||
disabled={disabled || buttonDisabled}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.PureComponent {
|
||||
props: ButtonGroupProps;
|
||||
|
||||
render() {
|
||||
const {disabled, className} = this.props;
|
||||
|
||||
const buttons = this.props.buttons.map((data, i) => <Button key={i} data={data} disabled={disabled} />);
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-button-group', className, {disabled})}>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ButtonGroup;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/ButtonGroup.js
|
47
509bba0_unpacked/discord_app/uikit/CalendarPicker.js
Executable file
47
509bba0_unpacked/discord_app/uikit/CalendarPicker.js
Executable file
|
@ -0,0 +1,47 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import Moment from 'moment';
|
||||
import './CalendarPicker.styl';
|
||||
|
||||
type Props = {
|
||||
value?: Moment,
|
||||
minDate?: Moment,
|
||||
maxDate?: Moment,
|
||||
locale?: string,
|
||||
onChange?: (value: Moment, event: Event) => void,
|
||||
};
|
||||
|
||||
class CalendarPicker extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
static defaultProps = {
|
||||
value: Moment().local(),
|
||||
};
|
||||
|
||||
render() {
|
||||
const {minDate, maxDate, onChange, locale, value} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ui-calendar-picker">
|
||||
<DatePicker
|
||||
fixedHeight
|
||||
inline
|
||||
selected={value}
|
||||
locale={locale}
|
||||
onChange={onChange}
|
||||
maxDate={maxDate}
|
||||
minDate={minDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CalendarPicker;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/CalendarPicker.js
|
153
509bba0_unpacked/discord_app/uikit/CallAvatar.js
Executable file
153
509bba0_unpacked/discord_app/uikit/CallAvatar.js
Executable file
|
@ -0,0 +1,153 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import './CallAvatar.styl';
|
||||
import classNames from 'classnames';
|
||||
import {CallModes} from '../Constants';
|
||||
import Flex from './Flex';
|
||||
import Spinner from './Spinner';
|
||||
|
||||
export const CallAvatarSizes = {
|
||||
XXLARGE: 'xxlarge',
|
||||
XLARGE: 'xlarge',
|
||||
LARGE: 'large',
|
||||
SMALL: 'small',
|
||||
};
|
||||
|
||||
export const CallAvatarPixelSizes = {
|
||||
[CallAvatarSizes.XXLARGE]: 100,
|
||||
[CallAvatarSizes.XLARGE]: 60,
|
||||
[CallAvatarSizes.LARGE]: 40,
|
||||
[CallAvatarSizes.SMALL]: 30,
|
||||
};
|
||||
|
||||
export const CALL_AVATAR_VIDEO_RATIO = 1.78;
|
||||
|
||||
type Props = {
|
||||
id: string,
|
||||
mode?: string,
|
||||
size?: string,
|
||||
selected?: boolean,
|
||||
speaking?: boolean,
|
||||
ringing?: boolean,
|
||||
muted?: boolean,
|
||||
deafen?: boolean,
|
||||
loading?: boolean,
|
||||
name?: string,
|
||||
src: string,
|
||||
streamId?: string,
|
||||
mirror: boolean,
|
||||
onClick?: Function,
|
||||
onContextMenu?: Function,
|
||||
videoComponent?: ReactClass<*>,
|
||||
className?: string,
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
// TODO: add hover and ring, appear/disappear animations
|
||||
class CallAvatar extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
static defaultProps = {
|
||||
mode: CallModes.VOICE,
|
||||
size: CallAvatarSizes.XXLARGE,
|
||||
selected: false,
|
||||
speaking: false,
|
||||
ringing: false,
|
||||
muted: false,
|
||||
deafen: false,
|
||||
loading: false,
|
||||
mirror: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
renderContent() {
|
||||
const {mode, selected, size, name, streamId, loading, mirror, videoComponent: Video, disabled} = this.props;
|
||||
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mode === CallModes.VIDEO && selected) {
|
||||
return (
|
||||
<Flex
|
||||
className="ui-call-avatar-overlay"
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}
|
||||
direction={Flex.Direction.VERTICAL}>
|
||||
<img className="icon" src={require('../images/ic_videocam_white_24px.svg')} />
|
||||
<div className={classNames('name', 'margin-top-4', size)}>{name}</div>
|
||||
</Flex>
|
||||
);
|
||||
} else if (mode === CallModes.VIDEO && streamId != null && Video != null) {
|
||||
return (
|
||||
<div className={classNames('ui-call-avatar-video', {mirror})}>
|
||||
<Video streamId={streamId} />
|
||||
</div>
|
||||
);
|
||||
} else if (mode === CallModes.VIDEO && loading) {
|
||||
return (
|
||||
<Flex className="ui-call-avatar-overlay" align={Flex.Align.CENTER} justify={Flex.Justify.CENTER}>
|
||||
<Spinner type={Spinner.Type.SPINNING_CIRCLE} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderRipples() {
|
||||
if (this.props.ringing) {
|
||||
return [
|
||||
<div className="ripple ripple-0" key="ripple-0" />,
|
||||
<div className="ripple ripple-1" key="ripple-1" />,
|
||||
<div className="ripple ripple-2" key="ripple-2" />,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderTopLayer() {
|
||||
const {mode, selected: _selected, speaking: _speaking, muted: _muted, deafen: _deafen, disabled} = this.props;
|
||||
|
||||
const selected = _selected && !disabled;
|
||||
const speaking = _speaking && !disabled;
|
||||
const muted = _muted && !disabled;
|
||||
const deafen = _deafen && !disabled;
|
||||
|
||||
const border = <div key="border" className={classNames('ui-call-avatar-border', mode, {selected, speaking})} />;
|
||||
const status = <div key="status" className={classNames('ui-call-avatar-status', {muted, deafen, selected})} />;
|
||||
|
||||
return mode === CallModes.VOICE ? [border, status] : [status, border];
|
||||
}
|
||||
|
||||
render() {
|
||||
const {src, mode, size, id, onClick, onContextMenu, className, disabled} = this.props;
|
||||
|
||||
const imageStyle = {
|
||||
backgroundImage: `url('${src}')`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-call-avatar-wrapper', mode, size, className)}>
|
||||
{this.renderRipples()}
|
||||
<div
|
||||
className={classNames('ui-call-avatar', mode, {disabled})}
|
||||
style={imageStyle}
|
||||
onClick={e => onClick && onClick(id, e)}
|
||||
onContextMenu={e => onContextMenu && onContextMenu(e, id)}>
|
||||
{this.renderContent()}
|
||||
{this.renderTopLayer()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CallAvatar;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/CallAvatar.js
|
64
509bba0_unpacked/discord_app/uikit/Card.js
Executable file
64
509bba0_unpacked/discord_app/uikit/Card.js
Executable file
|
@ -0,0 +1,64 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
import Styles from './Card.mod.css';
|
||||
|
||||
export const Types = {
|
||||
PRIMARY: 'cardPrimary',
|
||||
DANGER: 'cardDanger',
|
||||
WARNING: 'cardWarning',
|
||||
SUCCESS: 'cardSuccess',
|
||||
BRAND: 'cardBrand',
|
||||
CUSTOM: 'cardCustom',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
outline: boolean,
|
||||
editable: boolean,
|
||||
className?: string,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
type: string,
|
||||
outline: boolean,
|
||||
editable: boolean,
|
||||
};
|
||||
|
||||
class Card extends React.PureComponent {
|
||||
static Types = Types;
|
||||
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
type: Types.PRIMARY,
|
||||
outline: false,
|
||||
editable: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, editable, type, className, outline, ...attrs} = this.props;
|
||||
|
||||
let mode;
|
||||
if (outline) {
|
||||
mode = 'outline';
|
||||
} else if (type === Types.PRIMARY && editable) {
|
||||
mode = 'editable';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(className, getClass(Styles, type, mode))} {...attrs}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Card;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Card.js
|
9
509bba0_unpacked/discord_app/uikit/Card.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Card.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"card":"card-3DrRmC","cardDanger":"cardDanger-2CkE7R card-3DrRmC","cardDangerOutline":"cardDangerOutline-25eKzG card-3DrRmC","cardWarning":"cardWarning-31DHBH card-3DrRmC","cardWarningOutline":"cardWarningOutline-2Rp1Zn card-3DrRmC","cardSuccess":"cardSuccess-1UBFjp card-3DrRmC","cardSuccessOutline":"cardSuccessOutline-G0P3Ys card-3DrRmC","cardBrand":"cardBrand-1LvZTh card-3DrRmC","cardBrandOutline":"cardBrandOutline-2uIT1U card-3DrRmC","cardPrimary":"cardPrimary-ZVL9Jr card-3DrRmC","cardPrimaryEditable":"cardPrimaryEditable-2IQ7-V card-3DrRmC","cardPrimaryOutline":"cardPrimaryOutline-2YyAz2 card-3DrRmC","cardPrimaryOutlineEditable":"cardPrimaryOutlineEditable-1s-Uqd card-3DrRmC"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Card.mod.css
|
||||
// module id = 2090
|
||||
// module chunks = 2
|
50
509bba0_unpacked/discord_app/uikit/Checkbox.js
Executable file
50
509bba0_unpacked/discord_app/uikit/Checkbox.js
Executable file
|
@ -0,0 +1,50 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import CheckMarkIcon from './icons/CheckMarkIcon';
|
||||
import {NOOP, Colors} from '../Constants';
|
||||
import './Checkbox.styl';
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean,
|
||||
readOnly?: boolean,
|
||||
value?: boolean,
|
||||
inverted?: boolean,
|
||||
onChange?: Function,
|
||||
color?: string,
|
||||
type?: string,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
export const CheckboxTypes = {
|
||||
BOX: 'box',
|
||||
ROUND: 'round',
|
||||
};
|
||||
|
||||
const Checkbox = ({
|
||||
disabled = false,
|
||||
readOnly = false,
|
||||
inverted = false,
|
||||
value = false,
|
||||
onChange = NOOP,
|
||||
color = Colors.BRAND,
|
||||
type = CheckboxTypes.BOX,
|
||||
children,
|
||||
}: Props) =>
|
||||
<label className={classNames('ui-checkbox-wrapper', {disabled, 'read-only': readOnly})}>
|
||||
<input className="input" type="checkbox" onChange={!disabled && !readOnly ? onChange : NOOP} checked={value} />
|
||||
<div
|
||||
className={classNames('ui-checkbox', type, {checked: value, inverted})}
|
||||
style={value && inverted ? {backgroundColor: color, borderColor: color} : {}}>
|
||||
<CheckMarkIcon width={18} height={18} color={value ? (inverted ? Colors.WHITE : color) : 'none'} />
|
||||
</div>
|
||||
{children ? <div className="label">{children}</div> : null}
|
||||
</label>;
|
||||
|
||||
export default Checkbox;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Checkbox.js
|
180
509bba0_unpacked/discord_app/uikit/CodeField.js
Executable file
180
509bba0_unpacked/discord_app/uikit/CodeField.js
Executable file
|
@ -0,0 +1,180 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from './Flex';
|
||||
import Styles from './CodeField.mod.css';
|
||||
|
||||
type CodeBlockProps = {
|
||||
autoFocus: boolean,
|
||||
code: ?string,
|
||||
onChange?: (value: string) => void,
|
||||
onKeyDown?: (e: KeyboardEvent) => void,
|
||||
};
|
||||
|
||||
class CodeBlock extends React.PureComponent {
|
||||
props: CodeBlockProps;
|
||||
|
||||
static defaultProps = {
|
||||
autoFocus: false,
|
||||
};
|
||||
|
||||
_codeBlockRef = null;
|
||||
|
||||
constructor(props: CodeBlockProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleKeyDown = this.handleKeyDown.bind(this);
|
||||
(this: any).handleChange = this.handleChange.bind(this);
|
||||
(this: any).setCodeBlockRef = this.setCodeBlockRef.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
ref={this.setCodeBlockRef}
|
||||
className={Styles.input}
|
||||
maxLength={1}
|
||||
value={this.props.code}
|
||||
autoFocus={this.props.autoFocus}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
setCodeBlockRef(ref: HTMLInputElement) {
|
||||
this._codeBlockRef = ref;
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this._codeBlockRef) {
|
||||
this._codeBlockRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
blur() {
|
||||
if (this._codeBlockRef) {
|
||||
this._codeBlockRef.blur();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e: KeyboardEvent) {
|
||||
const actionKeys = e.which === 8 || e.which === 37 || e.which === 39;
|
||||
const numKeys = e.which >= 48 && e.which <= 57;
|
||||
if (!actionKeys && !numKeys) {
|
||||
e.preventDefault();
|
||||
}
|
||||
const {onKeyDown} = this.props;
|
||||
onKeyDown && onKeyDown(e);
|
||||
}
|
||||
|
||||
handleChange(e: InputEvent) {
|
||||
const {onChange} = this.props;
|
||||
onChange && onChange(e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
count: number,
|
||||
onSubmit?: (code: string) => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
codes: Array<string>,
|
||||
};
|
||||
|
||||
class CodeField extends React.PureComponent {
|
||||
state: State;
|
||||
props: Props;
|
||||
|
||||
_codeBlockRefs = new Array(this.props.count);
|
||||
|
||||
static defaultProps = {
|
||||
count: 6,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
codes: new Array(props.count),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {codes} = this.state;
|
||||
|
||||
const blocks = [];
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
if (i === codes.length / 2) {
|
||||
blocks.push(<div key="spacer" className={Styles.spacer} />);
|
||||
}
|
||||
blocks.push(
|
||||
<CodeBlock
|
||||
ref={ref => this.setCodeBlockRef(i, ref)}
|
||||
key={i}
|
||||
code={codes[i]}
|
||||
autoFocus={i === 0}
|
||||
onChange={value => this.handleChange(i, value)}
|
||||
onKeyDown={e => this.handleKeyDown(i, e)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex align={Flex.Align.CENTER} justify={Flex.Justify.CENTER}>
|
||||
{blocks}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
setCodeBlockRef(i: number, ref: HTMLInputElement) {
|
||||
this._codeBlockRefs[i] = ref;
|
||||
}
|
||||
|
||||
handleChange(i: number, value: string) {
|
||||
this.state.codes[i] = value;
|
||||
const result = this.getCodeOrFirstEmptyIndex();
|
||||
if (typeof result === 'string') {
|
||||
this.submit(result);
|
||||
} else {
|
||||
this._codeBlockRefs[result].focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(i: number, e: KeyboardEvent) {
|
||||
const {codes} = this.state;
|
||||
// Move to prev input box and delete the content if available if backspacing in an empty input box
|
||||
if (e.which === 8 && i > 0 && (codes[i] == null || codes[i].length == 0)) {
|
||||
const prevIndex = i - 1;
|
||||
codes[prevIndex] = '';
|
||||
this._codeBlockRefs[prevIndex].focus();
|
||||
}
|
||||
}
|
||||
|
||||
getCodeOrFirstEmptyIndex() {
|
||||
const {codes} = this.state;
|
||||
|
||||
let generatedCode = '';
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
if (isNaN(parseInt(codes[i]))) {
|
||||
return i;
|
||||
}
|
||||
generatedCode += codes[i];
|
||||
}
|
||||
|
||||
return generatedCode;
|
||||
}
|
||||
|
||||
submit(code: string) {
|
||||
const {onSubmit} = this.props;
|
||||
onSubmit && onSubmit(code);
|
||||
}
|
||||
}
|
||||
|
||||
export default CodeField;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/CodeField.js
|
9
509bba0_unpacked/discord_app/uikit/CodeField.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/CodeField.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"spacer":"spacer-1Ko1y_","input":"input-3_iTLI"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/CodeField.mod.css
|
||||
// module id = 2091
|
||||
// module chunks = 2
|
166
509bba0_unpacked/discord_app/uikit/CollapsingActionGroup.js
Executable file
166
509bba0_unpacked/discord_app/uikit/CollapsingActionGroup.js
Executable file
|
@ -0,0 +1,166 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Animated from '../lib/animated';
|
||||
import Flex from './Flex';
|
||||
import Styles from './CollapsingActionGroup.mod.css';
|
||||
|
||||
type Item = {
|
||||
value: any,
|
||||
render: Function,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value: any,
|
||||
items: Array<Item>,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
open: boolean,
|
||||
itemAnimations: Array<Animated.Value>,
|
||||
itemHeight: number,
|
||||
itemWidth: number,
|
||||
};
|
||||
|
||||
class CollapsingActionGroup extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
_itemRef: HTMLElement;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
itemAnimations: props.items.map(() => new Animated.Value(0)),
|
||||
itemHeight: 0,
|
||||
itemWidth: 0,
|
||||
};
|
||||
|
||||
(this: any).setItemRef = this.setItemRef.bind(this);
|
||||
(this: any).handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
(this: any).handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this._itemRef != null) {
|
||||
const width = this._itemRef.clientWidth;
|
||||
const height = this._itemRef.clientHeight;
|
||||
this.setState({itemWidth: width, itemHeight: height});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.value !== nextProps.value) {
|
||||
this.handleMouseLeave();
|
||||
}
|
||||
}
|
||||
|
||||
getContainerStyle() {
|
||||
const {itemHeight, itemWidth, open} = this.state;
|
||||
|
||||
return {
|
||||
height: itemHeight,
|
||||
width: open ? itemWidth * this.props.items.length : itemWidth,
|
||||
};
|
||||
}
|
||||
|
||||
getItemStyle(index: number) {
|
||||
const {itemWidth} = this.state;
|
||||
|
||||
return {
|
||||
transform: [
|
||||
{
|
||||
translateX: this.state.itemAnimations[index].interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0px', `${itemWidth * index}px`],
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
setItemRef(ref: HTMLElement) {
|
||||
this._itemRef = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {open} = this.state;
|
||||
const {onChange, value} = this.props;
|
||||
|
||||
const items = this.props.items.map((item, i) => {
|
||||
const setRef = i === 0 ? this.setItemRef : null;
|
||||
const active = value === item.value;
|
||||
return (
|
||||
<Animated.div
|
||||
style={this.getItemStyle(i)}
|
||||
className={classNames({
|
||||
[Styles.itemClosed]: !open && !active,
|
||||
[Styles.itemOpen]: open && !active,
|
||||
[Styles.itemActive]: active,
|
||||
})}
|
||||
key={i}
|
||||
onClick={() => onChange(item.value)}>
|
||||
<div ref={setRef}>
|
||||
{item.render()}
|
||||
</div>
|
||||
</Animated.div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={this.getContainerStyle()}
|
||||
className={Styles.wrapper}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}>
|
||||
<Flex>
|
||||
{items}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleMouseEnter() {
|
||||
const {open, itemAnimations} = this.state;
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({open: true});
|
||||
|
||||
const animations = itemAnimations.map(anim =>
|
||||
Animated.spring(anim, {
|
||||
toValue: 1,
|
||||
})
|
||||
);
|
||||
Animated.parallel(animations).start();
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
const {open, itemAnimations} = this.state;
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({open: false});
|
||||
|
||||
const animations = itemAnimations.map(anim =>
|
||||
Animated.spring(anim, {
|
||||
toValue: 0,
|
||||
})
|
||||
);
|
||||
Animated.parallel(animations).start();
|
||||
}
|
||||
}
|
||||
|
||||
export default CollapsingActionGroup;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/CollapsingActionGroup.js
|
9
509bba0_unpacked/discord_app/uikit/CollapsingActionGroup.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/CollapsingActionGroup.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"wrapper":"wrapper-17ZYn1","item":"item-Qf_Fts","itemClosed":"itemClosed-1xGx-f item-Qf_Fts","itemOpen":"itemOpen-1CvG4N item-Qf_Fts","itemActive":"itemActive-1wbvnV item-Qf_Fts"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/CollapsingActionGroup.mod.css
|
||||
// module id = 2092
|
||||
// module chunks = 2
|
291
509bba0_unpacked/discord_app/uikit/ColorPicker.js
Executable file
291
509bba0_unpacked/discord_app/uikit/ColorPicker.js
Executable file
|
@ -0,0 +1,291 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import ReactColorPicker from 'react-color-picker';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import TextInput from './TextInput';
|
||||
import CheckMarkIcon from './icons/CheckMarkIcon';
|
||||
import DropperIcon from './icons/DropperIcon';
|
||||
import {int2hex, hex2int, isValidHex, getDarkness} from '../../discord_common/js/utils/ColorUtils';
|
||||
import {Colors} from '../Constants';
|
||||
import './ColorPicker.styl';
|
||||
|
||||
const MAX_HEX_LENGTH = 7;
|
||||
|
||||
type SwatchProps = {
|
||||
color: number,
|
||||
isDefault?: boolean,
|
||||
isCustom?: boolean,
|
||||
isSelected?: boolean,
|
||||
disabled?: boolean,
|
||||
style: Object,
|
||||
onClick?: Function,
|
||||
};
|
||||
|
||||
class Swatch extends React.PureComponent {
|
||||
props: SwatchProps;
|
||||
|
||||
static defaultProps = {
|
||||
isDefault: false,
|
||||
isCustom: false,
|
||||
isSelected: false,
|
||||
disabled: false,
|
||||
style: {},
|
||||
};
|
||||
|
||||
constructor(props: SwatchProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
renderCustomIcon(isDark) {
|
||||
if (this.props.isCustom) {
|
||||
return <DropperIcon color={isDark ? Colors.WHITE : Colors.BLACK} />;
|
||||
}
|
||||
}
|
||||
|
||||
renderSelected(isDark) {
|
||||
if (this.props.isSelected) {
|
||||
return <CheckMarkIcon color={isDark ? Colors.WHITE : Colors.BLACK} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {color, isDefault, isCustom, isSelected, disabled, style} = this.props;
|
||||
if (color != null) {
|
||||
style.backgroundColor = int2hex(color);
|
||||
}
|
||||
let isDark = false;
|
||||
if (isCustom && !isSelected) {
|
||||
isDark = true;
|
||||
} else if (isCustom || isSelected) {
|
||||
isDark = getDarkness(color) > 0.1;
|
||||
}
|
||||
|
||||
const classes = classNames('ui-color-picker-swatch', {
|
||||
disabled,
|
||||
default: isDefault,
|
||||
custom: isCustom,
|
||||
'no-color': color == null,
|
||||
});
|
||||
|
||||
return (
|
||||
<button className={classes} style={style} disabled={disabled} onClick={this.handleClick} style={style}>
|
||||
{this.renderCustomIcon(isDark)}
|
||||
{this.renderSelected(isDark)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const {color, onClick} = this.props;
|
||||
onClick && onClick(color);
|
||||
}
|
||||
}
|
||||
|
||||
type DefaultColorButtonProps = {
|
||||
color: number,
|
||||
value: number,
|
||||
onChange: Function,
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
export class DefaultColorButton extends React.PureComponent {
|
||||
props: DefaultColorButtonProps;
|
||||
|
||||
render() {
|
||||
const {color, onChange, value, disabled} = this.props;
|
||||
return (
|
||||
<Swatch
|
||||
isDefault
|
||||
color={color}
|
||||
isSelected={color === value || value === 0}
|
||||
onClick={onChange}
|
||||
disabled={disabled}
|
||||
style={{marginLeft: 0}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type CustomColorButtonProps = {
|
||||
customColor: number,
|
||||
value: number,
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
export class CustomColorButton extends React.PureComponent {
|
||||
props: CustomColorButtonProps;
|
||||
|
||||
render() {
|
||||
const {customColor, value, disabled} = this.props;
|
||||
return (
|
||||
<Swatch
|
||||
isCustom
|
||||
color={customColor}
|
||||
isSelected={value === customColor}
|
||||
disabled={disabled}
|
||||
style={{marginLeft: 10}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type CustomColorPickerProps = {
|
||||
onChange: Function,
|
||||
onClose?: Function,
|
||||
value: ?number,
|
||||
};
|
||||
|
||||
type CustomColorButtonState = {
|
||||
color: string,
|
||||
validColor: string,
|
||||
};
|
||||
|
||||
export class CustomColorPicker extends React.PureComponent {
|
||||
props: CustomColorPickerProps;
|
||||
state: CustomColorButtonState;
|
||||
|
||||
constructor(props: CustomColorPickerProps) {
|
||||
super(props);
|
||||
|
||||
const {value} = props;
|
||||
const color = int2hex(value != null ? value : 0);
|
||||
|
||||
this.state = {
|
||||
color,
|
||||
validColor: color,
|
||||
};
|
||||
|
||||
(this: any).handleHexChange = this.handleHexChange.bind(this);
|
||||
(this: any).handleOnChange = this.handleOnChange.bind(this);
|
||||
(this: any).handleDrag = this.handleDrag.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {onClose} = this.props;
|
||||
onClose && onClose();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {color, validColor} = this.state;
|
||||
|
||||
return (
|
||||
<Flex direction={Flex.Direction.VERTICAL} className="ui-color-picker-custom">
|
||||
<ReactColorPicker
|
||||
key={0}
|
||||
saturationWidth={200}
|
||||
saturationHeight={150}
|
||||
hueWidth={10}
|
||||
onChange={this.handleOnChange}
|
||||
onDrag={this.handleDrag}
|
||||
value={validColor}
|
||||
/>
|
||||
<TextInput
|
||||
className="custom-color-picker-input margin-top-8"
|
||||
value={color}
|
||||
onChange={this.handleHexChange}
|
||||
maxLength={MAX_HEX_LENGTH}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
callOnChange(colorHex: string) {
|
||||
const {onChange} = this.props;
|
||||
const color = hex2int(colorHex);
|
||||
onChange(color);
|
||||
}
|
||||
|
||||
handleHexChange(value: string) {
|
||||
const color = value[0] === '#' ? value : `#${value}`;
|
||||
|
||||
const isValid = isValidHex(color);
|
||||
this.setState({color, validColor: isValid ? color : this.state.validColor});
|
||||
if (isValid) {
|
||||
this.callOnChange(color);
|
||||
}
|
||||
}
|
||||
|
||||
handleDrag(colorHex: string) {
|
||||
this.setState({color: colorHex, validColor: colorHex});
|
||||
}
|
||||
|
||||
handleOnChange(color: string) {
|
||||
this.callOnChange(color);
|
||||
}
|
||||
}
|
||||
|
||||
type ColorPickerProps = {
|
||||
defaultColor: number,
|
||||
customColor: ?number,
|
||||
colors: Array<number>,
|
||||
value: number,
|
||||
disabled: boolean,
|
||||
onChange: Function,
|
||||
renderDefaultButton: Function,
|
||||
renderCustomButton: Function,
|
||||
};
|
||||
|
||||
class ColorPicker extends React.PureComponent {
|
||||
props: ColorPickerProps;
|
||||
|
||||
constructor(props: ColorPickerProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).renderColor = this.renderColor.bind(this);
|
||||
}
|
||||
|
||||
renderColor(color: number) {
|
||||
const {value, onChange, disabled} = this.props;
|
||||
return <Swatch key={color} color={color} isSelected={color === value} onClick={onChange} disabled={disabled} />;
|
||||
}
|
||||
|
||||
renderRow(colors: Array<number>) {
|
||||
return (
|
||||
<Flex className="ui-color-picker-row" wrap={Flex.Wrap.WRAP}>
|
||||
{colors.map(color => this.renderColor(color))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
defaultColor,
|
||||
customColor,
|
||||
colors,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
renderDefaultButton,
|
||||
renderCustomButton,
|
||||
} = this.props;
|
||||
|
||||
const row1 = colors.slice(0, colors.length / 2);
|
||||
const row2 = colors.slice(colors.length / 2, colors.length);
|
||||
|
||||
return (
|
||||
<Flex>
|
||||
<Flex.Child shrink={0} grow={0} className="margin-reset" wrap>
|
||||
{renderDefaultButton({value, color: defaultColor, onChange, disabled})}
|
||||
</Flex.Child>
|
||||
<Flex.Child shrink={0} grow={0} className="margin-reset" wrap>
|
||||
{renderCustomButton({value, customColor, disabled})}
|
||||
</Flex.Child>
|
||||
<Flex direction={Flex.Direction.VERTICAL} className="margin-reset" grow={1}>
|
||||
{this.renderRow(row1)}
|
||||
{this.renderRow(row2)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorPicker;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/ColorPicker.js
|
114
509bba0_unpacked/discord_app/uikit/CopyInput.js
Executable file
114
509bba0_unpacked/discord_app/uikit/CopyInput.js
Executable file
|
@ -0,0 +1,114 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import Button, {ButtonLooks, ButtonColors, ButtonSizes} from './Button';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
import Styles from './CopyInput.mod.css';
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
text?: any,
|
||||
mode?: string,
|
||||
hideMessage?: string,
|
||||
className?: string,
|
||||
onCopy: Function,
|
||||
};
|
||||
|
||||
const CopyInputModes = {
|
||||
DEFAULT: 'default',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
class CopyInput extends React.PureComponent {
|
||||
props: Props;
|
||||
input: HTMLInputElement;
|
||||
|
||||
static defaultProps = {
|
||||
text: 'Copy',
|
||||
};
|
||||
static Modes: typeof CopyInputModes = CopyInputModes;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
(this: any).handleButtonClick = this.handleButtonClick.bind(this);
|
||||
(this: any).handleInputClick = this.handleInputClick.bind(this);
|
||||
(this: any).setRef = this.setRef.bind(this);
|
||||
}
|
||||
|
||||
setRef(ref: HTMLInputElement) {
|
||||
this.input = ref;
|
||||
}
|
||||
|
||||
select() {
|
||||
const {input} = this;
|
||||
if (input) {
|
||||
input.select();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, text, mode, hideMessage, className} = this.props;
|
||||
const hidden = hideMessage != null;
|
||||
|
||||
let buttonColor;
|
||||
switch (mode) {
|
||||
case CopyInputModes.SUCCESS:
|
||||
buttonColor = ButtonColors.GREEN;
|
||||
break;
|
||||
case CopyInputModes.ERROR:
|
||||
buttonColor = ButtonColors.RED;
|
||||
break;
|
||||
default:
|
||||
buttonColor = ButtonColors.GREY;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(getClass(Styles, 'copyInput', mode), className)}>
|
||||
<Flex className={Styles.layout}>
|
||||
<Flex className={Styles.inputWrapper}>
|
||||
<input
|
||||
className={classNames(getClass(Styles, 'input', mode), {[Styles.inputHidden]: hidden})}
|
||||
ref={this.setRef}
|
||||
placeholder="input here"
|
||||
type="text"
|
||||
value={value}
|
||||
onClick={this.handleInputClick}
|
||||
readOnly
|
||||
/>
|
||||
{hidden ? <div className={Styles.hiddenMessage}>{hideMessage}</div> : null}
|
||||
</Flex>
|
||||
<Flex shrink={1} grow={0} style={{margin: 0}}>
|
||||
<Button
|
||||
className={Styles.button}
|
||||
onClick={this.handleButtonClick}
|
||||
size={ButtonSizes.MIN}
|
||||
color={buttonColor}
|
||||
look={ButtonLooks.GHOST}>
|
||||
{text}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleButtonClick() {
|
||||
this.select();
|
||||
const {onCopy, value} = this.props;
|
||||
onCopy(value);
|
||||
}
|
||||
|
||||
handleInputClick() {
|
||||
this.select();
|
||||
}
|
||||
}
|
||||
|
||||
export default CopyInput;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/CopyInput.js
|
9
509bba0_unpacked/discord_app/uikit/CopyInput.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/CopyInput.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"copyInput":"copyInput-1AA48i","copyInputDefault":"copyInputDefault-36NOFa copyInput-1AA48i","copyInputError":"copyInputError-1I9VjE copyInput-1AA48i","copyInputSuccess":"copyInputSuccess-2VxITA copyInput-1AA48i","layout":"layout-3qFkdA","inputWrapper":"inputWrapper-btcCdn","button":"button-2wYlsU","hiddenMessage":"hiddenMessage-1szyv2","input":"input-Z9iiFC","inputDefault":"inputDefault-3MyqtX input-Z9iiFC","inputError":"inputError-2SXd4k input-Z9iiFC","inputSuccess":"inputSuccess-1HmyX5 input-Z9iiFC","inputHidden":"inputHidden-1L16Mg"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/CopyInput.mod.css
|
||||
// module id = 2093
|
||||
// module chunks = 2
|
226
509bba0_unpacked/discord_app/uikit/Draggable.js
Executable file
226
509bba0_unpacked/discord_app/uikit/Draggable.js
Executable file
|
@ -0,0 +1,226 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import Animated from '../lib/animated';
|
||||
import {MouseButtons} from '../Constants';
|
||||
|
||||
type Props = {
|
||||
children?: any,
|
||||
className?: string,
|
||||
style?: any,
|
||||
initialX: number,
|
||||
initialY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
selector?: string,
|
||||
dragAnywhere: boolean,
|
||||
onDragStart?: (mouseX: number, mouseY: number) => void,
|
||||
onDrag?: (mouseX: number, mouseY: number) => void,
|
||||
onDragEnd?: (mouseX: number, mouseY: number) => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
dragging: boolean,
|
||||
position: Animated.ValueXY,
|
||||
};
|
||||
|
||||
const distanceBetweenSq = ({x: x1, y: y1}, {x: x2, y: y2}) => Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
|
||||
|
||||
// How many pixels the mouse has to move before we actually start dragging.
|
||||
const DRAG_START_THRESHOLD = 3;
|
||||
const DRAG_START_THRESHOLD_SQ = Math.pow(DRAG_START_THRESHOLD, 2);
|
||||
|
||||
class Draggable extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
_ref: Animated.div;
|
||||
_height = 0;
|
||||
_width = 0;
|
||||
_dragStart = {x: 0, y: 0};
|
||||
_offsetX = 0;
|
||||
_offsetY = 0;
|
||||
|
||||
static defaultProps = {
|
||||
maxX: 0,
|
||||
maxY: 0,
|
||||
initialX: 0,
|
||||
initialY: 0,
|
||||
dragAnywhere: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const x = new Animated.Value(props.initialX);
|
||||
const y = new Animated.Value(props.initialY);
|
||||
|
||||
this.state = {
|
||||
dragging: false,
|
||||
position: new Animated.ValueXY({x, y}),
|
||||
};
|
||||
|
||||
(this: any).handleSetRef = this.handleSetRef.bind(this);
|
||||
(this: any).handleMouseDown = this.handleMouseDown.bind(this);
|
||||
(this: any).handleMouseMove = this.handleMouseMove.bind(this);
|
||||
(this: any).handleMouseUp = this.handleMouseUp.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {initialX, initialY} = this.props;
|
||||
this.setPosition(initialX, initialY);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
|
||||
animateToPosition(
|
||||
x: number,
|
||||
y: number,
|
||||
animationConfig: any = {},
|
||||
callback?: ?Function,
|
||||
grabDimensions: boolean = true
|
||||
) {
|
||||
if (grabDimensions) {
|
||||
this.grabDimensions();
|
||||
}
|
||||
|
||||
const translatedValues = this.translate(x, y);
|
||||
|
||||
Animated.spring(this.state.position, {
|
||||
toValue: {x: translatedValues.x, y: translatedValues.y},
|
||||
...animationConfig,
|
||||
}).start(callback);
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number, grabDimensions: boolean = true) {
|
||||
const translatedValues = this.translate(x, y);
|
||||
if (grabDimensions) {
|
||||
this.grabDimensions();
|
||||
}
|
||||
this.state.position.setValue({x: translatedValues.x, y: translatedValues.y});
|
||||
}
|
||||
|
||||
grabDimensions() {
|
||||
const node = findDOMNode(this._ref);
|
||||
if (node != null && node instanceof Element) {
|
||||
this._height = node.clientHeight;
|
||||
this._width = node.clientWidth;
|
||||
}
|
||||
}
|
||||
|
||||
translate(x: number, y: number) {
|
||||
const {maxX, maxY} = this.props;
|
||||
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
} else if (x > maxX - this._width) {
|
||||
x = maxX - this._width;
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
} else if (y > maxY - this._height) {
|
||||
y = maxY - this._height;
|
||||
}
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {dragging, position} = this.state;
|
||||
const {className, children} = this.props;
|
||||
|
||||
const inputRange = [0, 1];
|
||||
const outputRange = ['0px', '1px'];
|
||||
const style = Animated.accelerate({
|
||||
pointerEvents: dragging ? 'none' : 'auto',
|
||||
transform: [
|
||||
{translateX: position.x.interpolate({inputRange, outputRange})},
|
||||
{translateY: position.y.interpolate({inputRange, outputRange})},
|
||||
],
|
||||
...this.props.style,
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.div ref={this.handleSetRef} className={className} onMouseDown={this.handleMouseDown} style={style}>
|
||||
{children}
|
||||
</Animated.div>
|
||||
);
|
||||
}
|
||||
|
||||
handleSetRef(ref: Animated.div) {
|
||||
this._ref = ref;
|
||||
}
|
||||
|
||||
handleMouseDown(e: MouseEvent) {
|
||||
const {dragAnywhere, selector} = this.props;
|
||||
const {position} = this.state;
|
||||
// $FlowFixMe: flow doesn't know about matches
|
||||
if (e.button === MouseButtons.PRIMARY && (dragAnywhere || e.target.matches(selector))) {
|
||||
this.grabDimensions();
|
||||
this._dragStart = {x: e.clientX, y: e.clientY};
|
||||
this._offsetX = e.clientX - position.x._value;
|
||||
this._offsetY = e.clientY - position.y._value;
|
||||
window.addEventListener('mousemove', this.handleMouseMove);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
const {onDragStart, onDrag} = this.props;
|
||||
let {dragging, dragging: wasDragging} = this.state;
|
||||
|
||||
// If they've mouse-downed and moved over DRAG_START_THRESHOLD pixels, we can switch into dragging mode.
|
||||
if (!dragging && distanceBetweenSq(this._dragStart, {x: e.clientX, y: e.clientY}) > DRAG_START_THRESHOLD_SQ) {
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.animateToPosition(
|
||||
e.clientX - this._offsetX,
|
||||
e.clientY - this._offsetY,
|
||||
{
|
||||
tension: 80,
|
||||
friction: 8,
|
||||
},
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
this.setState({dragging}, () => {
|
||||
if (!wasDragging) {
|
||||
onDragStart && onDragStart(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
onDrag && onDrag(e.clientX, e.clientY);
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseUp(e: MouseEvent) {
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
|
||||
// Only emit dragEnd event if we were dragging.
|
||||
if (this.state.dragging) {
|
||||
this.setState({dragging: false}, () => {
|
||||
const {onDragEnd} = this.props;
|
||||
onDragEnd && onDragEnd(e.clientX, e.clientY);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Draggable;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Draggable.js
|
230
509bba0_unpacked/discord_app/uikit/EmailVerificationModal.js
Executable file
230
509bba0_unpacked/discord_app/uikit/EmailVerificationModal.js
Executable file
|
@ -0,0 +1,230 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import bodymovin from 'bodymovin';
|
||||
import lodash from 'lodash';
|
||||
import Modal from './Modal';
|
||||
import Button, {ButtonColors, ButtonSizes, ButtonLooks} from './Button';
|
||||
import {FormItem} from './form';
|
||||
import TextInput from './TextInput';
|
||||
import './EmailVerificationModal.styl';
|
||||
import i18n from '../i18n';
|
||||
import {NOOP} from '../Constants';
|
||||
import {Timeout} from '../lib/timers';
|
||||
|
||||
type Props = {
|
||||
email: ?string,
|
||||
emailError: string,
|
||||
passwordError: string,
|
||||
canResend: boolean,
|
||||
submitting: boolean,
|
||||
onVerify: (email: string, password: string) => void,
|
||||
onResend: () => void,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
canResend: boolean,
|
||||
resent: boolean,
|
||||
shouldClose: boolean,
|
||||
email: string,
|
||||
password: string,
|
||||
};
|
||||
|
||||
class EmailVerificationModal extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
static defaultProps = {
|
||||
canResend: false,
|
||||
onVerify: (_email, _password) => {},
|
||||
onResend: NOOP,
|
||||
onClose: NOOP,
|
||||
};
|
||||
|
||||
_bodyMovinRef: HTMLElement;
|
||||
_animItem = null;
|
||||
_timeout = new Timeout();
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
canResend: props.canResend,
|
||||
resent: false,
|
||||
shouldClose: false,
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
lodash.bindAll(this, [
|
||||
'setBodyMovinRef',
|
||||
'handleVerify',
|
||||
'handleResendEmail',
|
||||
'handleChangeEmailClick',
|
||||
'handleEmailChange',
|
||||
'handlePasswordChange',
|
||||
'handleKeyPress',
|
||||
]);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// $FlowFixMe: flow doesnt understand require.ensure
|
||||
require.ensure([], require => {
|
||||
const animationData = require('../../discord_common/js/animations/app/verification/email.json');
|
||||
this._animItem = bodymovin.loadAnimation({
|
||||
container: this._bodyMovinRef,
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
animationData,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._timeout.stop();
|
||||
if (this._animItem != null) {
|
||||
this._animItem.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (nextProps.canResend !== this.state.canResend) {
|
||||
this.setState({canResend: nextProps.canResend});
|
||||
}
|
||||
}
|
||||
|
||||
renderInputs() {
|
||||
const {emailError, passwordError, submitting} = this.props;
|
||||
const {email, password} = this.state;
|
||||
return (
|
||||
<div className="container">
|
||||
<FormItem title={i18n.Messages.FORM_LABEL_EMAIL} className="margin-bottom-20">
|
||||
<TextInput
|
||||
value={email}
|
||||
error={emailError}
|
||||
onChange={this.handleEmailChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem title={i18n.Messages.FORM_LABEL_PASSWORD} className="margin-bottom-40">
|
||||
<TextInput
|
||||
type="password"
|
||||
value={password}
|
||||
error={passwordError}
|
||||
onChange={this.handlePasswordChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button
|
||||
className="full-width-button"
|
||||
size={ButtonSizes.LARGE}
|
||||
onClick={this.handleVerify}
|
||||
submitting={submitting}
|
||||
disabled={email.length === 0 || password.length === 0}>
|
||||
{i18n.Messages.VERIFY_ACCOUNT}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions() {
|
||||
return (
|
||||
<div className="container">
|
||||
<Button
|
||||
className="full-width-button margin-top-20"
|
||||
size={ButtonSizes.LARGE}
|
||||
look={ButtonLooks.INVERTED}
|
||||
color={ButtonColors.PRIMARY}
|
||||
onClick={this.handleResendEmail}>
|
||||
{i18n.Messages.RESEND_EMAIL}
|
||||
</Button>
|
||||
<Button
|
||||
className="full-width-button margin-top-20"
|
||||
look={ButtonLooks.LINK}
|
||||
color={ButtonColors.PRIMARY}
|
||||
onClick={this.handleChangeEmailClick}>
|
||||
{i18n.Messages.CHANGE_EMAIL}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {canResend, resent} = this.state;
|
||||
const {email} = this.props;
|
||||
|
||||
let body;
|
||||
if (resent && email != null) {
|
||||
body = i18n.Messages.VERIFY_EMAIL_BODY_RESENT.format({email});
|
||||
} else if (canResend) {
|
||||
body = i18n.Messages.VERIFY_EMAIL_BODY;
|
||||
} else {
|
||||
body = i18n.Messages.ENTER_EMAIL_BODY;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal className="ui-email-verification-modal flex-vertical flex-align-center flex-justify-center margin-top-60">
|
||||
<div className="animation-container" ref={this.setBodyMovinRef} />
|
||||
<div className="title margin-bottom-8">{i18n.Messages.VERIFY_BY_EMAIL}</div>
|
||||
<div className="body margin-bottom-20">{body}</div>
|
||||
{canResend ? this.renderActions() : this.renderInputs()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
setBodyMovinRef(ref: HTMLElement) {
|
||||
this._bodyMovinRef = ref;
|
||||
}
|
||||
|
||||
handleVerify() {
|
||||
const {onVerify} = this.props;
|
||||
onVerify(this.state.email, this.state.password);
|
||||
}
|
||||
|
||||
handleResendEmail() {
|
||||
const {onResend, onClose} = this.props;
|
||||
onResend();
|
||||
|
||||
if (!this.state.resent) {
|
||||
this.setState({resent: true, shouldClose: true});
|
||||
this._timeout.start(3000, () => {
|
||||
if (this.state.shouldClose) {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeEmailClick() {
|
||||
this.setState({canResend: false, resent: false, shouldClose: false});
|
||||
}
|
||||
|
||||
handleEmailChange(email: string) {
|
||||
this.setState({email});
|
||||
}
|
||||
|
||||
handlePasswordChange(password: string) {
|
||||
this.setState({password});
|
||||
}
|
||||
|
||||
handleKeyPress(e: KeyboardEvent) {
|
||||
const {email, password} = this.state;
|
||||
if (e.which === 13 && email.length > 0 && password.length > 0) {
|
||||
// Enter
|
||||
e.preventDefault();
|
||||
this.handleVerify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailVerificationModal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/EmailVerificationModal.js
|
82
509bba0_unpacked/discord_app/uikit/EmptyState.js
Executable file
82
509bba0_unpacked/discord_app/uikit/EmptyState.js
Executable file
|
@ -0,0 +1,82 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import {ThemeTypes} from '../Constants';
|
||||
import Styles from './EmptyState.mod.css';
|
||||
|
||||
type EmptyStateProps = {
|
||||
theme: string,
|
||||
className?: string,
|
||||
children?: ReactComponent | Array<ReactComponent>,
|
||||
};
|
||||
|
||||
type EmptyImageProps = {
|
||||
theme?: string,
|
||||
lightSrc: any,
|
||||
darkSrc: any,
|
||||
width: number,
|
||||
height: number,
|
||||
offsetX?: number,
|
||||
offsetY?: number,
|
||||
};
|
||||
|
||||
type EmptyTextProps = {
|
||||
children?: string,
|
||||
note?: string,
|
||||
style?: {[key: string]: string | number},
|
||||
};
|
||||
|
||||
export class EmptyStateImage extends React.PureComponent {
|
||||
props: EmptyImageProps;
|
||||
|
||||
render() {
|
||||
const {theme, lightSrc, darkSrc, width, height, offsetX, offsetY} = this.props;
|
||||
|
||||
return (
|
||||
<Flex.Child
|
||||
grow={0}
|
||||
className={classNames(Styles.image, 'margin-bottom-40')}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
marginLeft: offsetX,
|
||||
marginTop: offsetY,
|
||||
backgroundImage: `url(${theme === ThemeTypes.DARK ? darkSrc : lightSrc})`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const EmptyStateText = ({children, note, style}: EmptyTextProps) =>
|
||||
<Flex.Child grow={0} direction={Flex.Direction.VERTICAL} style={style}>
|
||||
<h4 className={Styles.title}>{children}</h4>
|
||||
{note && <div className={classNames(Styles.text, 'margin-top-8')}>{note}</div>}
|
||||
</Flex.Child>;
|
||||
|
||||
export default class EmptyState extends React.PureComponent {
|
||||
props: EmptyStateProps;
|
||||
|
||||
render() {
|
||||
const {children, theme, className} = this.props;
|
||||
|
||||
const childrenWithTheme = React.Children.map(children, child => React.cloneElement(child, {theme}));
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}
|
||||
className={classNames(Styles.wrapper, className)}>
|
||||
{childrenWithTheme}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/EmptyState.js
|
9
509bba0_unpacked/discord_app/uikit/EmptyState.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/EmptyState.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"wrapper":"wrapper-1-BJK5","image":"image-3Y4mXQ","title":"title-38OOBL","text":"text-1rLRsh"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/EmptyState.mod.css
|
||||
// module id = 2094
|
||||
// module chunks = 2
|
165
509bba0_unpacked/discord_app/uikit/Flex.js
Executable file
165
509bba0_unpacked/discord_app/uikit/Flex.js
Executable file
|
@ -0,0 +1,165 @@
|
|||
/* @flow */
|
||||
|
||||
import React, {PureComponent, Children} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Flex.mod.css';
|
||||
import flexStyles from '../styles/shared/Flex.mod.css';
|
||||
|
||||
export type FlexProps = {
|
||||
className?: string,
|
||||
direction: string,
|
||||
justify: string,
|
||||
align: string,
|
||||
wrap: string,
|
||||
shrink?: number | string,
|
||||
grow?: number | string,
|
||||
basis?: number | string,
|
||||
style?: {},
|
||||
children?: ReactElement,
|
||||
};
|
||||
|
||||
type DefaultFlexProps = {
|
||||
direction: string,
|
||||
justify: string,
|
||||
align: string,
|
||||
wrap: string,
|
||||
shrink?: number | string,
|
||||
grow?: number | string,
|
||||
basis?: number | string,
|
||||
style: Object,
|
||||
};
|
||||
|
||||
export type FlexChildProps = {
|
||||
className?: string,
|
||||
style?: any,
|
||||
shrink?: number | string,
|
||||
grow?: number | string,
|
||||
basis?: number | string,
|
||||
wrap?: boolean,
|
||||
children?: ReactElement,
|
||||
style: {},
|
||||
};
|
||||
|
||||
type DefaultFlexChildProps = {
|
||||
className: string,
|
||||
shrink: number,
|
||||
grow: number,
|
||||
basis: string,
|
||||
style: Object,
|
||||
wrap: boolean,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
VERTICAL: flexStyles.vertical,
|
||||
HORIZONTAL: styles.horizontal,
|
||||
HORIZONTAL_REVERSE: styles.horizontalReverse,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
START: flexStyles.justifyStart,
|
||||
END: flexStyles.justifyEnd,
|
||||
CENTER: flexStyles.justifyCenter,
|
||||
BETWEEN: flexStyles.justifyBetween,
|
||||
AROUND: flexStyles.justifyAround,
|
||||
};
|
||||
|
||||
const Align = {
|
||||
START: flexStyles.alignStart,
|
||||
END: flexStyles.alignEnd,
|
||||
CENTER: flexStyles.alignCenter,
|
||||
STRETCH: flexStyles.alignStretch,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NO_WRAP: flexStyles.noWrap,
|
||||
WRAP: flexStyles.wrap,
|
||||
WRAP_REVERSE: flexStyles.wrapReverse,
|
||||
};
|
||||
|
||||
class FlexChild extends PureComponent {
|
||||
props: FlexChildProps;
|
||||
static defaultProps: DefaultFlexChildProps = {
|
||||
className: styles.flexChild,
|
||||
shrink: 1,
|
||||
grow: 1,
|
||||
basis: 'auto',
|
||||
style: {},
|
||||
wrap: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
shrink: flexShrink,
|
||||
grow: flexGrow,
|
||||
basis: flexBasis,
|
||||
style,
|
||||
wrap,
|
||||
...props
|
||||
} = this.props;
|
||||
props.style = {flexGrow, flexShrink, flexBasis, ...style};
|
||||
props.className = className;
|
||||
|
||||
if (!wrap && typeof children !== 'string' && Children.count(children) === 1) {
|
||||
const child = Children.only(children);
|
||||
const {style: childStyle = {}} = child.props;
|
||||
// Merge style and className
|
||||
props.style = {...props.style, ...childStyle};
|
||||
props.className = classNames(child.props.className, className);
|
||||
return React.cloneElement(child, props);
|
||||
}
|
||||
return <div {...props}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Flex extends PureComponent {
|
||||
props: FlexProps;
|
||||
|
||||
static Child = FlexChild;
|
||||
static Direction = Direction;
|
||||
static Align = Align;
|
||||
static Justify = Justify;
|
||||
static Wrap = Wrap;
|
||||
|
||||
static defaultProps: DefaultFlexProps = {
|
||||
direction: Direction.HORIZONTAL,
|
||||
justify: Justify.START,
|
||||
align: Align.STRETCH,
|
||||
wrap: Wrap.NO_WRAP,
|
||||
shrink: 1,
|
||||
grow: 1,
|
||||
basis: 'auto',
|
||||
style: {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
direction,
|
||||
justify,
|
||||
align,
|
||||
wrap,
|
||||
shrink: flexShrink,
|
||||
grow: flexGrow,
|
||||
basis: flexBasis,
|
||||
style,
|
||||
...props
|
||||
} = this.props;
|
||||
const newStyle = {flexShrink, flexGrow, flexBasis, ...style};
|
||||
|
||||
return (
|
||||
<div style={newStyle} className={classNames(styles.flex, direction, justify, align, wrap, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Flex;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Flex.js
|
9
509bba0_unpacked/discord_app/uikit/Flex.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Flex.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"_flex":"flex-3B1Tl4","_horizontal":"horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ","_horizontalReverse":"horizontalReverse-k5PqxT flex-3B1Tl4 directionRowReverse-2eZTxP","flex":"flex-lFgbSz flex-3B1Tl4","horizontal":"horizontal-2BEEBe horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ","horizontalReverse":"horizontalReverse-2LanvO horizontalReverse-k5PqxT flex-3B1Tl4 directionRowReverse-2eZTxP","flexChild":"flexChild-1KGW5q"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Flex.mod.css
|
||||
// module id = 2095
|
||||
// module chunks = 2
|
62
509bba0_unpacked/discord_app/uikit/Grid.js
Executable file
62
509bba0_unpacked/discord_app/uikit/Grid.js
Executable file
|
@ -0,0 +1,62 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from './Flex';
|
||||
import Styles from './Grid.mod.css';
|
||||
|
||||
const Layout = {
|
||||
WRAP: 'wrap',
|
||||
STACK: 'stack',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: Array<any>,
|
||||
layout: string,
|
||||
columns: number,
|
||||
};
|
||||
|
||||
class Grid extends React.PureComponent {
|
||||
props: Props;
|
||||
static Layout: Object;
|
||||
static defaultProps = {
|
||||
children: [],
|
||||
layout: Layout.WRAP,
|
||||
itemPerRow: 4,
|
||||
};
|
||||
|
||||
renderChildren() {
|
||||
const {children, layout, columns} = this.props;
|
||||
let basis;
|
||||
switch (layout) {
|
||||
case Layout.STACK:
|
||||
basis = '100%';
|
||||
break;
|
||||
case Layout.WRAP:
|
||||
basis = `${1 / columns * 100}%`;
|
||||
break;
|
||||
}
|
||||
|
||||
return children.map((child, i) =>
|
||||
<Flex.Child className={Styles.tile} key={i} basis={basis} grow={0}>
|
||||
{child}
|
||||
</Flex.Child>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex className={Styles.grid} wrap={Flex.Wrap.WRAP}>
|
||||
{this.renderChildren()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Grid.Layout = Layout;
|
||||
|
||||
export default Grid;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Grid.js
|
9
509bba0_unpacked/discord_app/uikit/Grid.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Grid.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"grid":"grid-3Iq89E","tile":"tile-3G1_cN"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Grid.mod.css
|
||||
// module id = 2096
|
||||
// module chunks = 2
|
61
509bba0_unpacked/discord_app/uikit/GuildNSFW.js
Executable file
61
509bba0_unpacked/discord_app/uikit/GuildNSFW.js
Executable file
|
@ -0,0 +1,61 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import i18n from '../i18n';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import Button, {ButtonSizes, ButtonColors, ButtonLooks} from './Button';
|
||||
import {NOOP} from '../Constants';
|
||||
import Styles from './GuildNSFW.mod.css';
|
||||
|
||||
type Props = {
|
||||
onAgree: Function,
|
||||
onDisagree: Function,
|
||||
};
|
||||
|
||||
class GuildNSFW extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
onAgree: NOOP,
|
||||
onDisagree: NOOP,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex
|
||||
className={Styles.nsfw}
|
||||
justify={Flex.Justify.CENTER}
|
||||
align={Flex.Align.CENTER}
|
||||
direction={Flex.Direction.VERTICAL}>
|
||||
<div className={classNames(Styles.image, 'margin-bottom-20')} />
|
||||
<div className={classNames(Styles.title, 'margin-bottom-8')}>
|
||||
{i18n.Messages.NSFW_TITLE}
|
||||
</div>
|
||||
<div className={classNames(Styles.description, 'margin-bottom-20')}>
|
||||
{i18n.Messages.NSFW_DESCRIPTION}
|
||||
</div>
|
||||
<div className={classNames(Styles.separator, 'margin-bottom-40')} />
|
||||
<Flex justify={Flex.Justify.CENTER} align={Flex.Align.CENTER} grow={0}>
|
||||
<Button
|
||||
className={Styles.action}
|
||||
size={ButtonSizes.LARGE}
|
||||
look={ButtonLooks.OUTLINED}
|
||||
color={ButtonColors.PRIMARY}
|
||||
onClick={this.props.onDisagree}>
|
||||
{i18n.Messages.NSFW_DECLINE}
|
||||
</Button>
|
||||
<Button className={Styles.actionRed} size={ButtonSizes.LARGE} onClick={this.props.onAgree}>
|
||||
{i18n.Messages.NSFW_ACCEPT}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GuildNSFW;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/GuildNSFW.js
|
9
509bba0_unpacked/discord_app/uikit/GuildNSFW.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/GuildNSFW.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"nsfw":"nsfw-1APjPl","image":"image-2XJctU","title":"title-2mmhgH","description":"description-15n-1L","separator":"separator-3Ozi37","action":"action-2mLTX4","actionRed":"actionRed-sT3Bw5 action-2mLTX4"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/GuildNSFW.mod.css
|
||||
// module id = 2097
|
||||
// module chunks = 2
|
46
509bba0_unpacked/discord_app/uikit/HoverRoll.js
Executable file
46
509bba0_unpacked/discord_app/uikit/HoverRoll.js
Executable file
|
@ -0,0 +1,46 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Styles from './HoverRoll.mod.css';
|
||||
|
||||
type Props = {
|
||||
tag: 'a' | 'div' | 'abbr',
|
||||
hoverText?: string,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
tag: 'a' | 'div' | 'abbr',
|
||||
};
|
||||
|
||||
class HoverRoll extends React.PureComponent {
|
||||
/* Flow */
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps;
|
||||
|
||||
render() {
|
||||
const {tag: Tag, children, hoverText, ...props} = this.props;
|
||||
|
||||
return (
|
||||
<div {...props} className={Styles.hoverRoll}>
|
||||
<Tag className={Styles.default}>
|
||||
{children}
|
||||
</Tag>
|
||||
<Tag className={Styles.hovered}>
|
||||
{hoverText}
|
||||
</Tag>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HoverRoll.defaultProps = {
|
||||
tag: 'div',
|
||||
};
|
||||
|
||||
export default HoverRoll;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/HoverRoll.js
|
9
509bba0_unpacked/discord_app/uikit/HoverRoll.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/HoverRoll.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"hoverRoll":"hoverRoll-O9_zYp","default":"default-2ICJ3w","hovered":"hovered-1gq2YI"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/HoverRoll.mod.css
|
||||
// module id = 2098
|
||||
// module chunks = 2
|
106
509bba0_unpacked/discord_app/uikit/IconButton.js
Executable file
106
509bba0_unpacked/discord_app/uikit/IconButton.js
Executable file
|
@ -0,0 +1,106 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
import Styles from './IconButton.mod.css';
|
||||
|
||||
const IconSizes = {
|
||||
XSMALL: Styles.xsmall,
|
||||
SMALL: Styles.small,
|
||||
MEDIUM: Styles.medium,
|
||||
LARGE: Styles.large,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
size: string,
|
||||
src: string,
|
||||
srcHover?: string,
|
||||
disabled: boolean,
|
||||
className?: string,
|
||||
onMouseOver?: Function,
|
||||
onMouseOut?: Function,
|
||||
onClick?: Function,
|
||||
onContextMenu?: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hovered: boolean,
|
||||
};
|
||||
|
||||
const Modes = {
|
||||
DEFAULT: 'default',
|
||||
STATIC: 'static',
|
||||
};
|
||||
|
||||
class IconButton extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps = {
|
||||
size: IconSizes.MEDIUM,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
static Sizes = IconSizes;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
|
||||
(this: any).handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
(this: any).handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
(this: any).getMode = this.getMode.bind(this);
|
||||
}
|
||||
|
||||
getMode() {
|
||||
if (this.props.srcHover != null) {
|
||||
return Modes.STATIC;
|
||||
}
|
||||
return Modes.DEFAULT;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {size, src, srcHover, className, ...props} = this.props;
|
||||
const {hovered} = this.state;
|
||||
|
||||
const iconSrc = hovered && srcHover ? srcHover : src;
|
||||
|
||||
const style = {
|
||||
backgroundImage: `url('${iconSrc}')`,
|
||||
};
|
||||
|
||||
const mode = this.getMode();
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames(getClass(Styles, 'iconButton', mode), className, size)}
|
||||
style={style}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
if (!this.state.hovered) {
|
||||
this.setState({hovered: true});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
if (this.state.hovered) {
|
||||
this.setState({hovered: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default IconButton;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/IconButton.js
|
9
509bba0_unpacked/discord_app/uikit/IconButton.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/IconButton.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"iconButton":"iconButton-3mKjyp","iconButtonDefault":"iconButtonDefault-3QZh-A iconButton-3mKjyp","iconButtonStatic":"iconButtonStatic-BQvIo6 iconButton-3mKjyp","large":"large-2EphbG","medium":"medium-C4dspv","small":"small-1ChI_f","xsmall":"xsmall-3Nz9ph"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/IconButton.mod.css
|
||||
// module id = 2099
|
||||
// module chunks = 2
|
95
509bba0_unpacked/discord_app/uikit/Integration.js
Executable file
95
509bba0_unpacked/discord_app/uikit/Integration.js
Executable file
|
@ -0,0 +1,95 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import i18n from '../i18n';
|
||||
import FormText from './form/FormText';
|
||||
import Card, {Types} from './Card';
|
||||
import Checkbox from './Checkbox';
|
||||
import Flex from './Flex';
|
||||
import {Colors, ThemeTypes} from '../Constants';
|
||||
import './Integration.styl';
|
||||
import '../styles/elevations.styl';
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
label?: string,
|
||||
labelUrl?: string,
|
||||
color: string,
|
||||
icon: string,
|
||||
enabled: boolean,
|
||||
syncing: boolean,
|
||||
theme: string,
|
||||
handleSyncToggle: Function,
|
||||
children: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
name: string,
|
||||
color: string,
|
||||
icon: ?ReactComponent,
|
||||
};
|
||||
|
||||
class Integration extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
name: 'Twitch',
|
||||
color: Colors.TWITCH,
|
||||
icon: null,
|
||||
};
|
||||
|
||||
renderHeader() {
|
||||
const {color, name, label, labelUrl, icon, enabled, syncing, handleSyncToggle, children} = this.props;
|
||||
|
||||
const labelJSX = labelUrl && label
|
||||
? <a href={`//${labelUrl}`} className="url" target="_blank">
|
||||
<FormText className="ui-integration-header-text" type={FormText.Types.DESCRIPTION}>{label}</FormText>
|
||||
</a>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={classNames('ui-integration-header', {simple: children == null})}
|
||||
justify={Flex.Justify.BETWEEN}
|
||||
style={{backgroundColor: color}}>
|
||||
<Flex>
|
||||
<img className="ui-integration-icon no-user-drag" src={icon} />
|
||||
<div>
|
||||
<FormText className="ui-integration-header-text" type={FormText.Types.DEFAULT}>{name}</FormText>
|
||||
{labelJSX}
|
||||
</div>
|
||||
</Flex>
|
||||
<Flex align={Flex.Align.CENTER} grow={0}>
|
||||
<Checkbox color={color} value={enabled} disabled={syncing} onChange={handleSyncToggle}>
|
||||
<span className="ui-integration-header-sync">
|
||||
{i18n.Messages.SYNC}
|
||||
</span>
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, theme} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="ui-integration elevation-low margin-bottom-8"
|
||||
type={Types.CUSTOM}
|
||||
style={{borderWidth: 0, backgroundColor: theme === ThemeTypes.DARK ? Colors.PRIMARY_700 : Colors.PRIMARY_100}}>
|
||||
{this.renderHeader()}
|
||||
<div className="ui-integration-body">
|
||||
{children}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Integration;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Integration.js
|
179
509bba0_unpacked/discord_app/uikit/JoinCallButton.js
Executable file
179
509bba0_unpacked/discord_app/uikit/JoinCallButton.js
Executable file
|
@ -0,0 +1,179 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import i18n from '../i18n';
|
||||
import Flex from './Flex';
|
||||
import Animated from '../lib/animated';
|
||||
import Button, {ButtonSizes, ButtonColors} from './Button';
|
||||
import './JoinCallButton.styl';
|
||||
|
||||
const Type = {
|
||||
VOICE: 'voice',
|
||||
VIDEO: 'video',
|
||||
};
|
||||
|
||||
const SPRING_SETTINGS = {
|
||||
friction: 16,
|
||||
tension: 400,
|
||||
};
|
||||
|
||||
type CallButtonProps = {
|
||||
children: React.Component,
|
||||
};
|
||||
|
||||
class CallButton extends React.PureComponent {
|
||||
constructor(props: CallButtonProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex
|
||||
className="call-button margin-reset"
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}
|
||||
onClick={this.handleClick}>
|
||||
{this.props.children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const {type, onClick} = this.props;
|
||||
onClick && onClick(type);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
onClick?: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hovered: boolean,
|
||||
offsetX: Animated.Value,
|
||||
};
|
||||
|
||||
class JoinCallButton extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps = {
|
||||
type: Type.VOICE,
|
||||
};
|
||||
static Type = Type;
|
||||
|
||||
_width = 0;
|
||||
_left = 0;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hovered: false,
|
||||
offsetX: new Animated.Value(0),
|
||||
};
|
||||
|
||||
lodash.bindAll(this, ['handleMouseMove', 'handleMouseLeave']);
|
||||
}
|
||||
|
||||
renderVoiceButton() {
|
||||
return (
|
||||
<Button
|
||||
className="ui-join-voice-call-button"
|
||||
size={ButtonSizes.SMALL}
|
||||
color={ButtonColors.GREEN}
|
||||
onClick={this.props.onClick}>
|
||||
{i18n.Messages.JOIN_CALL}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
renderUnderlay() {
|
||||
const {hovered, offsetX} = this.state;
|
||||
|
||||
const style = {
|
||||
transform: [
|
||||
{
|
||||
translateX: offsetX.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0px', '1px'],
|
||||
}),
|
||||
},
|
||||
],
|
||||
opacity: hovered ? 1 : 0,
|
||||
};
|
||||
|
||||
return <Animated.div className="underlay" style={style} />;
|
||||
}
|
||||
|
||||
renderVideoButton() {
|
||||
return (
|
||||
<div
|
||||
className="ui-join-video-call-button"
|
||||
onMouseMove={this.handleMouseMove}
|
||||
onMouseLeave={this.handleMouseLeave}>
|
||||
{this.renderUnderlay()}
|
||||
<Flex className="inner" align={Flex.Align.CENTER} justify={Flex.Justify.CENTER}>
|
||||
<CallButton type={Type.VIDEO} onClick={this.props.onClick}>
|
||||
<img className="icon" src={require('../images/calls/ic_videocam_white_16px.svg')} />
|
||||
<div className="button-text">{i18n.Messages.VIDEO}</div>
|
||||
</CallButton>
|
||||
<div className="call-divider" />
|
||||
<CallButton type={Type.VOICE} onClick={this.props.onClick}>
|
||||
<div className="button-text">{i18n.Messages.VOICE}</div>
|
||||
<img className="icon" src={require('../images/calls/ic_mic_white_16px.svg')} />
|
||||
</CallButton>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.type === Type.VOICE) {
|
||||
return this.renderVoiceButton();
|
||||
}
|
||||
|
||||
return this.renderVideoButton();
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleMouseMove(e: MouseEvent) {
|
||||
const x = e.clientX;
|
||||
|
||||
if (this._width == 0) {
|
||||
const domNode = findDOMNode(this);
|
||||
if (domNode == null || domNode instanceof Text) {
|
||||
throw new Error('[UIKit]JoinCallButton.handleMouseOver(): assert failed: domNode instanceof Element');
|
||||
}
|
||||
this._left = domNode.getBoundingClientRect().left;
|
||||
this._width = domNode.getBoundingClientRect().width;
|
||||
}
|
||||
|
||||
const calculatedX = Math.min(this._width / 2 - 4, Math.max(0, x - this._left - this._width / 4));
|
||||
|
||||
Animated.spring(this.state.offsetX, {
|
||||
toValue: calculatedX,
|
||||
...SPRING_SETTINGS,
|
||||
}).start();
|
||||
|
||||
if (!this.state.hovered) {
|
||||
this.setState({hovered: true});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
this.setState({hovered: false});
|
||||
}
|
||||
}
|
||||
|
||||
export default JoinCallButton;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/JoinCallButton.js
|
137
509bba0_unpacked/discord_app/uikit/KeyRecorder.js
Executable file
137
509bba0_unpacked/discord_app/uikit/KeyRecorder.js
Executable file
|
@ -0,0 +1,137 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Combokeys from 'combokeys';
|
||||
import Button, {ButtonLooks, ButtonColors, ButtonSizes} from './Button';
|
||||
import Flex from './Flex';
|
||||
import {toString} from '../utils/web/KeyboardUtils';
|
||||
import i18n from '../i18n';
|
||||
import './KeyRecorder.styl';
|
||||
import '../styles/shared/input_button.styl';
|
||||
|
||||
export const RecordModes = {
|
||||
DEFAULT: 'DEFAULT',
|
||||
RECORDING: 'RECORDING',
|
||||
};
|
||||
|
||||
function preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
mode: string,
|
||||
onClick: Function,
|
||||
disabled?: boolean,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
class KeyRecorder extends React.PureComponent {
|
||||
props: Props;
|
||||
_input: HTMLInputElement;
|
||||
|
||||
componentDidMount() {
|
||||
const {onChange} = this.props;
|
||||
if (onChange) {
|
||||
const combokeys = new Combokeys(this._input);
|
||||
combokeys.handleKey = onChange;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {mode} = this.props;
|
||||
const {mode: prevMode} = prevProps;
|
||||
if (prevMode === mode) {
|
||||
return;
|
||||
}
|
||||
const {_input} = this;
|
||||
const {activeElement} = document;
|
||||
if (mode === RecordModes.DEFAULT && _input === activeElement) {
|
||||
_input.blur();
|
||||
}
|
||||
if (mode === RecordModes.RECORDING && _input !== activeElement) {
|
||||
_input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
setInputRef = (input: HTMLInputElement) => {
|
||||
this._input = input;
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
if (this.props.mode === RecordModes.RECORDING) {
|
||||
const {onClick} = this.props;
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
if (this.props.mode === RecordModes.DEFAULT) {
|
||||
const {onClick} = this.props;
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
|
||||
handleClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
const {onClick} = this.props;
|
||||
onClick();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {mode, value, disabled} = this.props;
|
||||
const prettyValue = toString(value, true);
|
||||
let buttonMessage;
|
||||
if (mode === RecordModes.RECORDING) {
|
||||
buttonMessage = i18n.Messages.SHORTCUT_RECORDER_BUTTON_RECORDING;
|
||||
} else if (value.length === 0) {
|
||||
buttonMessage = i18n.Messages.SHORTCUT_RECORDER_BUTTON;
|
||||
} else {
|
||||
buttonMessage = i18n.Messages.SHORTCUT_RECORDER_BUTTON_EDIT;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={classNames('ui-key-recorder', 'ui-input-button', mode.toLowerCase(), {
|
||||
'has-value': mode === RecordModes.DEFAULT && value.length > 0,
|
||||
disabled,
|
||||
})}>
|
||||
<Flex className="layout">
|
||||
<Flex.Child className="input">
|
||||
<input
|
||||
placeholder={i18n.Messages.SHORTCUT_RECORDER_NO_BIND}
|
||||
type="text"
|
||||
ref={this.setInputRef}
|
||||
readOnly
|
||||
value={prettyValue}
|
||||
onKeyDown={preventDefault}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Flex.Child>
|
||||
<Flex shrink={1} grow={0} style={{margin: 0}}>
|
||||
<Button
|
||||
className="ui-input-button-ui-button key-recorder-button"
|
||||
disabled={disabled}
|
||||
onMouseDown={preventDefault}
|
||||
onMouseUp={this.handleClick}
|
||||
size={ButtonSizes.MIN}
|
||||
color={ButtonColors.GREY}
|
||||
look={ButtonLooks.GHOST}>
|
||||
<span className="key-recorder-button-text">{buttonMessage}</span>
|
||||
<span className="key-recorder-edit-icon" />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyRecorder;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/KeyRecorder.js
|
90
509bba0_unpacked/discord_app/uikit/Layout.js
Executable file
90
509bba0_unpacked/discord_app/uikit/Layout.js
Executable file
|
@ -0,0 +1,90 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Scroller from './Scroller';
|
||||
import Flex from './Flex';
|
||||
import type {FlexProps} from './Flex';
|
||||
import {ThemeTypes, Colors} from '../Constants';
|
||||
import styles from './Layout.mod.css';
|
||||
|
||||
// Extends FlexChildProps with one additional props (scrollable)
|
||||
type SidebarProps = {
|
||||
className?: string,
|
||||
style?: any,
|
||||
theme: string,
|
||||
shrink?: number | string,
|
||||
grow?: number | string,
|
||||
basis?: number | string,
|
||||
wrap?: boolean,
|
||||
children?: ReactElement,
|
||||
style: {},
|
||||
scrollable?: boolean,
|
||||
};
|
||||
|
||||
export class Sidebar extends React.PureComponent {
|
||||
props: SidebarProps;
|
||||
|
||||
static defaultProps = {
|
||||
basis: 232,
|
||||
grow: 0,
|
||||
shrink: 0,
|
||||
className: styles.sidebar,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {scrollable, theme, ...props} = this.props;
|
||||
|
||||
const backgroundColor = theme === ThemeTypes.LIGHT ? Colors.WHITE : Colors.PRIMARY_600;
|
||||
|
||||
if (scrollable) {
|
||||
return (
|
||||
<aside className={styles.sidebarScrollable}>
|
||||
<Scroller fade theme={Scroller.Themes.GHOST} backgroundColor={backgroundColor} className={styles.scroller}>
|
||||
<Flex.Child {...props} wrap />
|
||||
</Scroller>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
return <Flex.Child {...props} wrap />;
|
||||
}
|
||||
}
|
||||
|
||||
export class Content extends React.PureComponent {
|
||||
static defaultProps = {
|
||||
className: styles.content,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Flex.Child wrap {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Layout extends React.PureComponent {
|
||||
props: FlexProps;
|
||||
static defaultProps = {
|
||||
className: styles.layout,
|
||||
direction: Flex.Direction.HORIZONTAL,
|
||||
justify: Flex.Justify.START,
|
||||
align: Flex.Align.START,
|
||||
wrap: Flex.Wrap.NO_WRAP,
|
||||
};
|
||||
|
||||
static Direction = Flex.Direction;
|
||||
static Justify = Flex.Justify;
|
||||
static Align = Flex.Align;
|
||||
static Wrap = Flex.Wrap;
|
||||
|
||||
static Sidebar = Sidebar;
|
||||
static Content = Content;
|
||||
|
||||
render() {
|
||||
return <Flex {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Layout.js
|
9
509bba0_unpacked/discord_app/uikit/Layout.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Layout.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"sidebarScrollable":"sidebarScrollable-3YmeW_","content":"content-1orzGj","scroller":"scroller-2C0yyO"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Layout.mod.css
|
||||
// module id = 2100
|
||||
// module chunks = 2
|
59
509bba0_unpacked/discord_app/uikit/Modal.js
Executable file
59
509bba0_unpacked/discord_app/uikit/Modal.js
Executable file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {ThemeTypes} from '../Constants';
|
||||
import styles from './Modal.mod.css';
|
||||
|
||||
type ModalProps = {
|
||||
className?: string,
|
||||
children: any,
|
||||
theme: string,
|
||||
};
|
||||
|
||||
type ModalBlockProps = {
|
||||
className?: string,
|
||||
children: any,
|
||||
};
|
||||
|
||||
class Content extends React.PureComponent {
|
||||
props: ModalBlockProps;
|
||||
|
||||
render() {
|
||||
const {className, children} = this.props;
|
||||
return <div className={classNames(styles.content, className)}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Footer extends React.PureComponent {
|
||||
props: ModalBlockProps;
|
||||
|
||||
render() {
|
||||
const {className, children} = this.props;
|
||||
return <div className={classNames(styles.footer, className)}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Modal extends React.PureComponent {
|
||||
props: ModalProps;
|
||||
static Footer = Footer;
|
||||
static Content = Content;
|
||||
|
||||
render() {
|
||||
const {className, children, theme} = this.props;
|
||||
const themeClass = classNames({'theme-light': theme === ThemeTypes.LIGHT, 'theme-dark': theme === ThemeTypes.DARK});
|
||||
|
||||
return (
|
||||
<div className={themeClass}>
|
||||
<div className={classNames(styles.modal, className)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Modal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Modal.js
|
9
509bba0_unpacked/discord_app/uikit/Modal.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Modal.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"modal":"modal-3HOjGZ","content":"content-1Cut5s","footer":"footer-1PYmcw"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Modal.mod.css
|
||||
// module id = 2101
|
||||
// module chunks = 2
|
95
509bba0_unpacked/discord_app/uikit/NewTermsModal.js
Executable file
95
509bba0_unpacked/discord_app/uikit/NewTermsModal.js
Executable file
|
@ -0,0 +1,95 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import Button from './Button';
|
||||
import Checkbox from './Checkbox';
|
||||
import FormText from './form/FormText';
|
||||
import FormDivider from './form/FormDivider';
|
||||
import i18n from '../i18n';
|
||||
import './NewTermsModal.styl';
|
||||
import {NOOP, MARKETING_URLS} from '../Constants';
|
||||
|
||||
type Props = {
|
||||
onAccept: Function,
|
||||
accepting: boolean,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
type State = {
|
||||
checked: boolean,
|
||||
};
|
||||
|
||||
class NewTermsModal extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps = {
|
||||
onAccept: NOOP,
|
||||
accepting: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
checked: false,
|
||||
};
|
||||
|
||||
(this: any).handleCheck = this.handleCheck.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, accepting, onAccept} = this.props;
|
||||
const {checked} = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-new-terms-modal', 'light-elevation-border-high', 'flex-vertical', className)}>
|
||||
<div className="title margin-horizontal-40">{i18n.Messages.NEW_TERMS_TITLE}</div>
|
||||
<FormDivider />
|
||||
<FormText className="margin-horizontal-40 margin-top-20">{i18n.Messages.NEW_TERMS_DESCRIPTION}</FormText>
|
||||
<Flex
|
||||
className="margin-horizontal-40 margin-top-20 margin-bottom-20"
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}>
|
||||
<Flex direction={Flex.Direction.VERTICAL}>
|
||||
<div className="margin-top-8 margin-bottom-8">
|
||||
{i18n.Messages.TERMS_OF_SERVICE.format({url: MARKETING_URLS.TERMS})}
|
||||
</div>
|
||||
<div className="margin-top-8 margin-bottom-8">
|
||||
{i18n.Messages.PRIVACY_POLICY.format({url: MARKETING_URLS.PRIVACY})}
|
||||
</div>
|
||||
</Flex>
|
||||
<img src={require('../images/img_new_tos_light_theme.svg')} />
|
||||
</Flex>
|
||||
<FormDivider />
|
||||
<div className="margin-horizontal-40 margin-top-20 margin-bottom-20">
|
||||
<Checkbox onChange={this.handleCheck} value={checked}>
|
||||
<div className="ack">{i18n.Messages.NEW_TERMS_ACK}</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<FormDivider />
|
||||
<Flex className="button-container" justify={Flex.Justify.END}>
|
||||
<Button
|
||||
className="margin-top-20 margin-bottom-20"
|
||||
disabled={!checked}
|
||||
submitting={accepting}
|
||||
onClick={onAccept}>
|
||||
{i18n.Messages.NEW_TERMS_CONTINUE}
|
||||
</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleCheck() {
|
||||
this.setState({checked: !this.state.checked});
|
||||
}
|
||||
}
|
||||
|
||||
export default NewTermsModal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/NewTermsModal.js
|
98
509bba0_unpacked/discord_app/uikit/PermissionOverride.js
Executable file
98
509bba0_unpacked/discord_app/uikit/PermissionOverride.js
Executable file
|
@ -0,0 +1,98 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import ButtonGroup from './ButtonGroup';
|
||||
import classNames from 'classnames';
|
||||
import './PermissionOverride.styl';
|
||||
|
||||
export const PermissionOverrideTypes = {
|
||||
DENY: 'DENY',
|
||||
PASSTHROUGH: 'PASSTHROUGH',
|
||||
ALLOW: 'ALLOW',
|
||||
};
|
||||
|
||||
export type PermissionOverrideType = 'DENY' | 'PASSTHROUGH' | 'ALLOW';
|
||||
|
||||
type Props = {
|
||||
value?: PermissionOverrideType,
|
||||
disabled?: boolean,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
value: PermissionOverrideType,
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
class PermissionOverride extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
value: PermissionOverrideTypes.PASSTHROUGH,
|
||||
disabled: false,
|
||||
};
|
||||
static Types = PermissionOverrideTypes;
|
||||
|
||||
keys = Object.keys(PermissionOverrideTypes);
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleToggle = this.handleToggle.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, disabled} = this.props;
|
||||
|
||||
const buttons = this.keys.map(type => {
|
||||
const selected = value === type;
|
||||
|
||||
let imgSrc;
|
||||
let className;
|
||||
switch (type) {
|
||||
case PermissionOverrideTypes.DENY:
|
||||
imgSrc = selected
|
||||
? require('../images/ic_off_white_18px.svg')
|
||||
: require('../images/ic_off_status_red_18px.svg');
|
||||
className = PermissionOverrideTypes.DENY;
|
||||
break;
|
||||
case PermissionOverrideTypes.ALLOW:
|
||||
imgSrc = selected
|
||||
? require('../images/ic_check_white_18px.svg')
|
||||
: require('../images/ic_check_status_green_18px.svg');
|
||||
className = PermissionOverrideTypes.ALLOW;
|
||||
break;
|
||||
case PermissionOverrideTypes.PASSTHROUGH:
|
||||
default:
|
||||
imgSrc = selected
|
||||
? require('../images/ic_neutral_white_18px.svg')
|
||||
: require('../images/ic_neutral_status_grey_18px.svg');
|
||||
className = PermissionOverrideTypes.PASSTHROUGH;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
content: <img src={imgSrc} className="override-item no-user-drag" />,
|
||||
className: classNames('ui-permission-override-box', className.toLowerCase(), {selected}),
|
||||
onClick: this.handleToggle.bind(this, type),
|
||||
};
|
||||
});
|
||||
|
||||
return <ButtonGroup buttons={buttons} disabled={disabled} />;
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleToggle(type: PermissionOverrideType) {
|
||||
const {value, onChange} = this.props;
|
||||
if (value !== type) {
|
||||
onChange(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PermissionOverride;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PermissionOverride.js
|
72
509bba0_unpacked/discord_app/uikit/PermissionOverrideItem.js
Executable file
72
509bba0_unpacked/discord_app/uikit/PermissionOverrideItem.js
Executable file
|
@ -0,0 +1,72 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PermissionOverride from './PermissionOverride';
|
||||
import FormTitle, {Tags} from './form/FormTitle';
|
||||
import FormText, {Types} from './form/FormText';
|
||||
import FormDivider from './form/FormDivider';
|
||||
import Flex from './Flex';
|
||||
import type {PermissionOverrideType} from './PermissionOverride';
|
||||
|
||||
export const PermissionOverrideTypes = PermissionOverride.Types;
|
||||
|
||||
type Props = {
|
||||
value?: PermissionOverrideType,
|
||||
disabled?: boolean,
|
||||
onChange: Function,
|
||||
hideBorder?: boolean,
|
||||
className?: string,
|
||||
note?: any,
|
||||
style?: Object,
|
||||
children?: string,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
disabled: boolean,
|
||||
hideBorder: boolean,
|
||||
className: string,
|
||||
};
|
||||
|
||||
class PermissionOverrideItem extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
disabled: false,
|
||||
hideBorder: false,
|
||||
className: 'margin-bottom-20',
|
||||
};
|
||||
static Types = PermissionOverride.Types;
|
||||
|
||||
render() {
|
||||
const {value, onChange, disabled, hideBorder, children, className, note, style} = this.props;
|
||||
return (
|
||||
<Flex
|
||||
style={style}
|
||||
className={classNames('ui-permission-override-item', className)}
|
||||
direction={Flex.Direction.VERTICAL}>
|
||||
<Flex>
|
||||
<Flex.Child>
|
||||
<FormTitle className="margin-reset" tag={Tags.H3} disabled={disabled}>
|
||||
{children}
|
||||
</FormTitle>
|
||||
</Flex.Child>
|
||||
<Flex.Child grow={0} shrink={0}>
|
||||
<PermissionOverride value={value} onChange={onChange} disabled={disabled} />
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
{note &&
|
||||
<Flex.Child className="margin-top-4">
|
||||
<FormText type={Types.DESCRIPTION}>{note}</FormText>
|
||||
</Flex.Child>}
|
||||
{!hideBorder && <Flex.Child className="margin-top-20"><FormDivider /></Flex.Child>}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PermissionOverrideItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PermissionOverrideItem.js
|
209
509bba0_unpacked/discord_app/uikit/PhoneField.js
Executable file
209
509bba0_unpacked/discord_app/uikit/PhoneField.js
Executable file
|
@ -0,0 +1,209 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import lodash from 'lodash';
|
||||
import fuzzysearch from 'fuzzysearch';
|
||||
import CountryCodes from '../data/country-codes.json';
|
||||
import Button, {ButtonSizes} from './Button';
|
||||
import ExpandIcon from './icons/ExpandIcon';
|
||||
import Scroller from './Scroller';
|
||||
import PopoutList from './PopoutList';
|
||||
import Flex from './Flex';
|
||||
import i18n from '../i18n';
|
||||
import './PhoneField.styl';
|
||||
|
||||
type PopoutProps = {
|
||||
onClick: Function,
|
||||
};
|
||||
|
||||
type PopoutState = {
|
||||
query: string,
|
||||
};
|
||||
|
||||
class CountryPopoutList extends React.PureComponent {
|
||||
state: PopoutState;
|
||||
|
||||
constructor(props: PopoutProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
query: '',
|
||||
};
|
||||
|
||||
(this: any).onChangeQuery = this.onChangeQuery.bind(this);
|
||||
(this: any).onClearQuery = this.onClearQuery.bind(this);
|
||||
(this: any).onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
renderItems() {
|
||||
const {query} = this.state;
|
||||
|
||||
const items = CountryCodes.map(({code, name}, i) => {
|
||||
const [countryCode] = code.split(' ');
|
||||
return {
|
||||
id: i,
|
||||
value: name,
|
||||
children: (
|
||||
<Flex className="country-item" justify={Flex.Justify.CENTER} align={Flex.Align.CENTER}>
|
||||
<Flex.Child className="country-name">{name}</Flex.Child>
|
||||
<Flex.Child className="country-code" grow={0} shrink={0}>{countryCode}</Flex.Child>
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const itemsList = lodash(items)
|
||||
.filter(item => query.length === 0 || fuzzysearch(query.toLowerCase(), item.value.toLowerCase()))
|
||||
.map(item => <PopoutList.Item key={item.id} {...item} onClick={this.onClick} />)
|
||||
.value();
|
||||
|
||||
if (itemsList.length === 0) {
|
||||
return <PopoutList.Empty>{i18n.Messages.NONE}</PopoutList.Empty>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Scroller>
|
||||
{itemsList}
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PopoutList className="ui-phone-field-popout elevation-border-low">
|
||||
<PopoutList.SearchBar
|
||||
query={this.state.query}
|
||||
placeholder={i18n.Messages.SEARCH_COUNTRY}
|
||||
onChange={this.onChangeQuery}
|
||||
onClear={this.onClearQuery}
|
||||
/>
|
||||
<PopoutList.Divider />
|
||||
{this.renderItems()}
|
||||
</PopoutList>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeQuery(query: string) {
|
||||
this.setState({query});
|
||||
}
|
||||
|
||||
onClearQuery() {
|
||||
this.setState({query: ''});
|
||||
}
|
||||
|
||||
onClick({id}: {id: number}) {
|
||||
const {onClick} = this.props;
|
||||
onClick(id);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
onSubmit?: (phone: string) => void,
|
||||
className?: string,
|
||||
submitting: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
countryCode: string,
|
||||
phone: string,
|
||||
open: boolean,
|
||||
};
|
||||
|
||||
class PhoneField extends React.PureComponent {
|
||||
state: State;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const country = CountryCodes[224]; // default to united states
|
||||
const [countryCode, areaCode] = country.code.split(' ');
|
||||
|
||||
this.state = {
|
||||
countryCode,
|
||||
phone: areaCode || '',
|
||||
open: false,
|
||||
};
|
||||
|
||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||
(this: any).handleTogglePopout = this.handleTogglePopout.bind(this);
|
||||
(this: any).handleChange = this.handleChange.bind(this);
|
||||
(this: any).handleKeyPress = this.handleKeyPress.bind(this);
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {countryCode, phone, open} = this.state;
|
||||
const {className, submitting} = this.props;
|
||||
const [, countryCodeWithoutPlus] = countryCode.split('+');
|
||||
|
||||
return (
|
||||
<Flex className={classNames('ui-phone-field', 'elevation-low', className)} align={Flex.Align.CENTER} grow={0}>
|
||||
<Flex className="country-button" grow={0} align={Flex.Align.CENTER} onClick={this.handleTogglePopout}>
|
||||
<Flex className="country-code-container margin-reset" justify={Flex.Justify.CENTER}>
|
||||
<div className="plus-sign">+</div>
|
||||
<div className="country-code">{countryCodeWithoutPlus}</div>
|
||||
</Flex>
|
||||
<ExpandIcon expanded={open} width={16} height={16} />
|
||||
</Flex>
|
||||
<input
|
||||
className="input-field"
|
||||
value={phone}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
<Button className="send-button" size={ButtonSizes.SMALL} submitting={submitting} onClick={this.handleSubmit}>
|
||||
{i18n.Messages.SEND}
|
||||
</Button>
|
||||
{open ? <CountryPopoutList onClick={this.handleClick} /> : null}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
handleChange(e: InputEvent) {
|
||||
this.closePopout();
|
||||
this.setState({phone: e.target.value});
|
||||
}
|
||||
|
||||
handleKeyPress(e: KeyboardEvent) {
|
||||
this.closePopout();
|
||||
if (e.which === 13) {
|
||||
/* Enter */
|
||||
e.preventDefault();
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const {onSubmit, submitting} = this.props;
|
||||
const {countryCode, phone} = this.state;
|
||||
if (!submitting) {
|
||||
onSubmit && onSubmit(`${countryCode}${phone}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleTogglePopout() {
|
||||
this.setState({open: !this.state.open});
|
||||
}
|
||||
|
||||
handleClick(i: number) {
|
||||
const country = CountryCodes[i];
|
||||
const [countryCode, areaCode] = country.code.split(' ');
|
||||
this.setState({open: false, countryCode, phone: areaCode || ''});
|
||||
}
|
||||
|
||||
closePopout() {
|
||||
if (this.state.open) {
|
||||
this.setState({open: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PhoneField;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PhoneField.js
|
140
509bba0_unpacked/discord_app/uikit/PhoneVerificationModal.js
Executable file
140
509bba0_unpacked/discord_app/uikit/PhoneVerificationModal.js
Executable file
|
@ -0,0 +1,140 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import bodymovin from 'bodymovin';
|
||||
import Modal from './Modal';
|
||||
import Flex from './Flex';
|
||||
import PhoneField from './PhoneField';
|
||||
import CodeField from './CodeField';
|
||||
import Button, {ButtonLooks, ButtonColors, ButtonSizes} from './Button';
|
||||
import './PhoneVerificationModal.styl';
|
||||
import i18n from '../i18n';
|
||||
|
||||
type State = {
|
||||
animated: boolean,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
error: ?string,
|
||||
onAddPhone?: (phone: string) => void,
|
||||
onVerifyPhone?: (code: string) => void,
|
||||
working: boolean,
|
||||
validPhone: boolean,
|
||||
};
|
||||
|
||||
class PhoneVerificationModal extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
_animItem = null;
|
||||
_bodyMovinRef: HTMLElement;
|
||||
_phone: string;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
animated: false,
|
||||
};
|
||||
|
||||
(this: any).setBodyMovinRef = this.setBodyMovinRef.bind(this);
|
||||
(this: any).handleAddPhone = this.handleAddPhone.bind(this);
|
||||
(this: any).handleVerifyPhone = this.handleVerifyPhone.bind(this);
|
||||
(this: any).handleResendCode = this.handleResendCode.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// $FlowFixMe: flow doesnt understand require.ensure
|
||||
require.ensure([], require => {
|
||||
const animationData = require('../../discord_common/js/animations/app/verification/phone.json');
|
||||
this._animItem = bodymovin.loadAnimation({
|
||||
container: this._bodyMovinRef,
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: false,
|
||||
animationData,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._animItem != null) {
|
||||
this._animItem.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {error, working, validPhone} = this.props;
|
||||
|
||||
let subtitle;
|
||||
if (error) {
|
||||
subtitle = <div className="description error margin-bottom-20">{error}</div>;
|
||||
} else if (validPhone) {
|
||||
subtitle = <div className="description margin-bottom-20">{i18n.Messages.VERIFICATION_PHONE_DESCRIPTION}</div>;
|
||||
} else {
|
||||
subtitle = <div className="description margin-bottom-20">{i18n.Messages.ENTER_PHONE_DESCRIPTION}</div>;
|
||||
}
|
||||
|
||||
let field;
|
||||
if (validPhone) {
|
||||
field = (
|
||||
<Flex className="field" direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER}>
|
||||
<CodeField onSubmit={this.handleVerifyPhone} />
|
||||
<Button
|
||||
className="margin-top-8"
|
||||
size={ButtonSizes.SMALL}
|
||||
look={ButtonLooks.LINK}
|
||||
color={ButtonColors.PRIMARY}
|
||||
onClick={this.handleResendCode}>
|
||||
{i18n.Messages.RESEND_CODE}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
} else {
|
||||
field = <PhoneField className="field" onSubmit={this.handleAddPhone} submitting={working} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal className="ui-phone-verification-modal flex-vertical flex-align-center flex-justify-center margin-top-60">
|
||||
<div className="animation-container" ref={this.setBodyMovinRef} />
|
||||
<div className="title margin-bottom-8">
|
||||
{validPhone ? i18n.Messages.VERIFICATION_PHONE_TITLE : i18n.Messages.ENTER_PHONE_TITLE}
|
||||
</div>
|
||||
{subtitle}
|
||||
{field}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
setBodyMovinRef(ref: HTMLElement) {
|
||||
this._bodyMovinRef = ref;
|
||||
}
|
||||
|
||||
handleAddPhone(phone: string) {
|
||||
if (!this.state.animated && this._animItem != null) {
|
||||
this._animItem.play();
|
||||
this.setState({animated: true});
|
||||
}
|
||||
|
||||
this._phone = phone;
|
||||
const {onAddPhone} = this.props;
|
||||
onAddPhone && onAddPhone(phone);
|
||||
}
|
||||
|
||||
handleVerifyPhone(code: string) {
|
||||
const {onVerifyPhone} = this.props;
|
||||
onVerifyPhone && onVerifyPhone(code);
|
||||
}
|
||||
|
||||
handleResendCode() {
|
||||
const {onAddPhone} = this.props;
|
||||
onAddPhone && onAddPhone(this._phone);
|
||||
}
|
||||
}
|
||||
|
||||
export default PhoneVerificationModal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PhoneVerificationModal.js
|
357
509bba0_unpacked/discord_app/uikit/PictureInPicture.js
Executable file
357
509bba0_unpacked/discord_app/uikit/PictureInPicture.js
Executable file
|
@ -0,0 +1,357 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import Animated from '../lib/animated';
|
||||
import Draggable from './Draggable';
|
||||
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
||||
import {PictureInPicturePositions} from '../Constants';
|
||||
import Styles from './PictureInPicture.mod.css';
|
||||
import type Store from '../lib/flux/Store';
|
||||
import type {PictureInPictureWindow} from '../flow/Client';
|
||||
|
||||
const APPEAR_SPRING_SETTINGS = {
|
||||
friction: 16,
|
||||
tension: 400,
|
||||
};
|
||||
|
||||
const DECAY_TIME = 200;
|
||||
const STAGGER_TIME = 30;
|
||||
|
||||
type WindowProps = {
|
||||
position: string,
|
||||
id: string | number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
edgeOffsetTop: number,
|
||||
edgeOffsetRight: number,
|
||||
edgeOffsetBottom: number,
|
||||
edgeOffsetRight: number,
|
||||
onMove: (id: string | number, position: string) => void,
|
||||
};
|
||||
|
||||
class Window extends React.PureComponent {
|
||||
_draggable: Draggable;
|
||||
_innerDiv: Animated.div;
|
||||
|
||||
_width: number;
|
||||
_height: number;
|
||||
|
||||
_velocityX: number;
|
||||
_velocityY: number;
|
||||
_lastMoveTime: Date;
|
||||
_lastMoveX: number;
|
||||
_lastMoveY: number;
|
||||
|
||||
constructor(props: WindowProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
appearAnimation: new Animated.Value(0),
|
||||
positionAnimation: new Animated.Value(0),
|
||||
};
|
||||
|
||||
(this: any).ensureIsInPosition = this.ensureIsInPosition.bind(this);
|
||||
(this: any).handleSetInnerDivRef = this.handleSetInnerDivRef.bind(this);
|
||||
(this: any).handleSetDraggableRef = this.handleSetDraggableRef.bind(this);
|
||||
(this: any).handleDragStart = this.handleDragStart.bind(this);
|
||||
(this: any).handleDrag = this.handleDrag.bind(this);
|
||||
(this: any).handleDragEnd = this.handleDragEnd.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setPosition(this.props.position);
|
||||
}
|
||||
|
||||
componentDidAppear() {
|
||||
this.state.appearAnimation.setValue(1);
|
||||
this.state.positionAnimation.setValue(1);
|
||||
}
|
||||
|
||||
componentWillEnter(callback) {
|
||||
const {appearAnimation, positionAnimation} = this.state;
|
||||
|
||||
Animated.stagger(STAGGER_TIME, [
|
||||
Animated.spring(appearAnimation, {
|
||||
toValue: 1,
|
||||
...APPEAR_SPRING_SETTINGS,
|
||||
}),
|
||||
Animated.spring(positionAnimation, {
|
||||
toValue: 1,
|
||||
...APPEAR_SPRING_SETTINGS,
|
||||
}),
|
||||
]).start(callback);
|
||||
}
|
||||
|
||||
componentWillLeave(callback) {
|
||||
const {appearAnimation, positionAnimation} = this.state;
|
||||
|
||||
Animated.stagger(STAGGER_TIME, [
|
||||
Animated.spring(positionAnimation, {
|
||||
toValue: 0,
|
||||
...APPEAR_SPRING_SETTINGS,
|
||||
}),
|
||||
Animated.spring(appearAnimation, {
|
||||
toValue: 0,
|
||||
...APPEAR_SPRING_SETTINGS,
|
||||
}),
|
||||
]).start(callback);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: WindowProps) {
|
||||
if (
|
||||
prevProps.edgeOffsetTop !== this.props.edgeOffsetTop ||
|
||||
prevProps.edgeOffsetLeft !== this.props.edgeOffsetLeft ||
|
||||
prevProps.edgeOffsetBottom !== this.props.edgeOffsetBottom ||
|
||||
prevProps.edgeOffsetRight !== this.props.edgeOffsetRight ||
|
||||
prevProps.maxX !== this.props.maxX ||
|
||||
prevProps.maxY !== this.props.maxY
|
||||
) {
|
||||
this.ensureIsInPosition();
|
||||
}
|
||||
}
|
||||
|
||||
getPosition(position: string) {
|
||||
const windowNode = findDOMNode(this._innerDiv);
|
||||
|
||||
if (windowNode != null && windowNode instanceof Element) {
|
||||
const bounds = windowNode.getBoundingClientRect();
|
||||
this._width = bounds.width;
|
||||
this._height = bounds.height;
|
||||
}
|
||||
|
||||
const {edgeOffsetTop, edgeOffsetLeft, edgeOffsetBottom, edgeOffsetRight} = this.props;
|
||||
|
||||
const top = edgeOffsetTop;
|
||||
const bottom = window.innerHeight - edgeOffsetBottom - this._height;
|
||||
const left = edgeOffsetLeft;
|
||||
const right = window.innerWidth - edgeOffsetRight - this._width;
|
||||
|
||||
switch (position) {
|
||||
case PictureInPicturePositions.TOP_LEFT:
|
||||
return {y: top, x: left};
|
||||
case PictureInPicturePositions.BOTTOM_LEFT:
|
||||
return {y: bottom, x: left};
|
||||
case PictureInPicturePositions.TOP_RIGHT:
|
||||
return {y: top, x: right};
|
||||
default:
|
||||
return {y: bottom, x: right};
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(position: string) {
|
||||
const {x, y} = this.getPosition(position);
|
||||
if (this._draggable != null) {
|
||||
this._draggable.setPosition(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
animateToPosition(position: string, callback?: Function) {
|
||||
const {x, y} = this.getPosition(position);
|
||||
if (this._draggable != null) {
|
||||
this._draggable.animateToPosition(x, y, {}, callback);
|
||||
}
|
||||
}
|
||||
|
||||
ensureIsInPosition() {
|
||||
this.setPosition(this.props.position);
|
||||
}
|
||||
|
||||
calculateDecayingPosition(posX: number, posY: number, velX: number, velY: number) {
|
||||
const distanceX = velX * DECAY_TIME;
|
||||
const distanceY = velY * DECAY_TIME;
|
||||
|
||||
return {
|
||||
x: posX + distanceX,
|
||||
y: posY + distanceY,
|
||||
};
|
||||
}
|
||||
|
||||
getXOffset() {
|
||||
const {position, edgeOffsetLeft, edgeOffsetRight} = this.props;
|
||||
switch (position) {
|
||||
case PictureInPicturePositions.TOP_LEFT:
|
||||
case PictureInPicturePositions.BOTTOM_LEFT:
|
||||
return -edgeOffsetLeft;
|
||||
default:
|
||||
return edgeOffsetRight;
|
||||
}
|
||||
}
|
||||
|
||||
getYOffset() {
|
||||
const {position, edgeOffsetTop, edgeOffsetBottom} = this.props;
|
||||
switch (position) {
|
||||
case PictureInPicturePositions.TOP_LEFT:
|
||||
case PictureInPicturePositions.TOP_RIGHT:
|
||||
return -edgeOffsetTop;
|
||||
default:
|
||||
return edgeOffsetBottom;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {appearAnimation, positionAnimation} = this.state;
|
||||
|
||||
const xOffset = this.getXOffset();
|
||||
const yOffset = this.getYOffset();
|
||||
|
||||
const inputRange = [0, 1];
|
||||
const style = Animated.accelerate({
|
||||
opacity: appearAnimation,
|
||||
transform: [
|
||||
{scale: appearAnimation},
|
||||
{
|
||||
translateX: positionAnimation.interpolate({
|
||||
inputRange,
|
||||
outputRange: [`${xOffset}px`, '0px'],
|
||||
}),
|
||||
},
|
||||
{
|
||||
translateY: positionAnimation.interpolate({
|
||||
inputRange,
|
||||
outputRange: [`${yOffset}px`, '0px'],
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
dragAnywhere
|
||||
ref={this.handleSetDraggableRef}
|
||||
className={Styles.pictureInPictureWindow}
|
||||
maxX={window.innerWidth}
|
||||
maxY={window.innerHeight}
|
||||
onDragStart={this.handleDragStart}
|
||||
onDrag={this.handleDrag}
|
||||
onDragEnd={this.handleDragEnd}>
|
||||
<Animated.div ref={this.handleSetInnerDivRef} className="elevation-high" style={style}>
|
||||
{this.props.children}
|
||||
</Animated.div>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
handleSetInnerDivRef(ref: Animated.div) {
|
||||
this._innerDiv = ref;
|
||||
}
|
||||
|
||||
handleSetDraggableRef(ref: Draggable) {
|
||||
this._draggable = ref;
|
||||
}
|
||||
|
||||
handleDragStart(x, y) {
|
||||
this._velocityX = 0;
|
||||
this._velocityY = 0;
|
||||
this._lastMoveX = x;
|
||||
this._lastMoveY = y;
|
||||
this._lastMoveTime = new Date();
|
||||
}
|
||||
|
||||
handleDrag(x, y) {
|
||||
const newTime = new Date();
|
||||
const timeTook = newTime - this._lastMoveTime;
|
||||
this._velocityX = (x - this._lastMoveX) / timeTook;
|
||||
this._velocityY = (y - this._lastMoveY) / timeTook;
|
||||
this._lastMoveX = x;
|
||||
this._lastMoveY = y;
|
||||
this._lastMoveTime = newTime;
|
||||
}
|
||||
|
||||
handleDragEnd(x: number, y: number) {
|
||||
const decayedPosition = this.calculateDecayingPosition(x, y, this._velocityX, this._velocityY);
|
||||
|
||||
let top = true;
|
||||
let left = true;
|
||||
if (decayedPosition.x > window.innerWidth / 2) {
|
||||
left = false;
|
||||
}
|
||||
if (decayedPosition.y > window.innerHeight / 2) {
|
||||
top = false;
|
||||
}
|
||||
|
||||
let position;
|
||||
if (top && left) {
|
||||
position = PictureInPicturePositions.TOP_LEFT;
|
||||
} else if (top && !left) {
|
||||
position = PictureInPicturePositions.TOP_RIGHT;
|
||||
} else if (!top && left) {
|
||||
position = PictureInPicturePositions.BOTTOM_LEFT;
|
||||
} else {
|
||||
position = PictureInPicturePositions.BOTTOM_RIGHT;
|
||||
}
|
||||
|
||||
this.animateToPosition(position, this.ensureIsInPosition);
|
||||
|
||||
if (position !== this.props.position) {
|
||||
const {id, onMove} = this.props;
|
||||
onMove && onMove(id, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
pictureInPictureComponents: {[key: string]: {component: string | Function, store: Store}},
|
||||
windows: {[id: string | number]: PictureInPictureWindow},
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
theme: string,
|
||||
onWindowMove: (id: string | number, position: string) => void,
|
||||
};
|
||||
|
||||
class PictureInPicture extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
renderWindows() {
|
||||
const {windows, maxX, maxY, onWindowMove, pictureInPictureComponents} = this.props;
|
||||
const pictures = [];
|
||||
|
||||
for (const key in windows) {
|
||||
const picture = windows[key];
|
||||
let Component = picture.component;
|
||||
|
||||
if (typeof Component === 'string' && pictureInPictureComponents[Component]) {
|
||||
Component = pictureInPictureComponents[Component].component;
|
||||
}
|
||||
|
||||
const offsetTop = picture.offsets.top;
|
||||
const offsetBottom = picture.offsets.bottom;
|
||||
const offsetLeft = picture.offsets.left;
|
||||
const offsetRight = picture.offsets.right;
|
||||
|
||||
pictures.push(
|
||||
<Window
|
||||
key={key}
|
||||
position={picture.position}
|
||||
id={key}
|
||||
onMove={onWindowMove}
|
||||
maxX={maxX}
|
||||
maxY={maxY}
|
||||
edgeOffsetTop={offsetTop}
|
||||
edgeOffsetBottom={offsetBottom}
|
||||
edgeOffsetLeft={offsetLeft}
|
||||
edgeOffsetRight={offsetRight}>
|
||||
<Component pipId={key} pipPosition={picture.position} {...picture.props} />
|
||||
</Window>
|
||||
);
|
||||
}
|
||||
|
||||
return pictures;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TransitionGroup component="div" className={classNames(Styles.pictureInPicture, `theme-${this.props.theme}`)}>
|
||||
{this.renderWindows()}
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PictureInPicture;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PictureInPicture.js
|
9
509bba0_unpacked/discord_app/uikit/PictureInPicture.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/PictureInPicture.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"pictureInPicture":"pictureInPicture-Ryvh67","pictureInPictureWindow":"pictureInPictureWindow-3BoWYS"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/PictureInPicture.mod.css
|
||||
// module id = 2102
|
||||
// module chunks = 2
|
250
509bba0_unpacked/discord_app/uikit/PictureInPictureVideo.js
Executable file
250
509bba0_unpacked/discord_app/uikit/PictureInPictureVideo.js
Executable file
|
@ -0,0 +1,250 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
||||
import Flex from './Flex';
|
||||
import {BackgroundTransition} from './Video';
|
||||
import RemoveIcon from './icons/RemoveIcon';
|
||||
import ScreenshareIcon from './icons/ScreenshareIcon';
|
||||
import VideoCameraIcon from './icons/VideoCameraIcon';
|
||||
import VideoCameraDisabledIcon from './icons/VideoCameraDisabledIcon';
|
||||
import FullscreenIcon from './icons/FullscreenIcon';
|
||||
import FullscreenExitIcon from './icons/FullscreenExitIcon';
|
||||
import Styles from './PictureInPictureVideo.mod.css';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
import i18n from '../i18n';
|
||||
import type ChannelRecord from '../records/ChannelRecord';
|
||||
|
||||
type Props = {
|
||||
children?: any,
|
||||
|
||||
channel: ChannelRecord,
|
||||
backgroundKey: string,
|
||||
|
||||
isVideoAvailable: boolean,
|
||||
isVideoEnabled: boolean,
|
||||
|
||||
isScreenShareAvailable: boolean,
|
||||
isScreenShareEnabled: boolean,
|
||||
|
||||
isFullScreen: boolean,
|
||||
|
||||
onClose: Function,
|
||||
|
||||
onEnterFullScreen: Function,
|
||||
onLeaveFullScreen: Function,
|
||||
|
||||
onStartScreenSharing: Function,
|
||||
onStartScreenSharingWhenUnavailable: Function,
|
||||
onChangeScreenSharing: (e: Event) => void,
|
||||
|
||||
onEnableVideo: Function,
|
||||
onEnableVideoWhenUnavailable: Function,
|
||||
onDisableVideo: Function,
|
||||
|
||||
onJumpToChannel: Function,
|
||||
|
||||
tooltipComponent: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hovered: boolean,
|
||||
};
|
||||
|
||||
const Modes = {
|
||||
FULL_SCREEN: 'FullScreen',
|
||||
};
|
||||
|
||||
class PictureInPictureVideo extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
|
||||
(this: any).handleMouseOver = this.handleMouseOver.bind(this);
|
||||
(this: any).handleMouseOut = this.handleMouseOut.bind(this);
|
||||
}
|
||||
|
||||
getMode() {
|
||||
if (this.props.isFullScreen) {
|
||||
return Modes.FULL_SCREEN;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
renderTopLeft() {
|
||||
const {channel, onJumpToChannel} = this.props;
|
||||
return <div className={Styles.channel} onClick={onJumpToChannel}>{channel.toString()}</div>;
|
||||
}
|
||||
|
||||
renderTopRight() {
|
||||
return (
|
||||
<RemoveIcon onClick={this.props.onClose} className={Styles.iconButton} foreground={Styles.iconButtonForeground} />
|
||||
);
|
||||
}
|
||||
|
||||
renderScreenShareIcon() {
|
||||
const {
|
||||
onStartScreenSharingWhenUnavailable,
|
||||
onStartScreenSharing,
|
||||
onChangeScreenSharing,
|
||||
isScreenShareAvailable,
|
||||
isScreenShareEnabled,
|
||||
tooltipComponent: Tooltip,
|
||||
} = this.props;
|
||||
|
||||
let action;
|
||||
let iconClass;
|
||||
let tooltip;
|
||||
if (!isScreenShareAvailable) {
|
||||
action = onStartScreenSharingWhenUnavailable;
|
||||
iconClass = Styles.iconButtonDisabled;
|
||||
tooltip = i18n.Messages.SCREENSHARE_UNAVAILABLE;
|
||||
} else if (isScreenShareEnabled) {
|
||||
action = onChangeScreenSharing;
|
||||
iconClass = Styles.iconButton;
|
||||
tooltip = i18n.Messages.SCREEN_SHARE_OPTIONS;
|
||||
} else {
|
||||
action = onStartScreenSharing;
|
||||
iconClass = Styles.iconButton;
|
||||
tooltip = i18n.Messages.SCREEN_SHARE_ON;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip text={tooltip}>
|
||||
<div>
|
||||
<ScreenshareIcon className={iconClass} foreground={Styles.iconButtonForeground} onClick={action} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
renderVideoIcon() {
|
||||
const {
|
||||
onEnableVideoWhenUnavailable,
|
||||
onEnableVideo,
|
||||
onDisableVideo,
|
||||
isVideoAvailable,
|
||||
isVideoEnabled,
|
||||
tooltipComponent: Tooltip,
|
||||
} = this.props;
|
||||
|
||||
let Icon;
|
||||
let action;
|
||||
let iconClass;
|
||||
let tooltip;
|
||||
|
||||
if (!isVideoAvailable) {
|
||||
Icon = VideoCameraIcon;
|
||||
action = onEnableVideoWhenUnavailable;
|
||||
iconClass = Styles.iconButtonDisabled;
|
||||
tooltip = i18n.Messages.CAMERA_UNAVAILABLE;
|
||||
} else if (isVideoEnabled) {
|
||||
Icon = VideoCameraIcon;
|
||||
action = onDisableVideo;
|
||||
iconClass = Styles.iconButton;
|
||||
tooltip = i18n.Messages.CAMERA_OFF;
|
||||
} else {
|
||||
Icon = VideoCameraDisabledIcon;
|
||||
action = onEnableVideo;
|
||||
iconClass = Styles.iconButton;
|
||||
tooltip = i18n.Messages.CAMERA_ON;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip text={tooltip}>
|
||||
<div className={Styles.iconButtonMargin}>
|
||||
<Icon className={iconClass} foreground={Styles.iconButtonForeground} onClick={action} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
renderBottomLeft() {
|
||||
return (
|
||||
<Flex align={Flex.Align.CENTER}>
|
||||
{this.renderScreenShareIcon()}
|
||||
{this.renderVideoIcon()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
renderBottomRight() {
|
||||
const {isFullScreen, onEnterFullScreen, onLeaveFullScreen} = this.props;
|
||||
|
||||
if (isFullScreen) {
|
||||
return (
|
||||
<FullscreenExitIcon
|
||||
className={Styles.iconButton}
|
||||
foreground={Styles.iconButtonForeground}
|
||||
onClick={onLeaveFullScreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FullscreenIcon
|
||||
className={Styles.iconButton}
|
||||
foreground={Styles.iconButtonForeground}
|
||||
onClick={onEnterFullScreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderOverlaySide(left: React$Element, right: React$Element, className: string) {
|
||||
return (
|
||||
<Flex grow={0} className={className}>
|
||||
<Flex align={Flex.Align.CENTER} grow={0} className={Styles.overlayLeft}>
|
||||
{left}
|
||||
</Flex>
|
||||
<Flex grow={1} />
|
||||
<Flex align={Flex.Align.CENTER} grow={0} className={Styles.overlayRight}>
|
||||
{right}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, backgroundKey} = this.props;
|
||||
const mode = this.getMode();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={getClass(Styles, 'pictureInPictureVideo', mode)}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseOut={this.handleMouseOut}>
|
||||
<TransitionGroup>
|
||||
<BackgroundTransition key={backgroundKey}>
|
||||
{children}
|
||||
</BackgroundTransition>
|
||||
</TransitionGroup>
|
||||
<Flex direction={Flex.Direction.VERTICAL} className={Styles.overlay}>
|
||||
{this.renderOverlaySide(this.renderTopLeft(), this.renderTopRight(), Styles.overlayTop)}
|
||||
<Flex grow={1} />
|
||||
{this.renderOverlaySide(this.renderBottomLeft(), this.renderBottomRight(), Styles.overlayBottom)}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleMouseOver() {
|
||||
this.setState({hovered: true});
|
||||
}
|
||||
|
||||
handleMouseOut() {
|
||||
this.setState({hovered: false});
|
||||
}
|
||||
}
|
||||
|
||||
export default PictureInPictureVideo;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PictureInPictureVideo.js
|
9
509bba0_unpacked/discord_app/uikit/PictureInPictureVideo.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/PictureInPictureVideo.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"pictureInPictureVideo":"pictureInPictureVideo-2w62Uz","pictureInPictureVideoFullscreen":"pictureInPictureVideoFullscreen-387xiZ","overlay":"overlay-3hR73C","overlayTop":"overlayTop-2QNtcu","overlayLeft":"overlayLeft-3R4E2L","overlayRight":"overlayRight-uHXbje","overlayBottom":"overlayBottom-IS74c2","channel":"channel-1wgtPC","icon":"icon-1d4Bon","iconButton":"iconButton-23ZMpe icon-1d4Bon","iconButtonDisabled":"iconButtonDisabled-33srvh icon-1d4Bon","iconButtonMargin":"iconButtonMargin-3k5x3x","iconButtonForeground":"iconButtonForeground-1wy8a5"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/PictureInPictureVideo.mod.css
|
||||
// module id = 2103
|
||||
// module chunks = 2
|
95
509bba0_unpacked/discord_app/uikit/Popout.js
Executable file
95
509bba0_unpacked/discord_app/uikit/Popout.js
Executable file
|
@ -0,0 +1,95 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import './Popout.styl';
|
||||
import classNames from 'classnames';
|
||||
import lodash from 'lodash';
|
||||
import {NOOP} from '../Constants';
|
||||
|
||||
export const PopoutSizes = {
|
||||
LARGE: 'large',
|
||||
MEDIUM: 'medium',
|
||||
};
|
||||
|
||||
export const PopoutPositions = {
|
||||
TOP: 'top',
|
||||
TOP_RIGHT: 'top-right',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
BOTTOM_RIGHT: 'bottom-right',
|
||||
};
|
||||
|
||||
type Option = {
|
||||
name: string,
|
||||
value: string,
|
||||
color?: string,
|
||||
image?: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Array<Option>,
|
||||
title?: string,
|
||||
size?: string,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
class PopoutItem extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
lodash.bindAll(this, ['handleClick']);
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
return this.props.onClick(this.props.option, e);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {size} = this.props;
|
||||
const {image, name, color} = this.props.option;
|
||||
return (
|
||||
<div className={classNames('item', size)} onClick={this.handleClick}>
|
||||
<div className={classNames('name', size)} style={{color}}>
|
||||
{name}
|
||||
</div>
|
||||
<img className={classNames({image})} src={image} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Popout extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: Props = {
|
||||
options: [],
|
||||
size: PopoutSizes.LARGE,
|
||||
onChange: NOOP,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
lodash.bindAll(this, ['renderItem']);
|
||||
}
|
||||
|
||||
renderItem(option: Option) {
|
||||
return <PopoutItem key={option.value} size={this.props.size} option={option} onClick={this.props.onChange} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {title, size} = this.props;
|
||||
return (
|
||||
<div className={classNames('ui-popout', size)}>
|
||||
<div className={classNames(size, {title})}>{title}</div>
|
||||
{this.props.options.map(this.renderItem)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Popout;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Popout.js
|
68
509bba0_unpacked/discord_app/uikit/PopoutList.js
Executable file
68
509bba0_unpacked/discord_app/uikit/PopoutList.js
Executable file
|
@ -0,0 +1,68 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import FormDivider from './form/FormDivider';
|
||||
import SearchBar from './SearchBar';
|
||||
import SelectableItem from './SelectableItem';
|
||||
import './PopoutList.styl';
|
||||
|
||||
type SearchBarProps = {
|
||||
query: string,
|
||||
onChange: (query: string) => void,
|
||||
onClear: () => void,
|
||||
autoFocus?: boolean,
|
||||
className?: string,
|
||||
placeholder: string,
|
||||
};
|
||||
|
||||
type DefaultSearchBarProps = {
|
||||
autoFocus: boolean,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
children?: React.Children,
|
||||
};
|
||||
|
||||
const PopoutListDivider = () => <FormDivider className="margin-top-8 margin-bottom-8" />;
|
||||
const PopoutListEmpty = ({children}) => <div className="ui-popout-list-empty">{children}</div>;
|
||||
|
||||
class PopoutListSearchBar extends React.PureComponent {
|
||||
props: SearchBarProps;
|
||||
static defaultProps: DefaultSearchBarProps = {
|
||||
autoFocus: true,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {className, ...props} = this.props;
|
||||
|
||||
return <SearchBar {...props} className={classNames('ui-popout-list-input', className)} />;
|
||||
}
|
||||
}
|
||||
|
||||
class PopoutList extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
static SearchBar = PopoutListSearchBar;
|
||||
static Item = SelectableItem;
|
||||
static Divider = PopoutListDivider;
|
||||
static Empty = PopoutListEmpty;
|
||||
|
||||
render() {
|
||||
const {className, children} = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-popout-list', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PopoutList;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PopoutList.js
|
105
509bba0_unpacked/discord_app/uikit/PrefixInput.js
Executable file
105
509bba0_unpacked/discord_app/uikit/PrefixInput.js
Executable file
|
@ -0,0 +1,105 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import './PrefixInput.styl';
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
prefix: string,
|
||||
onChange: (value: string) => void,
|
||||
autoFocus?: boolean,
|
||||
placeholder?: string,
|
||||
maxLength?: number,
|
||||
error?: boolean,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
type State = {
|
||||
focused: boolean,
|
||||
};
|
||||
|
||||
class PrefixInput extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
_inputRef: HTMLInputElement;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).setInputRef = this.setInputRef.bind(this);
|
||||
(this: any).handleChange = this.handleChange.bind(this);
|
||||
(this: any).handleFocus = this.handleFocus.bind(this);
|
||||
(this: any).handleBlur = this.handleBlur.bind(this);
|
||||
(this: any).handleFocusInput = this.handleFocusInput.bind(this);
|
||||
|
||||
const {autoFocus} = this.props;
|
||||
this.state = {
|
||||
focused: autoFocus != null ? autoFocus : false,
|
||||
};
|
||||
}
|
||||
|
||||
setInputRef(ref: any) {
|
||||
this._inputRef = ref;
|
||||
}
|
||||
|
||||
handleChange(e: Event) {
|
||||
const {onChange} = this.props;
|
||||
onChange((e.target: window.HTMLInputElement).value);
|
||||
}
|
||||
|
||||
handleFocus() {
|
||||
this.setState({
|
||||
focused: true,
|
||||
});
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.setState({
|
||||
focused: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleFocusInput() {
|
||||
this._inputRef.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {focused} = this.state;
|
||||
const {value, placeholder, error, className, maxLength, autoFocus, prefix} = this.props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.START}
|
||||
className={classNames('ui-prefix-input', className, {focused, error})}
|
||||
onClick={this.handleFocusInput}>
|
||||
<Flex.Child grow={0} className="ui-prefix-input-prefix">
|
||||
{prefix}
|
||||
</Flex.Child>
|
||||
<Flex.Child className="margin-reset">
|
||||
<input
|
||||
ref={this.setInputRef}
|
||||
className="ui-prefix-input-input"
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
maxLength={maxLength}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PrefixInput;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/PrefixInput.js
|
122
509bba0_unpacked/discord_app/uikit/QuickSelect.js
Executable file
122
509bba0_unpacked/discord_app/uikit/QuickSelect.js
Executable file
|
@ -0,0 +1,122 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import Scroller from './Scroller';
|
||||
import './QuickSelect.styl';
|
||||
|
||||
export type Option = {label: string, value: any, key?: any};
|
||||
|
||||
type Props = {
|
||||
label: string,
|
||||
value: Option,
|
||||
renderValue?: (option: Option) => React$Element,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
class QuickSelect extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {label, value, renderValue, className} = this.props;
|
||||
|
||||
return (
|
||||
<Flex className={classNames('ui-quick-select', className)} align={Flex.Align.CENTER}>
|
||||
<div className="ui-quick-select-label">{label}</div>
|
||||
<Flex align={Flex.Align.CENTER} className="ui-quick-select-click">
|
||||
<div className="ui-quick-select-value">{renderValue ? renderValue(value) : value.label}</div>
|
||||
<div className="ui-quick-select-arrow" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QuickSelectPopoutOption extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {selected, renderOption, option} = this.props;
|
||||
|
||||
return (
|
||||
<Flex className={classNames('ui-quick-select-popout-option', {selected})} onClick={this.handleClick}>
|
||||
{renderOption(option, selected)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const {option, onChange} = this.props;
|
||||
onChange && onChange(option);
|
||||
}
|
||||
}
|
||||
|
||||
type QuickSelectPopoutProps = {
|
||||
options: Array<Option>,
|
||||
renderOption: (option: Option, selected: boolean) => React$Element,
|
||||
value?: Option,
|
||||
className: string,
|
||||
onChange: (option: Option) => void,
|
||||
scroller: boolean,
|
||||
};
|
||||
|
||||
export class QuickSelectPopout extends React.PureComponent {
|
||||
props: QuickSelectPopoutProps;
|
||||
|
||||
renderContent() {
|
||||
const {options, renderOption, value, scroller, onChange} = this.props;
|
||||
|
||||
const optionNodes = options.map(option => {
|
||||
const selected = value != null && option.value === value.value;
|
||||
const handleOnChange = selected ? null : onChange;
|
||||
return (
|
||||
<QuickSelectPopoutOption
|
||||
key={option.key || option.value}
|
||||
className="ui-quick-select-popout-option"
|
||||
renderOption={renderOption}
|
||||
option={option}
|
||||
onChange={handleOnChange}
|
||||
selected={selected}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
if (scroller) {
|
||||
return (
|
||||
<Scroller>
|
||||
{optionNodes}
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
|
||||
return optionNodes;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, scroller} = this.props;
|
||||
|
||||
const classes = classNames('ui-quick-select-popout', className, {'ui-quick-select-popout-scroll': scroller});
|
||||
|
||||
return (
|
||||
<Flex className={classes} direction={Flex.Direction.VERTICAL}>
|
||||
{this.renderContent()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QuickSelect;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/QuickSelect.js
|
122
509bba0_unpacked/discord_app/uikit/RadioGroup.js
Executable file
122
509bba0_unpacked/discord_app/uikit/RadioGroup.js
Executable file
|
@ -0,0 +1,122 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Card from './Card';
|
||||
import Checkbox from './Checkbox';
|
||||
import {NOOP, Colors} from '../Constants';
|
||||
import './RadioGroup.styl';
|
||||
|
||||
type Option = {
|
||||
name: string,
|
||||
value: string | number,
|
||||
color?: string,
|
||||
desc?: string,
|
||||
disabled?: boolean,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Array<Option>,
|
||||
onChange: Function,
|
||||
value: any,
|
||||
disabled?: boolean,
|
||||
};
|
||||
|
||||
class RadioItem extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {checked, disabled, option} = this.props;
|
||||
|
||||
const desc = option.desc
|
||||
? <div className="desc margin-top-4">
|
||||
{option.desc}
|
||||
</div>
|
||||
: null;
|
||||
|
||||
const checkboxColor = checked ? option.color || Colors.BRAND : '';
|
||||
const titleColor = checked ? Colors.WHITE : option.color;
|
||||
const isDisabled = disabled || option.disabled;
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={classNames('radio-item', 'flex-horizontal', 'margin-bottom-8', {checked, disabled: isDisabled})}
|
||||
style={{
|
||||
borderColor: checkboxColor,
|
||||
backgroundColor: checkboxColor,
|
||||
padding: 10,
|
||||
}}
|
||||
editable
|
||||
onClick={!isDisabled && this.handleClick}>
|
||||
<Checkbox value={checked} disabled={isDisabled} onChange={this.handleClick} color={checkboxColor} />
|
||||
<div className="info">
|
||||
<div className="title" style={{color: titleColor}}>
|
||||
{option.name}
|
||||
</div>
|
||||
{desc}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleClick() {
|
||||
const {onClick, option} = this.props;
|
||||
return onClick && onClick(option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RadioGroup PureComponent
|
||||
*/
|
||||
class RadioGroup extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: Props = {
|
||||
options: [],
|
||||
onChange: NOOP,
|
||||
value: null,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).renderRadio = this.renderRadio.bind(this);
|
||||
}
|
||||
|
||||
renderRadio(option: Option) {
|
||||
const {value, disabled} = this.props;
|
||||
const checked = value === option.value;
|
||||
|
||||
return (
|
||||
<RadioItem
|
||||
disabled={disabled}
|
||||
key={option.value}
|
||||
checked={checked}
|
||||
option={option}
|
||||
onClick={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {options} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ui-radiogroup">
|
||||
{options.map(this.renderRadio)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RadioGroup;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/RadioGroup.js
|
100
509bba0_unpacked/discord_app/uikit/RadioList.js
Executable file
100
509bba0_unpacked/discord_app/uikit/RadioList.js
Executable file
|
@ -0,0 +1,100 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {FormTitle, FormTitleTags, FormText} from './form';
|
||||
import Flex from './Flex';
|
||||
import Checkbox, {CheckboxTypes} from './Checkbox';
|
||||
import {NOOP, Colors} from '../Constants';
|
||||
import './RadioList.styl';
|
||||
|
||||
type RadioItemType = {
|
||||
name: string,
|
||||
localizedName?: string,
|
||||
value: string,
|
||||
image?: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Array<RadioItemType>,
|
||||
value: ?string | number,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
class RadioItem extends React.PureComponent {
|
||||
handleClick = () => {
|
||||
return this.props.onClick(this.props.option);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isChecked, option} = this.props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={classNames('ui-radio-list-item', {selected: isChecked})}
|
||||
justify={Flex.Justify.BETWEEN}
|
||||
align={Flex.Align.CENTER}
|
||||
onClick={this.handleClick}>
|
||||
<Flex grow={0} className="ui-radio-list-label" justify={Flex.Justify.START}>
|
||||
<Flex.Child>
|
||||
<Checkbox
|
||||
readOnly
|
||||
value={isChecked}
|
||||
type={CheckboxTypes.ROUND}
|
||||
color={isChecked ? Colors.STATUS_GREEN : 'none'}
|
||||
inverted={isChecked}
|
||||
/>
|
||||
</Flex.Child>
|
||||
<Flex.Child>
|
||||
<FormTitle className="ui-radio-list-label-text" tag={FormTitleTags.H3}>
|
||||
{option.name}
|
||||
</FormTitle>
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
<Flex grow={0} className="ui-radio-list-item-desc">
|
||||
<Flex.Child className="margin-reset">
|
||||
<FormText type={FormText.Types.DESCRIPTION}>
|
||||
{option.localizedName}
|
||||
</FormText>
|
||||
</Flex.Child>
|
||||
{option.image
|
||||
? <Flex.Child>
|
||||
<img src={option.image} className="ui-radio-list-image no-user-drag" />
|
||||
</Flex.Child>
|
||||
: null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RadioList extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: Props = {
|
||||
options: [],
|
||||
onChange: NOOP,
|
||||
value: null,
|
||||
};
|
||||
|
||||
renderRadio = (option: RadioItemType, index: number) => {
|
||||
const {value} = this.props;
|
||||
const isChecked = value === option.value;
|
||||
|
||||
return <RadioItem key={index} isChecked={isChecked} option={option} onClick={this.props.onChange} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ui-radio-list">
|
||||
{this.props.options.map(this.renderRadio)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RadioList;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/RadioList.js
|
147
509bba0_unpacked/discord_app/uikit/RegionSelector.js
Executable file
147
509bba0_unpacked/discord_app/uikit/RegionSelector.js
Executable file
|
@ -0,0 +1,147 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from './Flex';
|
||||
import classNames from 'classnames';
|
||||
import Button, {ButtonLooks, ButtonColors, ButtonSizes} from './Button';
|
||||
import i18n from '../i18n';
|
||||
import './RegionSelector.styl';
|
||||
import '../styles/shared/input_button.styl';
|
||||
|
||||
type Region = {
|
||||
name: string,
|
||||
deprecated: boolean,
|
||||
custom: boolean,
|
||||
vip: boolean,
|
||||
optimal: boolean,
|
||||
id: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
region: Region,
|
||||
error?: boolean,
|
||||
onClick: Function,
|
||||
disabled?: boolean,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hover: boolean,
|
||||
};
|
||||
|
||||
const RegionFlag = ({region, className, style}) => {
|
||||
let backgroundSrc;
|
||||
if (region.id === 'unknown') {
|
||||
backgroundSrc = require('../images/regions/unknown.png');
|
||||
} else {
|
||||
const icon = region.id.replace(/^vip-/, '');
|
||||
try {
|
||||
// $FlowFixMe Not sure if there is a valid workaround for this?
|
||||
backgroundSrc = require(`../images/regions/${icon}.png`);
|
||||
} catch (e) {
|
||||
backgroundSrc = require(`../images/regions/unknown.png`);
|
||||
}
|
||||
}
|
||||
style = {
|
||||
...style,
|
||||
backgroundImage: `url("${backgroundSrc}")`,
|
||||
};
|
||||
return <div className={classNames('ui-region-flag', className, {vip: region.vip})} style={style} />;
|
||||
};
|
||||
|
||||
const RegionName = ({region, className, style}) => {
|
||||
// This is being trimmed here because mobile devices still need the text.
|
||||
const name = region.name.replace(/ \(VIP\)$/, '');
|
||||
return (
|
||||
<div className={classNames('ui-region-name', 'overflow-ellipsis', className)} style={style}>
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class RegionSelector extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps: DefaultProps = {
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hover: false,
|
||||
};
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
(this: any).handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
(this: any).handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const {onClick} = this.props;
|
||||
onClick();
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
this.setState({hover: false});
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
this.setState({hover: true});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {region, error, disabled} = this.props;
|
||||
const {hover} = this.state;
|
||||
|
||||
let buttonColor = ButtonColors.GREY;
|
||||
if (!disabled) {
|
||||
if (hover) {
|
||||
buttonColor = ButtonColors.BRAND;
|
||||
} else if (error === true) {
|
||||
buttonColor = ButtonColors.RED;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('ui-region-selector', 'ui-input-button', {hover, disabled, error})}
|
||||
onClick={!disabled && this.handleClick}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}>
|
||||
<Flex className="layout">
|
||||
<Flex className="input" align={Flex.Align.CENTER}>
|
||||
<Flex.Child grow={0} shrink={0}>
|
||||
<RegionFlag region={region} />
|
||||
</Flex.Child>
|
||||
<Flex.Child>
|
||||
<RegionName region={region} />
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
<Flex shrink={1} grow={0} style={{margin: 0}}>
|
||||
<Button
|
||||
className="ui-input-button-ui-button"
|
||||
onClick={() => {}}
|
||||
size={ButtonSizes.MIN}
|
||||
color={buttonColor}
|
||||
disabled={disabled}
|
||||
look={ButtonLooks.GHOST}>
|
||||
{i18n.Messages.CHANGE}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RegionSelector;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/RegionSelector.js
|
244
509bba0_unpacked/discord_app/uikit/RoleList.js
Executable file
244
509bba0_unpacked/discord_app/uikit/RoleList.js
Executable file
|
@ -0,0 +1,244 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import lodash from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import PlusIcon from './icons/PlusIcon';
|
||||
import type {Role as RoleRecord} from '../records/GuildRecord';
|
||||
import {int2rgba} from '../../discord_common/js/utils/ColorUtils';
|
||||
import './RoleList.styl';
|
||||
|
||||
const MAX_ADD_BUTTON_WIDTH = 36;
|
||||
const MARGIN_SIZE = 10;
|
||||
|
||||
type AddRoleButtonProps = {
|
||||
hiddenCount: number,
|
||||
canManage: boolean,
|
||||
};
|
||||
|
||||
export class AddRoleButton extends React.PureComponent {
|
||||
props: AddRoleButtonProps;
|
||||
|
||||
renderText(hiddenCount: number) {
|
||||
if (hiddenCount > 0) {
|
||||
return (
|
||||
<Flex align={Flex.Align.CENTER}>
|
||||
<PlusIcon />
|
||||
<span className="extra-roles-count">{hiddenCount}</span>
|
||||
</Flex>
|
||||
);
|
||||
} else {
|
||||
return <PlusIcon />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {hiddenCount, canManage, ...props} = this.props;
|
||||
|
||||
if (!canManage && hiddenCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex.Child {...props} wrap className="ui-role-list-add" grow={0}>
|
||||
{this.renderText(hiddenCount)}
|
||||
</Flex.Child>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type RoleProps = {
|
||||
role: RoleRecord,
|
||||
defaultColor: number,
|
||||
getRemovable: Function,
|
||||
onRemove: Function,
|
||||
};
|
||||
|
||||
class Role extends React.PureComponent {
|
||||
props: RoleProps;
|
||||
|
||||
constructor(props: RoleProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleRemove = this.handleRemove.bind(this);
|
||||
}
|
||||
|
||||
renderRemoveButton() {
|
||||
const {getRemovable, role} = this.props;
|
||||
|
||||
if (getRemovable && getRemovable(role)) {
|
||||
return <div className="ui-role-list-remove" onClick={this.handleRemove} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {defaultColor, role} = this.props;
|
||||
let {color} = role;
|
||||
|
||||
if (color === 0) {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const style = {
|
||||
backgroundColor: int2rgba(color, 0.1),
|
||||
borderColor: int2rgba(color, 0.5),
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex className="ui-role-list-role" align={Flex.Align.CENTER} grow={0} style={style}>
|
||||
<div>{role.name}</div>
|
||||
{this.renderRemoveButton()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
handleRemove() {
|
||||
const {onRemove, role} = this.props;
|
||||
onRemove && onRemove(role);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
roles: Array<RoleRecord>,
|
||||
defaultColor: number,
|
||||
canManage: boolean,
|
||||
className: string,
|
||||
renderAddRole: Function,
|
||||
getRemovable: Function,
|
||||
width: number,
|
||||
};
|
||||
|
||||
type State = {
|
||||
offscreen: boolean,
|
||||
visibleRoles: Array<RoleRecord>,
|
||||
};
|
||||
|
||||
class RoleList extends React.PureComponent {
|
||||
state: State;
|
||||
_ref: Flex;
|
||||
_roleRefs: {[key: string]: Role} = {};
|
||||
_isMounted = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
offscreen: true,
|
||||
visibleRoles: props.roles,
|
||||
};
|
||||
|
||||
(this: any).setRef = this.setRef.bind(this);
|
||||
(this: any).setRoleRef = this.setRoleRef.bind(this);
|
||||
}
|
||||
|
||||
getHiddenCount() {
|
||||
return this.props.roles.length - this.state.visibleRoles.length;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
// Widths are completely off if we calculate right away
|
||||
process.nextTick(() => this.calculateBounds());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {roles, width} = this.props;
|
||||
const roleChange = roles !== prevProps.roles;
|
||||
if (roleChange || width != prevProps.width) {
|
||||
this.setState({offscreen: true, visibleRoles: roles}, () => this.calculateBounds(roleChange));
|
||||
}
|
||||
}
|
||||
|
||||
calculateBounds(changedRoles: boolean = false) {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old refs from a removed role
|
||||
if (changedRoles) {
|
||||
for (const id in this._roleRefs) {
|
||||
if (this._roleRefs[id] == null) {
|
||||
delete this._roleRefs[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {canManage, roles} = this.props;
|
||||
|
||||
let boundingWidth = 0;
|
||||
const boundingNode = findDOMNode(this._ref);
|
||||
if (boundingNode instanceof HTMLElement) {
|
||||
boundingWidth = boundingNode.getBoundingClientRect().width;
|
||||
}
|
||||
|
||||
let totalWidth = canManage ? MAX_ADD_BUTTON_WIDTH : 0;
|
||||
const refs = lodash(this._roleRefs)
|
||||
.values()
|
||||
.takeWhile((ref, i) => {
|
||||
const node = findDOMNode(ref);
|
||||
if (node instanceof HTMLElement) {
|
||||
const marginSize = i === 0 ? MARGIN_SIZE : MARGIN_SIZE * 2;
|
||||
const {width} = node.getBoundingClientRect();
|
||||
totalWidth += width + marginSize;
|
||||
return totalWidth < boundingWidth;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.value();
|
||||
|
||||
const visibleRoles = [];
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
visibleRoles.push(roles[i]);
|
||||
}
|
||||
|
||||
if (this._isMounted) {
|
||||
this.setState({offscreen: false, visibleRoles});
|
||||
}
|
||||
}
|
||||
|
||||
setRef(ref: Flex) {
|
||||
this._ref = ref;
|
||||
}
|
||||
|
||||
setRoleRef(ref: Role, role: RoleRecord) {
|
||||
this._roleRefs[role.id] = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {renderAddRole, defaultColor, className, canManage, getRemovable, onRemove} = this.props;
|
||||
const {visibleRoles, offscreen} = this.state;
|
||||
|
||||
const classes = classNames('ui-role-list', className, {'ui-role-list-offscreen': offscreen});
|
||||
const hiddenCount = this.getHiddenCount();
|
||||
const roleNodes = visibleRoles.map(role =>
|
||||
<Role
|
||||
ref={ref => this.setRoleRef(ref, role)}
|
||||
key={role.id}
|
||||
defaultColor={defaultColor}
|
||||
role={role}
|
||||
getRemovable={getRemovable}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex ref={this.setRef} className={classes} align={Flex.Align.CENTER}>
|
||||
{roleNodes}
|
||||
{renderAddRole({hiddenCount, canManage})}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleList;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/RoleList.js
|
162
509bba0_unpacked/discord_app/uikit/ScreenshareModal.js
Executable file
162
509bba0_unpacked/discord_app/uikit/ScreenshareModal.js
Executable file
|
@ -0,0 +1,162 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import SegmentControl from './SegmentControl';
|
||||
import Grid from './Grid';
|
||||
import Button, {ButtonLooks, ButtonColors} from './Button';
|
||||
import {NOOP} from '../Constants';
|
||||
import i18n from '../i18n';
|
||||
import Scroller from './Scroller';
|
||||
import Spinner from './Spinner';
|
||||
import Flex from './Flex';
|
||||
import Modal from './Modal';
|
||||
import './ScreenshareModal.styl';
|
||||
|
||||
type Option = {
|
||||
id: string,
|
||||
name: string,
|
||||
url: string,
|
||||
};
|
||||
|
||||
type Item = {
|
||||
name: string,
|
||||
value: string,
|
||||
layout: string,
|
||||
options: Array<Option>,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
items: Array<Item>,
|
||||
value: ?string,
|
||||
description?: string,
|
||||
onCancel: Function,
|
||||
onSelect: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
selectedSegment: ?string,
|
||||
selectedTile: ?string,
|
||||
};
|
||||
|
||||
class ScreenshareModal extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps = {
|
||||
items: [],
|
||||
value: null,
|
||||
onCancel: NOOP,
|
||||
onSelect: NOOP,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedSegment: props.value,
|
||||
selectedTile: null,
|
||||
};
|
||||
|
||||
lodash.bindAll(this, ['handleSelectSegment', 'handleShare']);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.value !== nextProps.value) {
|
||||
this.setState({selectedSegment: nextProps.value});
|
||||
}
|
||||
}
|
||||
|
||||
renderGrids() {
|
||||
const {items} = this.props;
|
||||
const {selectedSegment, selectedTile} = this.state;
|
||||
|
||||
const item = lodash(items).filter(({value}) => value === selectedSegment).first();
|
||||
|
||||
if (item == null || item.options == null) {
|
||||
return (
|
||||
<div className="flex-center margin-top-20">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const {layout, options} = item;
|
||||
|
||||
const tiles = options.map(({id, name, url}) => {
|
||||
const selected = selectedTile === id;
|
||||
return (
|
||||
<div key={id} className="ui-screenshare-modal-tile" onClick={() => this.handleSelectTile(id)}>
|
||||
<Flex direction={Flex.Direction.VERTICAL} justify={Flex.Justify.CENTER} align={Flex.Align.CENTER}>
|
||||
<div className={classNames('image-container flex-center', layout)}>
|
||||
<img className={classNames({selected})} src={url} />
|
||||
</div>
|
||||
<div className={classNames('title margin-top-4', layout, {selected})}>{name}</div>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Scroller>
|
||||
<Grid layout={layout} columns={3}>
|
||||
{tiles}
|
||||
</Grid>
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {items, description, onCancel} = this.props;
|
||||
const {selectedSegment, selectedTile} = this.state;
|
||||
const segmentOptions = [];
|
||||
|
||||
lodash(items).forEach(({name, value}) => {
|
||||
segmentOptions.push({name, value});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal className="ui-screenshare-modal flex-vertical">
|
||||
<Flex className="content" direction={Flex.Direction.VERTICAL}>
|
||||
<SegmentControl options={segmentOptions} value={selectedSegment} onChange={this.handleSelectSegment} />
|
||||
<Flex className="tiles margin-top-20 margin-bottom-20" direction={Flex.Direction.VERTICAL}>
|
||||
{this.renderGrids()}
|
||||
</Flex>
|
||||
<div>{description}</div>
|
||||
</Flex>
|
||||
<Flex className="actions" justify={Flex.Justify.END} grow={0}>
|
||||
<Button look={ButtonLooks.LINK} color={ButtonColors.PRIMARY} onClick={onCancel}>
|
||||
{i18n.Messages.CANCEL}
|
||||
</Button>
|
||||
<Button disabled={selectedTile == null} onClick={this.handleShare}>{i18n.Messages.SHARE}</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleSelectSegment({value}: {value: ?string}) {
|
||||
if (value !== this.state.selectedSegment) {
|
||||
this.setState({selectedSegment: value});
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectTile(id: string) {
|
||||
if (id !== this.state.selectedTile) {
|
||||
this.setState({selectedTile: id});
|
||||
}
|
||||
}
|
||||
|
||||
handleShare() {
|
||||
const {onSelect} = this.props;
|
||||
onSelect(this.state.selectedTile);
|
||||
}
|
||||
}
|
||||
|
||||
export default ScreenshareModal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/ScreenshareModal.js
|
363
509bba0_unpacked/discord_app/uikit/Scroller.js
Executable file
363
509bba0_unpacked/discord_app/uikit/Scroller.js
Executable file
|
@ -0,0 +1,363 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import platform from 'platform';
|
||||
import classNames from 'classnames';
|
||||
import Animated from '../lib/animated';
|
||||
import Springer from '../lib/Springer';
|
||||
import {NOOP} from '../Constants';
|
||||
import './Scroller.styl';
|
||||
|
||||
const SCROLL_CONTEXT_BUFFER = 50;
|
||||
const PAGE_HEIGHT_BUFFER = 0.8;
|
||||
|
||||
type Props = {
|
||||
onUpdate: Function,
|
||||
onScroll: Function,
|
||||
onResize: Function,
|
||||
style?: Object,
|
||||
fade?: boolean,
|
||||
track?: boolean,
|
||||
theme?: string,
|
||||
backgroundColor?: string,
|
||||
windowSize?: {width: string, height: string},
|
||||
renderStickyHeader?: Function,
|
||||
className?: string,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
thumbHeight: any,
|
||||
thumbTop: any,
|
||||
needsScrollbar: boolean,
|
||||
};
|
||||
|
||||
type ScrollerState = {
|
||||
offsetHeight: number,
|
||||
scrollTop: number,
|
||||
scrollHeight: number,
|
||||
};
|
||||
|
||||
export const Themes = {
|
||||
DARK: 'ui-scroller-dark',
|
||||
LIGHT: 'ui-scroller-light',
|
||||
GHOST: 'ui-scroller-ghost',
|
||||
GHOST_HAIRLINE: 'ui-scroller-ghost-hairline',
|
||||
};
|
||||
|
||||
class Scroller extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
scrollerState: ScrollerState;
|
||||
static defaultProps = {
|
||||
onUpdate: NOOP,
|
||||
onScroll: _ => {},
|
||||
onResize: NOOP,
|
||||
fade: false,
|
||||
track: false,
|
||||
};
|
||||
|
||||
static Themes = Themes;
|
||||
|
||||
_springer = null;
|
||||
_queueUpdate = false;
|
||||
_scrollerRef = null;
|
||||
_stickyHeaderRef = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// This is a specialized state we use to manage scroller specs outside of
|
||||
// the typical react lifecycle since performance is very important and we
|
||||
// don't want to be subject to delays
|
||||
this.scrollerState = {
|
||||
offsetHeight: 0,
|
||||
scrollTop: 0,
|
||||
scrollHeight: 0,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
thumbHeight: new Animated.Value(0),
|
||||
thumbTop: new Animated.Value(0),
|
||||
needsScrollbar: platform.layout !== 'Blink' && platform.layout !== 'WebKit',
|
||||
};
|
||||
|
||||
lodash.bindAll(this, [
|
||||
'handleOnScroll',
|
||||
'handleResize',
|
||||
'updateScrollBar',
|
||||
'setScrollerRef',
|
||||
'setStickyHeaderRef',
|
||||
'setLightScrollerState',
|
||||
'setFullScrollerState',
|
||||
]);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._springer = new Springer((scrollTop, instance) => {
|
||||
const node = this._scrollerRef;
|
||||
const {offsetHeight, scrollHeight} = this.scrollerState;
|
||||
if (!node) {
|
||||
instance.cancel();
|
||||
return;
|
||||
}
|
||||
// Prevent scrolling from going into negative values
|
||||
if (scrollTop < 0) {
|
||||
scrollTop = 0;
|
||||
}
|
||||
node.scrollTop = scrollTop;
|
||||
// Cancel the animation if we've hit the bounds - this is useful for
|
||||
// scroll regions that may be loading in more data, so we don't
|
||||
// experience a jump after the content loads
|
||||
if ((scrollTop === 0 && instance._vel < 0) || (scrollTop + offsetHeight > scrollHeight && instance._vel > 0)) {
|
||||
instance.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setFullScrollerState(this.props.onUpdate);
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._springer) {
|
||||
this._springer.cancel();
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this._queueUpdate = true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.windowSize !== this.props.windowSize) {
|
||||
return this.handleResize();
|
||||
}
|
||||
|
||||
if (this._queueUpdate) {
|
||||
this._queueUpdate = false;
|
||||
this.setFullScrollerState(this.props.onUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
setScrollerRef(ref: any) {
|
||||
this._scrollerRef = ref;
|
||||
}
|
||||
|
||||
setStickyHeaderRef(ref: any) {
|
||||
this._stickyHeaderRef = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {renderStickyHeader, className, fade, track, theme, children, style} = this.props;
|
||||
const {needsScrollbar} = this.state;
|
||||
|
||||
let scrollbar = null;
|
||||
if (needsScrollbar) {
|
||||
const {thumbHeight, thumbTop} = this.state;
|
||||
const {backgroundColor} = this.props;
|
||||
|
||||
scrollbar = (
|
||||
<div className="scrollbar" style={{backgroundColor}}>
|
||||
<div className="track" />
|
||||
<Animated.div
|
||||
className="thumb"
|
||||
style={{
|
||||
height: thumbHeight,
|
||||
top: thumbTop,
|
||||
}}
|
||||
/>
|
||||
<div className="pad" style={{backgroundColor}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let stickyHeader = null;
|
||||
if (renderStickyHeader) {
|
||||
const theHeader = renderStickyHeader();
|
||||
if (theHeader) {
|
||||
stickyHeader = React.cloneElement(theHeader, {ref: this.setStickyHeaderRef});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('ui-scroller-wrap', theme, {
|
||||
polyfill: scrollbar != null,
|
||||
'ui-scroller-themed': theme,
|
||||
'ui-scroller-fade': fade,
|
||||
'ui-scroller-track': track,
|
||||
})}>
|
||||
<div
|
||||
ref={this.setScrollerRef}
|
||||
className={classNames('scroller', className)}
|
||||
onScroll={this.handleOnScroll}
|
||||
style={style}>
|
||||
{stickyHeader}
|
||||
{children}
|
||||
</div>
|
||||
{scrollbar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleOnScroll() {
|
||||
this.setLightScrollerState(this.props.onScroll);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.setFullScrollerState(this.props.onResize);
|
||||
}
|
||||
|
||||
getScrollbarOffset() {
|
||||
const {scrollHeight, offsetHeight, scrollTop} = this.scrollerState;
|
||||
const offsetAvailable = scrollHeight - offsetHeight;
|
||||
const offsetPercent = offsetAvailable === 0 ? 0 : scrollTop / offsetAvailable;
|
||||
|
||||
return (offsetHeight - this.getScrollbarHeight()) * offsetPercent;
|
||||
}
|
||||
|
||||
getScrollbarHeight() {
|
||||
const {scrollHeight, offsetHeight} = this.scrollerState;
|
||||
if (scrollHeight === 0 || scrollHeight <= offsetHeight) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return offsetHeight / scrollHeight * offsetHeight;
|
||||
}
|
||||
|
||||
updateScrollBar() {
|
||||
const {thumbHeight, thumbTop} = this.state;
|
||||
const height = this.getScrollbarHeight() >> 0;
|
||||
const top = this.getScrollbarOffset() >> 0;
|
||||
thumbHeight.setValue(height);
|
||||
thumbTop.setValue(top);
|
||||
}
|
||||
|
||||
setLightScrollerState(callback: Function) {
|
||||
if (!this._scrollerRef) return;
|
||||
|
||||
this.scrollerState.scrollTop = this._scrollerRef.scrollTop;
|
||||
|
||||
callback(this);
|
||||
if (this.state.needsScrollbar) {
|
||||
this.updateScrollBar();
|
||||
}
|
||||
}
|
||||
|
||||
setFullScrollerState(callback: Function) {
|
||||
if (!this._scrollerRef) return;
|
||||
|
||||
const scroller = this._scrollerRef;
|
||||
const {scrollerState} = this;
|
||||
scrollerState.offsetHeight = scroller.offsetHeight;
|
||||
scrollerState.scrollTop = scroller.scrollTop;
|
||||
scrollerState.scrollHeight = scroller.scrollHeight;
|
||||
|
||||
callback(this);
|
||||
if (this.state.needsScrollbar) {
|
||||
this.updateScrollBar();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollerNode() {
|
||||
return this._scrollerRef;
|
||||
}
|
||||
|
||||
scrollTo(offset: number, animated: boolean = false, onComplete: ?Function = null) {
|
||||
if (!this._scrollerRef || !this._springer) return;
|
||||
|
||||
const node = this._scrollerRef;
|
||||
if (animated && node.scrollTop !== offset) {
|
||||
this._springer.to(offset >> 0, node.scrollTop, onComplete);
|
||||
} else {
|
||||
this._springer.cancel();
|
||||
node.scrollTop = offset;
|
||||
onComplete && process.nextTick(onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
scrollIntoViewRect(top: number, bottom: number, animated: boolean = false) {
|
||||
let {scrollTop, offsetHeight} = this.scrollerState;
|
||||
let stickyHeaderHeight = 0;
|
||||
|
||||
if (this._stickyHeaderRef) {
|
||||
stickyHeaderHeight = this._stickyHeaderRef.offsetTop + this._stickyHeaderRef.offsetHeight;
|
||||
scrollTop += stickyHeaderHeight;
|
||||
offsetHeight -= stickyHeaderHeight;
|
||||
}
|
||||
|
||||
if (top >= scrollTop && bottom <= scrollTop + offsetHeight) {
|
||||
// do nothing
|
||||
} else if (top < scrollTop) {
|
||||
// scroll so that the target position places the 'top' directly under the header height
|
||||
this.scrollTo(top - stickyHeaderHeight, animated);
|
||||
} else {
|
||||
// the height of the area doesn't change with the sticky header, so always use the full height
|
||||
this.scrollTo(bottom - offsetHeight, animated);
|
||||
}
|
||||
}
|
||||
|
||||
scrollIntoView(
|
||||
domNode: any,
|
||||
animated: boolean = false,
|
||||
padding: {top: number, bottom: number} = {top: 0, bottom: 0}
|
||||
) {
|
||||
const top = domNode.offsetTop;
|
||||
const bottom = top + domNode.offsetHeight;
|
||||
this.scrollIntoViewRect(top - padding.top, bottom + padding.bottom, animated);
|
||||
}
|
||||
|
||||
getScrollData() {
|
||||
const {offsetHeight, scrollTop, scrollHeight} = this.scrollerState;
|
||||
let pageHeight = (offsetHeight * PAGE_HEIGHT_BUFFER - SCROLL_CONTEXT_BUFFER) >> 0;
|
||||
if (pageHeight < SCROLL_CONTEXT_BUFFER) {
|
||||
pageHeight = SCROLL_CONTEXT_BUFFER;
|
||||
}
|
||||
return {
|
||||
scrollHeight,
|
||||
scrollTop,
|
||||
offsetHeight,
|
||||
pageHeight,
|
||||
};
|
||||
}
|
||||
|
||||
scrollPageUp(animated: boolean = false, precise: boolean = false) {
|
||||
const data = this.getScrollData();
|
||||
let scrollTo;
|
||||
if (precise) {
|
||||
scrollTo = Math.max(data.scrollTop - data.pageHeight, 0);
|
||||
} else {
|
||||
scrollTo = Math.max(data.scrollTop - data.pageHeight, 0 - data.pageHeight);
|
||||
}
|
||||
this.scrollTo(scrollTo, animated);
|
||||
}
|
||||
|
||||
scrollPageDown(animated: boolean = false, precise: boolean = false) {
|
||||
const data = this.getScrollData();
|
||||
let scrollTo;
|
||||
if (precise) {
|
||||
scrollTo = Math.min(data.scrollTop + data.pageHeight, data.scrollHeight - data.offsetHeight);
|
||||
} else {
|
||||
scrollTo = Math.min(data.scrollTop + data.pageHeight, data.scrollHeight);
|
||||
}
|
||||
this.scrollTo(scrollTo, animated);
|
||||
}
|
||||
|
||||
scrollToBottom(animated: boolean = false, onComplete: ?Function = null) {
|
||||
const data = this.getScrollData();
|
||||
this.scrollTo(data.scrollHeight, animated, onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
export default Scroller;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Scroller.js
|
79
509bba0_unpacked/discord_app/uikit/SearchBar.js
Executable file
79
509bba0_unpacked/discord_app/uikit/SearchBar.js
Executable file
|
@ -0,0 +1,79 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import i18n from '../i18n';
|
||||
import Flex from './Flex';
|
||||
import './SearchBar.styl';
|
||||
|
||||
type Props = {
|
||||
query: string,
|
||||
onChange: Function,
|
||||
onClear: Function,
|
||||
autoFocus?: boolean,
|
||||
className?: string,
|
||||
placeholder: ?string,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
placeholder: string,
|
||||
};
|
||||
|
||||
export class SearchIcon extends React.PureComponent {
|
||||
render() {
|
||||
const {onClear, hasContent} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ui-search-bar-icon" onClick={onClear}>
|
||||
<i className={classNames('icon eye-glass', {visible: !hasContent})} />
|
||||
<i className={classNames('icon clear', {visible: hasContent})} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchBar extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
placeholder: i18n.Messages.SEARCH,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleOnChange = this.handleOnChange.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {query, autoFocus, onClear, className, placeholder} = this.props;
|
||||
|
||||
return (
|
||||
<Flex className={classNames('ui-search-bar', className)}>
|
||||
<Flex.Child>
|
||||
<input
|
||||
className="input"
|
||||
value={query}
|
||||
onChange={this.handleOnChange}
|
||||
placeholder={placeholder}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
</Flex.Child>
|
||||
<Flex.Child>
|
||||
<SearchIcon hasContent={query.length > 0} onClear={onClear} />
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
handleOnChange(event: InputEvent) {
|
||||
const {onChange} = this.props;
|
||||
onChange && onChange(event.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchBar;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/SearchBar.js
|
77
509bba0_unpacked/discord_app/uikit/SegmentControl.js
Executable file
77
509bba0_unpacked/discord_app/uikit/SegmentControl.js
Executable file
|
@ -0,0 +1,77 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import './SegmentControl.styl';
|
||||
import {NOOP} from '../Constants';
|
||||
|
||||
type Option = {
|
||||
name: string,
|
||||
value: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Array<Option>,
|
||||
value: ?string,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
class SegmentControlItem extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
lodash.bindAll(this, ['handleClick']);
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
const {onClick} = this.props;
|
||||
return onClick && onClick(this.props.option, e);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {selected} = this.props;
|
||||
const {name} = this.props.option;
|
||||
return (
|
||||
<div className={classNames('item', {selected})} onClick={this.handleClick}>
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SegmentControl extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: Props = {
|
||||
options: [],
|
||||
onChange: NOOP,
|
||||
value: null,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
lodash.bindAll(this, ['renderItem']);
|
||||
}
|
||||
|
||||
renderItem(option: Option) {
|
||||
const {value} = this.props;
|
||||
const selected = value === option.value;
|
||||
|
||||
return <SegmentControlItem key={option.value} selected={selected} option={option} onClick={this.props.onChange} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ui-segment-control flex-horizontal">
|
||||
{this.props.options.map(this.renderItem)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default SegmentControl;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/SegmentControl.js
|
33
509bba0_unpacked/discord_app/uikit/Select.js
Executable file
33
509bba0_unpacked/discord_app/uikit/Select.js
Executable file
|
@ -0,0 +1,33 @@
|
|||
/* @flow */
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import Select from 'react-select-1';
|
||||
import './Select.styl';
|
||||
|
||||
type Props = any;
|
||||
|
||||
/*
|
||||
* TODO Remove this wrapper once all the Selects in the App are using UIKit-Select
|
||||
*/
|
||||
const SelectTempWrapper = ({className, error, ...props}: Props) => {
|
||||
const errorMessage = error
|
||||
? <div className="error-message">
|
||||
{error}
|
||||
</div>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-select', className, {error})}>
|
||||
<Select {...props} />
|
||||
{errorMessage}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectTempWrapper;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Select.js
|
99
509bba0_unpacked/discord_app/uikit/SelectableItem.js
Executable file
99
509bba0_unpacked/discord_app/uikit/SelectableItem.js
Executable file
|
@ -0,0 +1,99 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Flex from './Flex';
|
||||
import CheckMarkIcon from './icons/CheckMarkIcon';
|
||||
import {isValidHex, hex2int, getDarkness} from '../../discord_common/js/utils/ColorUtils';
|
||||
import {Colors} from '../Constants';
|
||||
import './SelectableItem.styl';
|
||||
|
||||
export type SelectableItemProps = {
|
||||
className?: string,
|
||||
color?: string,
|
||||
selected?: boolean,
|
||||
onClick: Function,
|
||||
children?: React.Children,
|
||||
style?: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
color: string,
|
||||
};
|
||||
|
||||
class SelectableItem extends React.PureComponent {
|
||||
props: SelectableItemProps;
|
||||
state: State;
|
||||
|
||||
constructor(props: SelectableItemProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
(this: any)._getColor = this._getColor.bind(this);
|
||||
|
||||
this.state = {
|
||||
color: this._getColor(props.color),
|
||||
};
|
||||
}
|
||||
|
||||
_getColor(color?: string) {
|
||||
let isLight = false;
|
||||
if (color != null && isValidHex(color)) {
|
||||
const intColor = hex2int(color);
|
||||
if (intColor != null) {
|
||||
isLight = getDarkness(intColor) < 0.2;
|
||||
}
|
||||
}
|
||||
return isLight ? Colors.BLACK : Colors.WHITE;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: SelectableItemProps) {
|
||||
if (this.props.color !== nextProps.color) {
|
||||
this.setState({
|
||||
color: this._getColor(nextProps.color),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {color: contrastColor} = this.state;
|
||||
const {selected, color, className, children} = this.props;
|
||||
|
||||
let style;
|
||||
if (selected) {
|
||||
style = {color: contrastColor, background: color || Colors.BRAND};
|
||||
} else if (color != null) {
|
||||
style = {color};
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={classNames('ui-selectable-item', className, {selected})}
|
||||
onClick={this.handleClick}
|
||||
align={Flex.Align.CENTER}
|
||||
style={{...style, ...this.props.style}}>
|
||||
<Flex align={Flex.Align.CENTER} className="ui-selectable-item-label" shrink={1}>
|
||||
{children}
|
||||
</Flex>
|
||||
{selected &&
|
||||
<Flex wrap className="margin-reset" grow={0} shrink={0}>
|
||||
<CheckMarkIcon color={contrastColor} />
|
||||
</Flex>}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleClick() {
|
||||
const {onClick, ...props} = this.props;
|
||||
onClick(props);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectableItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/SelectableItem.js
|
363
509bba0_unpacked/discord_app/uikit/Slider.js
Executable file
363
509bba0_unpacked/discord_app/uikit/Slider.js
Executable file
|
@ -0,0 +1,363 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import lodash from 'lodash';
|
||||
import './Slider.styl';
|
||||
|
||||
type Props = {
|
||||
defaultValue: number,
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
handleSize: number,
|
||||
disabled: boolean,
|
||||
equidistant?: boolean,
|
||||
markers: Array<number>,
|
||||
className?: string,
|
||||
stickToMarkers: boolean,
|
||||
onValueChange?: Function,
|
||||
onValueRender?: Function,
|
||||
onMarkerRender?: Function,
|
||||
asValueChanges?: Function,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
defaultValue: number,
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
handleSize: number,
|
||||
disabled: boolean,
|
||||
stickToMarkers: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: number,
|
||||
x?: number,
|
||||
boundingRect?: {
|
||||
left: number,
|
||||
right: number,
|
||||
},
|
||||
min: number,
|
||||
max: number,
|
||||
range: number,
|
||||
sortedMarkers: Array<number>,
|
||||
markerPositions: Array<number>,
|
||||
closestMarkerIndex?: number,
|
||||
newClosestIndex: ?number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Slider PureComponent
|
||||
*/
|
||||
class Slider extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps: DefaultProps = {
|
||||
defaultValue: 10,
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
handleSize: 10,
|
||||
disabled: false,
|
||||
stickToMarkers: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.defaultValue,
|
||||
...this.getMarkerState(props),
|
||||
newClosestIndex: null,
|
||||
};
|
||||
|
||||
lodash.bindAll(this, ['getMarkerState', 'handleMouseDown', 'handleMouseMove', 'handleMouseUp']);
|
||||
}
|
||||
|
||||
getMarkerState({markers, defaultValue, minValue, maxValue}: Props) {
|
||||
let sortedMarkers = [];
|
||||
let markerPositions = [];
|
||||
|
||||
if (!markers) {
|
||||
return {
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
range: maxValue - minValue,
|
||||
sortedMarkers,
|
||||
markerPositions,
|
||||
};
|
||||
}
|
||||
|
||||
// Sort the markers
|
||||
sortedMarkers = markers.sort((a, b) => a - b);
|
||||
|
||||
// Get the closest marker to the default value
|
||||
const closestMarkerIndex = this.findClosestMarkerByIndex(defaultValue, sortedMarkers);
|
||||
|
||||
// Update the min / max / range
|
||||
const min = sortedMarkers[0];
|
||||
const max = sortedMarkers[sortedMarkers.length - 1];
|
||||
const range = max - min;
|
||||
|
||||
// Cache the marker positions from the left
|
||||
if (this.props.equidistant) {
|
||||
const markerInterval = 100 / (sortedMarkers.length - 1);
|
||||
markerPositions = sortedMarkers.map((_, i) => i * markerInterval);
|
||||
} else {
|
||||
const scaleValue = value => 100.0 * (value - min) / range;
|
||||
markerPositions = sortedMarkers.map(marker => scaleValue(marker));
|
||||
}
|
||||
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
range,
|
||||
sortedMarkers,
|
||||
markerPositions,
|
||||
closestMarkerIndex,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.defaultValue != nextProps.defaultValue) {
|
||||
this.setState({
|
||||
value: nextProps.defaultValue,
|
||||
...this.getMarkerState(nextProps),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
renderBubble(valueScaled: number) {
|
||||
const {value} = this.state;
|
||||
const {stickToMarkers, onValueRender} = this.props;
|
||||
|
||||
if (!stickToMarkers) {
|
||||
return (
|
||||
<span className="bubble elevation-high">
|
||||
{onValueRender ? onValueRender(value) : `${valueScaled.toFixed(0)}%`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderMark(value: number) {
|
||||
const {onMarkerRender} = this.props;
|
||||
return onMarkerRender != null ? onMarkerRender(value) : value;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, sortedMarkers, markerPositions, closestMarkerIndex, newClosestIndex} = this.state;
|
||||
const {disabled, stickToMarkers, className, children} = this.props;
|
||||
|
||||
let valueScaled = 0;
|
||||
if (!stickToMarkers) {
|
||||
valueScaled = this.scaleValue(value);
|
||||
} else if (markerPositions != null) {
|
||||
if (newClosestIndex != null) {
|
||||
valueScaled = markerPositions[newClosestIndex];
|
||||
} else if (closestMarkerIndex != null) {
|
||||
valueScaled = markerPositions[closestMarkerIndex];
|
||||
}
|
||||
}
|
||||
|
||||
const valuePercent = `${valueScaled}%`;
|
||||
|
||||
const marks = markerPositions && sortedMarkers
|
||||
? markerPositions.map((marker, i) => {
|
||||
const markValue = this.renderMark(sortedMarkers[i]);
|
||||
|
||||
return (
|
||||
<div key={i} className="slider-mark" style={{left: `${marker}%`}}>
|
||||
<div className="slider-mark-value">{markValue}</div>
|
||||
<div className={classNames('slider-mark-dash', {simple: markValue == null})} />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-slider', className, {disabled, marked: marks})}>
|
||||
<input type="number" className="slider-input" value={valueScaled.toFixed(0)} readOnly />
|
||||
<div className="slider-handle-track">
|
||||
{marks}
|
||||
</div>
|
||||
<div className="slider-bar">
|
||||
<div className="slider-bar-fill" style={{width: valuePercent}} />
|
||||
</div>
|
||||
{children}
|
||||
<div className="slider-handle-track">
|
||||
<div className="slider-grabber" style={{left: valuePercent}} onMouseDown={this.handleMouseDown}>
|
||||
{this.renderBubble(valueScaled)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Utility Methods
|
||||
|
||||
reset() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp, true);
|
||||
}
|
||||
|
||||
findClosestMarkerByIndex(value: number, markers?: Array<number>) {
|
||||
if (markers == null) {
|
||||
markers = this.state.sortedMarkers;
|
||||
}
|
||||
let diff = 0;
|
||||
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
const currentMarker = markers[i];
|
||||
|
||||
if (value === currentMarker) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (value < currentMarker) {
|
||||
if (diff === 0) {
|
||||
return i;
|
||||
}
|
||||
|
||||
// Closer to next segment
|
||||
if (currentMarker - value < diff) {
|
||||
return i;
|
||||
}
|
||||
// Otherwise closer to previous segment
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
diff = value - currentMarker;
|
||||
}
|
||||
}
|
||||
|
||||
scaleValue(value: number): number {
|
||||
return 100.0 * (value - this.state.min) / this.state.range;
|
||||
}
|
||||
|
||||
unscaleValue(value: number): number {
|
||||
return value * this.state.range / 100 + this.state.min;
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
|
||||
handleMouseDown(e: MouseEvent) {
|
||||
if (this.props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
document.addEventListener('mouseup', this.handleMouseUp, true);
|
||||
|
||||
const domNode = ReactDOM.findDOMNode(this);
|
||||
if (domNode == null || domNode instanceof Text) {
|
||||
throw new Error('[UIKit]Slider.handleMouseDown(): assert failed: domNode instanceof Element');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
x: e.clientX,
|
||||
boundingRect: domNode.getBoundingClientRect(),
|
||||
newClosestIndex: this.state.closestMarkerIndex,
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseUp() {
|
||||
this.reset();
|
||||
|
||||
const {onValueChange, stickToMarkers} = this.props;
|
||||
const {newClosestIndex} = this.state;
|
||||
|
||||
if (stickToMarkers && newClosestIndex != null) {
|
||||
onValueChange && onValueChange(this.state.sortedMarkers[newClosestIndex]);
|
||||
|
||||
this.setState({
|
||||
newClosestIndex: null,
|
||||
closestMarkerIndex: newClosestIndex,
|
||||
});
|
||||
} else {
|
||||
onValueChange && onValueChange(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.props.stickToMarkers) {
|
||||
this.moveStaggered(e);
|
||||
} else {
|
||||
this.moveSmoothly(e);
|
||||
}
|
||||
}
|
||||
|
||||
moveStaggered(e: MouseEvent) {
|
||||
const {boundingRect, x, closestMarkerIndex, markerPositions} = this.state;
|
||||
// $FlowFixMe - Can never be undefined in this case, only null on initial mounting
|
||||
const {left, right} = boundingRect;
|
||||
|
||||
// Don't waste time if already at edges
|
||||
if (e.clientX <= left || e.clientX >= right) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sliderWidth = right - left;
|
||||
|
||||
// $FlowFixMe
|
||||
const deltaPixels = e.clientX - x;
|
||||
|
||||
// How far has the grabber moved in % ?
|
||||
const percentMoved = deltaPixels / sliderWidth;
|
||||
|
||||
// What's the % position at this moved location?
|
||||
// Current position + percentMoved
|
||||
// $FlowFixMe
|
||||
const posDestination = markerPositions[closestMarkerIndex] + percentMoved * 100;
|
||||
|
||||
let newClosestIndex;
|
||||
if (this.props.equidistant) {
|
||||
newClosestIndex = this.findClosestMarkerByIndex(posDestination, markerPositions);
|
||||
} else {
|
||||
// Convert % position to segment value unit
|
||||
const deltaValue = this.unscaleValue(posDestination);
|
||||
|
||||
// Compute the closest segment to this new segment location
|
||||
newClosestIndex = this.findClosestMarkerByIndex(deltaValue);
|
||||
}
|
||||
|
||||
// Set this new index into state under a different key to avoid breaking the math
|
||||
// On mouseup, swap closestMarkerIndex with newClosestIndex and nulls the temp new index.
|
||||
this.setState({newClosestIndex});
|
||||
}
|
||||
|
||||
moveSmoothly(e: MouseEvent) {
|
||||
const {minValue, maxValue, handleSize, asValueChanges} = this.props;
|
||||
// $FlowFixMe - Can never be undefined in this case, only null on initial mounting
|
||||
const {left, right} = this.state.boundingRect;
|
||||
// $FlowFixMe - Can never be undefined in this case, only null on initial mounting
|
||||
const delta = this.state.x - e.clientX;
|
||||
let scaledValue = (this.state.value - minValue) / (maxValue - minValue);
|
||||
|
||||
if (!(e.clientX <= left && delta < 0) && !(e.clientX >= right && delta > 0)) {
|
||||
const totalWidth = right - left - handleSize;
|
||||
const width = totalWidth * scaledValue;
|
||||
const newWidth = Math.min(Math.max(width - delta, 0), totalWidth);
|
||||
scaledValue = newWidth / totalWidth;
|
||||
}
|
||||
const value = minValue + scaledValue * (maxValue - minValue);
|
||||
|
||||
asValueChanges && asValueChanges(value);
|
||||
|
||||
this.setState({value, x: e.clientX});
|
||||
}
|
||||
}
|
||||
|
||||
export default Slider;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Slider.js
|
63
509bba0_unpacked/discord_app/uikit/Spinner.js
Executable file
63
509bba0_unpacked/discord_app/uikit/Spinner.js
Executable file
|
@ -0,0 +1,63 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getClass} from '../utils/StylesheetUtils';
|
||||
import Styles from './Spinner.mod.css';
|
||||
|
||||
const Type = {
|
||||
WANDERING_CUBES: 'wanderingCubes',
|
||||
CHASING_DOTS: 'chasingDots',
|
||||
PULSING_ELLIPSIS: 'pulsingEllipsis',
|
||||
SPINNING_CIRCLE: 'spinningCircle',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: 'wanderingCubes' | 'chasingDots' | 'pulsingEllipsis' | 'spinningCircle',
|
||||
className?: string,
|
||||
};
|
||||
|
||||
class Spinner extends React.PureComponent {
|
||||
static Type = Type;
|
||||
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
type: Type.WANDERING_CUBES,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {type, className, ...props} = this.props;
|
||||
|
||||
if (type === Type.SPINNING_CIRCLE) {
|
||||
return (
|
||||
<div className={classNames(Styles.spinner, Styles[type], className)} {...props}>
|
||||
<div className={Styles.spinningCircleInner}>
|
||||
<svg className={Styles.circular} viewBox="25 25 50 50">
|
||||
<circle className={classNames(Styles.path, Styles.path3)} cx="50" cy="50" r="20" />
|
||||
<circle className={classNames(Styles.path, Styles.path2)} cx="50" cy="50" r="20" />
|
||||
<circle className={Styles.path} cx="50" cy="50" r="20" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const itemClass = getClass(Styles, type, 'item');
|
||||
return (
|
||||
<span className={classNames(Styles.spinner, className)} {...props}>
|
||||
<span className={classNames(Styles.inner, Styles[type])}>
|
||||
<span className={itemClass} />
|
||||
<span className={itemClass} />
|
||||
{type === Type.PULSING_ELLIPSIS ? <span className={itemClass} /> : null}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Spinner;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Spinner.js
|
9
509bba0_unpacked/discord_app/uikit/Spinner.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Spinner.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"spinner":"spinner-2szMS3","inner":"inner-3X2_kk","wanderingCubesItem":"wanderingCubesItem-35kyMu","spinner-wandering-cubes":"spinner-wandering-cubes-1AK53J","chasingDots":"chasingDots-2qWgu6","spinner-chasing-dots-rotate":"spinner-chasing-dots-rotate-3Fvw4X","chasingDotsItem":"chasingDotsItem-1fMB4T","spinner-chasing-dots-bounce":"spinner-chasing-dots-bounce-GCoXlp","pulsingEllipsis":"pulsingEllipsis-36SsC3","pulsingEllipsisItem":"pulsingEllipsisItem-1oPdBE","spinner-pulsing-ellipsis":"spinner-pulsing-ellipsis-2q8oVr","spinningCircle":"spinningCircle-2mRBGy","spinningCircleInner":"spinningCircleInner-2putKt inner-3X2_kk","circular":"circular-2U7_1N","spinner-spinning-circle-rotate":"spinner-spinning-circle-rotate-31ddRE","path":"path-2DIvxA","spinner-spinning-circle-dash":"spinner-spinning-circle-dash-2Trd0K","path2":"path2-3n_CYd","path3":"path3-1Ti7dU"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Spinner.mod.css
|
||||
// module id = 2104
|
||||
// module chunks = 2
|
28
509bba0_unpacked/discord_app/uikit/Switch.js
Executable file
28
509bba0_unpacked/discord_app/uikit/Switch.js
Executable file
|
@ -0,0 +1,28 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './Switch.styl';
|
||||
|
||||
type Props = {
|
||||
value?: boolean,
|
||||
disabled?: boolean,
|
||||
fill?: string,
|
||||
clear?: boolean,
|
||||
style?: Object,
|
||||
className?: string,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
const Switch = ({onChange, value, clear = false, fill, disabled, style, className}: Props) =>
|
||||
<label className={classNames('ui-switch-wrapper', className, {disabled, clear})} style={style}>
|
||||
<input className="ui-switch-checkbox" type="checkbox" disabled={disabled} onChange={onChange} checked={value} />
|
||||
<div className="ui-switch" style={fill != null && value ? {backgroundColor: fill} : null} />
|
||||
</label>;
|
||||
|
||||
export default Switch;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Switch.js
|
75
509bba0_unpacked/discord_app/uikit/SwitchItem.js
Executable file
75
509bba0_unpacked/discord_app/uikit/SwitchItem.js
Executable file
|
@ -0,0 +1,75 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Switch from './Switch';
|
||||
import FormTitle, {Tags} from './form/FormTitle';
|
||||
import FormText, {Types} from './form/FormText';
|
||||
import FormDivider from './form/FormDivider';
|
||||
import Flex from './Flex';
|
||||
import './SwitchItem.styl';
|
||||
|
||||
type Props = {
|
||||
value?: boolean,
|
||||
disabled?: boolean,
|
||||
clear?: boolean,
|
||||
fill?: string,
|
||||
onChange: Function,
|
||||
hideBorder?: boolean,
|
||||
className?: string,
|
||||
note?: any,
|
||||
style?: Object,
|
||||
children?: string,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
disabled: boolean,
|
||||
clear: boolean,
|
||||
hideBorder: boolean,
|
||||
className: string,
|
||||
};
|
||||
|
||||
class SwitchItem extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps;
|
||||
|
||||
render() {
|
||||
const {value, onChange, disabled, clear, fill, hideBorder, children, className, note, style} = this.props;
|
||||
return (
|
||||
<Flex
|
||||
style={style}
|
||||
className={classNames('ui-switch-item', className, {disabled})}
|
||||
direction={Flex.Direction.VERTICAL}>
|
||||
<Flex>
|
||||
<Flex.Child>
|
||||
<FormTitle className="margin-reset" tag={Tags.H3} disabled={disabled}>
|
||||
{children}
|
||||
</FormTitle>
|
||||
</Flex.Child>
|
||||
<Flex.Child grow={0} shrink={0}>
|
||||
<Switch value={value} fill={fill} onChange={onChange} disabled={disabled} clear={clear} />
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
{note &&
|
||||
<Flex.Child className="margin-top-4">
|
||||
<FormText disabled={disabled} type={Types.DESCRIPTION}>{note}</FormText>
|
||||
</Flex.Child>}
|
||||
{!hideBorder && <Flex.Child className="margin-top-20"><FormDivider /></Flex.Child>}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SwitchItem.defaultProps = {
|
||||
disabled: false,
|
||||
clear: false,
|
||||
hideBorder: false,
|
||||
className: 'margin-bottom-20',
|
||||
};
|
||||
|
||||
export default SwitchItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/SwitchItem.js
|
189
509bba0_unpacked/discord_app/uikit/TabBar.js
Executable file
189
509bba0_unpacked/discord_app/uikit/TabBar.js
Executable file
|
@ -0,0 +1,189 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import keyMirror from 'keymirror';
|
||||
import {hex2rgb} from '../../discord_common/js/utils/ColorUtils';
|
||||
import {Colors} from '../Constants';
|
||||
|
||||
import './TabBar.styl';
|
||||
|
||||
export const TabBarTypes = keyMirror({
|
||||
SIDE: null,
|
||||
TOP: null,
|
||||
UNIQUE: null,
|
||||
});
|
||||
|
||||
type ItemProps = {
|
||||
onClick?: (event: Event) => ?boolean,
|
||||
id?: string,
|
||||
className?: ClassValue,
|
||||
children?: any,
|
||||
selected?: boolean,
|
||||
color?: string,
|
||||
};
|
||||
|
||||
type TabBarProps = {
|
||||
selectedItem: string,
|
||||
onItemSelect: Function,
|
||||
className?: string,
|
||||
type?: 'SIDE' | 'TOP' | 'UNIQUE',
|
||||
children?: any,
|
||||
};
|
||||
|
||||
export const TabBarHeader = ({className, onClick, children}: ItemProps) =>
|
||||
<div className={classNames('ui-tab-bar-header', className)} onClick={onClick}>
|
||||
{children}
|
||||
</div>;
|
||||
|
||||
const Modes = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
HOVER: 'HOVER',
|
||||
};
|
||||
|
||||
function getColorStyles(color, mode) {
|
||||
if (color == null) {
|
||||
return null;
|
||||
}
|
||||
const style = {};
|
||||
if (mode === Modes.ACTIVE) {
|
||||
style.backgroundColor = color;
|
||||
style.color = Colors.WHITE;
|
||||
return style;
|
||||
} else if (mode === Modes.HOVER) {
|
||||
style.backgroundColor = hex2rgb(color, 0.1);
|
||||
}
|
||||
style.color = color;
|
||||
return style;
|
||||
}
|
||||
|
||||
export class TabBarItem extends React.Component {
|
||||
props: ItemProps;
|
||||
state: {
|
||||
hover: boolean,
|
||||
active: boolean,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
onItemSelect: React.PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props: ItemProps) {
|
||||
super(props);
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
(this: any).handleMouseOver = this.handleMouseOver.bind(this);
|
||||
(this: any).handleMouseOut = this.handleMouseOut.bind(this);
|
||||
(this: any).handleMouseDown = this.handleMouseDown.bind(this);
|
||||
(this: any).handleMouseUp = this.handleMouseUp.bind(this);
|
||||
this.state = {
|
||||
hover: false,
|
||||
active: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleClick(event: MouseEvent) {
|
||||
const {onClick, id} = this.props;
|
||||
const {onItemSelect} = this.context;
|
||||
if (onClick != null) {
|
||||
onClick(event);
|
||||
} else {
|
||||
onItemSelect(id);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown(event: MouseEvent) {
|
||||
this.handleClick(event);
|
||||
this.setState({active: true});
|
||||
}
|
||||
|
||||
handleMouseUp() {
|
||||
this.setState({active: false});
|
||||
}
|
||||
|
||||
handleMouseOver() {
|
||||
this.setState({hover: true});
|
||||
}
|
||||
|
||||
handleMouseOut() {
|
||||
this.setState({hover: false});
|
||||
}
|
||||
|
||||
getMouseEvents(hasColor: boolean) {
|
||||
if (hasColor) {
|
||||
return {
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseOut: this.handleMouseOut,
|
||||
onMouseDown: this.handleMouseDown,
|
||||
onMouseUp: this.handleMouseUp,
|
||||
};
|
||||
}
|
||||
return {onMouseDown: this.handleClick};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, className, selected, color} = this.props;
|
||||
|
||||
let mouseEvents = {};
|
||||
if (selected == null || selected === false) {
|
||||
mouseEvents = this.getMouseEvents(color ? true : false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-tab-bar-item', className, {selected})} style={this.getStyle()} {...mouseEvents}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getStyle() {
|
||||
const {color, selected} = this.props;
|
||||
const {hover, active} = this.state;
|
||||
if (selected || active) {
|
||||
return getColorStyles(color, Modes.ACTIVE);
|
||||
} else if (color && hover) {
|
||||
return getColorStyles(color, Modes.HOVER);
|
||||
}
|
||||
return getColorStyles(color);
|
||||
}
|
||||
}
|
||||
|
||||
export const TabBarSeparator = () => <div className="ui-tab-bar-separator margin-top-8 margin-bottom-8" />;
|
||||
|
||||
class TabBar extends React.Component {
|
||||
props: TabBarProps;
|
||||
|
||||
static childContextTypes = {
|
||||
onItemSelect: React.PropTypes.func,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
const {onItemSelect} = this.props;
|
||||
return {
|
||||
onItemSelect,
|
||||
};
|
||||
}
|
||||
|
||||
renderChildren(child: React$Element) {
|
||||
const {selectedItem: selected} = this.props;
|
||||
if (child.props.id === selected) {
|
||||
return React.cloneElement(child, {selected});
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, children, type = TabBarTypes.SIDE} = this.props;
|
||||
return (
|
||||
<div className={classNames('ui-tab-bar', className, type)}>
|
||||
{React.Children.map(children, this.renderChildren, this)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TabBar;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/TabBar.js
|
85
509bba0_unpacked/discord_app/uikit/Text.js
Executable file
85
509bba0_unpacked/discord_app/uikit/Text.js
Executable file
|
@ -0,0 +1,85 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import TextStyles from './Text.mod.css';
|
||||
import ColorStyles from '../styles/shared/colors.mod.css';
|
||||
import FontFamilyStyles from '../styles/shared/font_family.mod.css';
|
||||
import FontWeightStyles from '../styles/shared/font_weight.mod.css';
|
||||
|
||||
export const Sizes = {
|
||||
SMALL: TextStyles.small,
|
||||
MEDIUM: TextStyles.medium,
|
||||
LARGE: TextStyles.large,
|
||||
};
|
||||
|
||||
export const Colors = {
|
||||
PRIMARY: TextStyles.primary,
|
||||
BRAND: ColorStyles.brand,
|
||||
GREY: ColorStyles.statusGrey,
|
||||
RED: ColorStyles.statusRed,
|
||||
GREEN: ColorStyles.statusGreen,
|
||||
YELLOW: ColorStyles.statusYellow,
|
||||
LINK: ColorStyles.link,
|
||||
WHITE: ColorStyles.white,
|
||||
};
|
||||
|
||||
export const Family = {
|
||||
WHITNEY: FontFamilyStyles.whitney,
|
||||
CODE: FontFamilyStyles.code,
|
||||
};
|
||||
|
||||
export const Weights = {
|
||||
LIGHT: FontWeightStyles.weightLight,
|
||||
NORMAL: FontWeightStyles.weightNormal,
|
||||
MEDIUM: FontWeightStyles.weightMedium,
|
||||
SEMIBOLD: FontWeightStyles.weightSemiBold,
|
||||
BOLD: FontWeightStyles.weightBold,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tag?: string,
|
||||
size?: string,
|
||||
className?: string,
|
||||
color?: string,
|
||||
family?: string,
|
||||
weight?: string,
|
||||
selectable?: boolean,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
tag: string,
|
||||
selectable: boolean,
|
||||
};
|
||||
|
||||
class Text extends React.PureComponent {
|
||||
static Sizes = Sizes;
|
||||
static Colors = Colors;
|
||||
static Family = Family;
|
||||
static Weights = Weights;
|
||||
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
tag: 'div',
|
||||
selectable: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {tag: Tag = 'div', size, color, family, weight, selectable, children, className, ...props} = this.props;
|
||||
return (
|
||||
<Tag
|
||||
className={classNames(className, size, color, family, weight, {[TextStyles.selectable]: selectable})}
|
||||
{...props}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Text;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Text.js
|
9
509bba0_unpacked/discord_app/uikit/Text.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/Text.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"size12":"size12-3Dmxyp","size16":"size16-taxBJG","size24":"size24-2Y3hoM","height16":"height16-2O70g6","height20":"height20-3Y9N_z","height28":"height28-3nDabV","small":"small-3-03j1 size12-3Dmxyp height16-2O70g6","medium":"medium-2KnC-N size16-taxBJG height20-3Y9N_z","large":"large-yIak37 size24-2Y3hoM height28-3nDabV","selectable":"selectable-prgIYK","primary":"primary-2giqSn"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/Text.mod.css
|
||||
// module id = 2105
|
||||
// module chunks = 2
|
144
509bba0_unpacked/discord_app/uikit/TextArea.js
Executable file
144
509bba0_unpacked/discord_app/uikit/TextArea.js
Executable file
|
@ -0,0 +1,144 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './TextArea.styl';
|
||||
|
||||
type Props = {
|
||||
name?: string,
|
||||
disabled?: boolean,
|
||||
value: ?string,
|
||||
placeholder?: string,
|
||||
error?: string,
|
||||
autoFocus?: boolean,
|
||||
maxLength?: number,
|
||||
resizeable?: boolean,
|
||||
className?: string,
|
||||
id?: string,
|
||||
// Events
|
||||
onChange?: (value: string, name?: string) => void,
|
||||
onFocus?: (e: Event, name?: string) => void,
|
||||
onBlur?: (e: Event, name?: string) => void,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
name: string,
|
||||
disabled: boolean,
|
||||
placeholder: string,
|
||||
autoFocus: boolean,
|
||||
resizeable: boolean,
|
||||
};
|
||||
|
||||
const DEFAULT_PADDING_RIGHT = 10;
|
||||
const MAX_LEN_FONT_WIDTH = 7.23;
|
||||
|
||||
/**
|
||||
* TextArea PureComponent
|
||||
*/
|
||||
class TextArea extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
name: '',
|
||||
disabled: false,
|
||||
placeholder: '',
|
||||
autoFocus: false,
|
||||
resizeable: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).onChange = this.onChange.bind(this);
|
||||
(this: any).onFocus = this.onFocus.bind(this);
|
||||
(this: any).onBlur = this.onBlur.bind(this);
|
||||
}
|
||||
|
||||
getPaddingRight() {
|
||||
const {maxLength} = this.props;
|
||||
|
||||
if (maxLength == null) {
|
||||
return DEFAULT_PADDING_RIGHT;
|
||||
}
|
||||
|
||||
return MAX_LEN_FONT_WIDTH * `${maxLength}`.length + DEFAULT_PADDING_RIGHT;
|
||||
}
|
||||
|
||||
renderMaxLength() {
|
||||
const {maxLength, value} = this.props;
|
||||
if (maxLength == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const valueLength = value != null ? value.length : 0;
|
||||
const charsLeft = maxLength - valueLength;
|
||||
|
||||
return (
|
||||
<div className="ui-text-area-max-length">
|
||||
{charsLeft}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderErrorMessage() {
|
||||
const {error} = this.props;
|
||||
if (error == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="error-message">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {disabled, value, placeholder, error, autoFocus, maxLength, resizeable, className, id} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ui-text-input ui-text-area flex-vertical">
|
||||
<div className="ui-text-area-input-max-length">
|
||||
<textarea
|
||||
type="text"
|
||||
className={classNames('input', className, {error, disabled, resizeable})}
|
||||
style={{paddingRight: this.getPaddingRight()}}
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
autoFocus={autoFocus}
|
||||
maxLength={maxLength}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
/>
|
||||
{this.renderMaxLength()}
|
||||
</div>
|
||||
{this.renderErrorMessage()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* Handlers */
|
||||
onChange(e: Event) {
|
||||
const {onChange, name} = this.props;
|
||||
onChange && onChange((e.target: window.HTMLInputElement).value, name);
|
||||
}
|
||||
|
||||
onFocus(e: Event) {
|
||||
const {onFocus, name} = this.props;
|
||||
onFocus && onFocus(e, name);
|
||||
}
|
||||
|
||||
onBlur(e: Event) {
|
||||
const {onBlur, name} = this.props;
|
||||
onBlur && onBlur(e, name);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextArea;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/TextArea.js
|
109
509bba0_unpacked/discord_app/uikit/TextInput.js
Executable file
109
509bba0_unpacked/discord_app/uikit/TextInput.js
Executable file
|
@ -0,0 +1,109 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './TextInput.styl';
|
||||
import '../styles/shared/errors.styl';
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
name?: string,
|
||||
disabled?: boolean,
|
||||
type?: string,
|
||||
value: ?string,
|
||||
placeholder?: string,
|
||||
error?: string,
|
||||
autoFocus?: boolean,
|
||||
maxLength?: number,
|
||||
editable?: boolean,
|
||||
size?: string,
|
||||
className?: string,
|
||||
// Events
|
||||
onChange?: Function,
|
||||
onFocus?: Function,
|
||||
onBlur?: Function,
|
||||
onKeyPress?: Function,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
name: string,
|
||||
size: string,
|
||||
disabled: boolean,
|
||||
type: string,
|
||||
placeholder: string,
|
||||
autoFocus: boolean,
|
||||
maxLength: number,
|
||||
};
|
||||
|
||||
export const TextInputSizes = {
|
||||
DEFAULT: 'default',
|
||||
MINI: 'mini',
|
||||
};
|
||||
|
||||
class TextInput extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps;
|
||||
static Sizes = TextInputSizes;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
// Flow workaround for self binding
|
||||
(this: any).onChange = this.onChange.bind(this);
|
||||
(this: any).onFocus = this.onFocus.bind(this);
|
||||
(this: any).onBlur = this.onBlur.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {error, className, disabled, size, editable, ...props} = this.props;
|
||||
|
||||
const errorMessage = error
|
||||
? <div className="ui-error-message margin-top-8">
|
||||
{error}
|
||||
</div>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-text-input', 'flex-vertical', className)}>
|
||||
<input
|
||||
className={classNames('input', size, {error, disabled, editable})}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
/>
|
||||
{errorMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* Handlers */
|
||||
onChange(e: Event) {
|
||||
this.props.onChange && this.props.onChange((e.target: window.HTMLInputElement).value, this.props.name);
|
||||
}
|
||||
|
||||
onFocus(e: Event) {
|
||||
this.props.onFocus && this.props.onFocus(e, this.props.name);
|
||||
}
|
||||
|
||||
onBlur(e: Event) {
|
||||
this.props.onBlur && this.props.onBlur(e, this.props.name);
|
||||
}
|
||||
}
|
||||
|
||||
TextInput.defaultProps = {
|
||||
name: '',
|
||||
size: TextInputSizes.DEFAULT,
|
||||
disabled: false,
|
||||
type: 'text',
|
||||
placeholder: '',
|
||||
autoFocus: false,
|
||||
maxLength: 999,
|
||||
};
|
||||
|
||||
export default TextInput;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/TextInput.js
|
53
509bba0_unpacked/discord_app/uikit/ToggleIcon.js
Executable file
53
509bba0_unpacked/discord_app/uikit/ToggleIcon.js
Executable file
|
@ -0,0 +1,53 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import IconButton from './IconButton';
|
||||
import {NOOP} from '../Constants';
|
||||
|
||||
type Props = {
|
||||
size: string,
|
||||
value: boolean,
|
||||
inactiveSrc: string,
|
||||
activeSrc: string,
|
||||
inactiveSrcHover: string,
|
||||
activeSrcHover: string,
|
||||
className?: string,
|
||||
onMouseOver?: Function,
|
||||
onMouseOut?: Function,
|
||||
onChange: Function,
|
||||
onContextMenu?: Function,
|
||||
};
|
||||
|
||||
class ToggleIcon extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
value: false,
|
||||
size: IconButton.Sizes.MEDIUM,
|
||||
onChange: NOOP,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
lodash.bindAll(this, ['handleClick']);
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
const {onChange, value} = this.props;
|
||||
onChange(!value, e);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, activeSrc, inactiveSrc, activeSrcHover, inactiveSrcHover, ...props} = this.props;
|
||||
const src = value ? activeSrc : inactiveSrc;
|
||||
const srcHover = value ? activeSrcHover : inactiveSrcHover;
|
||||
return <IconButton {...props} onClick={this.handleClick} src={src} srcHover={srcHover} />;
|
||||
}
|
||||
}
|
||||
export default ToggleIcon;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/ToggleIcon.js
|
136
509bba0_unpacked/discord_app/uikit/Verification.js
Executable file
136
509bba0_unpacked/discord_app/uikit/Verification.js
Executable file
|
@ -0,0 +1,136 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import i18n from '../i18n';
|
||||
import HelpdeskUtils from '../utils/HelpdeskUtils';
|
||||
import Flex from './Flex';
|
||||
import {VerificationTypes, NOOP} from '../Constants';
|
||||
import Recaptcha from '../components/common/Recaptcha';
|
||||
import './Verification.styl';
|
||||
|
||||
type VerificationBlockProps = {
|
||||
onClick: () => void,
|
||||
};
|
||||
|
||||
class VerificationBlock extends React.PureComponent {
|
||||
constructor(props: VerificationBlockProps) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {type} = this.props;
|
||||
const body = type === VerificationTypes.EMAIL_OR_PHONE
|
||||
? i18n.Messages.VERIFY_BY_EMAIL_FORMATTED.format()
|
||||
: i18n.Messages.VERIFY_BY_PHONE_FORMATTED.format();
|
||||
return (
|
||||
<Flex
|
||||
className="ui-verification-block"
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}
|
||||
onClick={this.handleClick}>
|
||||
<div className={classNames('image', type)} />
|
||||
<div className="body">{body}</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const {onClick, type} = this.props;
|
||||
onClick(type);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
captchaKey?: number,
|
||||
onCaptchaChange: (captchaKey: string) => void,
|
||||
onClick?: (type: string) => void,
|
||||
onLogout: () => void,
|
||||
theme: 'light' | 'dark',
|
||||
};
|
||||
|
||||
class Verification extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps = {
|
||||
type: VerificationTypes.CAPTCHA,
|
||||
onCaptchaChange: NOOP,
|
||||
onLogout: NOOP,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
renderFields() {
|
||||
const {type, captchaKey, theme, onCaptchaChange} = this.props;
|
||||
const fields = [];
|
||||
switch (type) {
|
||||
case VerificationTypes.CAPTCHA:
|
||||
return <Recaptcha key={captchaKey} onChange={onCaptchaChange} theme={theme} />;
|
||||
case VerificationTypes.EMAIL_OR_PHONE:
|
||||
fields.push(
|
||||
<VerificationBlock
|
||||
key={VerificationTypes.EMAIL_OR_PHONE}
|
||||
type={VerificationTypes.EMAIL_OR_PHONE}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
);
|
||||
case VerificationTypes.PHONE:
|
||||
fields.push(
|
||||
<VerificationBlock key={VerificationTypes.PHONE} type={VerificationTypes.PHONE} onClick={this.handleClick} />
|
||||
);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex className="ui-verification" align={Flex.Align.CENTER} direction={Flex.Direction.VERTICAL}>
|
||||
<Flex
|
||||
className="container"
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
align={Flex.Align.CENTER}
|
||||
justify={Flex.Justify.CENTER}>
|
||||
<div className="image" />
|
||||
<div className="title margin-top-20">{i18n.Messages.VERIFICATION_TITLE}</div>
|
||||
<div className="body margin-top-4 margin-bottom-20">{i18n.Messages.VERIFICATION_BODY.format()}</div>
|
||||
<Flex grow={0} justify={Flex.Justify.CENTER}>
|
||||
{this.renderFields()}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<div className="footer">
|
||||
{i18n.Messages.VERIFICATION_FOOTER}
|
||||
</div>
|
||||
<Flex className="margin-top-4 margin-bottom-20" grow={0}>
|
||||
<div className="footer footer-action">
|
||||
{i18n.Messages.VERIFICATION_FOOTER_SUPPORT.format({supportURL: HelpdeskUtils.getSubmitRequestURL()})}
|
||||
</div>
|
||||
<div className="footer footer-bullet">•</div>
|
||||
<div className="footer footer-action">
|
||||
{i18n.Messages.VERIFICATION_FOOTER_LOGOUT.format({logoutOnClick: this.props.onLogout})}
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
handleClick(type: string) {
|
||||
const {onClick} = this.props;
|
||||
onClick && onClick(type);
|
||||
}
|
||||
}
|
||||
|
||||
export default Verification;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Verification.js
|
209
509bba0_unpacked/discord_app/uikit/Video.js
Executable file
209
509bba0_unpacked/discord_app/uikit/Video.js
Executable file
|
@ -0,0 +1,209 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import lodash from 'lodash';
|
||||
import Animated from '../lib/animated';
|
||||
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
||||
import {Timeout} from '../lib/timers';
|
||||
import Flex from './Flex';
|
||||
import {CallLayouts} from '../Constants';
|
||||
import './Video.styl';
|
||||
|
||||
const IDLE_TIMEOUT = 3000;
|
||||
|
||||
type Props = {
|
||||
layout: string,
|
||||
top: any,
|
||||
center: any,
|
||||
bottom: any,
|
||||
background: any,
|
||||
backgroundKey?: string,
|
||||
className?: string,
|
||||
onContextMenu?: Function,
|
||||
animated: boolean,
|
||||
focused: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
idle: boolean,
|
||||
backgroundAnimation: Animated.Value,
|
||||
};
|
||||
|
||||
export class BackgroundTransition extends React.PureComponent {
|
||||
state: *;
|
||||
|
||||
constructor(props: *) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
animation: new Animated.Value(0),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidAppear() {
|
||||
this.state.animation.setValue(1);
|
||||
}
|
||||
|
||||
componentWillEnter(callback: Function) {
|
||||
const {animation} = this.state;
|
||||
|
||||
animation.setValue(0);
|
||||
Animated.spring(animation, {
|
||||
toValue: 1,
|
||||
overshootClamping: true,
|
||||
}).start(callback);
|
||||
}
|
||||
|
||||
componentWillLeave(callback: Function) {
|
||||
Animated.spring(this.state.animation, {
|
||||
toValue: 0,
|
||||
overshootClamping: true,
|
||||
}).start(callback);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Animated.div className="ui-video-background-transition" style={{opacity: this.state.animation}}>
|
||||
{this.props.children}
|
||||
</Animated.div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Video extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
static defaultProps = {
|
||||
layout: CallLayouts.MINIMUM,
|
||||
animated: true,
|
||||
};
|
||||
|
||||
_timeout = new Timeout();
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
idle: false,
|
||||
backgroundAnimation: new Animated.Value(0),
|
||||
};
|
||||
|
||||
lodash.bindAll(this, ['handleMouseEvent']);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', this.handleMouseEvent, true);
|
||||
document.addEventListener('mousemove', this.handleMouseEvent, true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleMouseEvent, true);
|
||||
document.removeEventListener('mousemove', this.handleMouseEvent, true);
|
||||
this._timeout.stop();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.layout !== nextProps.layout) {
|
||||
this._timeout.stop();
|
||||
|
||||
if (this.state.idle) {
|
||||
this.setState({idle: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseEvent() {
|
||||
const {layout} = this.props;
|
||||
if (layout !== CallLayouts.FULL_SCREEN && layout !== CallLayouts.NO_CHAT) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._timeout.start(IDLE_TIMEOUT, () => this.setState({idle: true}));
|
||||
|
||||
if (this.state.idle) {
|
||||
this.setState({idle: false});
|
||||
}
|
||||
}
|
||||
|
||||
renderBackground() {
|
||||
const {background, backgroundKey, layout} = this.props;
|
||||
const bgTransitionKey = `${layout}-${backgroundKey || ''}`;
|
||||
|
||||
return (
|
||||
<TransitionGroup className="ui-video-background" component="div">
|
||||
<BackgroundTransition key={bgTransitionKey}>
|
||||
{background}
|
||||
</BackgroundTransition>
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
|
||||
renderContents() {
|
||||
// video has four components:
|
||||
// 1. background video
|
||||
// 2. top component i.e. region select
|
||||
// 3. center component i.e. call avatars
|
||||
// 4. bottom component i.e. action groups
|
||||
|
||||
const {top, center, bottom, layout, onContextMenu, focused} = this.props;
|
||||
const {idle} = this.state;
|
||||
|
||||
if (layout === CallLayouts.NO_CHAT) {
|
||||
return (
|
||||
<Flex
|
||||
className={classNames('ui-video', layout, {idle})}
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
justify={Flex.Justify.CENTER}
|
||||
onContextMenu={onContextMenu}>
|
||||
<Flex className="ui-video-top" grow={0}>{top}</Flex>
|
||||
<Flex className="ui-video-wrapper margin-bottom-20 margin-top-60" direction={Flex.Direction.VERTICAL}>
|
||||
{this.renderBackground()}
|
||||
<Flex
|
||||
className={classNames('ui-video-inner', {focused})}
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
justify={Flex.Justify.END}>
|
||||
<Flex className="ui-video-bottom" grow={0}>{bottom}</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex className="ui-video-center" grow={0}>{center}</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={classNames('ui-video', layout, {idle})}
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
justify={Flex.Justify.CENTER}>
|
||||
<Flex direction={Flex.Direction.VERTICAL}>
|
||||
{this.renderBackground()}
|
||||
<Flex
|
||||
className={classNames('ui-video-inner', {focused})}
|
||||
direction={Flex.Direction.VERTICAL}
|
||||
justify={Flex.Justify.BETWEEN}>
|
||||
<Flex className="ui-video-top" grow={0}>{top}</Flex>
|
||||
<Flex className="ui-video-center">{center}</Flex>
|
||||
<Flex className="ui-video-bottom" grow={0}>{bottom}</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {layout, className, animated} = this.props;
|
||||
return (
|
||||
<div className={classNames('ui-video-height', layout, className, {animated})}>
|
||||
{this.renderContents()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Video;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/Video.js
|
43
509bba0_unpacked/discord_app/uikit/VideoBackground.js
Executable file
43
509bba0_unpacked/discord_app/uikit/VideoBackground.js
Executable file
|
@ -0,0 +1,43 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from './Flex';
|
||||
import Avatar from './Avatar';
|
||||
import Styles from './VideoBackground.mod.css';
|
||||
import type UserRecord from '../records/UserRecord';
|
||||
|
||||
type Props = {
|
||||
user: UserRecord,
|
||||
avatarSize: string,
|
||||
style?: {},
|
||||
};
|
||||
|
||||
class VideoBackground extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
static AvatarSizes = Avatar.Sizes;
|
||||
|
||||
render() {
|
||||
const {avatarSize, user, style} = this.props;
|
||||
const avatarURL = user.getAvatarURL();
|
||||
|
||||
if (avatarURL == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex style={style} className={Styles.background} justify={Flex.Justify.CENTER} align={Flex.Align.CENTER}>
|
||||
<div className={Styles.backgroundBlur} style={{backgroundImage: `url('${avatarURL}')`}} />
|
||||
<div className={Styles.backgroundBlurOverlay} />
|
||||
<Avatar className="elevation-high" size={avatarSize} src={avatarURL} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoBackground;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/VideoBackground.js
|
9
509bba0_unpacked/discord_app/uikit/VideoBackground.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/VideoBackground.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"background":"background-2lD0PO","backgroundBlur":"backgroundBlur-2hMM9N","backgroundBlurOverlay":"backgroundBlurOverlay-278FQ6"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/VideoBackground.mod.css
|
||||
// module id = 2106
|
||||
// module chunks = 2
|
38
509bba0_unpacked/discord_app/uikit/VideoDevicesWarningModal.js
Executable file
38
509bba0_unpacked/discord_app/uikit/VideoDevicesWarningModal.js
Executable file
|
@ -0,0 +1,38 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Modal from './Modal';
|
||||
import Button, {ButtonSizes} from './Button';
|
||||
import Styles from './VideoDevicesWarningModal.mod.css';
|
||||
|
||||
type Props = {
|
||||
title?: string,
|
||||
description?: string,
|
||||
confirmText?: string,
|
||||
onConfirm?: Function,
|
||||
};
|
||||
class VideoDevicesWarningModal extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
const {title, description, confirmText, onConfirm} = this.props;
|
||||
|
||||
return (
|
||||
<Modal className={Styles.modal}>
|
||||
<img className={Styles.image} src={require('../images/img_no_video_source.svg')} />
|
||||
<div className={Styles.title}>{title}</div>
|
||||
<div className={Styles.description}>{description}</div>
|
||||
<Button size={ButtonSizes.LARGE} onClick={onConfirm}>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoDevicesWarningModal;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/VideoDevicesWarningModal.js
|
9
509bba0_unpacked/discord_app/uikit/VideoDevicesWarningModal.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/VideoDevicesWarningModal.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"vertical":"vertical-3X17r5 flex-3B1Tl4 directionColumn-2h-LPR","center":"center-1MLNrE flex-3B1Tl4 justifyCenter-29N31w alignCenter-3VxkQP","marginTop8":"marginTop8-2VMDNt","marginBottom8":"marginBottom8-t-wxCE","marginBottom40":"marginBottom40-1_QcFO","modal":"modal-1CXkYE vertical-3X17r5 flex-3B1Tl4 directionColumn-2h-LPR center-1MLNrE flex-3B1Tl4 justifyCenter-29N31w alignCenter-3VxkQP","image":"image-23RBgi marginTop8-2VMDNt marginBottom40-1_QcFO","title":"title-2lS8Wf marginBottom8-t-wxCE","description":"description-3M6Yzv marginBottom40-1_QcFO"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/VideoDevicesWarningModal.mod.css
|
||||
// module id = 2107
|
||||
// module chunks = 2
|
27
509bba0_unpacked/discord_app/uikit/form/FormDivider.js
Executable file
27
509bba0_unpacked/discord_app/uikit/form/FormDivider.js
Executable file
|
@ -0,0 +1,27 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './FormDivider.mod.css';
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
style?: Object,
|
||||
};
|
||||
|
||||
class FormDivider extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
const {className, style} = this.props;
|
||||
|
||||
return <div className={classNames(styles.divider, className)} style={style} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default FormDivider;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormDivider.js
|
9
509bba0_unpacked/discord_app/uikit/form/FormDivider.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/form/FormDivider.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"divider":"divider-1G01Z9"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/form/FormDivider.mod.css
|
||||
// module id = 2108
|
||||
// module chunks = 2
|
52
509bba0_unpacked/discord_app/uikit/form/FormItem.js
Executable file
52
509bba0_unpacked/discord_app/uikit/form/FormItem.js
Executable file
|
@ -0,0 +1,52 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import FormTitle, {Tags} from './FormTitle';
|
||||
import type {Tag} from './FormTitle';
|
||||
|
||||
type Props = {
|
||||
tag?: Tag,
|
||||
title?: string,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
shrink?: boolean,
|
||||
required?: boolean,
|
||||
style?: Object,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
class FormItem extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
shrink = false,
|
||||
disabled = false,
|
||||
className,
|
||||
tag = Tags.H5,
|
||||
required = false,
|
||||
style = {},
|
||||
title,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('ui-form-item', className, {shrink})} style={style}>
|
||||
{title &&
|
||||
<FormTitle tag={tag} disabled={disabled} required={required} {...props}>
|
||||
{title}
|
||||
</FormTitle>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormItem.js
|
99
509bba0_unpacked/discord_app/uikit/form/FormNotice.js
Executable file
99
509bba0_unpacked/discord_app/uikit/form/FormNotice.js
Executable file
|
@ -0,0 +1,99 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import FormTitle, {Tags} from './FormTitle';
|
||||
import FormText from './FormText';
|
||||
import Flex from '../Flex';
|
||||
import Card from '../Card';
|
||||
import styles from './FormNotice.mod.css';
|
||||
|
||||
export const Types = Card.Types;
|
||||
|
||||
export const ImagePositions = {
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
className?: string,
|
||||
iconClassName?: string,
|
||||
title?: string,
|
||||
body: string,
|
||||
imageData?: {
|
||||
src: string,
|
||||
width: number,
|
||||
height: number,
|
||||
position?: string,
|
||||
},
|
||||
button?: ReactComponent,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
type: string,
|
||||
};
|
||||
|
||||
class FormNotice extends React.PureComponent {
|
||||
static Types = Card.Types;
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
type: Card.Types.DANGER,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {type, imageData, button, className, iconClassName, title, body} = this.props;
|
||||
|
||||
let noticeExtra;
|
||||
let direction = Flex.Direction.HORIZONTAL;
|
||||
if (imageData != null) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const {position, ...attrs} = imageData;
|
||||
|
||||
noticeExtra = (
|
||||
<Flex.Child grow={0} shrink={0}>
|
||||
<img className={classNames(styles.icon, iconClassName)} {...attrs} />
|
||||
</Flex.Child>
|
||||
);
|
||||
|
||||
if (position === ImagePositions.RIGHT) {
|
||||
direction = Flex.Direction.HORIZONTAL_REVERSE;
|
||||
}
|
||||
} else if (button != null) {
|
||||
noticeExtra = button;
|
||||
}
|
||||
|
||||
let whiteText = true;
|
||||
if (type === Card.Types.PRIMARY) {
|
||||
whiteText = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={classNames(styles.formNotice, className)} type={type}>
|
||||
<Flex direction={direction} align={Flex.Align.START}>
|
||||
{noticeExtra}
|
||||
<Flex.Child>
|
||||
{title
|
||||
? <FormTitle
|
||||
faded
|
||||
tag={Tags.H5}
|
||||
className={classNames(styles.formNoticeTitle, {[styles.whiteText]: whiteText})}>
|
||||
{title}
|
||||
</FormTitle>
|
||||
: null}
|
||||
<FormText className={classNames(styles.formNoticeBody, {[styles.whiteText]: whiteText})}>
|
||||
{body}
|
||||
</FormText>
|
||||
</Flex.Child>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormNotice;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormNotice.js
|
9
509bba0_unpacked/discord_app/uikit/form/FormNotice.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/form/FormNotice.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"marginBottom4":"marginBottom4-JjxJY3","noUserDrag":"noUserDrag-aLJFCB","formNotice":"formNotice-2tZsrh","formNoticeTitle":"formNoticeTitle-1m5mND marginBottom4-JjxJY3","formNoticeBody":"formNoticeBody-1C0wup","icon":"icon-3j0iot noUserDrag-aLJFCB","whiteText":"whiteText-32USMe"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/form/FormNotice.mod.css
|
||||
// module id = 2109
|
||||
// module chunks = 2
|
51
509bba0_unpacked/discord_app/uikit/form/FormSection.js
Executable file
51
509bba0_unpacked/discord_app/uikit/form/FormSection.js
Executable file
|
@ -0,0 +1,51 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import FormTitle from './FormTitle';
|
||||
import type {Tag} from './FormTitle';
|
||||
|
||||
type Props = {
|
||||
title?: string,
|
||||
tag: Tag,
|
||||
className?: string,
|
||||
children?: ReactElement,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
tag: Tag,
|
||||
};
|
||||
|
||||
class FormSection extends React.PureComponent {
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
tag: FormTitle.Tags.H5,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, className, title, tag: Tag} = this.props;
|
||||
|
||||
if (title != null) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<FormTitle tag={Tag}>{title}</FormTitle>
|
||||
<div className="flex-vertical">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormSection;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormSection.js
|
72
509bba0_unpacked/discord_app/uikit/form/FormText.js
Executable file
72
509bba0_unpacked/discord_app/uikit/form/FormText.js
Executable file
|
@ -0,0 +1,72 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Text from '../Text';
|
||||
import Styles from './FormText.mod.css';
|
||||
|
||||
export const Types = {
|
||||
DEFAULT: 'default',
|
||||
INPUT_PLACEHOLDER: 'placeholder',
|
||||
DESCRIPTION: 'description',
|
||||
LABEL_BOLD: 'labelBold',
|
||||
LABEL_SELECTED: 'labelSelected',
|
||||
LABEL_DESCRIPTOR: 'labelDescriptor',
|
||||
};
|
||||
|
||||
const Modes = {
|
||||
DEFAULT: 'modeDefault',
|
||||
DISABLED: 'modeDisabled',
|
||||
SELECTABLE: 'modeSelectable',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
selectable?: boolean,
|
||||
style?: Object,
|
||||
children?: ReactElement,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
type: string,
|
||||
};
|
||||
|
||||
class FormText extends React.PureComponent {
|
||||
static Types = Types;
|
||||
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
type: Types.DEFAULT,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {type, className, disabled, selectable, children, style, ...props} = this.props;
|
||||
|
||||
let mode = Modes.DEFAULT;
|
||||
if (disabled) {
|
||||
mode = Modes.DISABLED;
|
||||
} else if (selectable) {
|
||||
mode = Modes.SELECTABLE;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
className={classNames(Styles[type], className, Styles[mode])}
|
||||
color={Text.Colors.PRIMARY}
|
||||
size={Text.Sizes.MEDIUM}
|
||||
style={style}
|
||||
{...props}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormText;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormText.js
|
9
509bba0_unpacked/discord_app/uikit/form/FormText.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/form/FormText.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"formText":"formText-1L-zZB","default":"default-3bB32Y formText-1L-zZB","labelDescriptor":"labelDescriptor-1BebCl formText-1L-zZB","labelSelected":"labelSelected-2H3qNC formText-1L-zZB","placeholder":"placeholder-2ZdNOt formText-1L-zZB","labelBold":"labelBold-1QoWDr formText-1L-zZB","description":"description-3MVziF formText-1L-zZB","modeDefault":"modeDefault-389VjU","modeSelectable":"modeSelectable-ZWJ_KZ","modeDisabled":"modeDisabled-1GfQwv"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/form/FormText.mod.css
|
||||
// module id = 2110
|
||||
// module chunks = 2
|
66
509bba0_unpacked/discord_app/uikit/form/FormTitle.js
Executable file
66
509bba0_unpacked/discord_app/uikit/form/FormTitle.js
Executable file
|
@ -0,0 +1,66 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Text from '../Text';
|
||||
import classNames from 'classnames';
|
||||
import Styles from './FormTitle.mod.css';
|
||||
|
||||
export type Tag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
||||
|
||||
export const Tags = {
|
||||
H1: 'h1',
|
||||
H2: 'h2',
|
||||
H3: 'h3',
|
||||
H4: 'h4',
|
||||
H5: 'h5',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tag: Tag,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
required: boolean,
|
||||
faded?: boolean,
|
||||
children?: string,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
tag: Tag,
|
||||
required: boolean,
|
||||
};
|
||||
|
||||
class FormTitle extends React.PureComponent {
|
||||
static Tags = Tags;
|
||||
|
||||
props: Props;
|
||||
static defaultProps: DefaultProps = {
|
||||
tag: Tags.H5,
|
||||
required: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {tag: Tag, className, disabled, required, children, faded, ...props} = this.props;
|
||||
const defaultFontColor = !props.color && Tag !== Tags.H5 ? Styles.defaultColor : null;
|
||||
|
||||
return (
|
||||
<Text
|
||||
tag={Tag}
|
||||
className={classNames(Styles[Tag], defaultFontColor, className, {
|
||||
[Styles[`defaultMargin${Tag}`]]: className == null,
|
||||
[Styles.disabled]: disabled,
|
||||
[Styles.faded]: faded,
|
||||
})}
|
||||
{...props}>
|
||||
{children}
|
||||
{required ? <span className={Styles.required}>*</span> : null}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormTitle;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/FormTitle.js
|
9
509bba0_unpacked/discord_app/uikit/form/FormTitle.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/form/FormTitle.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"size12":"size12-3Dmxyp","size14":"size14-2Q0sRz","size16":"size16-taxBJG","size20":"size20-1BrEEq","height16":"height16-2O70g6","height20":"height20-3Y9N_z","height24":"height24-2MTN63","height28":"height28-3nDabV","weightMedium":"weightMedium-3W3WrK","weightSemiBold":"weightSemiBold-W4vlRo","statusRed":"statusRed-2wPP2E","marginReset":"marginReset-32p2uQ","marginBottom20":"marginBottom20-3RkOLe","marginBottom8":"marginBottom8-t-wxCE","title":"title-1pmpPr marginReset-32p2uQ","h1":"h1-f0Txxg title-1pmpPr marginReset-32p2uQ size20-1BrEEq height28-3nDabV weightSemiBold-W4vlRo","h2":"h2-2ar_1B title-1pmpPr marginReset-32p2uQ size16-taxBJG height20-3Y9N_z weightSemiBold-W4vlRo","h3":"h3-gDcP8B title-1pmpPr marginReset-32p2uQ size16-taxBJG height24-2MTN63 weightMedium-3W3WrK","h4":"h4-2IXpeI title-1pmpPr marginReset-32p2uQ size14-2Q0sRz height20-3Y9N_z weightMedium-3W3WrK","h5":"h5-3KssQU title-1pmpPr marginReset-32p2uQ size12-3Dmxyp height16-2O70g6 weightSemiBold-W4vlRo","defaultMarginh1":"defaultMarginh1-36Scnc marginBottom20-3RkOLe","defaultMarginh2":"defaultMarginh2-37e5HZ marginBottom20-3RkOLe","defaultMarginh3":"defaultMarginh3-1a2POY marginBottom8-t-wxCE","defaultMarginh4":"defaultMarginh4-jAopYe marginBottom8-t-wxCE","defaultMarginh5":"defaultMarginh5-2UwwFY marginBottom8-t-wxCE","disabled":"disabled-1Jp4lJ","required":"required-15-TOo statusRed-2wPP2E","defaultColor":"defaultColor-v22dK1","faded":"faded-1KRDbu"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/form/FormTitle.mod.css
|
||||
// module id = 2111
|
||||
// module chunks = 2
|
24
509bba0_unpacked/discord_app/uikit/form/index.js
Executable file
24
509bba0_unpacked/discord_app/uikit/form/index.js
Executable file
|
@ -0,0 +1,24 @@
|
|||
import FormNotice, {Types as FormNoticeTypes, ImagePositions as FormNoticeImagePositions} from './FormNotice';
|
||||
import FormDivider from './FormDivider';
|
||||
import FormSection from './FormSection';
|
||||
import FormText, {Types as FormTextTypes} from './FormText';
|
||||
import FormTitle, {Tags as FormTitleTags} from './FormTitle';
|
||||
import FormItem from './FormItem';
|
||||
|
||||
export default {
|
||||
FormDivider,
|
||||
FormNotice,
|
||||
FormNoticeImagePositions,
|
||||
FormNoticeTypes,
|
||||
FormSection,
|
||||
FormText,
|
||||
FormTextTypes,
|
||||
FormTitle,
|
||||
FormTitleTags,
|
||||
FormItem,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/form/index.js
|
127
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelCategoryItem.js
Executable file
127
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelCategoryItem.js
Executable file
|
@ -0,0 +1,127 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from '../Flex';
|
||||
import {getClass} from '../../utils/StylesheetUtils';
|
||||
import styles from './ChannelCategoryItem.mod.css';
|
||||
|
||||
import type ChannelRecord from '../../records/ChannelRecord';
|
||||
|
||||
function stopPropagation(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
const Modes = {
|
||||
HOVERED: 'Hovered',
|
||||
UNREAD: 'Unread',
|
||||
MUTED: 'Muted',
|
||||
DEFAULT: 'Default',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
// FIXME: Remove the | statement once channel cat backend has been merged
|
||||
channel: ChannelRecord | {name: string},
|
||||
collapsed?: boolean,
|
||||
unread?: boolean,
|
||||
muted?: boolean,
|
||||
onClick?: ?Function,
|
||||
onMouseEnter?: Function,
|
||||
onMouseLeave?: Function,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hovered: boolean,
|
||||
};
|
||||
|
||||
class ChannelCategoryItem extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
(this: any).handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
(this: any).handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
getMode() {
|
||||
const {muted, collapsed, unread} = this.props;
|
||||
const {hovered} = this.state;
|
||||
if (hovered) {
|
||||
return Modes.HOVERED;
|
||||
}
|
||||
if (!muted && collapsed && unread) {
|
||||
return Modes.UNREAD;
|
||||
}
|
||||
if (muted) {
|
||||
return Modes.MUTED;
|
||||
}
|
||||
return Modes.DEFAULT;
|
||||
}
|
||||
|
||||
handleClick(event: MouseEvent) {
|
||||
const {onClick, channel} = this.props;
|
||||
onClick && onClick(channel, event);
|
||||
}
|
||||
|
||||
handleMouseEnter(event: MouseEvent) {
|
||||
const {onMouseEnter, channel} = this.props;
|
||||
this.setState({hovered: true});
|
||||
onMouseEnter && onMouseEnter(event, channel);
|
||||
}
|
||||
|
||||
handleMouseLeave(event: MouseEvent) {
|
||||
const {onMouseLeave, channel} = this.props;
|
||||
this.setState({hovered: false});
|
||||
onMouseLeave && onMouseLeave(event, channel);
|
||||
}
|
||||
|
||||
renderName() {
|
||||
const {channel: {name}} = this.props;
|
||||
const mode = this.getMode();
|
||||
return (
|
||||
<Flex.Child className={getClass(styles, 'name', mode)}>
|
||||
{name}
|
||||
</Flex.Child>
|
||||
);
|
||||
}
|
||||
|
||||
renderIcons() {
|
||||
const {children} = this.props;
|
||||
if (React.Children.count(children) === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Flex grow={0} onClick={stopPropagation} align={Flex.Align.CENTER} className="margin-reset">
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onClick} = this.props;
|
||||
const mode = this.getMode();
|
||||
return (
|
||||
<Flex
|
||||
className={getClass(styles, 'wrapper', mode)}
|
||||
align={Flex.Align.START}
|
||||
onClick={onClick && this.handleClick}
|
||||
onMouseEnter={onClick && this.handleMouseEnter}
|
||||
onMouseLeave={onClick && this.handleMouseLeave}>
|
||||
{this.renderName()}
|
||||
{this.renderIcons()}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelCategoryItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/guild_sidebar/ChannelCategoryItem.js
|
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"colorTransition":"colorTransition-2iZaYd","bgTransition":"bgTransition-2QYWCS","overflowEllipsis":"overflowEllipsis-2ynGQq","wrapperDefault":"wrapperDefault-1Dl4SS","wrapperHovered":"wrapperHovered-1KDCyZ","wrapperMuted":"wrapperMuted-PmsxPn","wrapperUnread":"wrapperUnread-450E16","nameDefault":"nameDefault-Lnjrwm colorTransition-2iZaYd overflowEllipsis-2ynGQq","nameHovered":"nameHovered-1YFSWq colorTransition-2iZaYd overflowEllipsis-2ynGQq","nameMuted":"nameMuted-1n0LSj colorTransition-2iZaYd overflowEllipsis-2ynGQq","nameUnread":"nameUnread-WKlSv- colorTransition-2iZaYd overflowEllipsis-2ynGQq"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/guild_sidebar/ChannelCategoryItem.mod.css
|
||||
// module id = 2112
|
||||
// module chunks = 2
|
225
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelItem.js
Executable file
225
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelItem.js
Executable file
|
@ -0,0 +1,225 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import Flex from '../Flex';
|
||||
|
||||
import {
|
||||
ChannelIconVoice,
|
||||
ChannelIconText,
|
||||
ChannelIconVoiceLimited,
|
||||
ChannelIconTextLimited,
|
||||
ChannelIconLocked,
|
||||
} from '../icons/ChannelIcons';
|
||||
|
||||
import {getClass} from '../../utils/StylesheetUtils';
|
||||
|
||||
import {ChannelTypes} from '../../Constants';
|
||||
import styles from './ChannelItem.mod.css';
|
||||
|
||||
import type ChannelRecord from '../../records/ChannelRecord';
|
||||
|
||||
function stopPropagation(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
const IconsRoleRequired = {
|
||||
[ChannelTypes.GUILD_TEXT]: ChannelIconTextLimited,
|
||||
[ChannelTypes.GUILD_VOICE]: ChannelIconVoiceLimited,
|
||||
};
|
||||
|
||||
const IconsDefault = {
|
||||
[ChannelTypes.GUILD_TEXT]: ChannelIconText,
|
||||
[ChannelTypes.GUILD_VOICE]: ChannelIconVoice,
|
||||
};
|
||||
|
||||
const ChannelTypeStrings = {
|
||||
[ChannelTypes.GUILD_TEXT]: 'Text',
|
||||
[ChannelTypes.GUILD_VOICE]: 'Voice',
|
||||
};
|
||||
|
||||
const Modes = {
|
||||
SELECTED: 'Selected',
|
||||
HOVERED: 'Hovered',
|
||||
UNREAD: 'Unread',
|
||||
MUTED: 'Muted',
|
||||
LOCKED: 'Locked',
|
||||
DEFAULT: 'Default',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
channel: ChannelRecord,
|
||||
selected?: boolean,
|
||||
unread?: boolean,
|
||||
muted?: boolean,
|
||||
locked?: boolean,
|
||||
onClick?: Function,
|
||||
onMouseDown?: Function,
|
||||
onMouseUp?: Function,
|
||||
onMouseEnter?: Function,
|
||||
onMouseLeave?: Function,
|
||||
onContextMenu?: Function,
|
||||
connectDragPreview?: Function,
|
||||
children?: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
hovered: boolean,
|
||||
};
|
||||
|
||||
class ChannelItem extends React.PureComponent {
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
(this: any).handleClick = this.handleClick.bind(this);
|
||||
(this: any).handleMouseDown = this.handleMouseDown.bind(this);
|
||||
(this: any).handleMouseUp = this.handleMouseUp.bind(this);
|
||||
(this: any).handleContextMenu = this.handleContextMenu.bind(this);
|
||||
(this: any).handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
(this: any).handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
getMode() {
|
||||
const {muted, selected, unread, locked} = this.props;
|
||||
const {hovered} = this.state;
|
||||
if (selected) {
|
||||
return Modes.SELECTED;
|
||||
}
|
||||
if (locked) {
|
||||
return Modes.LOCKED;
|
||||
}
|
||||
if (hovered) {
|
||||
return Modes.HOVERED;
|
||||
}
|
||||
if (muted) {
|
||||
return Modes.MUTED;
|
||||
}
|
||||
if (unread) {
|
||||
return Modes.UNREAD;
|
||||
}
|
||||
return Modes.DEFAULT;
|
||||
}
|
||||
|
||||
handleClick(event: MouseEvent) {
|
||||
const {onClick, channel} = this.props;
|
||||
onClick && onClick(event, channel);
|
||||
}
|
||||
|
||||
handleMouseEnter(event: MouseEvent) {
|
||||
const {onMouseEnter, channel} = this.props;
|
||||
this.setState({hovered: true});
|
||||
onMouseEnter && onMouseEnter(event, channel);
|
||||
}
|
||||
|
||||
handleMouseLeave(event: MouseEvent) {
|
||||
const {onMouseLeave, channel} = this.props;
|
||||
this.setState({hovered: false});
|
||||
onMouseLeave && onMouseLeave(event, channel);
|
||||
}
|
||||
|
||||
handleMouseDown(event: MouseEvent) {
|
||||
const {onMouseDown, channel} = this.props;
|
||||
onMouseDown && onMouseDown(event, channel);
|
||||
}
|
||||
|
||||
handleMouseUp(event: MouseEvent) {
|
||||
const {onMouseUp, channel} = this.props;
|
||||
onMouseUp && onMouseUp(event, channel);
|
||||
}
|
||||
|
||||
handleContextMenu(event: MouseEvent) {
|
||||
const {onContextMenu, channel} = this.props;
|
||||
onContextMenu && onContextMenu(event, channel);
|
||||
}
|
||||
|
||||
renderUnread() {
|
||||
const {unread, muted, selected} = this.props;
|
||||
if (muted || !unread || selected) {
|
||||
return null;
|
||||
}
|
||||
return <div className={styles.unread} />;
|
||||
}
|
||||
|
||||
renderTypeIcon() {
|
||||
const {channel, locked} = this.props;
|
||||
let Icon;
|
||||
if (locked) {
|
||||
Icon = ChannelIconLocked;
|
||||
} else if (channel.isRoleRequired()) {
|
||||
Icon = IconsRoleRequired[channel.type];
|
||||
} else {
|
||||
Icon = IconsDefault[channel.type];
|
||||
}
|
||||
const mode = this.getMode();
|
||||
return (
|
||||
<Flex.Child wrap grow={0} shrink={0} className="margin-reset">
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
colorClassName={getClass(styles, 'color', mode, ChannelTypeStrings[channel.type])}
|
||||
/>
|
||||
</Flex.Child>
|
||||
);
|
||||
}
|
||||
|
||||
renderName() {
|
||||
const {channel: {name, type}, unread} = this.props;
|
||||
let mode = this.getMode();
|
||||
// Don't change text color on unread hovers
|
||||
if (mode === Modes.HOVERED && unread) {
|
||||
mode = Modes.UNREAD;
|
||||
}
|
||||
return (
|
||||
<Flex.Child className={getClass(styles, 'name', mode, ChannelTypeStrings[type])}>
|
||||
{name}
|
||||
</Flex.Child>
|
||||
);
|
||||
}
|
||||
|
||||
renderIcons() {
|
||||
const {children} = this.props;
|
||||
if (React.Children.count(children) === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Flex grow={0} onClick={stopPropagation} align={Flex.Align.CENTER} className="margin-reset">
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onClick, onMouseDown, onMouseUp, onContextMenu, connectDragPreview, channel: {type}} = this.props;
|
||||
const mode = this.getMode();
|
||||
const content = (
|
||||
<div className={getClass(styles, 'content', mode, ChannelTypeStrings[type])}>
|
||||
{this.renderTypeIcon()}
|
||||
{this.renderName()}
|
||||
{this.renderIcons()}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={getClass(styles, 'wrapper', mode, ChannelTypeStrings[type])}
|
||||
onClick={onClick && this.handleClick}
|
||||
onMouseUp={onMouseUp && this.handleMouseUp}
|
||||
onMouseDown={onMouseDown && this.handleMouseDown}
|
||||
onContextMenu={onContextMenu && this.handleContextMenu}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}>
|
||||
{this.renderUnread()}
|
||||
{connectDragPreview ? connectDragPreview(content) : content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelItem;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/guild_sidebar/ChannelItem.js
|
9
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelItem.mod.css
Executable file
9
509bba0_unpacked/discord_app/uikit/guild_sidebar/ChannelItem.mod.css
Executable file
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"overflowEllipsis":"overflowEllipsis-3Rxxjf","wrapper":"wrapper-fDmxzK","wrapperDefaultText":"wrapperDefaultText-3M3F1R wrapper-fDmxzK","wrapperDefaultVoice":"wrapperDefaultVoice-2ud9mj wrapper-fDmxzK","wrapperHoveredText":"wrapperHoveredText-1PA_Uk wrapper-fDmxzK","wrapperHoveredVoice":"wrapperHoveredVoice-3tbfNN wrapper-fDmxzK","wrapperLockedText":"wrapperLockedText-Dsondf wrapper-fDmxzK","wrapperLockedVoice":"wrapperLockedVoice-1XZJmE wrapper-fDmxzK","wrapperMutedText":"wrapperMutedText-34VhKk wrapper-fDmxzK","wrapperMutedVoice":"wrapperMutedVoice-2BiLWu wrapper-fDmxzK","wrapperSelectedText":"wrapperSelectedText-31jJa8 wrapper-fDmxzK","wrapperSelectedVoice":"wrapperSelectedVoice-1Q1ocJ wrapper-fDmxzK","wrapperUnreadText":"wrapperUnreadText-1MykVG wrapper-fDmxzK","wrapperUnreadVoice":"wrapperUnreadVoice-13F9Gg wrapper-fDmxzK","icon":"icon-3tVJnl","colorDefaultText":"colorDefaultText-2v6rRX","colorDefaultVoice":"colorDefaultVoice-1x4dEl","colorHoveredText":"colorHoveredText-1CsxK1","colorHoveredVoice":"colorHoveredVoice-1P3kui","colorLockedText":"colorLockedText-2onHWv","colorLockedVoice":"colorLockedVoice-2VCSgk","colorSelectedText":"colorSelectedText-3YhFC6","colorSelectedVoice":"colorSelectedVoice-ieXC3Z","colorUnreadText":"colorUnreadText-1KwCnP","colorUnreadVoice":"colorUnreadVoice-1Ztv9I","colorMutedText":"colorMutedText-2DBGZ2","colorMutedVoice":"colorMutedVoice-2FZw4C","content":"content-2mSKOj","contentDefaultText":"contentDefaultText-2elG3R content-2mSKOj","contentDefaultVoice":"contentDefaultVoice-311dxZ content-2mSKOj","contentHoveredText":"contentHoveredText-2HYGIY content-2mSKOj","contentHoveredVoice":"contentHoveredVoice-3qGNKh content-2mSKOj","contentLockedText":"contentLockedText-1NPV7g content-2mSKOj","contentLockedVoice":"contentLockedVoice-2lGH0M content-2mSKOj","contentMutedText":"contentMutedText-Lcl-Wj content-2mSKOj","contentMutedVoice":"contentMutedVoice-1AvYom content-2mSKOj","contentSelectedText":"contentSelectedText-3j5CXt content-2mSKOj","contentSelectedVoice":"contentSelectedVoice-gTtYM9 content-2mSKOj","contentUnreadText":"contentUnreadText-3oxASB content-2mSKOj","contentUnreadVoice":"contentUnreadVoice-1vHNNW content-2mSKOj","name":"name-2SL4ev","nameDefaultText":"nameDefaultText-QoumjC name-2SL4ev overflowEllipsis-3Rxxjf","nameDefaultVoice":"nameDefaultVoice-1swZoh name-2SL4ev overflowEllipsis-3Rxxjf","nameHoveredText":"nameHoveredText-2FFqiz name-2SL4ev overflowEllipsis-3Rxxjf","nameHoveredVoice":"nameHoveredVoice-TIoHRJ name-2SL4ev overflowEllipsis-3Rxxjf","nameLockedText":"nameLockedText-2fGz07 name-2SL4ev overflowEllipsis-3Rxxjf","nameLockedVoice":"nameLockedVoice-wNOMNa name-2SL4ev overflowEllipsis-3Rxxjf","nameMutedText":"nameMutedText-1YDcP- name-2SL4ev overflowEllipsis-3Rxxjf","nameMutedVoice":"nameMutedVoice-26k4cP name-2SL4ev overflowEllipsis-3Rxxjf","nameSelectedText":"nameSelectedText-32NDX5 name-2SL4ev overflowEllipsis-3Rxxjf","nameSelectedVoice":"nameSelectedVoice-XpjYTw name-2SL4ev overflowEllipsis-3Rxxjf","nameUnreadText":"nameUnreadText-1pxldj name-2SL4ev overflowEllipsis-3Rxxjf","nameUnreadVoice":"nameUnreadVoice-QK4gxH name-2SL4ev overflowEllipsis-3Rxxjf","unread":"unread-23Kvxk"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/guild_sidebar/ChannelItem.mod.css
|
||||
// module id = 2113
|
||||
// module chunks = 2
|
29
509bba0_unpacked/discord_app/uikit/guild_sidebar/VoiceChannelUserLimit.js
Executable file
29
509bba0_unpacked/discord_app/uikit/guild_sidebar/VoiceChannelUserLimit.js
Executable file
|
@ -0,0 +1,29 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import styles from './VoiceChannelUserLimit.mod.css';
|
||||
|
||||
type Props = {
|
||||
users: number,
|
||||
total: number,
|
||||
};
|
||||
|
||||
class VoiceChannelUserLimit extends React.PureComponent {
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
const {total, users} = this.props;
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{users.toString().padStart(2, '0')}/{total.toString().padStart(2, '0')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VoiceChannelUserLimit;
|
||||
|
||||
|
||||
|
||||
// WEBPACK FOOTER //
|
||||
// ./discord_app/uikit/guild_sidebar/VoiceChannelUserLimit.js
|
|
@ -0,0 +1,9 @@
|
|||
// removed by extract-text-webpack-plugin
|
||||
module.exports = {"wrapper":"wrapper-2ldvyE"};
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./discord_app/uikit/guild_sidebar/VoiceChannelUserLimit.mod.css
|
||||
// module id = 2114
|
||||
// module chunks = 2
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue