diff --git a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js index a662ee6bd4ae489ec36b6a806750b5777798842f..97c79f89db793c1c1b78a35fd43f485f740916c6 100644 --- a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js +++ b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js @@ -21,6 +21,8 @@ export default function addUserSettings(credentials, meetingId, userId, settings 'askForFeedbackOnLogout', // BRANDING 'displayBrandingArea', + // SHORTCUTS + 'shortcuts', // KURENTO 'enableScreensharing', 'enableVideo', diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index 811a2214348a5dd7ecceb9391a69f89c8ee71016..05cb07ae6b47e44074e49653ee871ddaa40b5a9d 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -9,6 +9,7 @@ import DropdownList from '/imports/ui/components/dropdown/list/component'; import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; import PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container'; import { withModalMounter } from '/imports/ui/components/modal/service'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from '../styles'; const propTypes = { @@ -56,9 +57,6 @@ const intlMessages = defineMessages({ }, }); -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const OPEN_ACTIONS_AK = SHORTCUTS_CONFIG.openActions.accesskey; - class ActionsDropdown extends Component { constructor(props) { super(props); @@ -123,6 +121,7 @@ class ActionsDropdown extends Component { intl, isUserPresenter, isUserModerator, + shortcuts: OPEN_ACTIONS_AK, } = this.props; const availableActions = this.getAvailableActions(); @@ -156,4 +155,4 @@ class ActionsDropdown extends Component { ActionsDropdown.propTypes = propTypes; -export default withModalMounter(injectIntl(ActionsDropdown)); +export default withShortcutHelper(withModalMounter(injectIntl(ActionsDropdown)), 'openActions'); diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx index e2576b6e71a7f7e508a5cdf3e79606ce7d694670..0b4cc607394acb006ee40f8b8d511e2c96982ea0 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import { defineMessages, intlShape, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -41,11 +42,6 @@ const defaultProps = { unmute: false, }; -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const JOIN_AUDIO_AK = SHORTCUTS_CONFIG.joinAudio.accesskey; -const LEAVE_AUDIO_AK = SHORTCUTS_CONFIG.leaveAudio.accesskey; -const MUTE_UNMUTE_AK = SHORTCUTS_CONFIG.toggleMute.accesskey; - const AudioControls = ({ handleToggleMuteMicrophone, handleJoinAudio, @@ -56,6 +52,7 @@ const AudioControls = ({ glow, join, intl, + shortcuts, }) => ( <span className={styles.container}> {mute ? @@ -70,7 +67,7 @@ const AudioControls = ({ icon={unmute ? 'mute' : 'unmute'} size="lg" circle - accessKey={MUTE_UNMUTE_AK} + accessKey={shortcuts.toggleMute} /> : null} <Button className={styles.button} @@ -83,11 +80,11 @@ const AudioControls = ({ icon={join ? 'audio_off' : 'audio_on'} size="lg" circle - accessKey={join ? LEAVE_AUDIO_AK : JOIN_AUDIO_AK} + accessKey={join ? shortcuts.leaveAudio : shortcuts.joinAudio} /> </span>); AudioControls.propTypes = propTypes; AudioControls.defaultProps = defaultProps; -export default injectIntl(AudioControls); +export default withShortcutHelper(injectIntl(AudioControls), ['joinAudio', 'leaveAudio', 'toggleMute']); diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index e736e19cdc48b5e2ca9185dda943ce35a113f5cf..1f61e805ec6e4aca8c4372e360819e63d8d50095 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -4,11 +4,11 @@ import { defineMessages, injectIntl } from 'react-intl'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import Button from '/imports/ui/components/button/component'; import { Session } from 'meteor/session'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from './styles'; import MessageForm from './message-form/component'; import MessageList from './message-list/component'; import ChatDropdown from './chat-dropdown/component'; -import Icon from '../icon/component'; const ELEMENT_ID = 'chat-messages'; @@ -23,10 +23,6 @@ const intlMessages = defineMessages({ }, }); -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const HIDE_CHAT_AK = SHORTCUTS_CONFIG.hidePrivateChat.accesskey; -const CLOSE_CHAT_AK = SHORTCUTS_CONFIG.closePrivateChat.accesskey; - const Chat = (props) => { const { chatID, @@ -42,8 +38,12 @@ const Chat = (props) => { maxMessageLength, actions, intl, + shortcuts, } = props; + const HIDE_CHAT_AK = shortcuts.hidePrivateChat; + const CLOSE_CHAT_AK = shortcuts.closePrivateChat; + return ( <div data-test="publicChat" @@ -108,7 +108,7 @@ const Chat = (props) => { ); }; -export default injectWbResizeEvent(injectIntl(Chat)); +export default withShortcutHelper(injectWbResizeEvent(injectIntl(Chat)), ['hidePrivateChat', 'closePrivateChat']); const propTypes = { chatID: PropTypes.string.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx index 45318056af4e6a51b16b9eee11f9a9543bbff365..fb8ae22b3ab6542d7f53036d519838f999d07bab 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -10,6 +10,7 @@ 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 { withModalMounter } from '/imports/ui/components/modal/service'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles.scss'; import Button from '../button/component'; @@ -44,20 +45,19 @@ const intlMessages = defineMessages({ }); const propTypes = { - presentationTitle: PropTypes.string.isRequired, - hasUnreadMessages: PropTypes.bool.isRequired, - beingRecorded: PropTypes.object.isRequired, + presentationTitle: PropTypes.string, + hasUnreadMessages: PropTypes.bool, + beingRecorded: PropTypes.object, + shortcuts: PropTypes.string, }; const defaultProps = { presentationTitle: 'Default Room Title', hasUnreadMessages: false, beingRecorded: false, + shortcuts: '', }; -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const TOGGLE_USERLIST_AK = SHORTCUTS_CONFIG.toggleUserList.accesskey; - const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => mountModal(<BreakoutJoinConfirmation breakout={breakout} @@ -82,7 +82,6 @@ class NavBar extends Component { componentDidUpdate(oldProps) { const { breakouts, - getBreakoutJoinURL, isBreakoutRoom, mountModal, } = this.props; @@ -174,7 +173,11 @@ class NavBar extends Component { render() { const { - hasUnreadMessages, beingRecorded, isExpanded, intl, + hasUnreadMessages, + beingRecorded, + isExpanded, + intl, + shortcuts: TOGGLE_USERLIST_AK, } = this.props; const recordingMessage = beingRecorded.recording ? 'recordingIndicatorOn' : 'recordingIndicatorOff'; @@ -223,4 +226,4 @@ class NavBar extends Component { NavBar.propTypes = propTypes; NavBar.defaultProps = defaultProps; -export default withModalMounter(injectIntl(NavBar)); +export default withShortcutHelper(withModalMounter(injectIntl(NavBar)), 'toggleUserList'); diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx index 50f3859de848779befcd699a07df2e32c40a09ca..108b08ce51ebd79e96d65133a33e36c2deef8d87 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx @@ -3,12 +3,9 @@ import { defineMessages, injectIntl } from 'react-intl'; import cx from 'classnames'; import _ from 'lodash'; import { withModalMounter } from '/imports/ui/components/modal/service'; - - import LogoutConfirmationContainer from '/imports/ui/components/logout-confirmation/container'; import AboutContainer from '/imports/ui/components/about/container'; import SettingsMenuContainer from '/imports/ui/components/settings/container'; - 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'; @@ -17,6 +14,7 @@ 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 ShortcutHelpComponent from '/imports/ui/components/shortcut-help/component'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from '../styles'; @@ -83,9 +81,6 @@ const intlMessages = defineMessages({ }, }); -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const OPEN_OPTIONS_AK = SHORTCUTS_CONFIG.openOptions.accesskey; - class SettingsDropdown extends Component { constructor(props) { super(props); @@ -210,7 +205,10 @@ class SettingsDropdown extends Component { } render() { - const { intl } = this.props; + const { + intl, + shortcuts: OPEN_OPTIONS_AK, + } = this.props; return ( <Dropdown @@ -245,4 +243,4 @@ class SettingsDropdown extends Component { } } -export default withModalMounter(injectIntl(SettingsDropdown)); +export default withShortcutHelper(withModalMounter(injectIntl(SettingsDropdown)), 'openOptions'); diff --git a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx index f98004680a4378df34be8902d56cb25e241a435b..8f55e74f4a6f5cee2cec7c8748db1983865c3768 100644 --- a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx @@ -1,9 +1,11 @@ -import React, { Component } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, intlShape } from 'react-intl'; import browser from 'browser-detect'; import Modal from '/imports/ui/components/modal/simple/component'; import _ from 'lodash'; import { styles } from './styles'; +import withShortcutHelper from './service'; const intlMessages = defineMessages({ title: { @@ -72,60 +74,69 @@ const intlMessages = defineMessages({ }, }); -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; +const ShortcutHelpComponent = (props) => { + const { intl, shortcuts } = props; + const { name } = browser(); -class ShortcutHelpComponent extends Component { - render() { - const { intl } = this.props; - const shortcuts = Object.values(SHORTCUTS_CONFIG); - const { name } = browser(); + let accessMod = null; - let accessMod = null; - - switch (name) { - case 'chrome': - case 'edge': - accessMod = 'Alt'; - break; - case 'firefox': - accessMod = 'Alt + Shift'; - break; - case 'safari': - case 'crios': - case 'fxios': - accessMod = 'Control + Alt'; - break; - } + switch (name) { + case 'chrome': + case 'edge': + accessMod = 'Alt'; + break; + case 'firefox': + accessMod = 'Alt + Shift'; + break; + case 'safari': + case 'crios': + case 'fxios': + accessMod = 'Control + Alt'; + break; + default: + break; + } - return ( - <Modal - title={intl.formatMessage(intlMessages.title)} - dismiss={{ - label: intl.formatMessage(intlMessages.closeLabel), - description: intl.formatMessage(intlMessages.closeDesc), - }} - > - { !accessMod ? <p>{intl.formatMessage(intlMessages.accessKeyNotAvailable)}</p> : - <span> - <table className={styles.shortcutTable}> - <tbody> - <tr> - <th>{intl.formatMessage(intlMessages.comboLabel)}</th> - <th>{intl.formatMessage(intlMessages.functionLabel)}</th> + return ( + <Modal + title={intl.formatMessage(intlMessages.title)} + dismiss={{ + label: intl.formatMessage(intlMessages.closeLabel), + description: intl.formatMessage(intlMessages.closeDesc), + }} + > + {!accessMod ? <p>{intl.formatMessage(intlMessages.accessKeyNotAvailable)}</p> : + <span> + <table className={styles.shortcutTable}> + <tbody> + <tr> + <th>{intl.formatMessage(intlMessages.comboLabel)}</th> + <th>{intl.formatMessage(intlMessages.functionLabel)}</th> + </tr> + {shortcuts.map(shortcut => ( + <tr key={_.uniqueId('hotkey-item-')}> + <td className={styles.keyCell}>{`${accessMod} + ${shortcut.accesskey}`}</td> + <td className={styles.descCell}>{intl.formatMessage(intlMessages[`${shortcut.descId}`])}</td> </tr> - {shortcuts.map(shortcut => ( - <tr key={_.uniqueId('hotkey-item-')}> - <td className={styles.keyCell}>{`${accessMod} + ${shortcut.accesskey}`}</td> - <td className={styles.descCell}>{intl.formatMessage(intlMessages[`${shortcut.descId}`])}</td> - </tr> - ))} - </tbody> - </table> - </span> - } - </Modal> - ); - } -} + ))} + </tbody> + </table> + </span> + } + </Modal> + ); +}; + +ShortcutHelpComponent.defaultProps = { + intl: intlShape, +}; + +ShortcutHelpComponent.propTypes = { + intl: intlShape, + shortcuts: PropTypes.arrayOf(PropTypes.shape({ + accesskey: PropTypes.string.isRequired, + descId: PropTypes.string.isRequired, + })).isRequired, +}; -export default injectIntl(ShortcutHelpComponent); +export default withShortcutHelper(injectIntl(ShortcutHelpComponent)); diff --git a/bigbluebutton-html5/imports/ui/components/shortcut-help/service.jsx b/bigbluebutton-html5/imports/ui/components/shortcut-help/service.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9a29b56903986f0780bb54dd951d1ee3a0da0fe8 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/shortcut-help/service.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import getFromUserSettings from '/imports/ui/services/users-settings'; + +const BASE_SHORTCUTS = Meteor.settings.public.app.shortcuts; + +const withShortcutHelper = (WrappedComponent, param) => (props) => { + const ENABLED_SHORTCUTS = getFromUserSettings('shortcuts', null); + + let shortcuts = Object.values(BASE_SHORTCUTS); + + if (ENABLED_SHORTCUTS) { + shortcuts = Object.values(BASE_SHORTCUTS) + .filter(el => ENABLED_SHORTCUTS.includes(el.descId)); + } + + if (param !== undefined) { + if (!Array.isArray(param)) { + shortcuts = shortcuts + .filter(el => el.descId === param) + .map(el => el.accesskey) + .pop(); + } else { + shortcuts = shortcuts + .filter(el => param.includes(el.descId)) + .reduce((acc, current) => { + acc[current.descId] = current.accesskey; + return acc; + }, {}); + } + } + + return ( + <WrappedComponent {...props} shortcuts={shortcuts} /> + ); +}; + +export default withShortcutHelper; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index 5ff1de8675ca8a86366e109416a3a953b083a86d..ffaa708a1ccce41f22a48133a5cd6bb717ac4af2 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import { Session } from 'meteor/session'; +import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from './styles'; import ChatAvatar from './chat-avatar/component'; import ChatIcon from './chat-icon/component'; @@ -23,9 +24,6 @@ const intlMessages = defineMessages({ }, }); -const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; -const TOGGLE_CHAT_PUB_AK = SHORTCUTS_CONFIG.togglePublicChat.accesskey; - const propTypes = { chat: PropTypes.shape({ id: PropTypes.string.isRequired, @@ -39,10 +37,12 @@ const propTypes = { }).isRequired, tabIndex: PropTypes.number.isRequired, isPublicChat: PropTypes.func.isRequired, + shortcuts: PropTypes.string, }; const defaultProps = { openChat: '', + shortcuts: '', }; const toggleChatOpen = () => { @@ -57,6 +57,7 @@ const ChatListItem = (props) => { intl, tabIndex, isPublicChat, + shortcuts: TOGGLE_CHAT_PUB_AK, } = props; const isCurrentChat = chat.id === openChat; @@ -109,4 +110,4 @@ const ChatListItem = (props) => { ChatListItem.propTypes = propTypes; ChatListItem.defaultProps = defaultProps; -export default injectIntl(ChatListItem); +export default withShortcutHelper(injectIntl(ChatListItem), 'togglePublicChat');