2016-06-29 03:52:03 +08:00
|
|
|
import React, { Component, PropTypes } from 'react';
|
2016-06-02 21:00:57 +08:00
|
|
|
import { withRouter } from 'react-router';
|
2016-06-28 21:10:20 +08:00
|
|
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
|
|
|
import styles from './styles.scss';
|
2016-06-29 22:24:27 +08:00
|
|
|
import cx from 'classnames';
|
2017-04-04 02:05:47 +08:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
const defaultProps = {
|
|
|
|
};
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2016-06-02 21:00:57 +08:00
|
|
|
class UserList extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2016-08-25 02:56:06 +08:00
|
|
|
this.state = {
|
2016-09-15 01:48:50 +08:00
|
|
|
compact: this.props.compact,
|
2016-08-25 02:56:06 +08:00
|
|
|
};
|
2017-05-05 04:39:53 +08:00
|
|
|
|
|
|
|
this.rovingIndex = this.rovingIndex.bind(this);
|
2017-05-11 02:10:27 +08:00
|
|
|
this.focusList = this.focusList.bind(this);
|
|
|
|
this.focusListItem = this.focusListItem.bind(this);
|
2017-05-06 03:11:28 +08:00
|
|
|
this.counter = -1;
|
2017-05-05 04:39:53 +08:00
|
|
|
}
|
|
|
|
|
2017-05-11 02:10:27 +08:00
|
|
|
focusList(activeElement, list) {
|
|
|
|
activeElement.tabIndex = -1;
|
|
|
|
this.counter = 0;
|
|
|
|
list.tabIndex = 0;
|
|
|
|
list.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
focusListItem(active, direction, element, count) {
|
2017-05-11 02:24:09 +08:00
|
|
|
|
|
|
|
function select() {
|
|
|
|
element.tabIndex = 0;
|
|
|
|
element.focus();
|
|
|
|
}
|
|
|
|
|
2017-05-11 02:10:27 +08:00
|
|
|
active.tabIndex = -1;
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
case 'down':
|
|
|
|
element.childNodes[this.counter].tabIndex = 0;
|
|
|
|
element.childNodes[this.counter].focus();
|
|
|
|
this.counter++;
|
|
|
|
break;
|
|
|
|
case 'up':
|
|
|
|
this.counter--;
|
|
|
|
element.childNodes[this.counter].tabIndex = 0;
|
|
|
|
element.childNodes[this.counter].focus();
|
|
|
|
break;
|
|
|
|
case 'upLoopUp':
|
2017-05-11 02:39:03 +08:00
|
|
|
case 'upLoopDown':
|
2017-05-11 02:10:27 +08:00
|
|
|
this.counter = count - 1;
|
2017-05-11 02:24:09 +08:00
|
|
|
select();
|
2017-05-11 02:10:27 +08:00
|
|
|
break;
|
2017-05-11 02:39:03 +08:00
|
|
|
case 'downLoopDown':
|
|
|
|
this.counter = -1;
|
|
|
|
select();
|
|
|
|
break;
|
|
|
|
case 'downLoopUp':
|
|
|
|
this.counter = 1;
|
|
|
|
select();
|
2017-05-11 02:10:27 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-05 05:41:09 +08:00
|
|
|
rovingIndex(...Args) {
|
2017-05-13 02:11:42 +08:00
|
|
|
const { users, openChats } = this.props;
|
|
|
|
|
2017-05-05 22:37:01 +08:00
|
|
|
let active = document.activeElement;
|
2017-05-06 03:11:28 +08:00
|
|
|
let list;
|
|
|
|
let items;
|
|
|
|
let count;
|
2017-05-05 22:37:01 +08:00
|
|
|
|
2017-05-11 02:10:27 +08:00
|
|
|
switch (Args[1]) {
|
|
|
|
case 'users':
|
2017-05-11 22:11:17 +08:00
|
|
|
list = this._usersList;
|
|
|
|
items = this._userItems;
|
2017-05-11 02:10:27 +08:00
|
|
|
count = users.length;
|
|
|
|
break;
|
|
|
|
case 'messages':
|
2017-05-11 22:11:17 +08:00
|
|
|
list = this._msgsList;
|
|
|
|
items = this._msgItems;
|
2017-05-11 02:10:27 +08:00
|
|
|
count = openChats.length;
|
|
|
|
break;
|
2017-05-05 05:41:09 +08:00
|
|
|
}
|
2017-05-05 04:39:53 +08:00
|
|
|
|
2017-05-11 02:10:27 +08:00
|
|
|
if (Args[0].keyCode === KEY_CODES.ESCAPE
|
|
|
|
|| this.counter === -1
|
|
|
|
|| this.counter > count) {
|
|
|
|
this.focusList(active, list);
|
2017-05-06 03:11:28 +08:00
|
|
|
}
|
|
|
|
|
2017-05-05 22:37:01 +08:00
|
|
|
if (Args[0].keyCode === KEY_CODES.ENTER
|
|
|
|
|| Args[0].keyCode === KEY_CODES.ARROW_RIGHT
|
|
|
|
|| Args[0].keyCode === KEY_CODES.ARROW_LEFT) {
|
2017-05-06 03:11:28 +08:00
|
|
|
active.firstChild.click();
|
2017-05-05 22:37:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Args[0].keyCode === KEY_CODES.ARROW_DOWN) {
|
|
|
|
if (this.counter < count) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'down', items);
|
2017-05-11 02:10:27 +08:00
|
|
|
}else if (this.counter === count) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'downLoopDown', list);
|
2017-05-11 02:10:27 +08:00
|
|
|
}else if (this.counter === 0) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'downLoopUp', list);
|
2017-05-05 04:39:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-05 22:37:01 +08:00
|
|
|
if (Args[0].keyCode === KEY_CODES.ARROW_UP) {
|
2017-05-06 03:11:28 +08:00
|
|
|
if (this.counter < count && this.counter !== 0) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'up', items);
|
2017-05-11 02:10:27 +08:00
|
|
|
}else if (this.counter === 0) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'upLoopUp', list, count);
|
2017-05-11 02:10:27 +08:00
|
|
|
}else if (this.counter === count) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this.focusListItem(active, 'upLoopDown', list, count);
|
2017-05-05 04:39:53 +08:00
|
|
|
}
|
2017-05-05 22:37:01 +08:00
|
|
|
}
|
2017-05-05 04:39:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2017-05-05 05:41:09 +08:00
|
|
|
let _this = this;
|
2017-05-05 04:39:53 +08:00
|
|
|
|
|
|
|
if (!this.state.compact) {
|
2017-05-13 02:11:42 +08:00
|
|
|
this._msgsList.addEventListener('keypress', function (event) {
|
|
|
|
_this.rovingIndex.call(this, event, 'messages');
|
2017-05-05 04:39:53 +08:00
|
|
|
});
|
|
|
|
|
2017-05-13 02:11:42 +08:00
|
|
|
this._usersList.addEventListener('keypress', function (event) {
|
|
|
|
_this.rovingIndex.call(this, event, 'users');
|
2017-05-05 05:41:09 +08:00
|
|
|
});
|
2017-05-05 04:39:53 +08:00
|
|
|
}
|
2016-06-02 21:00:57 +08:00
|
|
|
}
|
|
|
|
|
2017-05-05 22:37:01 +08:00
|
|
|
componentWillUnmount() {
|
2017-05-13 02:11:42 +08:00
|
|
|
this._msgsList.removeEventListener('keypress', function (event) {}, false);
|
|
|
|
|
|
|
|
this._usersList.removeEventListener('keypress', function (event) {}, false);
|
2017-05-05 22:37:01 +08:00
|
|
|
}
|
|
|
|
|
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() {
|
2017-04-04 02:05:47 +08:00
|
|
|
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-05-13 01:45:41 +08:00
|
|
|
<div className={styles.headerTitle} role="banner">
|
2017-04-04 02:05:47 +08:00
|
|
|
{intl.formatMessage(intlMessages.participantsTitle)}
|
2017-05-13 01:45:41 +08:00
|
|
|
</div> : null
|
2016-08-25 02:56:06 +08:00
|
|
|
}
|
2016-05-26 03:29:22 +08:00
|
|
|
</div>
|
2016-06-02 21:00:57 +08:00
|
|
|
);
|
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,
|
2017-04-04 02:05:47 +08:00
|
|
|
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-05-13 01:45:41 +08:00
|
|
|
<div className={styles.smallTitle} role="banner">
|
2017-04-04 02:05:47 +08:00
|
|
|
{intl.formatMessage(intlMessages.messagesTitle)}
|
2017-05-13 01:45:41 +08:00
|
|
|
</div> : <hr className={styles.separator}></hr>
|
2016-08-25 02:56:06 +08:00
|
|
|
}
|
2017-05-11 22:11:17 +08:00
|
|
|
<div
|
|
|
|
tabIndex={0}
|
|
|
|
className={styles.scrollableList}
|
|
|
|
ref={(r) => this._msgsList = r}>
|
2016-06-28 21:10:20 +08:00
|
|
|
<ReactCSSTransitionGroup
|
|
|
|
transitionName={listTransition}
|
|
|
|
transitionAppear={true}
|
|
|
|
transitionEnter={true}
|
|
|
|
transitionLeave={false}
|
|
|
|
transitionAppearTimeout={0}
|
|
|
|
transitionEnterTimeout={0}
|
|
|
|
transitionLeaveTimeout={0}
|
2017-05-13 01:45:41 +08:00
|
|
|
component="div"
|
2017-05-11 22:11:17 +08:00
|
|
|
className={cx(styles.chatsList, styles.scrollableList)}>
|
|
|
|
<div ref={(r) => this._msgItems = r}>
|
2016-06-29 03:52:03 +08:00
|
|
|
{openChats.map(chat => (
|
2016-06-28 21:10:20 +08:00
|
|
|
<ChatListItem
|
2016-08-25 02:56:06 +08:00
|
|
|
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}
|
2017-05-05 04:39:53 +08:00
|
|
|
chat={chat}
|
|
|
|
tabIndex={-1} />
|
2016-06-28 21:10:20 +08:00
|
|
|
))}
|
2017-05-11 22:11:17 +08:00
|
|
|
</div>
|
2016-06-28 21:10:20 +08:00
|
|
|
</ReactCSSTransitionGroup>
|
|
|
|
</div>
|
2016-05-26 03:29:22 +08:00
|
|
|
</div>
|
2016-06-02 21:00:57 +08:00
|
|
|
);
|
2016-05-26 03:29:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
renderParticipants() {
|
2016-06-29 03:52:03 +08:00
|
|
|
const {
|
|
|
|
users,
|
|
|
|
currentUser,
|
2017-03-16 01:43:03 +08:00
|
|
|
isBreakoutRoom,
|
2017-04-04 02:05:47 +08:00
|
|
|
intl,
|
2017-05-04 00:56:27 +08:00
|
|
|
makeCall,
|
2017-05-16 03:26:03 +08:00
|
|
|
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),
|
2017-05-04 00:56:27 +08:00
|
|
|
handler: user => makeCall('setEmojiStatus', user.id, 'none'),
|
2017-04-29 02:28:55 +08:00
|
|
|
icon: 'clear_status',
|
|
|
|
},
|
|
|
|
setPresenter: {
|
|
|
|
label: intl.formatMessage(intlMessages.MakePresenterLabel),
|
2017-05-04 00:56:27 +08:00
|
|
|
handler: user => makeCall('assignPresenter', user.id),
|
2017-04-29 02:28:55 +08:00
|
|
|
icon: 'presentation',
|
|
|
|
},
|
|
|
|
kick: {
|
|
|
|
label: intl.formatMessage(intlMessages.KickUserLabel),
|
2017-05-04 00:56:27 +08:00
|
|
|
handler: user => makeCall('kickUser', user.id),
|
2017-04-29 02:28:55 +08:00
|
|
|
icon: 'circle_close',
|
|
|
|
},
|
|
|
|
mute: {
|
|
|
|
label: intl.formatMessage(intlMessages.MuteUserAudioLabel),
|
2017-05-04 00:56:27 +08:00
|
|
|
handler: user => makeCall('muteUser', user.id),
|
2017-04-29 02:28:55 +08:00
|
|
|
icon: 'audio_off',
|
|
|
|
},
|
|
|
|
unmute: {
|
|
|
|
label: intl.formatMessage(intlMessages.UnmuteUserAudioLabel),
|
2017-05-04 00:56:27 +08:00
|
|
|
handler: user => makeCall('unmuteUser', 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-05-13 01:45:41 +08:00
|
|
|
<div className={styles.smallTitle} role="banner">
|
2017-04-04 02:05:47 +08:00
|
|
|
{intl.formatMessage(intlMessages.usersTitle)}
|
2016-09-15 01:48:50 +08:00
|
|
|
({users.length})
|
2017-05-13 01:45:41 +08:00
|
|
|
</div> : <hr className={styles.separator}></hr>
|
2016-08-25 02:56:06 +08:00
|
|
|
}
|
2017-05-11 22:11:17 +08:00
|
|
|
<div
|
|
|
|
className={styles.scrollableList}
|
|
|
|
tabIndex={0}
|
|
|
|
ref={(r) => this._usersList = r}>
|
|
|
|
<ReactCSSTransitionGroup
|
|
|
|
transitionName={listTransition}
|
|
|
|
transitionAppear={true}
|
|
|
|
transitionEnter={true}
|
|
|
|
transitionLeave={true}
|
|
|
|
transitionAppearTimeout={0}
|
|
|
|
transitionEnterTimeout={0}
|
|
|
|
transitionLeaveTimeout={0}
|
2017-05-13 01:45:41 +08:00
|
|
|
component="div"
|
2017-05-11 22:11:17 +08:00
|
|
|
className={cx(styles.participantsList, styles.scrollableList)}>
|
|
|
|
<div ref={(r) => this._userItems = r}>
|
|
|
|
{
|
|
|
|
users.map(user => (
|
|
|
|
<UserListItem
|
|
|
|
compact={this.state.compact}
|
|
|
|
key={user.id}
|
|
|
|
isBreakoutRoom={isBreakoutRoom}
|
|
|
|
user={user}
|
|
|
|
currentUser={currentUser}
|
|
|
|
userActions={userActions}
|
|
|
|
/>))
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
</ReactCSSTransitionGroup>
|
2017-05-05 04:39:53 +08:00
|
|
|
</div>
|
2016-05-26 03:29:22 +08:00
|
|
|
</div>
|
2016-06-02 21:00:57 +08:00
|
|
|
);
|
2016-05-26 03:29:22 +08:00
|
|
|
}
|
2016-05-20 02:22:56 +08:00
|
|
|
}
|
2016-06-02 21:00:57 +08:00
|
|
|
|
2017-04-04 02:05:47 +08:00
|
|
|
const intlMessages = defineMessages({
|
|
|
|
usersTitle: {
|
|
|
|
id: 'app.userlist.usersTitle',
|
2017-04-06 06:49:05 +08:00
|
|
|
description: 'Title for the Header',
|
2017-04-04 02:05:47 +08:00
|
|
|
},
|
|
|
|
messagesTitle: {
|
|
|
|
id: 'app.userlist.messagesTitle',
|
2017-04-06 06:49:05 +08:00
|
|
|
description: 'Title for the messages list',
|
2017-04-04 02:05:47 +08:00
|
|
|
},
|
|
|
|
participantsTitle: {
|
|
|
|
id: 'app.userlist.participantsTitle',
|
2017-04-06 06:49:05 +08:00
|
|
|
description: 'Title for the Users list',
|
2017-04-04 02:05:47 +08:00
|
|
|
},
|
2017-04-29 02:28:55 +08:00
|
|
|
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',
|
|
|
|
},
|
2017-04-04 02:05:47 +08:00
|
|
|
});
|
|
|
|
|
2016-06-29 03:52:03 +08:00
|
|
|
UserList.propTypes = propTypes;
|
2017-04-04 02:05:47 +08:00
|
|
|
export default withRouter(injectIntl(UserList));
|