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",