bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/user-list/component.jsx

324 lines
8.6 KiB
React
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
2016-06-28 21:10:20 +08:00
import styles from './styles.scss';
2016-06-29 22:24:27 +08:00
import cx from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
2016-05-20 02:22:56 +08:00
import UserListItem from './user-list-item/component.jsx';
import ChatListItem from './chat-list-item/component.jsx';
2017-05-05 22:37:01 +08:00
import KEY_CODES from '/imports/utils/keyCodes';
2016-05-20 02:22:56 +08:00
2016-06-29 03:52:03 +08:00
const propTypes = {
openChats: PropTypes.array.isRequired,
users: PropTypes.array.isRequired,
};
2017-08-05 04:05:18 +08:00
const intlMessages = defineMessages({
usersTitle: {
id: 'app.userlist.usersTitle',
description: 'Title for the Header',
},
messagesTitle: {
id: 'app.userlist.messagesTitle',
description: 'Title for the messages list',
},
participantsTitle: {
id: 'app.userlist.participantsTitle',
description: 'Title for the Users list',
},
ChatLabel: {
id: 'app.userlist.menu.chat.label',
description: 'Save the changes and close the settings menu',
},
ClearStatusLabel: {
id: 'app.userlist.menu.clearStatus.label',
description: 'Clear the emoji status of this user',
},
MakePresenterLabel: {
id: 'app.userlist.menu.makePresenter.label',
description: 'Set this user to be the presenter in this meeting',
},
KickUserLabel: {
id: 'app.userlist.menu.kickUser.label',
description: 'Forcefully remove this user from the meeting',
},
MuteUserAudioLabel: {
id: 'app.userlist.menu.muteUserAudio.label',
description: 'Forcefully mute this user',
},
UnmuteUserAudioLabel: {
id: 'app.userlist.menu.unmuteUserAudio.label',
description: 'Forcefully unmute this user',
},
});
2016-06-29 03:52:03 +08:00
2016-06-28 21:10:20 +08:00
const listTransition = {
enter: styles.enter,
enterActive: styles.enterActive,
appear: styles.appear,
appearActive: styles.appearActive,
leave: styles.leave,
leaveActive: styles.leaveActive,
};
class UserList extends Component {
constructor(props) {
super(props);
this.state = {
2016-09-15 01:48:50 +08:00
compact: this.props.compact,
};
this.rovingIndex = this.rovingIndex.bind(this);
2017-05-11 02:10:27 +08:00
this.focusList = this.focusList.bind(this);
this.focusedItemIndex = -1;
}
focusList(list) {
document.activeElement.tabIndex = -1;
this.focusedItemIndex = -1;
2017-05-11 02:10:27 +08:00
list.tabIndex = 0;
list.focus();
}
2017-05-19 01:29:25 +08:00
rovingIndex(event, listType) {
2017-05-13 02:11:42 +08:00
const { users, openChats } = this.props;
2017-08-01 21:41:24 +08:00
2017-08-05 04:05:18 +08:00
const active = document.activeElement;
2017-05-06 03:11:28 +08:00
let list;
let items;
let numberOfItems;
2017-08-01 21:41:24 +08:00
2017-06-06 00:35:11 +08:00
const focusElement = () => {
active.tabIndex = -1;
items.childNodes[this.focusedItemIndex].tabIndex = 0;
items.childNodes[this.focusedItemIndex].focus();
2017-08-05 04:05:18 +08:00
};
2017-08-01 21:41:24 +08:00
2017-05-19 01:29:25 +08:00
switch (listType) {
2017-05-11 02:10:27 +08:00
case 'users':
2017-05-11 22:11:17 +08:00
list = this._usersList;
items = this._userItems;
numberOfItems = users.length;
2017-05-11 02:10:27 +08:00
break;
case 'messages':
2017-05-11 22:11:17 +08:00
list = this._msgsList;
items = this._msgItems;
numberOfItems = openChats.length;
2017-05-11 02:10:27 +08:00
break;
2017-05-05 05:41:09 +08:00
}
2017-05-19 01:29:25 +08:00
if (event.keyCode === KEY_CODES.ESCAPE
|| this.focusedItemIndex < 0
|| this.focusedItemIndex > numberOfItems) {
2017-08-01 21:41:24 +08:00
this.focusList(list);
2017-05-06 03:11:28 +08:00
}
2017-06-08 23:33:25 +08:00
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.ARROW_SPACE].includes(event.keyCode)) {
active.firstChild.click();
2017-05-05 22:37:01 +08:00
}
2017-05-19 01:29:25 +08:00
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
this.focusedItemIndex += 1;
if (this.focusedItemIndex == numberOfItems) {
this.focusedItemIndex = 0;
}
2017-06-06 00:35:11 +08:00
focusElement();
}
2017-05-19 01:29:25 +08:00
if (event.keyCode === KEY_CODES.ARROW_UP) {
this.focusedItemIndex -= 1;
2017-06-06 03:08:34 +08:00
if (this.focusedItemIndex < 0) {
this.focusedItemIndex = numberOfItems - 1;
}
2017-06-06 00:35:11 +08:00
focusElement();
2017-05-05 22:37:01 +08:00
}
}
componentDidMount() {
if (!this.state.compact) {
this._msgsList.addEventListener('keydown',
2017-08-05 04:05:18 +08:00
event => this.rovingIndex(event, 'messages'));
this._usersList.addEventListener('keydown',
2017-08-05 04:05:18 +08:00
event => this.rovingIndex(event, 'users'));
}
}
2016-05-20 02:22:56 +08:00
render() {
return (
<div className={styles.userList}>
2016-05-26 03:29:22 +08:00
{this.renderHeader()}
{this.renderContent()}
</div>
);
}
renderHeader() {
const { intl } = this.props;
2016-05-26 03:29:22 +08:00
return (
<div className={styles.header}>
2016-09-15 01:48:50 +08:00
{
!this.state.compact ?
2017-06-03 03:25:02 +08:00
<div className={styles.headerTitle} role="banner">
{intl.formatMessage(intlMessages.participantsTitle)}
</div> : null
}
2016-05-26 03:29:22 +08:00
</div>
);
2016-05-26 03:29:22 +08:00
}
2016-05-20 02:22:56 +08:00
2016-05-26 03:29:22 +08:00
renderContent() {
return (
<div className={styles.content}>
{this.renderMessages()}
{this.renderParticipants()}
2016-05-20 02:22:56 +08:00
</div>
);
}
2016-05-26 03:29:22 +08:00
renderMessages() {
2016-06-29 03:52:03 +08:00
const {
openChats,
2016-06-30 22:45:19 +08:00
openChat,
intl,
2016-06-29 03:52:03 +08:00
} = this.props;
2016-05-26 03:29:22 +08:00
return (
<div className={styles.messages}>
2016-09-15 01:48:50 +08:00
{
!this.state.compact ?
2017-06-03 03:25:02 +08:00
<div className={styles.smallTitle} role="banner">
{intl.formatMessage(intlMessages.messagesTitle)}
</div> : <hr className={styles.separator} />
}
2017-05-11 22:11:17 +08:00
<div
tabIndex={0}
className={styles.scrollableList}
2017-06-07 05:55:46 +08:00
ref={(ref) => { this._msgsList = ref; }}
2017-06-03 03:25:02 +08:00
>
<CSSTransitionGroup
2016-06-28 21:10:20 +08:00
transitionName={listTransition}
2017-08-05 04:05:18 +08:00
transitionAppear
transitionEnter
2016-06-28 21:10:20 +08:00
transitionLeave={false}
transitionAppearTimeout={0}
transitionEnterTimeout={0}
transitionLeaveTimeout={0}
2017-05-13 01:45:41 +08:00
component="div"
2017-06-03 03:25:02 +08:00
className={cx(styles.chatsList, styles.scrollableList)}
>
2017-06-07 05:55:46 +08:00
<div ref={(ref) => { this._msgItems = ref; }}>
2016-06-29 03:52:03 +08:00
{openChats.map(chat => (
2016-06-28 21:10:20 +08:00
<ChatListItem
compact={this.state.compact}
2016-06-28 21:10:20 +08:00
key={chat.id}
2016-06-30 22:45:19 +08:00
openChat={openChat}
chat={chat}
2017-06-03 03:25:02 +08:00
tabIndex={-1}
/>
2016-06-28 21:10:20 +08:00
))}
2017-05-11 22:11:17 +08:00
</div>
</CSSTransitionGroup>
2016-06-28 21:10:20 +08:00
</div>
2016-05-26 03:29:22 +08:00
</div>
);
2016-05-26 03:29:22 +08:00
}
renderParticipants() {
2016-06-29 03:52:03 +08:00
const {
users,
currentUser,
isBreakoutRoom,
intl,
makeCall,
meeting,
2016-06-29 03:52:03 +08:00
} = this.props;
2016-06-28 21:10:20 +08:00
2017-04-29 02:28:55 +08:00
const userActions = {
openChat: {
label: intl.formatMessage(intlMessages.ChatLabel),
handler: (router, user) => router.push(`/users/chat/${user.id}`),
icon: 'chat',
},
clearStatus: {
label: intl.formatMessage(intlMessages.ClearStatusLabel),
handler: user => makeCall('setEmojiStatus', user.id, 'none'),
2017-04-29 02:28:55 +08:00
icon: 'clear_status',
},
setPresenter: {
label: intl.formatMessage(intlMessages.MakePresenterLabel),
handler: user => makeCall('assignPresenter', user.id),
2017-04-29 02:28:55 +08:00
icon: 'presentation',
},
kick: {
label: intl.formatMessage(intlMessages.KickUserLabel),
handler: user => makeCall('kickUser', user.id),
2017-04-29 02:28:55 +08:00
icon: 'circle_close',
},
mute: {
label: intl.formatMessage(intlMessages.MuteUserAudioLabel),
2017-08-05 04:05:18 +08:00
handler: user => makeCall('toggleVoice', user.id),
2017-04-29 02:28:55 +08:00
icon: 'audio_off',
},
unmute: {
label: intl.formatMessage(intlMessages.UnmuteUserAudioLabel),
2017-08-05 04:05:18 +08:00
handler: user => makeCall('toggleVoice', user.id),
2017-04-29 02:28:55 +08:00
icon: 'audio_on',
},
};
2016-05-26 03:29:22 +08:00
return (
<div className={styles.participants}>
2016-09-15 01:48:50 +08:00
{
!this.state.compact ?
2017-06-03 03:25:02 +08:00
<div className={styles.smallTitle} role="banner">
{intl.formatMessage(intlMessages.usersTitle)}
2017-08-01 21:41:24 +08:00
&nbsp;({users.length})
2017-06-03 03:25:02 +08:00
</div> : <hr className={styles.separator} />
}
2017-05-11 22:11:17 +08:00
<div
className={styles.scrollableList}
tabIndex={0}
2017-06-07 05:55:46 +08:00
ref={(ref) => { this._usersList = ref; }}
2017-06-03 03:25:02 +08:00
>
<CSSTransitionGroup
2017-05-11 22:11:17 +08:00
transitionName={listTransition}
2017-08-05 04:05:18 +08:00
transitionAppear
transitionEnter
transitionLeave
2017-05-11 22:11:17 +08:00
transitionAppearTimeout={0}
transitionEnterTimeout={0}
transitionLeaveTimeout={0}
2017-05-13 01:45:41 +08:00
component="div"
2017-06-03 03:25:02 +08:00
className={cx(styles.participantsList, styles.scrollableList)}
>
2017-06-07 05:55:46 +08:00
<div ref={(ref) => { this._userItems = ref; }}>
2017-05-11 22:11:17 +08:00
{
users.map(user => (
2017-06-03 03:25:02 +08:00
<UserListItem
compact={this.state.compact}
key={user.id}
isBreakoutRoom={isBreakoutRoom}
user={user}
currentUser={currentUser}
userActions={userActions}
meeting={meeting}
/>
))
2017-05-11 22:11:17 +08:00
}
</div>
</CSSTransitionGroup>
</div>
2016-05-26 03:29:22 +08:00
</div>
);
2016-05-26 03:29:22 +08:00
}
2016-05-20 02:22:56 +08:00
}
2016-06-29 03:52:03 +08:00
UserList.propTypes = propTypes;
export default withRouter(injectIntl(UserList));