From 94b6808ec660b858125c00b86437ecdc42a5c4c6 Mon Sep 17 00:00:00 2001 From: Mihaela Dumitru Date: Fri, 19 Jul 2024 09:44:17 +0300 Subject: [PATCH] feat(visitors) add info dialog (#14926) --- lang/main.json | 7 ++ .../base/ui/components/web/Dialog.tsx | 7 +- .../components/native/RaiseHandButton.tsx | 32 +++++++- .../components/web/RaiseHandButton.ts | 20 ++++- .../visitors/components/index.native.ts | 1 + .../features/visitors/components/index.web.ts | 1 + .../components/native/JoinMeetingDialog.tsx | 40 ++++++++++ .../visitors/components/native/styles.ts | 12 +++ .../components/web/JoinMeetingDialog.tsx | 75 +++++++++++++++++++ react/features/visitors/middleware.ts | 4 + 10 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 react/features/visitors/components/index.native.ts create mode 100644 react/features/visitors/components/index.web.ts create mode 100644 react/features/visitors/components/native/JoinMeetingDialog.tsx create mode 100644 react/features/visitors/components/native/styles.ts create mode 100644 react/features/visitors/components/web/JoinMeetingDialog.tsx diff --git a/lang/main.json b/lang/main.json index 9f2e241bd4d7..5205197da15e 100644 --- a/lang/main.json +++ b/lang/main.json @@ -263,6 +263,7 @@ "Remove": "Remove", "Share": "Share", "Submit": "Submit", + "Understood": "Understood", "WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.", "WaitForHostNoAuthMsg": "The conference has not yet started because no moderators have yet arrived. Please wait.", "WaitingForHostButton": "Wait for moderator", @@ -1492,6 +1493,12 @@ }, "visitors": { "chatIndicator": "(visitor)", + "joinMeeting": { + "description": "You're currently an observer in this conference.", + "raiseHand": "Raise your hand", + "title": "Joining meeting", + "wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval." + }, "labelTooltip": "Number of visitors: {{count}}", "notification": { "demoteDescription": "Sent here by {{actor}}, raise your hand to participate", diff --git a/react/features/base/ui/components/web/Dialog.tsx b/react/features/base/ui/components/web/Dialog.tsx index 59c96d59dded..e52974304c09 100644 --- a/react/features/base/ui/components/web/Dialog.tsx +++ b/react/features/base/ui/components/web/Dialog.tsx @@ -111,12 +111,9 @@ const Dialog = ({ }, [ onCancel ]); const submit = useCallback(() => { - if (onSubmit && ( - (document.activeElement && !operatesWithEnterKey(document.activeElement)) - || !document.activeElement - )) { + if ((document.activeElement && !operatesWithEnterKey(document.activeElement)) || !document.activeElement) { !disableAutoHideOnSubmit && dispatch(hideDialog()); - onSubmit(); + onSubmit?.(); } }, [ onSubmit ]); diff --git a/react/features/reactions/components/native/RaiseHandButton.tsx b/react/features/reactions/components/native/RaiseHandButton.tsx index fafe68f3fe90..6cc94f5c4220 100644 --- a/react/features/reactions/components/native/RaiseHandButton.tsx +++ b/react/features/reactions/components/native/RaiseHandButton.tsx @@ -40,6 +40,11 @@ interface IProps extends AbstractButtonProps { */ _raisedHand: boolean; + /** + * Whether or not the click is disabled. + */ + disableClick?: boolean; + /** * Used to close the overflow menu after raise hand is clicked. */ @@ -75,8 +80,14 @@ class RaiseHandButton extends Component { * @returns {void} */ _onClick() { + const { disableClick, onCancel } = this.props; + + if (disableClick) { + return; + } + this._toggleRaisedHand(); - this.props.onCancel(); + onCancel(); } /** @@ -159,4 +170,23 @@ function _mapStateToProps(state: IReduxState) { }; } +/** + * Maps part of the Redux state to the props of this component. + * + * @param {Object} state - The Redux state. + * @private + * @returns {IProps} + */ +function _standaloneMapStateToProps(state: IReduxState) { + const _enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true); + + return { + _enabled + }; +} + +const StandaloneRaiseHandButton = translate(connect(_standaloneMapStateToProps)(RaiseHandButton)); + +export { StandaloneRaiseHandButton }; + export default translate(connect(_mapStateToProps)(RaiseHandButton)); diff --git a/react/features/reactions/components/web/RaiseHandButton.ts b/react/features/reactions/components/web/RaiseHandButton.ts index f4dd2fd42e9b..d71f26b2a0dd 100644 --- a/react/features/reactions/components/web/RaiseHandButton.ts +++ b/react/features/reactions/components/web/RaiseHandButton.ts @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { createToolbarEvent } from '../../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../../analytics/functions'; -import { IReduxState } from '../../../app/types'; +import { IReduxState, IStore } from '../../../app/types'; import { translate } from '../../../base/i18n/functions'; import { IconRaiseHand } from '../../../base/icons/svg'; import { raiseHand } from '../../../base/participants/actions'; @@ -15,6 +15,16 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too */ interface IProps extends AbstractButtonProps { + /** + * Whether or not the click is disabled. + */ + disableClick?: boolean; + + /** + * Redux dispatch function. + */ + dispatch: IStore['dispatch']; + /** * Whether or not the hand is raised. */ @@ -51,7 +61,11 @@ class RaiseHandButton extends AbstractButton { * @returns {void} */ _handleClick() { - const { dispatch, raisedHand } = this.props; + const { disableClick, dispatch, raisedHand } = this.props; + + if (disableClick) { + return; + } sendAnalytics(createToolbarEvent( 'raise.hand', @@ -76,4 +90,6 @@ const mapStateToProps = (state: IReduxState) => { }; }; +export { RaiseHandButton }; + export default translate(connect(mapStateToProps)(RaiseHandButton)); diff --git a/react/features/visitors/components/index.native.ts b/react/features/visitors/components/index.native.ts new file mode 100644 index 000000000000..477cbb9edd98 --- /dev/null +++ b/react/features/visitors/components/index.native.ts @@ -0,0 +1 @@ +export { default as JoinMeetingDialog } from './native/JoinMeetingDialog'; diff --git a/react/features/visitors/components/index.web.ts b/react/features/visitors/components/index.web.ts new file mode 100644 index 000000000000..fff10e4b688b --- /dev/null +++ b/react/features/visitors/components/index.web.ts @@ -0,0 +1 @@ +export { default as JoinMeetingDialog } from './web/JoinMeetingDialog'; diff --git a/react/features/visitors/components/native/JoinMeetingDialog.tsx b/react/features/visitors/components/native/JoinMeetingDialog.tsx new file mode 100644 index 000000000000..487de0dc959a --- /dev/null +++ b/react/features/visitors/components/native/JoinMeetingDialog.tsx @@ -0,0 +1,40 @@ +import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { View, ViewStyle } from 'react-native'; +import Dialog from 'react-native-dialog'; + +import { StandaloneRaiseHandButton as RaiseHandButton } from '../../../reactions/components/native/RaiseHandButton'; +import styles from '../../components/native/styles'; + +/** + * Component that renders the join meeting dialog for visitors. + * + * @returns {JSX.Element} + */ +export default function JoinMeetingDialog() { + const { t } = useTranslation(); + const [ visible, setVisible ] = useState(true); + + const closeDialog = useCallback(() => { + setVisible(false); + }, []); + + return ( + + { t('visitors.joinMeeting.title') } + + { t('visitors.joinMeeting.description') } + + {/* @ts-ignore */} + + + + {t('visitors.joinMeeting.wishToSpeak')} + + + ); +} diff --git a/react/features/visitors/components/native/styles.ts b/react/features/visitors/components/native/styles.ts new file mode 100644 index 000000000000..2189819b8768 --- /dev/null +++ b/react/features/visitors/components/native/styles.ts @@ -0,0 +1,12 @@ +/** + * The styles of the feature visitors. + */ +export default { + + raiseHandButton: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%' + } +}; diff --git a/react/features/visitors/components/web/JoinMeetingDialog.tsx b/react/features/visitors/components/web/JoinMeetingDialog.tsx new file mode 100644 index 000000000000..f287270ac4a0 --- /dev/null +++ b/react/features/visitors/components/web/JoinMeetingDialog.tsx @@ -0,0 +1,75 @@ +import { noop } from 'lodash'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { makeStyles } from 'tss-react/mui'; + +import { IconArrowUp } from '../../../base/icons/svg'; +import ToolboxButtonWithPopup from '../../../base/toolbox/components/web/ToolboxButtonWithPopup'; +import Dialog from '../../../base/ui/components/web/Dialog'; +import { RaiseHandButton } from '../../../reactions/components/web/RaiseHandButton'; + +const useStyles = makeStyles()(theme => { + return { + raiseHand: { + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + pointerEvents: 'none' + }, + raiseHandTooltip: { + border: '1px solid #444', + borderRadius: theme.shape.borderRadius, + paddingBottom: theme.spacing(1), + paddingTop: theme.spacing(1), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2) + }, + raiseHandButton: { + display: 'inline-block', + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + position: 'relative' + } + }; +}); + +/** + * Component that renders the join meeting dialog for visitors. + * + * @returns {JSX.Element} + */ +export default function JoinMeetingDialog() { + const { t } = useTranslation(); + const { classes } = useStyles(); + + return ( + +
+

{t('visitors.joinMeeting.description')}

+
+

{t('visitors.joinMeeting.raiseHand')}

+
+ + {/* @ts-ignore */} + + +
+
+

{t('visitors.joinMeeting.wishToSpeak')}

+
+
+ ); +} diff --git a/react/features/visitors/middleware.ts b/react/features/visitors/middleware.ts index e91c387e385a..ed388b2e4e5b 100644 --- a/react/features/visitors/middleware.ts +++ b/react/features/visitors/middleware.ts @@ -13,6 +13,7 @@ import { SET_CONFIG } from '../base/config/actionTypes'; import { CONNECTION_FAILED } from '../base/connection/actionTypes'; import { connect, setPreferVisitor } from '../base/connection/actions'; import { disconnect } from '../base/connection/actions.any'; +import { openDialog } from '../base/dialog/actions'; import { JitsiConferenceEvents, JitsiConnectionErrors } from '../base/lib-jitsi-meet'; import { PARTICIPANT_UPDATED } from '../base/participants/actionTypes'; import { raiseHand } from '../base/participants/actions'; @@ -48,6 +49,7 @@ import { updateVisitorsCount, updateVisitorsInQueueCount } from './actions'; +import { JoinMeetingDialog } from './components'; import { getPromotionRequests, getVisitorsCount, getVisitorsInQueueCount } from './functions'; import logger from './logger'; import { WebsocketClient } from './websocket-client'; @@ -70,6 +72,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { const { conference } = action; if (getState()['features/visitors'].iAmVisitor) { + dispatch(openDialog(JoinMeetingDialog)); + const { demoteActorDisplayName } = getState()['features/visitors']; dispatch(setVisitorDemoteActor(undefined));