diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
index ea5762df85..19cd42b6ce 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
@@ -255,6 +255,8 @@ class UserList extends Component {
intl,
makeCall,
meeting,
+ getAvailableActions,
+ normalizeEmojiName,
} = this.props;
const userActions = {
@@ -326,6 +328,8 @@ class UserList extends Component {
currentUser={currentUser}
userActions={userActions}
meeting={meeting}
+ getAvailableActions={getAvailableActions}
+ normalizeEmojiName={normalizeEmojiName}
/>
))
}
@@ -335,7 +339,7 @@ class UserList extends Component {
);
}
-
+
render() {
return (
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
index 3667e55d92..5b0e372adb 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
@@ -16,6 +16,8 @@ const UserListContainer = (props) => {
isBreakoutRoom,
children,
meeting,
+ getAvailableActions,
+ normalizeEmojiName,
} = props;
return (
@@ -28,6 +30,8 @@ const UserListContainer = (props) => {
isBreakoutRoom={isBreakoutRoom}
makeCall={makeCall}
userActions={userActions}
+ getAvailableActions={getAvailableActions}
+ normalizeEmojiName={normalizeEmojiName}
>
{children}
@@ -42,4 +46,6 @@ export default createContainer(({ params }) => ({
openChat: params.chatID,
userActions: Service.userActions,
isBreakoutRoom: meetingIsBreakout(),
+ getAvailableActions: Service.getAvailableActions,
+ normalizeEmojiName: Service.normalizeEmojiName,
}), UserListContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 071ccd5546..96ec13fd5b 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -203,6 +203,28 @@ const getOpenChats = (chatID) => {
.sort(sortChats);
};
+const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => {
+ const hasAuthority = currentUser.isModerator || user.isCurrent;
+ const allowedToChatPrivately = !user.isCurrent;
+ const allowedToMuteAudio = hasAuthority && user.isVoiceUser && user.isMuted;
+ const allowedToUnmuteAudio = hasAuthority && user.isVoiceUser && !user.isMuted;
+ const allowedToResetStatus = hasAuthority && user.emoji.status !== 'none';
+
+ // if currentUser is a moderator, allow kicking other users
+ const allowedToKick = currentUser.isModerator && !user.isCurrent && !isBreakoutRoom;
+
+ const allowedToSetPresenter = currentUser.isModerator && !user.isPresenter;
+
+ return {
+ allowedToChatPrivately,
+ allowedToMuteAudio,
+ allowedToUnmuteAudio,
+ allowedToResetStatus,
+ allowedToKick,
+ allowedToSetPresenter,
+ };
+};
+
const getCurrentUser = () => {
const currentUserId = Auth.userID;
const currentUser = Users.findOne({ userId: currentUserId });
@@ -210,8 +232,24 @@ const getCurrentUser = () => {
return (currentUser) ? mapUser(currentUser) : null;
};
+const normalizeEmojiName = (emoji) => {
+ const emojisNormalized = {
+ agree: 'thumbs_up',
+ disagree: 'thumbs_down',
+ thumbsUp: 'thumbs_up',
+ thumbsDown: 'thumbs_down',
+ raiseHand: 'hand',
+ away: 'time',
+ neutral: 'undecided',
+ };
+
+ return emoji in emojisNormalized ? emojisNormalized[emoji] : emoji;
+};
+
export default {
getUsers,
getOpenChats,
getCurrentUser,
+ getAvailableActions,
+ normalizeEmojiName,
};
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
index 71a705ff86..c25292b7ff 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
@@ -1,19 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import UserAvatar from '/imports/ui/components/user-avatar/component';
-import Icon from '/imports/ui/components/icon/component';
import { findDOMNode } from 'react-dom';
import { withRouter } from 'react-router';
-import { defineMessages, injectIntl } from 'react-intl';
-import cx from 'classnames';
+import { injectIntl } from 'react-intl';
import _ from 'lodash';
-import Dropdown from '/imports/ui/components/dropdown/component';
-import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
-import DropdownContent from '/imports/ui/components/dropdown/content/component';
-import DropdownList from '/imports/ui/components/dropdown/list/component';
-import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
-import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
-import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
+import UserActions from './user-actions/component';
+import UserListContent from './user-list-content/component';
import styles from './styles.scss';
const normalizeEmojiName = (emoji) => {
@@ -42,85 +34,22 @@ const propTypes = {
currentUser: PropTypes.shape({
id: PropTypes.string.isRequired,
}).isRequired,
-
- userActions: PropTypes.shape(),
};
const defaultProps = {
shouldShowActions: false,
};
-const messages = defineMessages({
- presenter: {
- id: 'app.userlist.presenter',
- description: 'Text for identifying presenter user',
- },
- you: {
- id: 'app.userlist.you',
- description: 'Text for identifying your user',
- },
- locked: {
- id: 'app.userlist.locked',
- description: 'Text for identifying locked user',
- },
- guest: {
- id: 'app.userlist.guest',
- description: 'Text for identifying guest user',
- },
- menuTitleContext: {
- id: 'app.userlist.menuTitleContext',
- description: 'adds context to userListItem menu title',
- },
- userAriaLabel: {
- id: 'app.userlist.userAriaLabel',
- description: 'aria label for each user in the userlist',
- },
-});
-
class UserListItem extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- isActionsOpen: false,
- dropdownOffset: 0,
- dropdownDirection: 'top',
- dropdownVisible: false,
- };
-
- this.handleScroll = this.handleScroll.bind(this);
- this.onActionsShow = this.onActionsShow.bind(this);
- this.onActionsHide = this.onActionsHide.bind(this);
- this.getDropdownMenuParent = this.getDropdownMenuParent.bind(this);
- }
-
- componentDidUpdate() {
- this.checkDropdownDirection();
- }
-
- onActionsShow() {
- const dropdown = findDOMNode(this.dropdown);
- const scrollContainer = dropdown.parentElement.parentElement;
- const dropdownTrigger = dropdown.children[0];
-
- this.setState({
- isActionsOpen: true,
- dropdownVisible: false,
- dropdownOffset: dropdownTrigger.offsetTop - scrollContainer.scrollTop,
- dropdownDirection: 'top',
- });
-
- scrollContainer.addEventListener('scroll', this.handleScroll, false);
- }
-
- getAvailableActions() {
+ getUsersActions() {
const {
currentUser,
user,
userActions,
router,
isBreakoutRoom,
+ getAvailableActions,
} = this.props;
const {
@@ -132,297 +61,48 @@ class UserListItem extends Component {
unmute,
} = userActions;
- const hasAuthority = currentUser.isModerator || user.isCurrent;
- const allowedToChatPrivately = !user.isCurrent;
- const allowedToMuteAudio = hasAuthority && user.isVoiceUser && user.isMuted;
- const allowedToUnmuteAudio = hasAuthority && user.isVoiceUser && !user.isMuted;
- const allowedToResetStatus = hasAuthority && user.emoji.status !== 'none';
+ const actions = getAvailableActions(currentUser, user, router, isBreakoutRoom);
- // if currentUser is a moderator, allow kicking other users
- const allowedToKick = currentUser.isModerator && !user.isCurrent && !isBreakoutRoom;
-
- const allowedToSetPresenter = currentUser.isModerator && !user.isPresenter;
+ const {
+ allowedToChatPrivately,
+ allowedToMuteAudio,
+ allowedToUnmuteAudio,
+ allowedToResetStatus,
+ allowedToKick,
+ allowedToSetPresenter } = actions;
return _.compact([
- (allowedToChatPrivately ? this.renderUserAction(openChat, router, user) : null),
- (allowedToMuteAudio ? this.renderUserAction(unmute, user) : null),
- (allowedToUnmuteAudio ? this.renderUserAction(mute, user) : null),
- (allowedToResetStatus ? this.renderUserAction(clearStatus, user) : null),
- (allowedToSetPresenter ? this.renderUserAction(setPresenter, user) : null),
- (allowedToKick ? this.renderUserAction(kick, user) : null),
+ (allowedToChatPrivately ?
: null),
+ (allowedToMuteAudio ?
: null),
+ (allowedToUnmuteAudio ?
: null),
+ (allowedToResetStatus ?
: null),
+ (allowedToSetPresenter ?
: null),
+ (allowedToKick ?
: null),
]);
}
- onActionsHide() {
- this.setState({
- isActionsOpen: false,
- dropdownVisible: false,
- });
-
- findDOMNode(this).parentElement.removeEventListener('scroll', this.handleScroll, false);
- }
-
getDropdownMenuParent() {
return findDOMNode(this.dropdown);
}
- /**
- * Return true if the content fit on the screen, false otherwise.
- *
- * @param {number} contentOffSetTop
- * @param {number} contentOffsetHeight
- * @return True if the content fit on the screen, false otherwise.
- */
- checkIfDropdownIsVisible(contentOffSetTop, contentOffsetHeight) {
- return (contentOffSetTop + contentOffsetHeight) < window.innerHeight;
- }
-
- /**
- * Check if the dropdown is visible, if so, check if should be draw on top or bottom direction.
- */
- checkDropdownDirection() {
- if (this.isDropdownActivedByUser()) {
- const dropdown = findDOMNode(this.dropdown);
- const dropdownTrigger = dropdown.children[0];
- const dropdownContent = dropdown.children[1];
-
- const scrollContainer = dropdown.parentElement.parentElement;
-
- const nextState = {
- dropdownVisible: true,
- };
-
- const isDropdownVisible =
- this.checkIfDropdownIsVisible(dropdownContent.offsetTop, dropdownContent.offsetHeight);
-
- if (!isDropdownVisible) {
- const offsetPageTop =
- ((dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight) - scrollContainer.scrollTop);
-
- nextState.dropdownOffset = window.innerHeight - offsetPageTop;
- nextState.dropdownDirection = 'bottom';
- }
-
- this.setState(nextState);
- }
- }
-
- /**
- * Check if the dropdown is visible and is opened by the user
- *
- * @return True if is visible and opened by the user.
- */
- isDropdownActivedByUser() {
- const { isActionsOpen, dropdownVisible } = this.state;
- const list = findDOMNode(this.list);
-
- if (isActionsOpen && dropdownVisible) {
- for (let i = 0; i < list.children.length; i++) {
- if (list.children[i].getAttribute('role') === 'menuitem') {
- list.children[i].focus();
- break;
- }
- }
- }
-
- return isActionsOpen && !dropdownVisible;
- }
-
- handleScroll() {
- this.setState({
- isActionsOpen: false,
- });
- }
-
- renderUserName() {
- const {
- user,
- intl,
- compact,
- } = this.props;
-
- if (compact) {
- return null;
- }
-
- const userNameSub = [];
-
- if (user.isLocked) {
- userNameSub.push(
-
- {intl.formatMessage(messages.locked)}
- );
- }
-
- if (user.isGuest) {
- userNameSub.push(intl.formatMessage(messages.guest));
- }
-
- return (
-
-
- {user.name} {(user.isCurrent) ? `(${intl.formatMessage(messages.you)})` : ''}
-
- {
- userNameSub.length ?
-
- {userNameSub.reduce((prev, curr) => [prev, ' | ', curr])}
-
- : null
- }
-
- );
- }
-
- renderUserIcons() {
- const {
- user,
- compact,
- } = this.props;
-
- if (compact) {
- return null;
- }
-
- if (!user.isSharingWebcam) {
- // Prevent rendering the markup when there is no icon to show
- return null;
- }
-
- return (
-
- {
- user.isSharingWebcam ?
-
-
-
- : null
- }
-
- );
- }
-
- renderUserAction(action, ...parameters) {
- const userAction = (
-
- );
-
- return userAction;
- }
-
render() {
const {
compact,
- } = this.props;
-
- const userItemContentsStyle = {};
- userItemContentsStyle[styles.userItemContentsCompact] = compact;
- userItemContentsStyle[styles.active] = this.state.isActionsOpen;
-
- const {
user,
intl,
} = this.props;
- const you = (user.isCurrent) ? intl.formatMessage(messages.you) : '';
+ const actions = this.getUsersActions();
- const presenter = (user.isPresenter)
- ? intl.formatMessage(messages.presenter)
- : '';
+ const contents = (
);
- const userAriaLabel = intl.formatMessage(messages.userAriaLabel,
- {
- 0: user.name,
- 1: presenter,
- 2: you,
- 3: user.emoji.status,
- });
-
- const actions = this.getAvailableActions();
- const contents = (
-
-
-
-
- {user.emoji.status !== 'none' ?
- :
- user.name.toLowerCase().slice(0, 2)}
-
-
- {this.renderUserName()}
- {this.renderUserIcons()}
-
-
- );
-
- if (!actions.length) {
- return contents;
- }
-
- const { dropdownOffset, dropdownDirection, dropdownVisible } = this.state;
-
- return (
-
{ this.dropdown = ref; }}
- isOpen={this.state.isActionsOpen}
- onShow={this.onActionsShow}
- onHide={this.onActionsHide}
- className={cx(styles.dropdown, styles.userListItem, userItemContentsStyle)}
- autoFocus={false}
- aria-haspopup="true"
- aria-live="assertive"
- aria-relevant="additions"
- >
-
- {contents}
-
-
-
- { this.list = ref; }}
- getDropdownMenuParent={this.getDropdownMenuParent}
- onActionsHide={this.onActionsHide}
- >
- {
- [
- (
- {user.name}
- ),
- (),
- ].concat(actions)
- }
-
-
-
- );
+ return contents;
}
}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-actions/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-actions/component.jsx
new file mode 100644
index 0000000000..64943eac9e
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-actions/component.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
+
+const propTypes = {
+ action: PropTypes.shape({
+ icon: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ handler: PropTypes.func.isRequired,
+ }).isRequired,
+ options: PropTypes.array.isRequired,
+};
+
+const UserActions = (props) => {
+ const { action, options } = props;
+
+ const userAction = (
+
+ );
+
+ return userAction;
+};
+
+UserActions.propTypes = propTypes;
+export default UserActions;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-icons/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-icons/component.jsx
new file mode 100644
index 0000000000..f742d97248
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-icons/component.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Icon from '/imports/ui/components/icon/component';
+import styles from './../styles.scss';
+
+const propTypes = {
+ user: PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ isPresenter: PropTypes.bool.isRequired,
+ isVoiceUser: PropTypes.bool.isRequired,
+ isModerator: PropTypes.bool.isRequired,
+ image: PropTypes.string,
+ }).isRequired,
+ compact: PropTypes.bool.isRequired,
+};
+
+const UserIcons = (props) => {
+ const {
+ user,
+ compact,
+ } = props;
+
+ if (compact || user.isSharingWebcam) {
+ return null;
+ }
+
+ return (
+
+ {
+ user.isSharingWebcam ?
+
+
+
+ : null
+ }
+
+ );
+};
+
+UserIcons.propTypes = propTypes;
+export default UserIcons;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-list-content/component.jsx
new file mode 100644
index 0000000000..d291971923
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-list-content/component.jsx
@@ -0,0 +1,288 @@
+import React, { Component } from 'react';
+import { defineMessages } from 'react-intl';
+import cx from 'classnames';
+import { findDOMNode } from 'react-dom';
+import UserAvatar from '/imports/ui/components/user-avatar/component';
+import Icon from '/imports/ui/components/icon/component';
+import Dropdown from '/imports/ui/components/dropdown/component';
+import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
+import DropdownContent from '/imports/ui/components/dropdown/content/component';
+import DropdownList from '/imports/ui/components/dropdown/list/component';
+import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
+import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
+import styles from './../styles.scss';
+import UserName from './../user-name/component';
+import UserIcons from './../user-icons/component';
+
+const messages = defineMessages({
+ presenter: {
+ id: 'app.userlist.presenter',
+ description: 'Text for identifying presenter user',
+ },
+ you: {
+ id: 'app.userlist.you',
+ description: 'Text for identifying your user',
+ },
+ locked: {
+ id: 'app.userlist.locked',
+ description: 'Text for identifying locked user',
+ },
+ guest: {
+ id: 'app.userlist.guest',
+ description: 'Text for identifying guest user',
+ },
+ menuTitleContext: {
+ id: 'app.userlist.menuTitleContext',
+ description: 'adds context to userListItem menu title',
+ },
+ userAriaLabel: {
+ id: 'app.userlist.userAriaLabel',
+ description: 'aria label for each user in the userlist',
+ },
+});
+
+class UserListContent extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isActionsOpen: false,
+ dropdownOffset: 0,
+ dropdownDirection: 'top',
+ dropdownVisible: false,
+ };
+
+ this.handleScroll = this.handleScroll.bind(this);
+ this.onActionsShow = this.onActionsShow.bind(this);
+ this.onActionsHide = this.onActionsHide.bind(this);
+ this.getDropdownMenuParent = this.getDropdownMenuParent.bind(this);
+ }
+
+ componentDidUpdate() {
+ this.checkDropdownDirection();
+ }
+
+ onActionsShow() {
+ const dropdown = findDOMNode(this.dropdown);
+ const scrollContainer = dropdown.parentElement.parentElement;
+ const dropdownTrigger = dropdown.children[0];
+
+ this.setState({
+ isActionsOpen: true,
+ dropdownVisible: false,
+ dropdownOffset: dropdownTrigger.offsetTop - scrollContainer.scrollTop,
+ dropdownDirection: 'top',
+ });
+
+ scrollContainer.addEventListener('scroll', this.handleScroll, false);
+ }
+
+ onActionsHide() {
+ this.setState({
+ isActionsOpen: false,
+ dropdownVisible: false,
+ });
+
+ findDOMNode(this).parentElement.removeEventListener('scroll', this.handleScroll, false);
+ }
+
+ getDropdownMenuParent() {
+ return findDOMNode(this.dropdown);
+ }
+
+ handleScroll() {
+ this.setState({
+ isActionsOpen: false,
+ });
+ }
+
+
+ /**
+ * Check if the dropdown is visible, if so, check if should be draw on top or bottom direction.
+ */
+ checkDropdownDirection() {
+ if (this.isDropdownActivedByUser()) {
+ const dropdown = findDOMNode(this.dropdown);
+ const dropdownTrigger = dropdown.children[0];
+ const dropdownContent = dropdown.children[1];
+
+ const scrollContainer = dropdown.parentElement.parentElement;
+
+ const nextState = {
+ dropdownVisible: true,
+ };
+
+ const isDropdownVisible =
+ this.checkIfDropdownIsVisible(dropdownContent.offsetTop, dropdownContent.offsetHeight);
+
+ if (!isDropdownVisible) {
+ const offsetPageTop =
+ ((dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight) - scrollContainer.scrollTop);
+
+ nextState.dropdownOffset = window.innerHeight - offsetPageTop;
+ nextState.dropdownDirection = 'bottom';
+ }
+
+ this.setState(nextState);
+ }
+ }
+
+ /**
+ * Return true if the content fit on the screen, false otherwise.
+ *
+ * @param {number} contentOffSetTop
+ * @param {number} contentOffsetHeight
+ * @return True if the content fit on the screen, false otherwise.
+ */
+ checkIfDropdownIsVisible(contentOffSetTop, contentOffsetHeight) {
+ return (contentOffSetTop + contentOffsetHeight) < window.innerHeight;
+ }
+
+ /**
+ * Check if the dropdown is visible and is opened by the user
+ *
+ * @return True if is visible and opened by the user.
+ */
+ isDropdownActivedByUser() {
+ const { isActionsOpen, dropdownVisible } = this.state;
+ if (isActionsOpen && dropdownVisible) {
+ this.focusDropdown();
+ }
+ return isActionsOpen && !dropdownVisible;
+ }
+
+ focusDropdown() {
+ const list = findDOMNode(this.list);
+ for (let i = 0; i < list.children.length; i++) {
+ if (list.children[i].getAttribute('role') === 'menuitem') {
+ list.children[i].focus();
+ break;
+ }
+ }
+
+ // The list children is a instance of HTMLCollection, there is no find, some, etc methods
+ /* const childrens = [].slice.call(list.children);
+
+ childrens.find(child => child.getAttribute('role') === 'menuitem').focus(); */
+ }
+
+ render() {
+ const {
+ compact,
+ user,
+ intl,
+ normalizeEmojiName,
+ actions,
+ } = this.props;
+
+ const {
+ isActionsOpen,
+ dropdownVisible,
+ dropdownDirection,
+ dropdownOffset,
+ } = this.state;
+ const userItemContentsStyle = {};
+
+ userItemContentsStyle[styles.userItemContentsCompact] = compact;
+ userItemContentsStyle[styles.active] = isActionsOpen;
+
+ const you = (user.isCurrent) ? intl.formatMessage(messages.you) : '';
+
+ const presenter = (user.isPresenter)
+ ? intl.formatMessage(messages.presenter)
+ : '';
+
+ const userAriaLabel = intl.formatMessage(messages.userAriaLabel,
+ {
+ 0: user.name,
+ 1: presenter,
+ 2: you,
+ 3: user.emoji.status,
+ });
+
+ const contents = (
+
+
+
+
+ {user.emoji.status !== 'none' ?
+ :
+ user.name.toLowerCase().slice(0, 2)}
+
+
+ {
}
+ {
}
+
+
+ );
+
+ if (!actions.length) {
+ return contents;
+ }
+
+ return (
+
{ this.dropdown = ref; }}
+ isOpen={this.state.isActionsOpen}
+ onShow={this.onActionsShow}
+ onHide={this.onActionsHide}
+ className={cx(styles.dropdown, styles.userListItem, userItemContentsStyle)}
+ autoFocus={false}
+ aria-haspopup="true"
+ aria-live="assertive"
+ aria-relevant="additions"
+ >
+
+ {contents}
+
+
+
+ { this.list = ref; }}
+ getDropdownMenuParent={this.getDropdownMenuParent}
+ onActionsHide={this.onActionsHide}
+ >
+ {
+ [
+ (
+ {user.name}
+ ),
+ (),
+ ].concat(actions)
+ }
+
+
+
+ );
+ }
+}
+
+export default UserListContent;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-name/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-name/component.jsx
new file mode 100644
index 0000000000..c938a50408
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/user-name/component.jsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages } from 'react-intl';
+import Icon from '/imports/ui/components/icon/component';
+import styles from './../styles.scss';
+
+
+const messages = defineMessages({
+ presenter: {
+ id: 'app.userlist.presenter',
+ description: 'Text for identifying presenter user',
+ },
+ you: {
+ id: 'app.userlist.you',
+ description: 'Text for identifying your user',
+ },
+ locked: {
+ id: 'app.userlist.locked',
+ description: 'Text for identifying locked user',
+ },
+ guest: {
+ id: 'app.userlist.guest',
+ description: 'Text for identifying guest user',
+ },
+ menuTitleContext: {
+ id: 'app.userlist.menuTitleContext',
+ description: 'adds context to userListItem menu title',
+ },
+ userAriaLabel: {
+ id: 'app.userlist.userAriaLabel',
+ description: 'aria label for each user in the userlist',
+ },
+});
+const propTypes = {
+ user: PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ isPresenter: PropTypes.bool.isRequired,
+ isVoiceUser: PropTypes.bool.isRequired,
+ isModerator: PropTypes.bool.isRequired,
+ image: PropTypes.string,
+ }).isRequired,
+ compact: PropTypes.bool.isRequired,
+ intl: PropTypes.object.isRequired,
+};
+
+const UserName = (props) => {
+ const {
+ user,
+ intl,
+ compact,
+ } = props;
+
+ if (compact) {
+ return null;
+ }
+
+ const userNameSub = [];
+
+ if (user.isLocked) {
+ userNameSub.push(
+
+ {intl.formatMessage(messages.locked)}
+ );
+ }
+
+ if (user.isGuest) {
+ userNameSub.push(intl.formatMessage(messages.guest));
+ }
+
+ return (
+
+
+ {user.name} {(user.isCurrent) ? `(${intl.formatMessage(messages.you)})` : ''}
+
+ {
+ userNameSub.length ?
+
+ {userNameSub.reduce((prev, curr) => [prev, ' | ', curr])}
+
+ : null
+ }
+
+ );
+};
+
+UserName.propTypes = propTypes;
+export default UserName;