diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
index 207091601e..ed7a2222bb 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import cx from 'classnames';
import { styles } from './styles.scss';
-import EmojiSelect from './emoji-select/component';
import DesktopShare from './desktop-share/component';
import ActionsDropdown from './actions-dropdown/component';
import AudioControlsContainer from '../audio/audio-controls/container';
@@ -16,9 +15,6 @@ class ActionsBar extends React.PureComponent {
handleShareScreen,
handleUnshareScreen,
isVideoBroadcasting,
- emojiList,
- emojiSelected,
- handleEmojiChange,
isUserModerator,
recordSettingsList,
toggleRecording,
@@ -55,13 +51,12 @@ class ActionsBar extends React.PureComponent {
handleCloseVideo={handleExitVideo}
/>
: null}
-
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
index ca2f85b1c1..b0a8150a32 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
@@ -10,9 +10,6 @@ const ActionsBarContainer = props => ;
export default withTracker(() => ({
isUserPresenter: Service.isUserPresenter(),
isUserModerator: Service.isUserModerator(),
- emojiList: Service.getEmojiList(),
- emojiSelected: Service.getEmoji(),
- handleEmojiChange: Service.setEmoji,
handleExitVideo: () => VideoService.exitVideo(),
handleJoinVideo: () => VideoService.joinVideo(),
handleShareScreen: () => shareScreen(),
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx
deleted file mode 100755
index 59931acdf3..0000000000
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { defineMessages, intlShape, injectIntl } from 'react-intl';
-
-import Button from '/imports/ui/components/button/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 DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
-import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
-import { styles } from '../styles';
-
-const intlMessages = defineMessages({
- statusTriggerLabel: {
- id: 'app.actionsBar.emojiMenu.statusTriggerLabel',
- description: 'Emoji status button label',
- },
- changeStatusLabel: {
- id: 'app.actionsBar.changeStatusLabel',
- description: 'Aria-label for emoji status button',
- },
- currentStatusDesc: {
- id: 'app.actionsBar.currentStatusDesc',
- description: 'Aria description for status button',
- },
-});
-
-const propTypes = {
- intl: intlShape.isRequired,
- options: PropTypes.objectOf(PropTypes.string).isRequired,
- selected: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
-};
-
-const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts;
-const OPEN_STATUS_AK = SHORTCUTS_CONFIG.openStatus.accesskey;
-
-const EmojiSelect = ({
- intl,
- options,
- selected,
- onChange,
-}) => {
- const statuses = Object.keys(options);
- const lastStatus = statuses.pop();
-
- const statusLabel = intl.formatMessage(intlMessages.statusTriggerLabel);
-
- return (
-
-
-
-
-
-
- {
- statuses.map(status => (
- onChange(status)}
- tabIndex={-1}
- />
- ))
- .concat(
- ,
- onChange(lastStatus)}
- tabIndex={-1}
- />,
- )
- }
-
-
-
- );
-};
-
-EmojiSelect.propTypes = propTypes;
-export default injectIntl(EmojiSelect);
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
index 844b7f2966..3eb815607d 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
@@ -1,14 +1,10 @@
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import { makeCall } from '/imports/ui/services/api';
-import { EMOJI_STATUSES } from '/imports/utils/statuses';
import Meetings from '/imports/api/meetings';
export default {
isUserPresenter: () => Users.findOne({ userId: Auth.userID }).presenter,
- getEmoji: () => Users.findOne({ userId: Auth.userID }).emoji,
- setEmoji: status => makeCall('setEmojiStatus', Auth.userID, status),
- getEmojiList: () => EMOJI_STATUSES,
isUserModerator: () => Users.findOne({ userId: Auth.userID }).moderator,
recordSettingsList: () => Meetings.findOne({ meetingId: Auth.meetingID }).recordProp,
toggleRecording: () => makeCall('toggleRecording'),
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
index 5abb5ba570..4aa485dd42 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
@@ -44,9 +44,3 @@
box-shadow: 0 2px 5px 0 rgb(0, 0, 0);
}
}
-
-.emojiSelected {
- span, i {
- color: $color-primary;
- }
-}
diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/styles.scss
index e33eb5c6fe..8889401573 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/chat/styles.scss
@@ -44,6 +44,7 @@ $background-active: darken($color-white, 5%);
flex-direction: column;
justify-content: space-around;
overflow: hidden;
+ height: 100vh;
}
.header {
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
index bc6d9d243d..bd86877320 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
@@ -73,15 +73,15 @@ class Dropdown extends Component {
return nextState.isOpen ? screenreaderTrap.trap(this.dropdown) : screenreaderTrap.untrap();
}
-
componentDidUpdate(prevProps, prevState) {
- if (this.state.isOpen && !prevState.isOpen) {
- this.props.onShow();
- }
+ const {
+ onShow,
+ onHide,
+ } = this.props;
- if (!this.state.isOpen && prevState.isOpen) {
- this.props.onHide();
- }
+ if (this.state.isOpen && !prevState.isOpen) { onShow(); }
+
+ if (!this.state.isOpen && prevState.isOpen) { onHide(); }
}
handleShow() {
@@ -98,14 +98,17 @@ class Dropdown extends Component {
});
}
- handleWindowClick(event) {
+ handleWindowClick() {
const triggerElement = findDOMNode(this.trigger);
+ const contentElement = findDOMNode(this.content);
+ const closeDropdown = this.props.isOpen && this.state.isOpen && triggerElement.contains(event.target);
+ const preventHide = this.props.isOpen && contentElement.contains(event.target) || !triggerElement;
- if (!triggerElement) return;
+ if (closeDropdown) {
+ return this.props.onHide();
+ }
- if (!this.state.isOpen
- || triggerElement === event.target
- || triggerElement.contains(event.target)) {
+ if (contentElement && preventHide) {
return;
}
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
index 744331f419..95496e1bad 100755
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
@@ -6,7 +6,6 @@ import { styles } from './styles';
import ListItem from './item/component';
import ListSeparator from './separator/component';
import ListTitle from './title/component';
-import UserActions from '../../user-list/user-list-content/user-participants/user-list-item/user-action/component';
const propTypes = {
/* We should recheck this proptype, sometimes we need to create an container and send to dropdown,
@@ -15,8 +14,7 @@ const propTypes = {
children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
if (propValue[key].type !== ListItem &&
propValue[key].type !== ListSeparator &&
- propValue[key].type !== ListTitle &&
- propValue[key].type !== UserActions) {
+ propValue[key].type !== ListTitle) {
return new Error(`Invalid prop \`${propFullName}\` supplied to` +
` \`${componentName}\`. Validation failed.`);
}
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
index cb7f49b57e..41c158f9da 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
@@ -26,11 +26,12 @@ export default class DropdownListItem extends Component {
}
renderDefault() {
- const { icon, label } = this.props;
+ const { icon, label, iconRight } = this.props;
return [
(icon ? : null),
({label}),
+ (iconRight ? : null),
];
}
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss b/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss
index 991fc52f73..06f65f09c8 100755
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss
@@ -1,5 +1,8 @@
@import "/imports/ui/stylesheets/variables/_all";
+$more-icon-font-size: 12px;
+$more-icon-line-height: 16px;
+
%list {
list-style: none;
font-size: $font-size-base;
@@ -107,6 +110,7 @@
border-radius: 0.2rem;
}
+ .iconRight,
.itemIcon,
.itemLabel {
color: inherit;
@@ -118,12 +122,20 @@
}
}
+.iconRight,
.itemIcon {
margin-right: ($line-height-computed / 2);
color: $color-text;
flex: 0 0;
}
+.iconRight {
+ margin-right: -$indicator-padding-left;
+ margin-left: $sm-padding-x;
+ font-size: $more-icon-font-size;
+ line-height: $more-icon-line-height;
+}
+
.itemLabel {
color: $color-gray-dark;
font-size: 90%;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
index 27c04e3ac7..5937ce0dfe 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
@@ -62,6 +62,9 @@ class UserList extends Component {
isPublicChat,
roving,
CustomLogoUrl,
+ handleEmojiChange,
+ getEmojiList,
+ getEmoji,
} = this.props;
return (
@@ -91,6 +94,9 @@ class UserList extends Component {
isMeetingLocked,
isPublicChat,
roving,
+ handleEmojiChange,
+ getEmojiList,
+ getEmoji,
}
}
/>}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
index f30d12964c..8ab9a238eb 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
@@ -46,4 +46,7 @@ export default withTracker(({ chatID, compact }) => ({
roving: Service.roving,
CustomLogoUrl: Service.getCustomLogoUrl(),
compact,
+ handleEmojiChange: Service.setEmojiStatus,
+ getEmojiList: Service.getEmojiList(),
+ getEmoji: Service.getEmoji(),
}))(UserListContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 11290c48f1..04c64e86cd 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -277,6 +277,8 @@ const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => {
&& user.isModerator
&& !isDialInUser;
+ const allowedToChangeStatus = user.isCurrent;
+
return {
allowedToChatPrivately,
allowedToMuteAudio,
@@ -286,6 +288,7 @@ const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => {
allowedToSetPresenter,
allowedToPromote,
allowedToDemote,
+ allowedToChangeStatus,
};
};
@@ -318,7 +321,13 @@ const isMeetingLocked = (id) => {
return isLocked;
};
-const setEmojiStatus = (userId) => { makeCall('setEmojiStatus', userId, 'none'); };
+const setEmojiStatus = (data) => {
+ const statusAvailable = (Object.keys(EMOJI_STATUSES).includes(data));
+
+ return statusAvailable
+ ? makeCall('setEmojiStatus', Auth.userID, data)
+ : makeCall('setEmojiStatus', data, 'none');
+};
const assignPresenter = (userId) => { makeCall('assignPresenter', userId); };
@@ -409,4 +418,6 @@ export default {
roving,
setCustomLogoUrl,
getCustomLogoUrl,
+ getEmojiList: () => EMOJI_STATUSES,
+ getEmoji: () => Users.findOne({ userId: Auth.userID }).emoji,
};
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
index 74025e823c..37e2b7931f 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import { styles } from './styles';
import UserParticipants from './user-participants/component';
@@ -34,8 +34,31 @@ const defaultProps = {
meeting: {},
};
-class UserContent extends Component {
+class UserContent extends React.PureComponent {
render() {
+ const {
+ users,
+ compact,
+ intl,
+ currentUser,
+ meeting,
+ isBreakoutRoom,
+ setEmojiStatus,
+ assignPresenter,
+ removeUser,
+ toggleVoice,
+ changeRole,
+ getAvailableActions,
+ normalizeEmojiName,
+ isMeetingLocked,
+ roving,
+ handleEmojiChange,
+ getEmojiList,
+ getEmoji,
+ isPublicChat,
+ openChats,
+ } = this.props;
+
return (
);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
index df0745e206..c772dc782d 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
-import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl';
+import PropTypes from 'prop-types';
import cx from 'classnames';
import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
import UserListItem from './user-list-item/component';
@@ -48,38 +48,6 @@ const intlMessages = defineMessages({
id: 'app.userList.usersTitle',
description: 'Title for the Header',
},
- 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',
- },
- RemoveUserLabel: {
- id: 'app.userList.menu.removeUser.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',
- },
- PromoteUserLabel: {
- id: 'app.userList.menu.promoteUser.label',
- description: 'Forcefully promote this viewer to a moderator',
- },
- DemoteUserLabel: {
- id: 'app.userList.menu.demoteUser.label',
- description: 'Forcefully demote this moderator to a viewer',
- },
});
class UserParticipants extends Component {
@@ -136,58 +104,16 @@ class UserParticipants extends Component {
normalizeEmojiName,
isMeetingLocked,
users,
- intl,
changeRole,
assignPresenter,
setEmojiStatus,
removeUser,
toggleVoice,
+ handleEmojiChange,
+ getEmojiList,
+ getEmoji,
} = this.props;
- 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 => setEmojiStatus(user.id, 'none'),
- icon: 'clear_status',
- },
- setPresenter: {
- label: () => intl.formatMessage(intlMessages.MakePresenterLabel),
- handler: user => assignPresenter(user.id),
- icon: 'presentation',
- },
- remove: {
- label: user => intl.formatMessage(intlMessages.RemoveUserLabel, { 0: user.name }),
- handler: user => removeUser(user.id),
- icon: 'circle_close',
- },
- mute: {
- label: () => intl.formatMessage(intlMessages.MuteUserAudioLabel),
- handler: user => toggleVoice(user.id),
- icon: 'mute',
- },
- unmute: {
- label: () => intl.formatMessage(intlMessages.UnmuteUserAudioLabel),
- handler: user => toggleVoice(user.id),
- icon: 'unmute',
- },
- promote: {
- label: () => intl.formatMessage(intlMessages.PromoteUserLabel),
- handler: user => changeRole(user.id, 'MODERATOR'),
- icon: 'promote',
- },
- demote: {
- label: () => intl.formatMessage(intlMessages.DemoteUserLabel),
- handler: user => changeRole(user.id, 'VIEWER'),
- icon: 'user',
- },
- };
-
let index = -1;
return users.map(user => (
@@ -203,15 +129,24 @@ class UserParticipants extends Component {
>
{ this.userRefs[index += 1] = node; }}>
@@ -220,9 +155,7 @@ class UserParticipants extends Component {
}
focusUserItem(index) {
- if (!this.userRefs[index]) {
- return;
- }
+ if (!this.userRefs[index]) return;
this.userRefs[index].firstChild.focus();
}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
index 26d2c09dc9..e3e4fc5857 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
@@ -2,9 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
-import _ from 'lodash';
-import UserListContent from './user-list-content/component';
-import UserAction from './user-action/component';
+import UserDropdown from './user-dropdown/component';
const propTypes = {
user: PropTypes.shape({
@@ -23,7 +21,6 @@ const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
- userActions: PropTypes.shape({}).isRequired,
router: PropTypes.shape({}).isRequired,
isBreakoutRoom: PropTypes.bool,
getAvailableActions: PropTypes.func.isRequired,
@@ -38,64 +35,6 @@ const defaultProps = {
};
class UserListItem extends Component {
- static createAction(action, ...options) {
- return (
-
- );
- }
-
- getUsersActions() {
- const {
- currentUser,
- user,
- userActions,
- router,
- isBreakoutRoom,
- getAvailableActions,
- } = this.props;
-
- const {
- openChat,
- clearStatus,
- setPresenter,
- remove,
- mute,
- unmute,
- promote,
- demote,
- } = userActions;
-
- const actions = getAvailableActions(currentUser, user, router, isBreakoutRoom);
-
- const {
- allowedToChatPrivately,
- allowedToMuteAudio,
- allowedToUnmuteAudio,
- allowedToResetStatus,
- allowedToRemove,
- allowedToSetPresenter,
- allowedToPromote,
- allowedToDemote,
- } = actions;
-
- return _.compact([
- (allowedToChatPrivately ? UserListItem.createAction(openChat, router, user) : null),
- (allowedToMuteAudio ? UserListItem.createAction(mute, user) : null),
- (allowedToUnmuteAudio ? UserListItem.createAction(unmute, user) : null),
- (allowedToResetStatus ? UserListItem.createAction(clearStatus, user) : null),
- (allowedToSetPresenter ? UserListItem.createAction(setPresenter, user) : null),
- (allowedToRemove ? UserListItem.createAction(remove, user) : null),
- (allowedToPromote ? UserListItem.createAction(promote, user) : null),
- (allowedToDemote ? UserListItem.createAction(demote, user) : null),
- ]);
- }
-
render() {
const {
compact,
@@ -105,19 +44,42 @@ class UserListItem extends Component {
isMeetingLocked,
normalizeEmojiName,
getScrollContainerRef,
+ assignPresenter,
+ removeUser,
+ toggleVoice,
+ changeRole,
+ setEmojiStatus,
+ currentUser,
+ router,
+ isBreakoutRoom,
+ getAvailableActions,
+ handleEmojiChange,
+ getEmojiList,
+ getEmoji,
} = this.props;
- const actions = this.getUsersActions();
-
- const contents = ();
return contents;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx
deleted file mode 100755
index 955f5a5468..0000000000
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
-
-const propTypes = {
- icon: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired,
- handler: PropTypes.func.isRequired,
- options: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
-};
-
-export default class UserActions extends React.PureComponent {
- render() {
- const {
- key, icon, label, handler, options,
- } = this.props;
-
- return (
- handler.call(this, ...options)}
- />
- );
- }
-}
-
-UserActions.propTypes = propTypes;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
similarity index 52%
rename from bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx
rename to bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
index 1ba403fe4b..f3c5a1c805 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
@@ -8,8 +8,9 @@ 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 _ from 'lodash';
import { styles } from './styles';
import UserName from './../user-name/component';
import UserIcons from './../user-icons/component';
@@ -39,6 +40,46 @@ const messages = defineMessages({
id: 'app.userList.userAriaLabel',
description: 'aria label for each user in the userlist',
},
+ statusTriggerLabel: {
+ id: 'app.actionsBar.emojiMenu.statusTriggerLabel',
+ description: 'label for option to show emoji menu',
+ },
+ backTriggerLabel: {
+ id: 'app.audio.backLabel',
+ description: 'label for option to hide emoji menu',
+ },
+ 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',
+ },
+ RemoveUserLabel: {
+ id: 'app.userList.menu.removeUser.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',
+ },
+ PromoteUserLabel: {
+ id: 'app.userList.menu.promoteUser.label',
+ description: 'Forcefully promote this viewer to a moderator',
+ },
+ DemoteUserLabel: {
+ id: 'app.userList.menu.demoteUser.label',
+ description: 'Forcefully demote this moderator to a viewer',
+ },
});
const propTypes = {
@@ -48,14 +89,12 @@ const propTypes = {
formatMessage: PropTypes.func.isRequired,
}).isRequired,
normalizeEmojiName: PropTypes.func.isRequired,
- actions: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
meeting: PropTypes.shape({}).isRequired,
isMeetingLocked: PropTypes.func.isRequired,
getScrollContainerRef: PropTypes.func.isRequired,
};
-
-class UserListContent extends Component {
+class UserDropdown extends Component {
/**
* Return true if the content fit on the screen, false otherwise.
*
@@ -75,6 +114,7 @@ class UserListContent extends Component {
dropdownOffset: 0,
dropdownDirection: 'top',
dropdownVisible: false,
+ showNestedOptions: false,
};
this.handleScroll = this.handleScroll.bind(this);
@@ -82,6 +122,8 @@ class UserListContent extends Component {
this.onActionsHide = this.onActionsHide.bind(this);
this.getDropdownMenuParent = this.getDropdownMenuParent.bind(this);
this.renderUserAvatar = this.renderUserAvatar.bind(this);
+ this.resetMenuState = this.resetMenuState.bind(this);
+ this.makeDropdownItem = this.makeDropdownItem.bind(this);
}
componentWillMount() {
@@ -89,30 +131,199 @@ class UserListContent extends Component {
this.seperator = _.uniqueId('action-separator-');
}
- componentDidUpdate() {
+ componentDidUpdate(prevProps, prevState) {
+ if (!this.state.isActionsOpen && this.state.showNestedOptions) {
+ return this.resetMenuState();
+ }
+
this.checkDropdownDirection();
}
+ makeDropdownItem(key, label, onClick, icon = null, iconRight = null) {
+ return (
+
+ );
+ }
+
+ resetMenuState() {
+ return this.setState({
+ isActionsOpen: false,
+ dropdownOffset: 0,
+ dropdownDirection: 'top',
+ dropdownVisible: false,
+ showNestedOptions: false,
+ });
+ }
+
+ getUsersActions() {
+ const {
+ intl,
+ currentUser,
+ user,
+ router,
+ isBreakoutRoom,
+ getAvailableActions,
+ handleEmojiChange,
+ getEmojiList,
+ setEmojiStatus,
+ assignPresenter,
+ removeUser,
+ toggleVoice,
+ changeRole,
+ } = this.props;
+
+ const actionPermissions = getAvailableActions(currentUser, user, router, isBreakoutRoom);
+ const actions = [];
+
+ const {
+ allowedToChatPrivately,
+ allowedToMuteAudio,
+ allowedToUnmuteAudio,
+ allowedToResetStatus,
+ allowedToRemove,
+ allowedToSetPresenter,
+ allowedToPromote,
+ allowedToDemote,
+ allowedToChangeStatus,
+ } = actionPermissions;
+
+ if (this.state.showNestedOptions) {
+ if (allowedToChangeStatus) {
+ actions.push(this.makeDropdownItem(
+ 'back',
+ intl.formatMessage(messages.backTriggerLabel),
+ () => this.setState({ showNestedOptions: false, isActionsOpen: true }),
+ 'left_arrow',
+ ));
+ }
+
+ actions.push();
+
+ const statuses = Object.keys(getEmojiList);
+ statuses.map(status => actions.push(this.makeDropdownItem(
+ status,
+ intl.formatMessage({ id: `app.actionsBar.emojiMenu.${status}Label` }),
+ () => { handleEmojiChange(status); this.resetMenuState(); },
+ getEmojiList[status],
+ )));
+
+ return actions;
+ }
+
+ if (allowedToChangeStatus) {
+ actions.push(this.makeDropdownItem(
+ 'setstatus',
+ intl.formatMessage(messages.statusTriggerLabel),
+ () => this.setState({ showNestedOptions: true, isActionsOpen: true }),
+ 'user',
+ 'right_arrow',
+ ));
+ }
+
+ if (allowedToChatPrivately) {
+ actions.push(this.makeDropdownItem(
+ 'openChat',
+ intl.formatMessage(messages.ChatLabel),
+ () => this.onActionsHide(router.push(`/users/chat/${user.id}`)),
+ 'chat',
+ ));
+ }
+
+ if (allowedToMuteAudio) {
+ actions.push(this.makeDropdownItem(
+ 'mute',
+ intl.formatMessage(messages.MuteUserAudioLabel),
+ () => this.onActionsHide(toggleVoice(user.id)),
+ 'mute',
+ ));
+ }
+
+ if (allowedToUnmuteAudio) {
+ actions.push(this.makeDropdownItem(
+ 'unmute',
+ intl.formatMessage(messages.UnmuteUserAudioLabel),
+ () => this.onActionsHide(toggleVoice(user.id)),
+ 'unmute',
+ ));
+ }
+
+ if (allowedToResetStatus && user.emoji.status !== 'none') {
+ actions.push(this.makeDropdownItem(
+ 'clearStatus',
+ intl.formatMessage(messages.ClearStatusLabel),
+ () => this.onActionsHide(setEmojiStatus(user.id, 'none')),
+ 'clear_status',
+ ));
+ }
+
+ if (allowedToSetPresenter) {
+ actions.push(this.makeDropdownItem(
+ 'setPresenter',
+ intl.formatMessage(messages.MakePresenterLabel),
+ () => this.onActionsHide(assignPresenter(user.id)),
+ 'presentation',
+ ));
+ }
+
+ if (allowedToRemove) {
+ actions.push(this.makeDropdownItem(
+ 'remove',
+ intl.formatMessage(messages.RemoveUserLabel, { 0: user.name }),
+ () => this.onActionsHide(removeUser(user.id)),
+ 'circle_close',
+ ));
+ }
+
+ if (allowedToPromote) {
+ actions.push(this.makeDropdownItem(
+ 'promote',
+ intl.formatMessage(messages.PromoteUserLabel),
+ () => this.onActionsHide(changeRole(user.id, 'MODERATOR')),
+ 'promote',
+ ));
+ }
+
+ if (allowedToDemote) {
+ actions.push(this.makeDropdownItem(
+ 'demote',
+ intl.formatMessage(messages.DemoteUserLabel),
+ () => this.onActionsHide(changeRole(user.id, 'VIEWER')),
+ 'user',
+ ));
+ }
+
+ return actions;
+ }
+
onActionsShow() {
const dropdown = this.getDropdownMenuParent();
const scrollContainer = this.props.getScrollContainerRef();
- const dropdownTrigger = dropdown.children[0];
- const list = findDOMNode(this.list);
- const children = [].slice.call(list.children);
- children.find(child => child.getAttribute('role') === 'menuitem').focus();
+ if (dropdown && scrollContainer) {
+ const dropdownTrigger = dropdown.children[0];
+ const list = findDOMNode(this.list);
+ const children = [].slice.call(list.children);
+ children.find(child => child.getAttribute('role') === 'menuitem').focus();
- this.setState({
- isActionsOpen: true,
- dropdownVisible: false,
- dropdownOffset: dropdownTrigger.offsetTop - scrollContainer.scrollTop,
- dropdownDirection: 'top',
- });
+ this.setState({
+ isActionsOpen: true,
+ dropdownVisible: false,
+ dropdownOffset: dropdownTrigger.offsetTop - scrollContainer.scrollTop,
+ dropdownDirection: 'top',
+ });
- scrollContainer.addEventListener('scroll', this.handleScroll, false);
+ scrollContainer.addEventListener('scroll', this.handleScroll, false);
+ }
}
- onActionsHide() {
+ onActionsHide(callback) {
this.setState({
isActionsOpen: false,
dropdownVisible: false,
@@ -120,6 +331,10 @@ class UserListContent extends Component {
const scrollContainer = this.props.getScrollContainerRef();
scrollContainer.removeEventListener('scroll', this.handleScroll, false);
+
+ if (callback) {
+ return callback;
+ }
}
getDropdownMenuParent() {
@@ -127,9 +342,7 @@ class UserListContent extends Component {
}
handleScroll() {
- this.setState({
- isActionsOpen: false,
- });
+ this.setState({ isActionsOpen: false });
}
/**
@@ -148,7 +361,7 @@ class UserListContent extends Component {
};
const isDropdownVisible =
- UserListContent.checkIfDropdownIsVisible(
+ UserDropdown.checkIfDropdownIsVisible(
dropdownContent.offsetTop,
dropdownContent.offsetHeight,
);
@@ -211,7 +424,6 @@ class UserListContent extends Component {
compact,
user,
intl,
- actions,
isMeetingLocked,
meeting,
} = this.props;
@@ -223,6 +435,8 @@ class UserListContent extends Component {
dropdownOffset,
} = this.state;
+ const actions = this.getUsersActions();
+
const userItemContentsStyle = {};
userItemContentsStyle[styles.userItemContentsCompact] = compact;
@@ -255,25 +469,27 @@ class UserListContent extends Component {
{ this.renderUserAvatar() }
{}
{}
);
- if (!actions.length) {
- return contents;
- }
+ if (!actions.length) return contents;
return (
-
{ this.list = ref; }}
getDropdownMenuParent={this.getDropdownMenuParent}
onActionsHide={this.onActionsHide}
>
- {
- [
- (
-
- {user.name}
- ),
- (),
- ].concat(actions)
- }
+ {actions}
@@ -323,5 +527,5 @@ class UserListContent extends Component {
}
}
-UserListContent.propTypes = propTypes;
-export default UserListContent;
+UserDropdown.propTypes = propTypes;
+export default UserDropdown;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss
similarity index 96%
rename from bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/styles.scss
rename to bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss
index 441d4377ac..e6e8b750e5 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss
@@ -103,3 +103,9 @@
max-width: 100%;
overflow: visible;
}
+
+.emojiSelected {
+ span, i {
+ color: $color-primary;
+ }
+}
diff --git a/bigbluebutton-html5/imports/utils/statuses.js b/bigbluebutton-html5/imports/utils/statuses.js
index 9aa785f639..848c521c47 100644
--- a/bigbluebutton-html5/imports/utils/statuses.js
+++ b/bigbluebutton-html5/imports/utils/statuses.js
@@ -9,7 +9,6 @@ export const EMOJI_STATUSES = {
applause: 'applause',
thumbsUp: 'thumbs_up',
thumbsDown: 'thumbs_down',
- none: 'clear_status',
};
export default { EMOJI_STATUSES };
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index 38f3081962..0b3a2ae5ed 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -200,7 +200,7 @@
"app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop sharing your screen with",
"app.actionsBar.actionsDropdown.startRecording": "Start recording",
"app.actionsBar.actionsDropdown.stopRecording": "Stop recording",
- "app.actionsBar.emojiMenu.statusTriggerLabel": "Set a Status",
+ "app.actionsBar.emojiMenu.statusTriggerLabel": "Set Status",
"app.actionsBar.emojiMenu.awayLabel": "Away",
"app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
"app.actionsBar.emojiMenu.raiseHandLabel": "Raise",