diff --git a/bigbluebutton-html5/.meteor/packages b/bigbluebutton-html5/.meteor/packages index 66027107eeef83d267304a3bff0bb92dd17f6ca6..a0f2677c4c0edd35479d857cb77bcf15a282cd30 100644 --- a/bigbluebutton-html5/.meteor/packages +++ b/bigbluebutton-html5/.meteor/packages @@ -23,3 +23,4 @@ shell-server@0.3.0 http@1.3.0 dynamic-import@0.2.0 rocketchat:streamer +session diff --git a/bigbluebutton-html5/client/main.jsx b/bigbluebutton-html5/client/main.jsx index f38a45090020abe155247467e8768f2c81b91027..bbcfaa44921b690400c9d2c989d55f4d7429cdd1 100755 --- a/bigbluebutton-html5/client/main.jsx +++ b/bigbluebutton-html5/client/main.jsx @@ -1,19 +1,43 @@ /* eslint no-unused-vars: 0 */ import React from 'react'; import { Meteor } from 'meteor/meteor'; +import { Session } from 'meteor/session'; import { render } from 'react-dom'; -import renderRoutes from '/imports/startup/client/routes'; import logger from '/imports/startup/client/logger'; +import { joinRouteHandler, authenticatedRouteHandler } from '/imports/startup/client/auth'; +import Base from '/imports/startup/client/base'; +import LoadingScreen from '/imports/ui/components/loading-screen/component'; Meteor.startup(() => { - render(renderRoutes(), document.getElementById('app')); + render(<LoadingScreen />, document.getElementById('app')); + // Logs all uncaught exceptions to the client logger window.addEventListener('error', (e) => { - const stack = e.error.stack; + const { stack } = e.error; let message = e.error.toString(); // Checks if stack includes the message, if not add the two together. - (stack.includes(message)) ? message = stack : message += `\n${stack}`; + if (stack.includes(message)) { + message = stack; + } else { + message += `\n${stack}`; + } logger.error(message); }); + + // TODO make this a Promise + joinRouteHandler((value, error) => { + if (error) { + logger.error(`User faced [${value}] on main.joinRouteHandler. Error was:`, JSON.stringify(error)); + } else { + logger.info(`User successfully went through main.joinRouteHandler with [${value}].`); + } + authenticatedRouteHandler(() => { + // set defaults + Session.set('isChatOpen', false); + Session.set('idChatOpen', ''); + Session.set('isMeetingEnded', false); + render(<Base />, document.getElementById('app')); + }); + }); }); diff --git a/bigbluebutton-html5/imports/api/acl/Acl.js b/bigbluebutton-html5/imports/api/acl/Acl.js index b5fcf8b91b6bfc01057f5a2e91d041bded544c3c..4485dee8884662be3b4991e271d166ad360579cd 100644 --- a/bigbluebutton-html5/imports/api/acl/Acl.js +++ b/bigbluebutton-html5/imports/api/acl/Acl.js @@ -28,7 +28,7 @@ export default class Acl { authToken, validated: true, connectionStatus: 'online', - // TODO: We cant check for approved until we move subscription login out of <Base /> + // TODO: We cant check for approved until we move subscription login out of <Base /> // TODO 4767 // approved: true, }); diff --git a/bigbluebutton-html5/imports/startup/client/auth.js b/bigbluebutton-html5/imports/startup/client/auth.js index ea5de6e4964e3941e68a0b336a73ceb379dd0db4..6f50e762c62d406821a8d7f65d603220c1b58bef 100755 --- a/bigbluebutton-html5/imports/startup/client/auth.js +++ b/bigbluebutton-html5/imports/startup/client/auth.js @@ -3,16 +3,23 @@ import { setCustomLogoUrl } from '/imports/ui/components/user-list/service'; import { log, makeCall } from '/imports/ui/services/api'; import deviceInfo from '/imports/utils/deviceInfo'; import logger from '/imports/startup/client/logger'; +import { Session } from 'meteor/session'; // disconnected and trying to open a new connection const STATUS_CONNECTING = 'connecting'; -export function joinRouteHandler(nextState, replace, callback) { - const { sessionToken } = nextState.location.query; +const setError = (errorCode) => { + Session.set('hasError', true); + Session.set('codeError', errorCode); +}; - if (!nextState || !sessionToken) { - replace({ pathname: '/error/404' }); - callback(); +export function joinRouteHandler(callback) { + const urlParams = new URLSearchParams(window.location.search); + const sessionToken = urlParams.get('sessionToken'); + + if (!sessionToken) { + setError('404'); + callback('failed - no sessionToken', urlParams); } // Old credentials stored in memory were being used when joining a new meeting @@ -30,42 +37,40 @@ export function joinRouteHandler(nextState, replace, callback) { } = response; if (returncode === 'FAILED') { - replace({ pathname: '/error/404' }); - callback(); - } - - setCustomLogoUrl(customLogoURL); - - if (customdata.length) { - makeCall('addUserSettings', meetingID, internalUserID, customdata); + setError('404'); + callback('failed during enter API call', response); + } else { + setCustomLogoUrl(customLogoURL); + + if (customdata.length) { + makeCall('addUserSettings', meetingID, internalUserID, customdata); + } + + Auth.set( + meetingID, internalUserID, authToken, logoutUrl, + sessionToken, fullname, externUserID, confname, + ); + + Session.set('isUserListOpen', deviceInfo.type().isPhone); + const userInfo = window.navigator; + + // Browser information is sent once on startup + // Sent here instead of Meteor.startup, as the + // user might not be validated by then, thus user's data + // would not be sent with this information + const clientInfo = { + language: userInfo.language, + userAgent: userInfo.userAgent, + screenSize: { width: window.screen.width, height: window.screen.height }, + windowSize: { width: window.innerWidth, height: window.innerHeight }, + bbbVersion: Meteor.settings.public.app.bbbServerVersion, + location: window.location.href, + }; + + logger.info(clientInfo); + + callback('all is good', null); } - - Auth.set( - meetingID, internalUserID, authToken, logoutUrl, - sessionToken, fullname, externUserID, confname, - ); - - const path = deviceInfo.type().isPhone ? '/' : '/users'; - const userInfo = window.navigator; - - // Browser information is sent once on startup - // Sent here instead of Meteor.startup, as the - // user might not be validiated by then, thus user's data - // would not be sent with this information - const clientInfo = { - language: userInfo.language, - userAgent: userInfo.userAgent, - screenSize: { width: window.screen.width, height: window.screen.height }, - windowSize: { width: window.innerWidth, height: window.innerHeight }, - bbbVersion: Meteor.settings.public.app.bbbServerVersion, - location: window.location.href, - }; - - replace({ pathname: path }); - - logger.info(clientInfo); - - return callback(); }); } @@ -112,7 +117,7 @@ function _addReconnectObservable() { }); } -export function authenticatedRouteHandler(nextState, replace, callback) { +export function authenticatedRouteHandler(callback) { if (Auth.loggedIn) { callback(); } @@ -123,7 +128,7 @@ export function authenticatedRouteHandler(nextState, replace, callback) { .then(callback) .catch((reason) => { log('error', reason); - replace({ pathname: `/error/${reason.error}` }); + setError(reason.error); callback(); }); } diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 23d9e8f0616c4b463dacba173347c6061ec6651d..e5da55a01d98705a1597fe0c448377722ef283bc 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import { withRouter } from 'react-router'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; import AppContainer from '/imports/ui/components/app/container'; @@ -14,6 +13,7 @@ import Users from '/imports/api/users'; import Annotations from '/imports/api/annotations'; import AnnotationsLocal from '/imports/ui/components/whiteboard/service'; import GroupChat from '/imports/api/group-chat'; +import { Session } from 'meteor/session'; import IntlStartup from './intl'; const CHAT_CONFIG = Meteor.settings.public.chat; @@ -21,20 +21,16 @@ const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id; const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public; const propTypes = { - error: PropTypes.object, - errorCode: PropTypes.number, subscriptionsReady: PropTypes.bool.isRequired, locale: PropTypes.string, - endedCode: PropTypes.string, approved: PropTypes.bool, + meetingEnded: PropTypes.bool, }; const defaultProps = { - error: undefined, - errorCode: undefined, locale: undefined, - endedCode: undefined, approved: undefined, + meetingEnded: false, }; class Base extends Component { @@ -43,11 +39,9 @@ class Base extends Component { this.state = { loading: false, - error: props.error || null, }; this.updateLoadingState = this.updateLoadingState.bind(this); - this.updateErrorState = this.updateErrorState.bind(this); } componentWillUpdate() { @@ -63,29 +57,23 @@ class Base extends Component { }); } - updateErrorState(error = null) { - this.setState({ - error, - }); - } - renderByState() { - const { updateLoadingState, updateErrorState } = this; - const stateControls = { updateLoadingState, updateErrorState }; + const { updateLoadingState } = this; + const stateControls = { updateLoadingState }; - const { loading, error } = this.state; + const { loading } = this.state; - const { subscriptionsReady, errorCode } = this.props; - const { endedCode } = this.props.params; + const codeError = Session.get('codeError'); + const { subscriptionsReady, meetingEnded } = this.props; - if (endedCode) { + if (meetingEnded) { AudioManager.exitAudio(); - return (<MeetingEnded code={endedCode} />); + return (<MeetingEnded code={Session.get('codeError')} />); } - if (error || errorCode) { - logger.error(`User could not log in HTML5, hit ${errorCode}`); - return (<ErrorScreen code={errorCode}>{error}</ErrorScreen>); + if (codeError) { + logger.error(`User could not log in HTML5, hit ${codeError}`); + return (<ErrorScreen code={codeError} />); } if (loading || !subscriptionsReady) { @@ -101,9 +89,9 @@ class Base extends Component { } render() { - const { updateLoadingState, updateErrorState } = this; + const { updateLoadingState } = this; const { locale } = this.props; - const stateControls = { updateLoadingState, updateErrorState }; + const stateControls = { updateLoadingState }; return ( <IntlStartup locale={locale} baseControls={stateControls}> @@ -122,9 +110,7 @@ const SUBSCRIPTIONS_NAME = [ 'group-chat', 'presentation-pods', 'users-settings', ]; -const BaseContainer = withRouter(withTracker(({ params, router }) => { - if (params.errorCode) return params; - +const BaseContainer = withTracker(() => { const { locale } = Settings.application; const { credentials, loggedIn } = Auth; const { meetingId, requesterUserId } = credentials; @@ -139,7 +125,8 @@ const BaseContainer = withRouter(withTracker(({ params, router }) => { const subscriptionErrorHandler = { onError: (error) => { logger.error(error); - return router.push('/logout'); + Session.set('isMeetingEnded', true); + Session.set('codeError', error.error); }, }; @@ -179,11 +166,12 @@ const BaseContainer = withRouter(withTracker(({ params, router }) => { const subscriptionsReady = subscriptionsHandlers.every(handler => handler.ready()); return { approved: Users.findOne({ userId: Auth.userID, approved: true, guest: true }), + meetingEnded: Session.get('isMeetingEnded'), locale, subscriptionsReady, annotationsHandler, groupChatMessageHandler, }; -})(Base)); +})(Base); export default BaseContainer; diff --git a/bigbluebutton-html5/imports/startup/client/routes.js b/bigbluebutton-html5/imports/startup/client/routes.js deleted file mode 100755 index 26f606b4a095c0ac2e3e637c8db70bd66d008802..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/imports/startup/client/routes.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { Router, Route, Redirect, IndexRoute, useRouterHistory } from 'react-router'; -import { createHistory } from 'history'; -import PollContainer from '/imports/ui/components/poll/container'; -import LoadingScreen from '/imports/ui/components/loading-screen/component'; -import ChatContainer from '/imports/ui/components/chat/container'; -import UserListContainer from '/imports/ui/components/user-list/container'; -import { joinRouteHandler, logoutRouteHandler, authenticatedRouteHandler } from './auth'; -import Base from './base'; - -const browserHistory = useRouterHistory(createHistory)({ - basename: Meteor.settings.public.app.basename, -}); - -const disconnect = () => { - Meteor.disconnect(); -}; - -const renderRoutes = () => ( - <Router history={browserHistory} > - <Route path="/logout" onEnter={logoutRouteHandler} /> - <Route - path="/join" - component={LoadingScreen} - onEnter={joinRouteHandler} - /> - <Route path="/" component={Base} onEnter={authenticatedRouteHandler} > - <IndexRoute components={{}} /> - <Route name="users" path="users" components={{ userList: UserListContainer }} /> - - <Route - name="poll" - path="users/poll" - components={{ - userList: UserListContainer, - poll: PollContainer, - }} - /> - - <Route - name="chat" - path="users/chat/:chatID" - components={{ - userList: UserListContainer, - chat: ChatContainer, - }} - /> - <Redirect from="users/chat" to="/users/chat/public" /> - </Route> - <Route - name="meeting-ended" - path="/ended/:endedCode" - component={Base} - onEnter={disconnect} - onLeave={logoutRouteHandler} - /> - <Route - name="error" - path="/error/:errorCode" - component={Base} - onEnter={disconnect} - /> - <Redirect from="*" to="/error/404" /> - </Router> -); - -export default renderRoutes; diff --git a/bigbluebutton-html5/imports/startup/server/redis.js b/bigbluebutton-html5/imports/startup/server/redis.js index 768402d784b1963a8e939ae64762b3c9e397ebce..432f34ad61fab2ef3205ef3ef67c6b34e74e552d 100644 --- a/bigbluebutton-html5/imports/startup/server/redis.js +++ b/bigbluebutton-html5/imports/startup/server/redis.js @@ -159,6 +159,9 @@ class RedisPubSub { if (ignoredMessages.includes(channel) || ignoredMessages.includes(eventName)) { + if (eventName === 'CheckAlivePongSysMsg') { + return; + } this.debug(`${eventName} skipped`); return; } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index fcfe49ef69251ecabdba7549d878982d64f1160f..0922c5984f01a1280edb7e8727c5d005ffa5b997 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; +import { Session } from 'meteor/session'; import getFromUserSettings from '/imports/ui/services/users-settings'; import ActionsBar from './component'; import Service from './service'; @@ -9,9 +10,18 @@ import { withRouter } from 'react-router'; const ActionsBarContainer = props => <ActionsBar {...props} />; -export default withRouter(withTracker(({ location, router }) => { - const togglePollMenu = () => (location.pathname.includes('poll') - ? router.push('/') : router.push('/users/poll')); +export default withRouter(withTracker(({ }) => { + const togglePollMenu = () => { + if (Session.equals('isChatOpen', true)) Session.set('isChatOpen', false); + + if (Session.equals('isPollOpen', false)) { + Session.set('isUserListOpen', true); + return Session.set('isPollOpen', true); + } + + Session.set('isUserListOpen', true); + return Session.set('isPollOpen', false); + }; return { isUserPresenter: Service.isUserPresenter(), diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index e8b2a3177c7ff7da4d17fdd60bda8c92130d6423..4123616f4ab7f2c0d4436a08dae349decbac4fab 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -13,6 +13,9 @@ import NotificationsBarContainer from '../notifications-bar/container'; import AudioContainer from '../audio/container'; import ChatAlertContainer from '../chat/alert/container'; import { styles } from './styles'; +import UserListContainer from '../user-list/container'; +import ChatContainer from '../chat/container'; +import PollContainer from '/imports/ui/components/poll/container'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; const USERLIST_COMPACT_WIDTH = 50; @@ -43,8 +46,9 @@ const propTypes = { media: PropTypes.element, actionsbar: PropTypes.element, closedCaption: PropTypes.element, - userList: PropTypes.element, - chat: PropTypes.element, + userListIsOpen: PropTypes.bool.isRequired, + chatIsOpen: PropTypes.bool.isRequired, + pollIsOpen: PropTypes.bool.isRequired, locale: PropTypes.string, intl: intlShape.isRequired, }; @@ -56,8 +60,6 @@ const defaultProps = { media: null, actionsbar: null, closedCaption: null, - userList: null, - chat: null, locale: 'en', }; @@ -106,13 +108,13 @@ class App extends Component { } renderPoll() { - const { poll } = this.props; + const { pollIsOpen } = this.props; - if (!poll) return null; + if (!pollIsOpen) return null; return ( <div className={styles.poll}> - {poll} + <PollContainer /> </div> ); } @@ -154,17 +156,19 @@ class App extends Component { } renderUserList() { - const { intl, chatIsOpen } = this.props; - let { userList } = this.props; + const { + intl, chatIsOpen, userListIsOpen, + } = this.props; + const { compactUserList } = this.state; - if (!userList) return null; + if (!userListIsOpen) return null; const userListStyle = {}; userListStyle[styles.compact] = compactUserList; - userList = React.cloneElement(userList, { - compact: compactUserList, - }); + // userList = React.cloneElement(userList, { + // compact: compactUserList, // TODO 4767 + // }); return ( <div @@ -172,13 +176,13 @@ class App extends Component { aria-label={intl.formatMessage(intlMessages.userListLabel)} aria-hidden={chatIsOpen} > - {userList} + <UserListContainer /> </div> ); } renderUserListResizable() { - const { userList } = this.props; + const { userListIsOpen } = this.props; // Variables for resizing user-list. const USERLIST_MIN_WIDTH_PX = 150; @@ -188,7 +192,7 @@ class App extends Component { // decide whether using pixel or percentage unit as a default width for userList const USERLIST_DEFAULT_WIDTH = (window.innerWidth * (USERLIST_DEFAULT_WIDTH_RELATIVE / 100.0)) < USERLIST_MAX_WIDTH_PX ? `${USERLIST_DEFAULT_WIDTH_RELATIVE}%` : USERLIST_MAX_WIDTH_PX; - if (!userList) return null; + if (!userListIsOpen) return null; const resizableEnableOptions = { top: false, @@ -222,29 +226,29 @@ class App extends Component { } renderChat() { - const { chat, intl } = this.props; + const { intl, chatIsOpen } = this.props; - if (!chat) return null; + if (!chatIsOpen) return null; return ( <section className={styles.chat} aria-label={intl.formatMessage(intlMessages.chatLabel)} > - {chat} + <ChatContainer /> </section> ); } renderChatResizable() { - const { chat } = this.props; + const { chatIsOpen } = this.props; // Variables for resizing chat. const CHAT_MIN_WIDTH = '10%'; const CHAT_MAX_WIDTH = '25%'; const CHAT_DEFAULT_WIDTH = '15%'; - if (!chat) return null; + if (!chatIsOpen) return null; const resizableEnableOptions = { top: false, @@ -273,7 +277,7 @@ class App extends Component { renderMedia() { const { - media, intl, chatIsOpen, userlistIsOpen, + media, intl, chatIsOpen, userListIsOpen, } = this.props; if (!media) return null; @@ -282,7 +286,7 @@ class App extends Component { <section className={styles.media} aria-label={intl.formatMessage(intlMessages.mediaLabel)} - aria-hidden={userlistIsOpen || chatIsOpen} + aria-hidden={userListIsOpen || chatIsOpen} > {media} {this.renderClosedCaption()} @@ -292,7 +296,7 @@ class App extends Component { renderActionsBar() { const { - actionsbar, intl, userlistIsOpen, chatIsOpen, + actionsbar, intl, userListIsOpen, chatIsOpen, } = this.props; if (!actionsbar) return null; @@ -301,7 +305,7 @@ class App extends Component { <section className={styles.actionsbar} aria-label={intl.formatMessage(intlMessages.actionsBarLabel)} - aria-hidden={userlistIsOpen || chatIsOpen} + aria-hidden={userListIsOpen || chatIsOpen} > {actionsbar} </section> @@ -310,8 +314,9 @@ class App extends Component { render() { const { - params, userlistIsOpen, customStyle, customStyleUrl, + params, userListIsOpen, customStyle, customStyleUrl, } = this.props; + const { enableResize } = this.state; return ( @@ -324,7 +329,7 @@ class App extends Component { {this.renderActionsBar()} </div> {enableResize ? this.renderUserListResizable() : this.renderUserList()} - {userlistIsOpen && enableResize ? <div className={styles.userlistPad} /> : null} + {userListIsOpen && enableResize ? <div className={styles.userlistPad} /> : null} {enableResize ? this.renderChatResizable() : this.renderChat()} {this.renderPoll()} {this.renderSidebar()} @@ -333,7 +338,7 @@ class App extends Component { <ModalContainer /> <AudioContainer /> <ToastContainer /> - <ChatAlertContainer currentChatID={params.chatID} /> + <ChatAlertContainer currentChatID={params} /> { customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null } { customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null } </main> diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index 98cb025c1ef5d83b60b5b06ab044a97d382830a2..63de9a39074c4fd43605b194b6840162d69e85c4 100755 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -1,6 +1,5 @@ -import React, { cloneElement } from 'react'; +import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import { withRouter } from 'react-router'; import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; @@ -29,7 +28,6 @@ const propTypes = { navbar: PropTypes.node, actionsbar: PropTypes.node, media: PropTypes.node, - location: PropTypes.shape({}).isRequired, }; const defaultProps = { @@ -45,8 +43,12 @@ const intlMessages = defineMessages({ }, }); +const endMeeting = (code) => { + Session.set('codeError', code); + Session.set('isMeetingEnded', true); +}; + const AppContainer = (props) => { - // inject location on the navbar container const { navbar, actionsbar, @@ -54,11 +56,9 @@ const AppContainer = (props) => { ...otherProps } = props; - const navbarWithLocation = cloneElement(navbar, { location: props.location }); - return ( <App - navbar={navbarWithLocation} + navbar={navbar} actionsbar={actionsbar} media={media} {...otherProps} @@ -67,7 +67,7 @@ const AppContainer = (props) => { }; -export default withRouter(injectIntl(withModalMounter(withTracker(({ router, intl, baseControls }) => { +export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => { const currentUser = Users.findOne({ userId: Auth.userID }); const isMeetingBreakout = meetingIsBreakout(); @@ -83,7 +83,7 @@ export default withRouter(injectIntl(withModalMounter(withTracker(({ router, int const hasNewConnection = 'connectionId' in fields && (fields.connectionId !== Meteor.connection._lastSessionId); if (fields.ejected || hasNewConnection) { - router.push(`/ended/${403}`); + endMeeting('403'); } }, }); @@ -94,7 +94,7 @@ export default withRouter(injectIntl(withModalMounter(withTracker(({ router, int if (isMeetingBreakout) { Auth.clearCredentials().then(window.close); } else { - router.push(`/ended/${410}`); + endMeeting('410'); } }, }); @@ -109,12 +109,13 @@ export default withRouter(injectIntl(withModalMounter(withTracker(({ router, int return { closedCaption: getCaptionsStatus() ? <ClosedCaptionsContainer /> : null, fontSize: getFontSize(), - userlistIsOpen: window.location.pathname.includes('users'), - chatIsOpen: window.location.pathname.includes('chat'), + userListIsOpen: Boolean(Session.get('isUserListOpen')), + chatIsOpen: Boolean(Session.get('isChatOpen') && Session.get('isUserListOpen')), + pollIsOpen: Boolean(Session.get('isPollOpen') && Session.get('isUserListOpen')), customStyle: getFromUserSettings('customStyle', false), customStyleUrl: getFromUserSettings('customStyleUrl', false), }; -})(AppContainer)))); +})(AppContainer))); AppContainer.defaultProps = defaultProps; AppContainer.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx index b84d4ed0c471afc4fa527e700dfbcf7b2962f543..923f1b97d5970f580c99747fc86cad3cc57e089f 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; +import { Session } from 'meteor/session'; import _ from 'lodash'; import UnreadMessages from '/imports/ui/services/unread-messages'; import ChatAudioAlert from './audio-alert/component'; @@ -113,7 +114,6 @@ class ChatAlert extends Component { disableNotify, openChats, intl, - currentChatID, } = this.props; if (disableNotify) return; @@ -121,7 +121,7 @@ class ChatAlert extends Component { const hasUnread = ({ unreadCounter }) => unreadCounter > 0; const isNotNotified = ({ id, unreadCounter }) => unreadCounter !== this.state.notified[id]; const isPrivate = ({ id }) => id !== PUBLIC_KEY; - const thisChatClosed = ({ id }) => id !== currentChatID; + const thisChatClosed = ({ id }) => !Session.equals('idChatOpen', id); const chatsNotify = openChats .filter(hasUnread) @@ -158,7 +158,7 @@ class ChatAlert extends Component { const limitingMessages = flatMessages; return (<ChatPushAlert - key={id} + key={_.uniqueId('id-')} chatId={id} content={limitingMessages} message={<span >{message}</span>} @@ -184,7 +184,6 @@ class ChatAlert extends Component { publicUserId, intl, disableNotify, - currentChatID, } = this.props; const publicUnread = UnreadMessages.getUnreadMessages(publicUserId); @@ -192,7 +191,7 @@ class ChatAlert extends Component { if (disableNotify) return; if (!Service.hasUnreadMessages(publicUserId)) return; - if (currentChatID === PUBLIC_KEY) return; + if (Session.equals('idChatOpen', PUBLIC_KEY)) return; const checkIfBeenNotified = ({ sender, time }) => time > (this.state.publicNotified[sender.id] || 0); diff --git a/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx index 19a0ddc66cee0dd7181a2969b0ecd1bfb2a01d2d..6cae1188ed8aaf5be5db05cfdf538b5e58ae679a 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx @@ -2,8 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import injectNotify from '/imports/ui/components/toast/inject-notify/component'; -import { Link } from 'react-router'; -import { styles } from '../../styles.scss'; +import { Session } from 'meteor/session'; const ALERT_INTERVAL = 2000; // 2 seconds const ALERT_LIFETIME = 4000; // 4 seconds @@ -11,14 +10,26 @@ const ALERT_LIFETIME = 4000; // 4 seconds const propTypes = { notify: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired, + chatId: PropTypes.string.isRequired, + message: PropTypes.node.isRequired, + content: PropTypes.node.isRequired, }; class ChatPushAlert extends React.Component { static link(message, chatId) { return ( - <Link className={styles.link} to={`/users/chat/${chatId}`}> - {message} - </Link> + <div + role="button" + aria-label={message} + tabIndex={0} + onClick={() => { + Session.set('isUserListOpen', true); + Session.set('isChatOpen', true); + Session.set('idChatOpen', chatId); + }} + > + { message } + </div> ); } diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index 18fb18fb7b0f31655a43cc626bf5088aeaae0252..9390c88e2a393823aa41c62e13195b712b768ab4 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; 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 { styles } from './styles'; import MessageForm from './message-form/component'; import MessageList from './message-list/component'; @@ -54,33 +54,34 @@ const Chat = (props) => { data-test="chatTitle" className={styles.title} > - <Link - to="/users" - role="button" + <Button + onClick={() => { + Session.set('isChatOpen', false); + }} aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })} accessKey={HIDE_CHAT_AK} - > - <Icon iconName="left_arrow" /> {title} - </Link> + label={title} + icon="left_arrow" + className={styles.hideBtn} + /> </div> { chatID !== 'public' ? - <Link - to="/users" - role="button" - tabIndex={-1} - > - <Button - className={styles.closeBtn} - icon="close" - size="md" - hideLabel - onClick={() => actions.handleClosePrivateChat(chatID)} - aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} - label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} - accessKey={CLOSE_CHAT_AK} - /> - </Link> : + <Button + className={styles.closeBtn} + icon="close" + size="md" + hideLabel + onClick={() => { + actions.handleClosePrivateChat(chatID); + Session.set('isChatOpen', false); + Session.set('idChatOpen', ''); + }} + aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} + label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} + accessKey={CLOSE_CHAT_AK} + /> + : <ChatDropdown /> } </header> diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index f4174e8266ef374b4abd2ea8f6e28a1741cea1fa..be1314c06f583ccafc5ec8017395f2229d37b751 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import { withTracker } from 'meteor/react-meteor-data'; +import { Session } from 'meteor/session'; import Auth from '/imports/ui/services/auth'; import Chat from './component'; import ChatService from './service'; @@ -31,7 +32,7 @@ const intlMessages = defineMessages({ class ChatContainer extends Component { componentDidMount() { // in case of reopening a chat, need to make sure it's removed from closed list - ChatService.removeFromClosedChatsSession(this.props.chatID); + ChatService.removeFromClosedChatsSession(); } render() { return ( @@ -42,9 +43,8 @@ class ChatContainer extends Component { } } -export default injectIntl(withTracker(({ params, intl }) => { - const chatID = params.chatID || PUBLIC_CHAT_KEY; - +export default injectIntl(withTracker(({ intl }) => { + const chatID = Session.get('idChatOpen') || PUBLIC_CHAT_KEY; let messages = []; let isChatLocked = ChatService.isChatLocked(chatID); let title = intl.formatMessage(intlMessages.titlePublic); @@ -109,7 +109,7 @@ export default injectIntl(withTracker(({ params, intl }) => { messages = messagesFormated.sort((a, b) => (a.time - b.time)); } else { - messages = ChatService.getPrivateGroupMessages(chatID); + messages = ChatService.getPrivateGroupMessages(); const user = ChatService.getUser(chatID); chatName = user.name; @@ -155,7 +155,7 @@ export default injectIntl(withTracker(({ params, intl }) => { const lastReadMessageTime = ChatService.lastReadMessageTime(chatID); return { - chatID, + chatID: Session.get('idChatOpen'), chatName, title, messages, @@ -170,13 +170,13 @@ export default injectIntl(withTracker(({ params, intl }) => { handleClosePrivateChat: chatId => ChatService.closePrivateChat(chatId), handleSendMessage: (message) => { - ChatService.updateScrollPosition(chatID, null); - return ChatService.sendGroupMessage(chatID, message); + ChatService.updateScrollPosition(null); + return ChatService.sendGroupMessage(message); }, - handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position), + handleScrollUpdate: position => ChatService.updateScrollPosition(position), - handleReadMessage: timestamp => ChatService.updateUnreadMessage(chatID, timestamp), + handleReadMessage: timestamp => ChatService.updateUnreadMessage(timestamp), }, }; })(ChatContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx index a4c569b573fc742cd3a5e8243a10075317e30fb8..78bcc8081ed27b1ccc14ae0dcdc0358bc505bd76 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -129,7 +129,6 @@ class MessageForm extends Component { render() { const { intl, chatTitle, chatName, disabled, - minMessageLength, maxMessageLength, } = this.props; const { hasErrors, error } = this.state; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx index 254d2d9ebd0849ed61b2f29ff4842214e7b18bfa..04dff92add6f08f587cc18068197663acbf3fef2 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -39,7 +39,7 @@ class MessageList extends Component { const { scrollArea } = this; this.setState({ - scrollArea: this.scrollArea + scrollArea: this.scrollArea, }); this.scrollTo(this.props.scrollPosition); @@ -60,7 +60,7 @@ class MessageList extends Component { partnerIsLoggedOut, } = this.props; - if(!this.state.scrollArea && nextState.scrollArea) return true; + if (!this.state.scrollArea && nextState.scrollArea) return true; const switchingCorrespondent = chatId !== nextProps.chatId; const hasNewUnreadMessages = hasUnreadMessages !== nextProps.hasUnreadMessages; @@ -164,14 +164,16 @@ class MessageList extends Component { } render() { - const { messages, intl } = this.props; + const { + messages, intl, id, lastReadMessageTime, handleReadMessage, + } = this.props; const isEmpty = messages.length === 0; return ( <div className={styles.messageListWrapper}> <div role="log" ref={(ref) => { if (ref != null) { this.scrollArea = ref; } }} - id={this.props.id} + id={id} className={styles.messageList} aria-live="polite" aria-atomic="false" @@ -180,14 +182,14 @@ class MessageList extends Component { > {messages.map(message => ( <MessageListItem - handleReadMessage={this.props.handleReadMessage} + handleReadMessage={handleReadMessage} className={styles.messageListItem} key={message.id} messages={message.content} user={message.sender} time={message.time} - chatAreaId={this.props.id} - lastReadMessageTime={this.props.lastReadMessageTime} + chatAreaId={id} + lastReadMessageTime={lastReadMessageTime} scrollArea={this.state.scrollArea} /> ))} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index 8956bbcba7360e98bcc5e04fa9c732d8bb2bb109..6cae52b42fce33aa9cb8980406ebc74302447415 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -17,6 +17,7 @@ .systemMessage { padding: var(--line-height-computed) 0; + padding-top: 0px; border-bottom: 1px solid var(--color-gray-lighter); .item:not(&) + & { diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 62e6aaae189e58094f074982e83ebf34419750df..f93eee9dc6c895a0f32adab86e4611d477d8c2f6 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -88,7 +88,8 @@ const getPublicGroupMessages = () => { return publicGroupMessages; }; -const getPrivateGroupMessages = (chatID) => { +const getPrivateGroupMessages = () => { + const chatID = Session.get('idChatOpen'); const sender = getUser(Auth.userID); const privateChat = GroupChat.findOne({ @@ -143,7 +144,8 @@ const lastReadMessageTime = (receiverID) => { return UnreadMessages.get(chatType); }; -const sendGroupMessage = (chatID, message) => { +const sendGroupMessage = (message) => { + const chatID = Session.get('idChatOpen'); const isPublicChat = chatID === PUBLIC_CHAT_ID; let chatId = PUBLIC_GROUP_CHAT_ID; @@ -188,20 +190,22 @@ const getScrollPosition = (receiverID) => { }; const updateScrollPosition = - (receiverID, position) => ScrollCollection.upsert( - { receiver: receiverID }, + position => ScrollCollection.upsert( + { receiver: Session.get('idChatOpen') }, { $set: { position } }, ); -const updateUnreadMessage = (receiverID, timestamp) => { - const isPublic = receiverID === PUBLIC_CHAT_ID; - const chatType = isPublic ? PUBLIC_GROUP_CHAT_ID : receiverID; +const updateUnreadMessage = (timestamp) => { + const chatID = Session.get('idChatOpen'); + const isPublic = chatID === PUBLIC_CHAT_ID; + const chatType = isPublic ? PUBLIC_GROUP_CHAT_ID : chatID; return UnreadMessages.update(chatType, timestamp); }; const clearPublicChatHistory = () => (makeCall('clearPublicChatHistory')); -const closePrivateChat = (chatID) => { +const closePrivateChat = () => { + const chatID = Session.get('idChatOpen'); const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || []; if (_.indexOf(currentClosedChats, chatID) < 0) { @@ -212,7 +216,8 @@ const closePrivateChat = (chatID) => { }; // if this private chat has been added to the list of closed ones, remove it -const removeFromClosedChatsSession = (chatID) => { +const removeFromClosedChatsSession = () => { + const chatID = Session.get('idChatOpen'); const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY); if (_.indexOf(currentClosedChats, chatID) > -1) { Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, chatID)); diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/styles.scss index 49fcb9e21855fd8421d8522b4ac3b4a103d90a10..c8754fe53080045e030e1847873a170856a9e5d3 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/styles.scss @@ -92,6 +92,23 @@ } } +.hideBtn { + position: relative; + background-color: white; + display: block; + margin: 4px; + margin-bottom: 2px; + padding-left: 0px; + + > i { + color: black; + } + + &:hover { + background-color: #fff; + } +} + .link { text-decoration: none; background-color: inherit; diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx index 95496e1bad2d39b88a75fd830c92a45cb8405884..abd09fe45840c6cd4ca50029ab3210699a104481 100755 --- a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx @@ -8,10 +8,9 @@ import ListSeparator from './separator/component'; import ListTitle from './title/component'; const propTypes = { - /* We should recheck this proptype, sometimes we need to create an container and send to dropdown, - but with this */ - // proptype, is not possible. - children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => { + /* We should recheck this proptype, sometimes we need to create an container and send to + dropdown, but with this proptype, is not possible. */ + children: PropTypes.arrayOf((propValue, key, componentName, propFullName) => { if (propValue[key].type !== ListItem && propValue[key].type !== ListSeparator && propValue[key].type !== ListTitle) { diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx index d9cd49a84afa00bc0e2f586c1be33561b6f73181..9770e2851b0e19fe9281eb37153e2fcba1e05e4e 100644 --- a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; -import { withRouter } from 'react-router'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -37,7 +36,9 @@ const defaultProps = { class ErrorScreen extends React.PureComponent { render() { const { - intl, code, children, router, + intl, + code, + children, } = this.props; let formatedMessage = intl.formatMessage(intlMessages[defaultProps.code]); @@ -60,7 +61,7 @@ class ErrorScreen extends React.PureComponent { <div className={styles.content}> <Button size="sm" - onClick={() => router.push('/logout/')} + onClick={() => Session.set('isMeetingEnded', true)} label={intl.formatMessage(intlMessages.leave)} /> </div> @@ -69,7 +70,7 @@ class ErrorScreen extends React.PureComponent { } } -export default withRouter(injectIntl(ErrorScreen)); +export default injectIntl(ErrorScreen); ErrorScreen.propTypes = propTypes; ErrorScreen.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx index 1f46906c7b848f40aeda64f198e5682425246666..e6d34c39f8af75c9444aec626035a714ac43cc94 100755 --- a/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx @@ -7,6 +7,7 @@ import { styles } from './styles'; const propTypes = { handleEndMeeting: PropTypes.func.isRequired, + confirmLeaving: PropTypes.func.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, @@ -55,15 +56,14 @@ const intlMessages = defineMessages({ const LeaveConfirmation = ({ intl, - router, handleEndMeeting, showEndMeeting, - showFeedback, + confirmLeaving, }) => ( <Modal title={intl.formatMessage(intlMessages.title)} confirm={{ - callback: showFeedback, + callback: confirmLeaving, label: intl.formatMessage(intlMessages.confirmLabel), description: intl.formatMessage(intlMessages.confirmDesc), }} @@ -79,7 +79,7 @@ const LeaveConfirmation = ({ className={styles.endMeeting} label={intl.formatMessage(intlMessages.endMeetingLabel)} onClick={handleEndMeeting} - aria-describedby={'modalEndMeetingDesc'} + aria-describedby="modalEndMeetingDesc" /> : null } <div id="modalEndMeetingDesc" hidden>{intl.formatMessage(intlMessages.endMeetingDesc)}</div> diff --git a/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx b/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx index 3bb70c02b42cdb4961c0ddd890049cb51eaf39eb..0e215ec5e87985aacf5ac144079039268453bb37 100755 --- a/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; import { withTracker } from 'meteor/react-meteor-data'; -import { withRouter } from 'react-router'; -import getFromUserSettings from '/imports/ui/services/users-settings'; +import { Session } from 'meteor/session'; + import LogoutConfirmation from './component'; import { isModerator, @@ -13,14 +13,15 @@ const LogoutConfirmationContainer = props => ( <LogoutConfirmation {...props} /> ); -export default withRouter(withTracker(({ router }) => { - const APP_CONFIG = Meteor.settings.public.app; - const shouldShowFeedback = !meetingIsBreakout() && getFromUserSettings('askForFeedbackOnLogout', APP_CONFIG.askForFeedbackOnLogout); - const showFeedback = shouldShowFeedback ? () => router.push('/ended/430') : () => router.push('/logout'); +export default withTracker(() => { + const confirmLeaving = () => { + Session.set('isMeetingEnded', true); + Session.set('codeError', '430'); + }; return { showEndMeeting: !meetingIsBreakout() && isModerator(), handleEndMeeting: endMeeting, - showFeedback, + confirmLeaving, }; -})(LogoutConfirmationContainer)); +})(LogoutConfirmationContainer); diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx index 60359a8904b1750c27b817b5d3667b1e50173f1b..f5e93483f9cd1da6400492cc0338e2e409ca55b9 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import { withRouter } from 'react-router'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Auth from '/imports/ui/services/auth'; import Button from '/imports/ui/components/button/component'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import { logoutRouteHandler } from '/imports/startup/client/auth'; import Rating from './rating/component'; import { styles } from './styles'; @@ -64,9 +64,6 @@ const propTypes = { formatMessage: PropTypes.func.isRequired, }).isRequired, code: PropTypes.string.isRequired, - router: PropTypes.shape({ - push: PropTypes.func.isRequired, - }).isRequired, }; class MeetingEnded extends React.PureComponent { @@ -96,12 +93,8 @@ class MeetingEnded extends React.PureComponent { selected, } = this.state; - const { - router, - } = this.props; - if (selected <= 0) { - router.push('/logout'); + logoutRouteHandler(); return; } @@ -122,7 +115,9 @@ class MeetingEnded extends React.PureComponent { }; fetch(url, options) - .finally(() => router.push('/logout')); + .finally(() => { + logoutRouteHandler(); + }); } render() { @@ -173,4 +168,4 @@ class MeetingEnded extends React.PureComponent { MeetingEnded.propTypes = propTypes; -export default injectIntl(withRouter(MeetingEnded)); +export default injectIntl(MeetingEnded); diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx index 0c009bcb50d2fbccaeaa798744ea17b90469bb4b..45318056af4e6a51b16b9eee11f9a9543bbff365 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -79,22 +79,19 @@ class NavBar extends Component { this.handleToggleUserList = this.handleToggleUserList.bind(this); } - handleToggleUserList() { - this.props.toggleUserList(); - } - componentDidUpdate(oldProps) { const { breakouts, getBreakoutJoinURL, isBreakoutRoom, + mountModal, } = this.props; const hadBreakouts = oldProps.breakouts.length; const hasBreakouts = breakouts.length; if (!hasBreakouts && hadBreakouts) { - closeBreakoutJoinConfirmation(this.props.mountModal); + closeBreakoutJoinConfirmation(mountModal); } breakouts.forEach((breakout) => { @@ -112,6 +109,10 @@ class NavBar extends Component { } } + handleToggleUserList() { + this.props.toggleUserList(); + } + inviteUserToBreakout(breakout) { const { mountModal, @@ -182,6 +183,9 @@ class NavBar extends Component { toggleBtnClasses[styles.btn] = true; toggleBtnClasses[styles.btnWithNotificationDot] = hasUnreadMessages; + let ariaLabel = intl.formatMessage(intlMessages.toggleUserListAria); + ariaLabel += hasUnreadMessages ? (` ${intl.formatMessage(intlMessages.newMessages)}`) : ''; + return ( <div className={styles.navbar}> <div className={styles.left}> @@ -192,17 +196,12 @@ class NavBar extends Component { circle hideLabel label={intl.formatMessage(intlMessages.toggleUserListLabel)} - aria-label={intl.formatMessage(intlMessages.toggleUserListAria)} + aria-label={ariaLabel} icon="user" className={cx(toggleBtnClasses)} aria-expanded={isExpanded} - aria-describedby="newMessage" accessKey={TOGGLE_USERLIST_AK} /> - <div - id="newMessage" - aria-label={hasUnreadMessages ? intl.formatMessage(intlMessages.newMessages) : null} - /> </div> <div className={styles.center}> {this.renderPresentationTitle()} diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx index 56796f79ebb1eacb443a0d7ddf618145eea9d203..25a03f51595c1c18d41ec314c0cc407c2edb22ed 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Meteor } from 'meteor/meteor'; import { withTracker } from 'meteor/react-meteor-data'; -import { withRouter } from 'react-router'; +import { Session } from 'meteor/session'; import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; @@ -20,7 +20,7 @@ const NavBarContainer = ({ children, ...props }) => ( </NavBar> ); -export default withRouter(withTracker(({ location, router }) => { +export default withTracker(() => { const CLIENT_TITLE = getFromUserSettings('clientTitle', PUBLIC_CONFIG.app.clientTitle); let meetingTitle; @@ -54,7 +54,7 @@ export default withRouter(withTracker(({ location, router }) => { const breakouts = Service.getBreakouts(); const currentUserId = Auth.userID; - const isExpanded = location.pathname.indexOf('/users') !== -1; + const isExpanded = Session.get('isUserListOpen'); return { isExpanded, @@ -66,11 +66,7 @@ export default withRouter(withTracker(({ location, router }) => { isBreakoutRoom: meetingIsBreakout(), beingRecorded: meetingRecorded, toggleUserList: () => { - if (location.pathname.indexOf('/users') !== -1) { - router.push('/'); - } else { - router.push('/users'); - } + Session.set('isUserListOpen', !isExpanded); }, }; -})(NavBarContainer)); +})(NavBarContainer); diff --git a/bigbluebutton-html5/imports/ui/components/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/component.jsx index 3d8455f04afd5ca910d96b391bb417494e0b5105..3f53f03e61b55221480f5b87ccf1283297a21fb8 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/component.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; -import { Link } from 'react-router'; +import { Session } from 'meteor/session'; import Button from '/imports/ui/components/button/component'; import Icon from '/imports/ui/components/icon/component'; import LiveResultContainer from './live-result/container'; @@ -37,14 +37,6 @@ const intlMessages = defineMessages({ id: 'app.poll.activePollInstruction', description: 'instructions displayed when a poll is active', }, - publishLabel: { - id: 'app.poll.publishLabel', - description: 'label for the publish button', - }, - backLabel: { - id: 'app.poll.backLabel', - description: 'label for the return to poll options button', - }, customPlaceholder: { id: 'app.poll.customPlaceholder', description: 'custom poll input field placeholder text', @@ -95,6 +87,13 @@ class Poll extends Component { this.nonPresenterRedirect = this.nonPresenterRedirect.bind(this); this.getInputFields = this.getInputFields.bind(this); this.handleInputChange = this.handleInputChange.bind(this); + this.back = this.back.bind(this); + } + + componentWillUnmount() { + const { stopPoll } = this.props; + + stopPoll(); } componentWillMount() { @@ -131,8 +130,11 @@ class Poll extends Component { } nonPresenterRedirect() { - const { currentUser, router } = this.props; - if (!currentUser.presenter) return router.push('/users'); + const { currentUser } = this.props; + if (!currentUser.presenter) { + Session.set('isUserListOpen', true); + return Session.set('isPollOpen', false); + } } toggleCustomFields() { @@ -192,9 +194,20 @@ class Poll extends Component { ); } + back() { + const { stopPoll } = this.props; + + stopPoll(); + this.inputEditor = []; + this.setState({ + isPolling: false, + customPollValues: this.inputEditor, + }, document.activeElement.blur()); + } + renderActivePollOptions() { const { - intl, router, publishPoll, stopPoll, + intl, publishPoll, stopPoll, } = this.props; return ( @@ -202,30 +215,7 @@ class Poll extends Component { <div className={styles.instructions}> {intl.formatMessage(intlMessages.activePollInstruction)} </div> - <LiveResultContainer /> - <Button - onClick={() => { - publishPoll(); - stopPoll(); - router.push('/users'); - }} - label={intl.formatMessage(intlMessages.publishLabel)} - color="primary" - className={styles.btn} - /> - <Button - onClick={() => { - stopPoll(); - this.inputEditor = []; - this.setState({ - isPolling: false, - customPollValues: this.inputEditor, - }, document.activeElement.blur()); - }} - label={intl.formatMessage(intlMessages.backLabel)} - color="default" - className={styles.btn} - /> + <LiveResultContainer publishPoll={publishPoll} stopPoll={stopPoll} back={this.back} /> </div> ); } @@ -264,18 +254,35 @@ class Poll extends Component { return ( <div> <header className={styles.header}> - <Link - to="/users" - role="button" + <Button + tabIndex={0} + label={intl.formatMessage(intlMessages.pollPaneTitle)} + icon="left_arrow" aria-label={intl.formatMessage(intlMessages.hidePollDesc)} + className={styles.hideBtn} onClick={() => { if (this.state.isPolling) { stopPoll(); } + Session.set('isPollOpen', false); + Session.set('forcePollOpen', true); + Session.set('isUserListOpen', true); }} - > - <Icon iconName="left_arrow" />{intl.formatMessage(intlMessages.pollPaneTitle)} - </Link> + /> + + <Button + onClick={() => { + if (this.state.isPolling) { + stopPoll(); + } + Session.set('isPollOpen', false); + Session.set('forcePollOpen', false); + Session.set('isUserListOpen', true); + }} + className={styles.closeBtn} + label="X" + /> + </header> { this.state.isPolling ? this.renderActivePollOptions() : this.renderPollOptions() diff --git a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx index 6630148abb9b375e318033adc21e8314cb1a123f..828f0b45cd1f84dea7a22d8eeda768696faafc6e 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx @@ -2,6 +2,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { defineMessages, injectIntl } from 'react-intl'; +import { Session } from 'meteor/session'; +import Button from '/imports/ui/components/button/component'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -13,6 +15,14 @@ const intlMessages = defineMessages({ id: 'app.poll.liveResult.responsesTitle', description: 'heading label for poll responses', }, + publishLabel: { + id: 'app.poll.publishLabel', + description: 'label for the publish button', + }, + backLabel: { + id: 'app.poll.backLabel', + description: 'label for the return to poll options button', + }, }); class LiveResult extends Component { @@ -116,13 +126,35 @@ class LiveResult extends Component { } render() { - const { intl } = this.props; + const { + intl, publishPoll, stopPoll, back, + } = this.props; return ( <div> <div className={styles.stats}> {this.getPollStats()} </div> + <Button + onClick={() => { + publishPoll(); + stopPoll(); + Session.set('isUserListOpen', true); + Session.set('isPollOpen', false); + Session.set('forcePollOpen', false); + }} + label={intl.formatMessage(intlMessages.publishLabel)} + color="primary" + className={styles.btn} + /> + <Button + onClick={() => { + back(); + }} + label={intl.formatMessage(intlMessages.backLabel)} + color="default" + className={styles.btn} + /> <div className={styles.container}> <div className={styles.usersHeading}>{intl.formatMessage(intlMessages.usersTitle)}</div> <div className={styles.responseHeading}>{intl.formatMessage(intlMessages.responsesTitle)}</div> diff --git a/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss index 6dbdc796d25b76be4bd139f4287f61ad3774d851..ad7a8acf052df71820d85eafb4e42349e7711073 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss @@ -7,6 +7,12 @@ $stats-element-width: 17%; $sm-margin: 0.3125rem; $user-line-height: 1.75rem; +.btn { + width: 100%; + margin-top: var(--sm-padding-y); + margin-bottom: var(--sm-padding-y); +} + .main { display: flex; justify-content: space-between; diff --git a/bigbluebutton-html5/imports/ui/components/poll/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/styles.scss index 403dcfe849ee4f5de41ec4d67afc202245257254..b97daef807c1517f1a83b6c011cf5933d9ff6e37 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/poll/styles.scss @@ -4,14 +4,37 @@ $column-amount: 2; $poll-blue: #1A73D4; -.customBtn, -.btn { - width: 100%; +.closeBtn { + position: relative; + margin-left: 9.125rem; } +<<<<<<< HEAD .btn { margin-top: var(--sm-padding-y); margin-bottom: var(--sm-padding-y); +======= +.hideBtn { + position: relative; + background-color: white; + display: block; + margin: 4px; + margin-bottom: 2px; + padding-left: 0px; + + > i { + color: black; + font-size: 85%; + } + + &:hover { + background-color: #fff; + } + } + +.customBtn { + width: 100%; +>>>>>>> test-merge-anton } .customInputWrapper { @@ -19,8 +42,13 @@ $poll-blue: #1A73D4; > input { width: 100%; +<<<<<<< HEAD margin-top: .3rem; margin-bottom: .3rem; +======= + margin-top: var(--sm-padding-y); + margin-bottom: var(--sm-padding-y); +>>>>>>> test-merge-anton } > input:first-child { @@ -38,6 +66,7 @@ $poll-blue: #1A73D4; display: flex; flex-direction: row; align-items: center; + justify-content: space-between; margin-bottom: var(--md-padding-y); > a { diff --git a/bigbluebutton-html5/imports/ui/components/polling/component.jsx b/bigbluebutton-html5/imports/ui/components/polling/component.jsx index c5397dabd96f53973a5115d35409f9e5deeb6625..c55e6654f4cc55730f079e7c60cb0aac21a968dc 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/polling/component.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Button from '/imports/ui/components/button/component'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; @@ -18,53 +18,71 @@ const intlMessages = defineMessages({ }, }); -const Polling = ({ intl, poll, handleVote }) => { - this.alert = new Audio(`${Meteor.settings.public.app.basename}/resources/sounds/Poll.mp3`); - this.alert.play(); +class Polling extends Component { + constructor(props) { + super(props); - return ( - <div className={styles.pollingContainer} role="alert"> - <div className={styles.pollingTitle}> - {intl.formatMessage(intlMessages.pollingTitleLabel)} - </div> - <div className={styles.pollingAnswers}> - {poll.answers.map(pollAnswer => ( - <div - key={pollAnswer.id} - className={styles.pollButtonWrapper} - > - <Tooltip - key={pollAnswer.id} - title={pollAnswer.key} - > - <Button - className={styles.pollingButton} - color="primary" - size="md" - label={pollAnswer.key} - key={pollAnswer.key} - onClick={() => handleVote(poll.pollId, pollAnswer)} - aria-labelledby={`pollAnswerLabel${pollAnswer.key}`} - aria-describedby={`pollAnswerDesc${pollAnswer.key}`} - /> - </Tooltip> - <div - className={styles.hidden} - id={`pollAnswerLabel${pollAnswer.key}`} - > - {intl.formatMessage(intlMessages.pollAnswerLabel, { 0: pollAnswer.key })} - </div> - <div - className={styles.hidden} - id={`pollAnswerDesc${pollAnswer.key}`} - > - {intl.formatMessage(intlMessages.pollAnswerDesc, { 0: pollAnswer.key })} - </div> + this.play = this.play.bind(this); + } + + componentDidMount() { + this.play(); + } + + play() { + this.alert = new Audio(`${Meteor.settings.public.app.basename}/resources/sounds/Poll.mp3`); + this.alert.play(); + } + + render() { + const { intl, poll, handleVote } = this.props; + + return ( + <div className={styles.overlay}> + <div className={styles.pollingContainer} role="alert"> + <div className={styles.pollingTitle}> + {intl.formatMessage(intlMessages.pollingTitleLabel)} </div> - ))} - </div> - </div>); -}; + <div className={styles.pollingAnswers}> + {poll.answers.map(pollAnswer => ( + <div + key={pollAnswer.id} + className={styles.pollButtonWrapper} + > + <Tooltip + key={pollAnswer.id} + title={pollAnswer.key} + > + <Button + className={styles.pollingButton} + color="primary" + size="md" + label={pollAnswer.key} + key={pollAnswer.key} + onClick={() => handleVote(poll.pollId, pollAnswer)} + aria-labelledby={`pollAnswerLabel${pollAnswer.key}`} + aria-describedby={`pollAnswerDesc${pollAnswer.key}`} + /> + </Tooltip> + <div + className={styles.hidden} + id={`pollAnswerLabel${pollAnswer.key}`} + > + {intl.formatMessage(intlMessages.pollAnswerLabel, { 0: pollAnswer.key })} + </div> + <div + className={styles.hidden} + id={`pollAnswerDesc${pollAnswer.key}`} + > + {intl.formatMessage(intlMessages.pollAnswerDesc, { 0: pollAnswer.key })} + </div> + </div> + ))} + </div> + </div> + </div>); + } +} export default injectIntl(injectWbResizeEvent(Polling)); diff --git a/bigbluebutton-html5/imports/ui/components/polling/styles.scss b/bigbluebutton-html5/imports/ui/components/polling/styles.scss index b34ff0f27769230169aff35c82d19c073e241048..19c1f343f3fe11d3dc818a8d4af29391d8407e02 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/polling/styles.scss @@ -4,16 +4,24 @@ $poll-width: 18rem; $max-btn-width: 9em; $col-amount: 2; -// pollingAnswer position offsets -$xs-portrait-offset: 8.75em; -$xs-landscape-offset: 4.75em; -$s-portrait-offset: 11.75em; -$s-landscape-offset: 1.75em; +.overlay { + position: absolute; + height: 100vh; + width: 100vw; + z-index: 1015; + pointer-events: none; + + @media screen and (max-width: 479px), screen and (max-height: 479px) { + background-color: rgba(0, 0, 0, 0.349); + } +} .pollingContainer { + pointer-events:auto; width: $poll-width; position: absolute; - z-index: 2; + + z-index: 1016; border: 1px solid var(--color-off-white); border-radius: var(--border-radius); box-shadow: var(--color-gray-dark) 0px 0px var(--lg-padding-y); @@ -27,10 +35,20 @@ $s-landscape-offset: 1.75em; } .pollingTitle { - color: var(--color-text); + color: var(--color-white); white-space: nowrap; padding-bottom: var(--md-padding-y); padding-top: var(--md-padding-y); + + @media screen and (max-height: 479px), screen and (max-width: 479px) { + bottom: auto; + right: auto; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + } + } .pollingAnswers { @@ -45,32 +63,33 @@ $s-landscape-offset: 1.75em; grid-column: $col-amount; } - z-index: 1; + @media screen and (max-width: 479px) { + grid-template-columns: repeat(1, 1fr); - :global(.browser-safari) & { - @include mq($ip5-portrait) { - bottom: 8.75em; - } - @include mq($ip678-portrait) { - bottom: 4.75em; + > pollButtonWrapper:nth-child(odd) { + grid-column: 1; } - @include mq($ip5-landscape) { - bottom: 11.75em; - } - @include mq($ip678-landscape) { - bottom: 1.75em; + + > pollButtonWrapper:nth-child(even) { + grid-column: 1; } } + + z-index: 1; } .pollButtonWrapper { text-align: center; padding: var(--sm-padding-y); + width: 100%; } .pollingButton { width: 100%; max-width: $max-btn-width; + @media screen and (max-width: 479px) { + max-width: none; + } white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/bigbluebutton-html5/imports/ui/components/settings/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/component.jsx index bd499f6835bd8ecc81f384205120221619d8d55b..7f7e6a3f7dc8f8caed4f840aaa210f295387419f 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/component.jsx @@ -208,8 +208,6 @@ class Settings extends Component { render() { const { intl, - router, - location, mountModal, } = this.props; @@ -219,7 +217,7 @@ class Settings extends Component { confirm={{ callback: () => { this.updateSettings(this.state.current); - router.push(location.pathname); + // router.push(location.pathname); // TODO 4767 /* We need to use mountModal(null) here to prevent submenu state updates, * from re-opening the modal. */ diff --git a/bigbluebutton-html5/imports/ui/components/settings/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/container.jsx index 0663334319a4d3629b5a406b70e396d4975e0a6b..bcff47570f5c477079dce9f36f45cac450fabe35 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/container.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import { withRouter } from 'react-router'; import SettingsService from '/imports/ui/services/settings'; import Settings from './component'; @@ -15,7 +14,7 @@ const SettingsContainer = props => ( <Settings {...props} /> ); -export default withRouter(withTracker(() => ({ +export default withTracker(() => ({ audio: SettingsService.audio, dataSaving: SettingsService.dataSaving, application: SettingsService.application, @@ -25,4 +24,4 @@ export default withRouter(withTracker(() => ({ locales: getClosedCaptionLocales(), availableLocales: getAvailableLocales(), isModerator: getUserRoles() === 'MODERATOR', -}))(SettingsContainer)); +}))(SettingsContainer); 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 824c2c59b758a2c3082531babb61b29a58db8892..286bc1cfdb0c24d653dfc4b83fc97f0595946172 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 @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { withRouter, Link } from 'react-router'; import cx from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; @@ -23,10 +22,6 @@ const intlMessages = defineMessages({ }, }); -const CHAT_CONFIG = Meteor.settings.public.chat; -const PRIVATE_CHAT_PATH = CHAT_CONFIG.path_route; -const CLOSED_CHAT_PATH = 'users/'; - const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; const TOGGLE_CHAT_PUB_AK = SHORTCUTS_CONFIG.togglePublicChat.accesskey; @@ -57,25 +52,32 @@ const ChatListItem = (props) => { intl, tabIndex, isPublicChat, - location, } = props; - let linkPath = [PRIVATE_CHAT_PATH, chat.id].join(''); - linkPath = location.pathname.includes(linkPath) ? CLOSED_CHAT_PATH : linkPath; const isCurrentChat = chat.id === openChat; const linkClasses = {}; linkClasses[styles.active] = isCurrentChat; return ( - <Link - data-test="publicChatLink" - to={linkPath} - className={cx(styles.chatListItem, linkClasses)} + <div role="button" + className={cx(styles.chatListItem, linkClasses)} aria-expanded={isCurrentChat} tabIndex={tabIndex} accessKey={isPublicChat(chat) ? TOGGLE_CHAT_PUB_AK : null} + onClick={() => { + Session.set('isChatOpen', true); + Session.set('idChatOpen', chat.id); + + if (Session.equals('isPollOpen', true)) { + Session.set('isPollOpen', false); + Session.set('forcePollOpen', true); + } + }} + id="chat-toggle-button" + aria-label={isPublicChat(chat) ? intl.formatMessage(intlMessages.titlePublic) : chat.name} > + <div className={styles.chatListItemLink}> <div className={styles.chatIcon}> {chat.icon ? @@ -90,7 +92,8 @@ const ChatListItem = (props) => { <div className={styles.chatName}> {!compact ? <span className={styles.chatNameMain}> - {isPublicChat(chat) ? intl.formatMessage(intlMessages.titlePublic) : chat.name} + {isPublicChat(chat) ? + intl.formatMessage(intlMessages.titlePublic) : chat.name} </span> : null} </div> {(chat.unreadCounter > 0) ? @@ -99,11 +102,11 @@ const ChatListItem = (props) => { /> : null} </div> - </Link> + </div> ); }; ChatListItem.propTypes = propTypes; ChatListItem.defaultProps = defaultProps; -export default withRouter(injectIntl(ChatListItem)); +export default injectIntl(ChatListItem); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss index f106dbd5854de1fe6c29a7a4fc5617c5498ea691..84e607db44144cfdb830ba654f779cf1a655d136 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss @@ -7,6 +7,7 @@ padding: 0; text-decoration: none; color: var(--color-gray-dark); + background-color: var(--color-off-white); } .chatListItemLink { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index 1cee41caf0a7ac6642fbdd6f9c12e6338bac0310..789117627a72808428547193593fc45bc6e20000 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -1,11 +1,10 @@ import React, { Component } from 'react'; import { injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; -import { withRouter } from 'react-router'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import { styles } from './styles'; import CustomLogo from './custom-logo/component'; -import UserContent from './user-list-content/component'; +import UserContentContainer from './user-list-content/container'; const propTypes = { openChats: PropTypes.arrayOf(String).isRequired, @@ -40,10 +39,6 @@ const defaultProps = { }; class UserList extends Component { - constructor(props) { - super(props); - } - render() { const { intl, @@ -79,7 +74,7 @@ class UserList extends Component { && CustomLogoUrl ? <CustomLogo CustomLogoUrl={CustomLogoUrl} /> : null } - {<UserContent + {<UserContentContainer {...{ intl, openChats, @@ -113,4 +108,4 @@ class UserList extends Component { UserList.propTypes = propTypes; UserList.defaultProps = defaultProps; -export default withRouter(injectWbResizeEvent(injectIntl(UserList))); +export default injectWbResizeEvent(injectIntl(UserList)); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index b54412be33cc08a3d24abe26550e0583bf685222..9594b4dbdd2940a0e02f1236dd6b821c38fd981a 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -258,7 +258,7 @@ const getOpenChats = (chatID) => { const isVoiceOnlyUser = userId => userId.toString().startsWith('v_'); -const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => { +const getAvailableActions = (currentUser, user, isBreakoutRoom) => { const isDialInUser = isVoiceOnlyUser(user.id) || user.isPhoneUser; const hasAuthority = currentUser.isModerator || user.isCurrent; 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 19ac118b3939da6d5e507817de2dd0f5da04652f..dd8d2a60e34e95543ee61f3802390e995dfa5a93 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 @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { styles } from './styles'; import UserParticipants from './user-participants/component'; import UserMessages from './user-messages/component'; +import UserPolls from './user-polls/component'; const propTypes = { openChats: PropTypes.arrayOf(String).isRequired, @@ -35,7 +36,7 @@ const defaultProps = { meeting: {}, }; -class UserContent extends React.PureComponent { +class UserContent extends React.Component { render() { const { users, @@ -59,8 +60,13 @@ class UserContent extends React.PureComponent { isPublicChat, openChats, getGroupChatPrivate, + pollIsOpen, + forcePollOpen, } = this.props; + const showPoll = pollIsOpen && currentUser.isPresenter; + const keepPollOpen = forcePollOpen && !pollIsOpen && currentUser.isPresenter; + return ( <div data-test="userListContent" @@ -76,7 +82,10 @@ class UserContent extends React.PureComponent { roving, }} /> - + { + showPoll || keepPollOpen ? + <UserPolls /> : null + } <UserParticipants {...{ users, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f9daa37e1f0ee3e31ca5e865d0da36af9dad3211 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import { Session } from 'meteor/session'; +import UserContent from './component'; + +const UserContentContainer = ({ ...props }) => <UserContent {...props} />; + +export default withTracker(({ }) => ({ + pollIsOpen: Session.equals('isPollOpen', true), + forcePollOpen: Session.equals('forcePollOpen', true), +}))(UserContentContainer); 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 6cf653de7481a201eab910cc9c639390f8a47414..54d5fa0d7b24d8bdb4f50fe855d5dfd3e4d7990c 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 @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withRouter } from 'react-router'; import { injectIntl } from 'react-intl'; import UserDropdown from './user-dropdown/component'; @@ -21,7 +20,6 @@ const propTypes = { intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, - router: PropTypes.shape({}).isRequired, isBreakoutRoom: PropTypes.bool, getAvailableActions: PropTypes.func.isRequired, meeting: PropTypes.shape({}).isRequired, @@ -53,7 +51,6 @@ class UserListItem extends Component { meeting, normalizeEmojiName, removeUser, - router, setEmojiStatus, toggleVoice, user, @@ -77,7 +74,6 @@ class UserListItem extends Component { meeting, normalizeEmojiName, removeUser, - router, setEmojiStatus, toggleVoice, user, @@ -91,4 +87,4 @@ class UserListItem extends Component { UserListItem.propTypes = propTypes; UserListItem.defaultProps = defaultProps; -export default withRouter(injectIntl(UserListItem)); +export default injectIntl(UserListItem); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx index a5f41d69f6b4b5a2c8457bca493d5b7868ab2384..14cf981a9381212a1305afceb567eabfc8a7b1ff 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx @@ -11,6 +11,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 _ from 'lodash'; +import { Session } from 'meteor/session'; import { styles } from './styles'; import UserName from './../user-name/component'; import UserIcons from './../user-icons/component'; @@ -169,7 +170,6 @@ class UserDropdown extends Component { intl, currentUser, user, - router, isBreakoutRoom, getAvailableActions, getGroupChatPrivate, @@ -182,7 +182,7 @@ class UserDropdown extends Component { changeRole, } = this.props; - const actionPermissions = getAvailableActions(currentUser, user, router, isBreakoutRoom); + const actionPermissions = getAvailableActions(currentUser, user, isBreakoutRoom); const actions = []; const { @@ -236,7 +236,13 @@ class UserDropdown extends Component { intl.formatMessage(messages.ChatLabel), () => { getGroupChatPrivate(currentUser, user); - this.onActionsHide(router.push(`/users/chat/${user.id}`)); + // this.onActionsHide(router.push(`/users/chat/${user.id}`)); + if (Session.equals('isPollOpen', true)) { + Session.set('isPollOpen', false); + Session.set('forcePollOpen', true); + } + Session.set('idChatOpen', user.id); + Session.set('isChatOpen', true); }, 'chat', )); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8d91f2b086f0c59ad6db11503cbbdbebd00e7562 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx @@ -0,0 +1,51 @@ +import React, { Component } from 'react'; +import _ from 'lodash'; +import { defineMessages, injectIntl } from 'react-intl'; +import Icon from '/imports/ui/components/icon/component'; +import { Session } from 'meteor/session'; +import { styles } from './styles'; + +const intlMessages = defineMessages({ + pollLabel: { + id: 'app.poll.pollPaneTitle', + description: 'label for user-list poll button', + }, +}); + +class UserPolls extends Component { + constructor(props) { + super(props); + } + + render() { + const { intl } = this.props; + + return ( + <div className={styles.messages}> + { + <h2 className={styles.smallTitle}> + {'Polling'} + </h2> + } + <div className={styles.scrollableList}> + <div + role="button" + tabIndex={0} + className={styles.pollLink} + label="Polling" + icon="polling" + onClick={() => { + if (Session.equals('isChatOpen', true)) Session.set('isChatOpen', false); + if (Session.equals('isPollOpen', false)) Session.set('isPollOpen', true); + }} + > + <Icon iconName="polling" className={styles.icon} /> + <span className={styles.label} >{intl.formatMessage(intlMessages.pollLabel)}</span> + </div> + </div> + </div> + ); + } +} + +export default injectIntl(UserPolls); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fe468dc689b9ec408661dbad3773d9098eaa6d36 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/container.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Users from '/imports/api/users'; +import Polls from '/imports/api/polls'; +import Auth from '/imports/ui/services/auth'; +import { withTracker } from 'meteor/react-meteor-data'; +import UserPolls from './component'; + +const UserPollsContainer = ({ ...props }) => <UserPolls {...props} />; + +export default withTracker(({ }) => { + Meteor.subscribe('results', Auth.meetingID); + + // const getUser = userId => Users.findOne({ userId }); + + // const currentUser = Users.findOne({ userId: Auth.userID }); + + // const currentPoll = Polls.findOne({ meetingId: Auth.meetingID }); + + return { + // currentUser, + // currentPoll, + }; +})(UserPollsContainer); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..8d2a89157dfb7d7956d2d04c488222578955b34d --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/styles.scss @@ -0,0 +1,64 @@ +@import "../../styles.scss"; +@import "/imports/ui/stylesheets/variables/_all"; + +$list-item-bg-hover: darken(#F3F6F9, 7%); +$item-focus-border: #92BCEA; + +.smallTitle { + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + padding: 0 0.75rem; + margin: 0 0 0.625rem 0; + color: #8B9AA8; + margin-bottom: 12px; + margin-top: 8px; +} + +.scrollableList { + margin-left: 0.45rem; + margin-bottom: 1px; + outline: none; +} + +.pollLink { + @extend %list-item; + + cursor: pointer; + display: flex; + flex-flow: row; + flex-grow: 0; + flex-shrink: 0; + padding-top: 10px; + padding-bottom: 8px; + padding-left: 12px; + text-decoration: none; + width: 100%; + color: var(--color-gray-dark); + background-color: var(--color-off-white); + + > i { + display: flex; + font-size: 175%; + color: #8B9AA8; + flex: 0 0 2.2rem; + } + + > span { + font-size: 0.9rem; + font-weight: 400; + color: black; + position: relative; + flex-grow: 1; + line-height: 2; + text-align: left; + padding-left: 10px; + text-overflow: ellipsis; + } + + &:active { + background-color: $list-item-bg-hover; + box-shadow: inset 0 0 0 var(--border-size) $item-focus-border, inset 1px 0 0 1px $item-focus-border; + outline: none; + } +} diff --git a/bigbluebutton-html5/imports/ui/services/api/index.js b/bigbluebutton-html5/imports/ui/services/api/index.js index eb93965ead32ce97eaa77c3d87b5a11ec1549955..1d0003969e0bbdae36c17050260e4d1dfb10a51f 100755 --- a/bigbluebutton-html5/imports/ui/services/api/index.js +++ b/bigbluebutton-html5/imports/ui/services/api/index.js @@ -56,7 +56,7 @@ export function log(type = 'error', message, ...args) { const topic = logContents[0] ? logContents[0].topic : null; const messageOrStack = message.stack || message.message || JSON.stringify(message); - console.debug(`CLIENT LOG (${topic ? type.toUpperCase() + '.' + topic : type.toUpperCase()}): `, messageOrStack, ...args); + console.debug(`CLIENT LOG (${topic ? `${type.toUpperCase()}.${topic}` : type.toUpperCase()}): `, messageOrStack, ...args); Meteor.call('logClient', type, messageOrStack, { clientInfo, diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 4aa691b9405c0ead6f698d9a19161f6a9495c279..24f444492dc48ee884b79e9fa7b1095b2ac4880b 100755 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -755,16 +755,6 @@ "readable-stream": "2.3.6" } }, - "create-react-class": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", - "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" - } - }, "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", @@ -1911,11 +1901,6 @@ "warning": "3.0.0" } }, - "hoist-non-react-statics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", - "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" - }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -4647,20 +4632,6 @@ "react-dom": "16.0.1" } }, - "react-router": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.0.5.tgz", - "integrity": "sha1-w7eHN1gEWou8lWKu9P9LyMznwTY=", - "requires": { - "create-react-class": "15.6.2", - "history": "3.3.0", - "hoist-non-react-statics": "1.2.0", - "invariant": "2.2.2", - "loose-envify": "1.3.1", - "prop-types": "15.6.2", - "warning": "3.0.0" - } - }, "react-tabs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.1.1.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 7d61104aff416d82da9b7363be42590d17e66fe7..353e348034c1916ef0b7f4c35f4c47e33c342169 100755 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -58,7 +58,6 @@ "react-intl": "~2.4.0", "react-modal": "~3.0.4", "react-render-in-browser": "^1.0.0", - "react-router": "~3.0.2", "react-tabs": "~2.1.0", "react-toastify": "~2.1.2", "react-toggle": "~4.0.2", diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index bed859076c6dc190cc46b4cb4ce0ee4566a991b7..285d378a7910ffeed43c5614ad80101d20a8e959 100644 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -173,7 +173,6 @@ public: public_userid: public_chat_userid public_username: public_chat_username storage_key: UNREAD_CHATS - path_route: users/chat/ system_messages_keys: chat_clear: PUBLIC_CHAT_CLEAR layout: